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

Draft
wants to merge 34 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
f966f31
fix: ensure embedded PostgreSQL is cleaned up on crash or forced term…
UdayHE Apr 13, 2025
fdc4931
fix: ensure embedded PostgreSQL is cleaned up on crash or forced term…
UdayHE Apr 13, 2025
49eca96
fix: ensure embedded PostgreSQL is cleaned up on crash or forced term…
UdayHE Apr 13, 2025
24f1b13
fix: ensure embedded PostgreSQL is cleaned up on crash or forced term…
UdayHE Apr 13, 2025
e245d2b
Merge branch 'issue-12844' of https://github.com/UdayHE/jabref into i…
UdayHE Apr 13, 2025
c430d80
fix: ensure embedded PostgreSQL is cleaned up on crash or forced term…
UdayHE Apr 13, 2025
d7b7586
fix: ensure embedded PostgreSQL is cleaned up on crash or forced term…
UdayHE Apr 13, 2025
a991627
fix: ensure embedded PostgreSQL is cleaned up on crash or forced term…
UdayHE Apr 13, 2025
318f01d
fix: ensure embedded PostgreSQL is cleaned up on crash or forced term…
UdayHE Apr 13, 2025
e85245f
fix: ensure embedded PostgreSQL is cleaned up on crash or forced term…
UdayHE Apr 15, 2025
69867cf
Merge branch 'main' into issue-12844
UdayHE Apr 15, 2025
b01d4a7
fix: ensure embedded PostgreSQL is cleaned up on crash or forced term…
UdayHE Apr 15, 2025
4abd5f1
fix: ensure embedded PostgreSQL is cleaned up on crash or forced term…
UdayHE Apr 15, 2025
ee83db3
fix: ensure embedded PostgreSQL is cleaned up on crash or forced term…
UdayHE Apr 15, 2025
482199b
fix: ensure embedded PostgreSQL is cleaned up on crash or forced term…
UdayHE Apr 15, 2025
9265211
fix: ensure embedded PostgreSQL is cleaned up on crash or forced term…
UdayHE Apr 15, 2025
0e51bba
fix: ensure embedded PostgreSQL is cleaned up on crash or forced term…
UdayHE Apr 15, 2025
6929226
fix: ensure embedded PostgreSQL is cleaned up on crash or forced term…
UdayHE Apr 15, 2025
9447181
Merge branch 'main' into issue-12844
UdayHE Apr 15, 2025
fe72794
fix: ensure embedded PostgreSQL is cleaned up on crash or forced term…
UdayHE Apr 15, 2025
ad960ec
fixes #12844
UdayHE Apr 15, 2025
5029657
fixes #12844
UdayHE Apr 15, 2025
27970d6
fixes #12844
UdayHE Apr 15, 2025
39f6df6
Merge remote-tracking branch 'upstream/main' into issue-12844
UdayHE Apr 16, 2025
2945b3b
Fixes https://github.com/JabRef/jabref/issues/12844
UdayHE Apr 16, 2025
7c590b2
Merge branch 'main' into issue-12844
UdayHE Apr 16, 2025
a9a192b
Merge remote-tracking branch 'origin' into issue-12844
UdayHE May 17, 2025
c4d90fc
Merge remote-tracking branch 'origin' into issue-12844
UdayHE May 19, 2025
fa8eab9
Fix for issue-12844:
UdayHE May 19, 2025
5d252e5
Merge branch 'main' into issue-12844
UdayHE May 19, 2025
9b5150d
Modified CHANGELOG.md.
UdayHE May 19, 2025
9cec567
Merge branch 'main' into issue-12844
UdayHE May 19, 2025
94bc898
Merge branch 'main' into issue-12844
UdayHE May 19, 2025
5bc6477
Merge branch 'main' into issue-12844
UdayHE May 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
- We added "Hanging Indent" as the default selected bibliography body format for CSL styles that specify it (e.g. APA). [melting-pot#894](https://github.com/JabRef/jabref-issue-melting-pot/issues/894)
- We fixed an issue where bibliography entries generated from CSL styles had leading spaces. [#13074](https://github.com/JabRef/jabref/pull/13074)
- We fixed an issue where the preview area in the "Select Style" dialog of the LibreOffice integration was too small to display full content. [#13051](https://github.com/JabRef/jabref/issues/13051)
- We fixed an issue where postgres processes remain running after JabRef crashes or is forcefully terminated. [#12927](https://github.com/JabRef/jabref/pull/12927)

### Removed

Expand Down
7 changes: 7 additions & 0 deletions jabgui/src/main/java/org/jabref/Launcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.jabref.logic.preferences.CliPreferences;
import org.jabref.logic.remote.RemotePreferences;
import org.jabref.logic.remote.client.RemoteClient;
import org.jabref.logic.search.PostgreProcessCleaner;
import org.jabref.logic.search.PostgreServer;
import org.jabref.logic.util.BuildInfo;
import org.jabref.logic.util.Directories;
Expand All @@ -46,6 +47,9 @@ public class Launcher {
public static void main(String[] args) {
initLogging(args);

// Clean up old Postgres instance if needed
PostgreProcessCleaner.getInstance().checkAndCleanupOldInstances();

Injector.setModelOrService(BuildInfo.class, new BuildInfo());

final JabRefGuiPreferences preferences = JabRefGuiPreferences.getInstance();
Expand Down Expand Up @@ -77,6 +81,9 @@ public static void main(String[] args) {

CSLStyleLoader.loadInternalStyles();

// Register shutdown hook
Runtime.getRuntime().addShutdownHook(new Thread(postgreServer::shutdown));
Copy link

Choose a reason for hiding this comment

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

Using 'new Thread()' is discouraged. Instead, 'org.jabref.logic.util.BackgroundTask' and its 'executeWith' method should be used for better resource management and consistency with the rest of the codebase.


JabRefGUI.setup(uiCommands, preferences);
JabRefGUI.launch(JabRefGUI.class, args);
}
Expand Down
2 changes: 2 additions & 0 deletions jablib/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,8 @@ dependencies {
implementation("io.zonky.test.postgres:embedded-postgres-binaries-darwin-arm64v8")
implementation("io.zonky.test.postgres:embedded-postgres-binaries-linux-arm64v8")

implementation("com.github.oshi:oshi-core:6.8.1")

testImplementation(project(":test-support"))

// loading of .fxml files in localization tests requires JabRef's GUI classes
Expand Down
1 change: 1 addition & 0 deletions jablib/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -246,5 +246,6 @@
requires mslinks;
requires org.antlr.antlr4.runtime;
requires org.libreoffice.uno;
requires com.github.oshi;
Copy link

Choose a reason for hiding this comment

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

The pull request title should contain a short title of the issue fixed or what the PR addresses, not just 'Fix issue xyz'. This is important for clarity and tracking purposes.

// endregion
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package org.jabref.logic.search;

import java.io.IOException;
import java.net.Socket;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import oshi.SystemInfo;
import oshi.software.os.OSProcess;
import oshi.software.os.OperatingSystem;

public class PostgreProcessCleaner {
private static final Logger LOGGER = LoggerFactory.getLogger(PostgreProcessCleaner.class);
private static final PostgreProcessCleaner INSTANCE = new PostgreProcessCleaner();
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final Path TEMP_DIR = Path.of(System.getProperty("java.io.tmpdir"));
private static final SystemInfo SYSTEM_INFO = new SystemInfo();
private static final OperatingSystem OS = SYSTEM_INFO.getOperatingSystem();
private static final String FILE_PREFIX = "jabref-postgres-info-";
private static final String FILE_SUFFIX = ".json";
private static final int POSTGRES_SHUTDOWN_WAIT_MILLIS = 1500;

private PostgreProcessCleaner() {
}

public static PostgreProcessCleaner getInstance() {
return INSTANCE;
}

public void checkAndCleanupOldInstances() {
try (Stream<Path> files = Files.list(TEMP_DIR)) {
Copy link

Choose a reason for hiding this comment

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

The try block covers too many statements, which can make it harder to identify where an exception occurs. It should cover as few statements as possible.

files.filter(path -> path.getFileName().toString().startsWith(FILE_PREFIX))
.filter(path -> path.getFileName().toString().endsWith(FILE_SUFFIX))
.forEach(this::cleanupIfDeadProcess);
} catch (IOException e) {
LOGGER.warn("Failed to list temp directory for Postgres metadata cleanup: {}", e.getMessage(), e);
}
}

private void cleanupIfDeadProcess(Path metadataPath) {
try {
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;
}
int postgresPort = ((Number) metadata.get("postgresPort")).intValue();
destroyPostgresProcess(postgresPort);
Files.deleteIfExists(metadataPath);
} catch (IOException e) {
LOGGER.warn("Failed to read or parse metadata file '{}': {}", metadataPath, e.getMessage(), e);
} catch (InterruptedException e) {
LOGGER.warn("Cleanup was interrupted for '{}': {}", metadataPath, e.getMessage(), e);
Thread.currentThread().interrupt();
} catch (Exception e) {
LOGGER.warn("Unexpected error during cleanup of '{}': {}", metadataPath, e.getMessage(), e);
}
}

private void destroyPostgresProcess(int port) throws InterruptedException {
if (isPortOpen(port)) {
long pid = getPidUsingPort(port);
if (pid != -1) {
LOGGER.info("Old Postgres instance found on port {} (PID {}). Killing it.", port, pid);
destroyProcessByPID(pid);
} else {
LOGGER.warn("Could not determine PID using port {}. Skipping kill step.", port);
}
}
}

private void destroyProcessByPID(long pid) throws InterruptedException {
Optional<ProcessHandle> aliveProcess = ProcessHandle.of(pid).filter(ProcessHandle::isAlive);
if (aliveProcess.isPresent()) {
aliveProcess.get().destroy();
Thread.sleep(PostgreProcessCleaner.POSTGRES_SHUTDOWN_WAIT_MILLIS);
}
}

private boolean isJavaProcessAlive(long javaPid) {
Optional<ProcessHandle> handle = ProcessHandle.of(javaPid);
return handle.map(ProcessHandle::isAlive).orElse(false);
}

private boolean isPortOpen(int port) {
try (Socket _ = new Socket("localhost", port)) {
return true;
} catch (IOException ex) {
return false;
}
}

private long getPidUsingPort(int port) {
for (OSProcess process : OS.getProcesses()) {
String command = process.getCommandLine();
if (command != null && command.toLowerCase().contains("postgres")
&& command.contains(String.valueOf(port))) {
return process.getProcessID();
}
}
return -1L;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

public class PostgreServer {
private static final Logger LOGGER = LoggerFactory.getLogger(PostgreServer.class);

private final EmbeddedPostgres embeddedPostgres;
private final DataSource dataSource;

Expand All @@ -26,6 +27,7 @@ public PostgreServer() {
.setOutputRedirector(ProcessBuilder.Redirect.DISCARD)
.start();
LOGGER.info("Postgres server started, connection port: {}", embeddedPostgres.getPort());
PostgresMetadataWriter.write(embeddedPostgres.getPort());
} catch (IOException e) {
LOGGER.error("Could not start Postgres server", e);
this.embeddedPostgres = null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.jabref.logic.search;

import java.io.IOException;
import java.nio.file.Path;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.jabref.logic.util.Directories.getTmpDirectory;

public class PostgresMetadataWriter {
private static final Logger LOGGER = LoggerFactory.getLogger(PostgresMetadataWriter.class);
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final String JAVA_PID = "javaPid";
private static final String POSTGRES_PORT = "postgresPort";
private static final String STARTED_BY = "startedBy";
private static final String STARTED_AT = "startedAt";
private static final String JABREF = "jabref";

private PostgresMetadataWriter() {
}

public static Path getMetadataFilePath() {
return getTmpDirectory().resolve("jabref-postgres-info-" + ProcessHandle.current().pid() + ".json");
}

public static void write(int port) {
try {
Path path = getMetadataFilePath();
OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValue(path.toFile(), createMetadata(port));
LOGGER.info("Postgres metadata file path: {}", path);
} catch (IOException e) {
LOGGER.warn("Failed to write Postgres metadata file.", e);
}
Comment on lines +33 to +39
Copy link

Choose a reason for hiding this comment

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

The try block covers multiple statements. It is recommended to minimize the scope of try blocks to only the statements that might throw exceptions.

}

private static Map<String, Object> createMetadata(int port) {
Map<String, Object> meta = new HashMap<>();
meta.put(JAVA_PID, ProcessHandle.current().pid());
meta.put(POSTGRES_PORT, port);
meta.put(STARTED_BY, JABREF);
meta.put(STARTED_AT, DateTimeFormatter.ISO_INSTANT.format(Instant.now()));
return meta;
}
}
4 changes: 4 additions & 0 deletions jablib/src/main/java/org/jabref/logic/util/Directories.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,8 @@ public static Path getSslDirectory() {
"ssl",
OS.APP_DIR_APP_AUTHOR));
}

public static Path getTmpDirectory() {
Copy link

Choose a reason for hiding this comment

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

The method getTmpDirectory() is newly added and should not return null. It should use java.util.Optional to handle potential null values, ensuring better null safety and adherence to modern Java practices.

return Path.of(System.getProperty("java.io.tmpdir"));
}
}
Loading