Skip to content

Fix for postgres instance process closing #12927

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 26 commits into
base: main
Choose a base branch
from

Conversation

UdayHE
Copy link

@UdayHE UdayHE commented Apr 13, 2025

Fixes #12844

Summary

  • Added logic to detect and clean up stale embedded PostgreSQL instances left behind by previous JabRef sessions.
  • Introduced PostgreProcessCleaner to safely shut down any orphaned PostgreSQL processes using port-based detection and Java PID tracking.
  • Registered a shutdown hook in Launcher to ensure embedded PostgreSQL is properly terminated during normal or abrupt shutdowns (excluding SIGKILL).

Mandatory checks

  • I own the copyright of the code submitted and I license it under the MIT license.
  • Change in CHANGELOG.md described in a way that is understandable for the average user (if change is visible to the user)
  • Tests created for changes (if applicable)
  • Manually tested changed features in running JabRef (always required)
  • Screenshots added in PR description (if change is visible to the user)
  • Checked developer's documentation: Is the information available and up to date? If not, I outlined it in this pull request.
  • Checked documentation: Is the information available and up to date? If not, I created an issue at https://github.com/JabRef/user-documentation/issues or, even better, I submitted a pull request to the documentation repository.

macOS Sequoia 15.4.
https://www.loom.com/share/9ed0da7815714797b4f0568c33f6df03?sid=34114b6c-ca32-43ff-9896-bfae39b36c81

Windows
https://github.com/user-attachments/assets/aa7a7da4-661e-464e-a08f-f6befba66363

