-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
base: main
Are you sure you want to change the base?
Changes from all commits
f966f31
fdc4931
49eca96
24f1b13
e245d2b
c430d80
d7b7586
a991627
318f01d
e85245f
69867cf
b01d4a7
4abd5f1
ee83db3
482199b
9265211
0e51bba
6929226
9447181
fe72794
ad960ec
5029657
27970d6
39f6df6
2945b3b
7c590b2
a9a192b
c4d90fc
fa8eab9
5d252e5
9b5150d
9cec567
94bc898
5bc6477
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -246,5 +246,6 @@ | |
requires mslinks; | ||
requires org.antlr.antlr4.runtime; | ||
requires org.libreoffice.uno; | ||
requires com.github.oshi; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
---|---|---|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -59,4 +59,8 @@ public static Path getSslDirectory() { | |
"ssl", | ||
OS.APP_DIR_APP_AUTHOR)); | ||
} | ||
|
||
public static Path getTmpDirectory() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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")); | ||
} | ||
} |
There was a problem hiding this comment.
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.