UdayHE added 7 commits April 13, 2025 10:22
…ination (Fix JabRef#12844)

* Added logic to detect and clean up stale embedded PostgreSQL instances left behind by previous JabRef sessions.

* Introduced PostgresProcessCleaner to safely shut down any orphaned PostgreSQL processes using PID-based detection.

* Registered a shutdown hook in Launcher to ensure embedded PostgreSQL is properly terminated during normal or abrupt shutdown.
…ination (Fix JabRef#12844)

* Added logic to detect and clean up stale embedded PostgreSQL instances left behind by previous JabRef sessions.

* Introduced PostgresProcessCleaner to safely shut down any orphaned PostgreSQL processes using PID-based detection.

* Registered a shutdown hook in Launcher to ensure embedded PostgreSQL is properly terminated during normal or abrupt shutdown.

* Updated CHANGELOG.md
…ination (Fix JabRef#12844)

* Added logic to detect and clean up stale embedded PostgreSQL instances left behind by previous JabRef sessions.

* Introduced PostgresProcessCleaner to safely shut down any orphaned PostgreSQL processes using PID-based detection.

* Registered a shutdown hook in Launcher to ensure embedded PostgreSQL is properly terminated during normal or abrupt shutdown.
…ination (Fix JabRef#12844)

* Added logic to detect and clean up stale embedded PostgreSQL instances left behind by previous JabRef sessions.

* Introduced PostgresProcessCleaner to safely shut down any orphaned PostgreSQL processes using PID-based detection.

* Registered a shutdown hook in Launcher to ensure embedded PostgreSQL is properly terminated during normal or abrupt shutdown.

* Updated CHANGELOG.md
…ination (Fix JabRef#12844)

* Added logic to detect and clean up stale embedded PostgreSQL instances left behind by previous JabRef sessions.

* Introduced PostgresProcessCleaner to safely shut down any orphaned PostgreSQL processes using PID-based detection.

* Registered a shutdown hook in Launcher to ensure embedded PostgreSQL is properly terminated during normal or abrupt shutdown.

* Updated CHANGELOG.md
…ination (Fix JabRef#12844)

* style changes in PostgreProcessCleaner, Launcher

private void destroyPreviousJavaProcess(Map<String, Object> meta) throws InterruptedException {
long javaPid = ((Number) meta.get("javaPid")).longValue();
destroyProcessByPID(javaPid, 1000);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does one know whether this stores JabRef data? Shouldn't a connection attempt be made?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In writeMetadataToFile method, I am using ProcessHandle.current().pid(), so that the current running process id will be stored in the json file.

@koppor
Copy link
Member

koppor commented Apr 13, 2025

This is AI generated, isn't it?

@subhramit
Copy link
Member

Have you tested this by observing the processes? If so, on which OS?
I tried on Windows. Doesn't work.

@UdayHE
Copy link
Author

UdayHE commented Apr 13, 2025

This is AI generated, isn't it?

nope.

import static org.jabref.model.search.PostgreConstants.BIB_FIELDS_SCHEME;

public class PostgreServer {
private static final Logger LOGGER = LoggerFactory.getLogger(PostgreServer.class);
public static final Path POSTGRES_METADATA_FILE = Path.of("/tmp/jabref-postgres-info.json");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will not work in Windows. Will not work if multiple JabRef instances run.

For the former, AppDirs can be used (can't it?) - for the latter, the whole flow needs to be thought through.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, I need to check about AppDirs once & get back on this.

@UdayHE
Copy link
Author

UdayHE commented Apr 13, 2025

Have you tested this by observing the processes? If so, on which OS? I tried on Windows. Doesn't work.

yes, I have tested on macOS Sequoia 15.4.
https://www.loom.com/share/9ed0da7815714797b4f0568c33f6df03?sid=34114b6c-ca32-43ff-9896-bfae39b36c81

I will check on windows and update here.

@subhramit
Copy link
Member

yes, I have tested on macOS Sequoia 15.4.
https://www.loom.com/share/9ed0da7815714797b4f0568c33f6df03?sid=34114b6c-ca32-43ff-9896-bfae39b36c81

Interesting - this seems to be going in a positive direction, if we can fix it for Windows.
Let me know if you want me to test and give feedback after a certain round of changes.

@Siedlerchr Siedlerchr changed the title Fix for Issue 12844 Fix for postgres instance process closing Apr 13, 2025
UdayHE added 3 commits April 13, 2025 18:17
…ination (Fix JabRef#12844)

* style changes in PostgreServer imports
…ination (Fix JabRef#12844)

* Modified PostgreProcessCleaner logic to fix windows os issue.
@UdayHE
Copy link
Author

UdayHE commented Apr 15, 2025

Have you tested this by observing the processes? If so, on which OS? I tried on Windows. Doesn't work.

checked on windows as well, can you please check from your end as well?

screen-capture-windows.mp4

…ination (Fix JabRef#12844)

* Used Path.of() instead of Paths.get() in PostgreProcessCleaner.
@UdayHE
Copy link
Author

UdayHE commented Apr 15, 2025

Embedded Postgres Cleanup Flow
1.Start the Application
When a new JabRef instance starts, it initiates the embedded Postgres server.

2.Write Metadata File
During startup, the application writes a JSON file to the temporary directory (java.io.tmpdir).
File naming convention: jabref-postgres-info-.json (e.g., jabref-postgres-info-1234.json).
This file contains:
javaPid: The PID of the current Java process.
postgresPort: The port used by the embedded Postgres instance.
etc..

3.Check for Metadata Files on Startup
On startup, the application scans the temp directory for any matching metadata files (jabref-postgres-info-*.json).
This supports scenarios where multiple instances may have left behind multiple metadata files.
Read Stored Process Details.
For each metadata file found:
It reads the javaPid and postgresPort from the JSON.

4.Verify Java Process Liveness
For each javaPid, the application checks whether the corresponding Java process is still alive.

5.Handle Stale Java Processes
If any Java process is no longer running:
It indicates that a previous JabRef instance was terminated (e.g., via kill -9 or SIGKILL).

6.Lookup Postgres Processes
For each stale entry:
The application performs a port-based lookup to check if any process is still listening on the postgresPort.

7.Kill Stale Postgres
If a Postgres process is found on that port:
It is considered stale and is safely terminated.

8.Delete Metadata Files
After handling each stale process:
The corresponding metadata file (jabref-postgres-info-.json) is deleted.
Metadata files for live Java processes are retained, to avoid interfering with other active instances.

9.Proceed with Fresh Initialization
The current instance proceeds to launch a new embedded Postgres server.
A new metadata file is created specific to this instance.

10. Multiple Instance Safety
Each JabRef instance maintains its own metadata file, isolated by Java PID.
Stale cleanup only affects processes no longer alive, ensuring that active instances are never disrupted.

@UdayHE UdayHE requested a review from koppor April 15, 2025 09:19
UdayHE added 3 commits April 15, 2025 20:36
* Fix for trag-bot comments.
* Fix for trag-bot comments.
* Fix for trag-bot comments.
* Created separate class PostgresMetadataWriter to follow SRP.
Map<String, Object> meta = new HashMap<>();
meta.put("javaPid", ProcessHandle.current().pid());
meta.put("postgresPort", port);
meta.put("startedBy", "jabref");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The string 'jabref' is a magic string and should be defined as a constant to improve maintainability and readability.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In some sense, trag-bot is right.

Is it possible to, maybe, make a serializable class?

Copy link

trag-bot bot commented Apr 16, 2025

@trag-bot didn't find any issues in the code! ✅✨

@jabref-machine

This comment was marked as resolved.

Map<String, Object> metadata = new HashMap<>(OBJECT_MAPPER.readValue(Files.readAllBytes(metadataPath), HashMap.class));
long javaPid = ((Number) metadata.get("javaPid")).longValue();
if (isJavaProcessAlive(javaPid)) {
return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the database is still running, is it necessary to clean up the data to ensure it's in the same state as a freshly started database?

}
int postgresPort = ((Number) metadata.get("postgresPort")).intValue();
destroyPostgresProcess(postgresPort);
Files.deleteIfExists(metadataPath);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting question—what happens if you're running multiple JabRef instances? How can you distinguish between them?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normally JabRef has only a single instance open, it is a setting in the preferences
grafik

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the question was for the other case.

Maybe, we should just start to disallow multiple instances?

.redirectErrorStream(true).start();
} else if (OS.WINDOWS) {
return executeWindowsCommand(port);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor question: Is lsof included by default on macOS and Linux? For example, if someone is running Ubuntu with a standard configuration—without adding any extra packages—the solution should ideally work out of the box.

Additionally, there could be security or access rights issues. For instance, on Windows, a domain admin might block users from executing cmd.exe.

At the very least, the documentation should specify the conditions under which the solution is expected to run successfully.

}

public static Path getMetadataFilePath() {
return Path.of(System.getProperty("java.io.tmpdir"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the org.jabref.logic.util.Directories class should be used here to obtain the directory in a standardized way.

private Process executeWindowsCommand(int port) throws IOException {
String systemRoot = System.getenv("SystemRoot");
if (systemRoot != null && !systemRoot.isBlank()) {
String netStatPath = systemRoot + "\\System32\\netstat.exe";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to use Java paths?

if (systemRoot != null && !systemRoot.isBlank()) {
String netStatPath = systemRoot + "\\System32\\netstat.exe";
String findStrPath = systemRoot + "\\System32\\findstr.exe";
String command = netStatPath + " -ano | " + findStrPath + " :" + port;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm.. is there an API, where you specify this for running a process:

  1. Command name
  2. List of strings that represent arguments

I think this is more stable than just passing a string

if (systemRoot != null && !systemRoot.isBlank()) {
String netStatPath = systemRoot + "\\System32\\netstat.exe";
String findStrPath = systemRoot + "\\System32\\findstr.exe";
String command = netStatPath + " -ano | " + findStrPath + " :" + port;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does | do here? It's like a pipe from Unix? Does it work on Windows, or it has other meaning?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

findstr should be programmed in Java

Code reads like AI generated...


tasklist ist the proposal by "the internet" - https://www.rgagnon.com/javadetails/java-0593.html

Copy link
Member

@Siedlerchr Siedlerchr Apr 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With java 9 + we can use Process Handles https://stackoverflow.com/a/45068036

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

findstr should be programmed in Java

Code reads like AI generated...

tasklist ist the proposal by "the internet" - https://www.rgagnon.com/javadetails/java-0593.html

Not AI, I have referred these for executing commands:
https://coderanch.com/t/688902/java/Executing-netstat-command-process-builder
https://mkyong.com/java/java-processbuilder-examples/

Also I will check the commands we need to program using java, We can make use of command design pattern, so that if any other command needs to be implemented, we can easily extend that as well

return null;
}

private long extractPidFromOutput(BufferedReader reader) throws IOException {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So you call some external command to get PID, right?

I wonder, is there a nicer package for Java? Or some alternative in packages that we have.. (If there is no such thing, then I think it's okay to use commands)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried google, did not find something immediatly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

PostgreSQL processes remain running after JabRef crashes or is forcefully terminated
7 participants