-
-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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
Changes from 26 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 |
|---|---|---|
| @@ -0,0 +1,169 @@ | ||
| package org.jabref.logic.search; | ||
|
|
||
| import java.io.BufferedReader; | ||
| import java.io.IOException; | ||
| import java.io.InputStreamReader; | ||
| 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 org.jabref.logic.os.OS; | ||
|
|
||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| 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 String FILE_PREFIX = "jabref-postgres-info-"; | ||
UdayHE marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| private static final String FILE_SUFFIX = ".json"; | ||
UdayHE marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| private static final int POSTGRES_SHUTDOWN_WAIT_MILLIS = 1500; | ||
UdayHE marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| private PostgreProcessCleaner() { | ||
| } | ||
|
|
||
| public static PostgreProcessCleaner getInstance() { | ||
| return INSTANCE; | ||
| } | ||
|
|
||
| public void checkAndCleanupOldInstances() { | ||
| try (Stream<Path> files = Files.list(TEMP_DIR)) { | ||
| 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; | ||
|
Contributor
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. 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? |
||
| } | ||
UdayHE marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| int postgresPort = ((Number) metadata.get("postgresPort")).intValue(); | ||
| destroyPostgresProcess(postgresPort); | ||
| Files.deleteIfExists(metadataPath); | ||
|
Contributor
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. Interesting question—what happens if you're running multiple JabRef instances? How can you distinguish between them?
Member
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.
Member
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. Yeah, the question was for the other case. Maybe, we should just start to disallow multiple instances? |
||
| } catch (IOException e) { | ||
| LOGGER.warn("Failed to read or parse metadata file '{}': {}", metadataPath, e.getMessage(), e); | ||
Siedlerchr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } 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); | ||
|
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 destroyProcessByPID should use ifPresent on Optional instead of checking isPresent and then calling get. |
||
| if (aliveProcess.isPresent()) { | ||
| aliveProcess.get().destroy(); | ||
|
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 code should use ifPresent to handle the Optional instead of checking isPresent and then calling get. |
||
| 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) { | ||
| try { | ||
| Process process = createPortLookupProcess(port); | ||
| if (process == null) { | ||
| return -1; | ||
| } | ||
| try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { | ||
| return extractPidFromOutput(reader); | ||
| } | ||
| } catch (Exception e) { | ||
| LOGGER.warn("Failed to get PID for port {}: {}", port, e.getMessage(), e); | ||
| } | ||
| return -1; | ||
| } | ||
|
|
||
| private Process createPortLookupProcess(int port) throws IOException { | ||
| if (OS.LINUX || OS.OS_X) { | ||
| return new ProcessBuilder("lsof", "-i", "tcp:" + port, "-sTCP:LISTEN", "-Pn") | ||
| .redirectErrorStream(true).start(); | ||
| } else if (OS.WINDOWS) { | ||
| return executeWindowsCommand(port); | ||
| } | ||
|
||
| return null; | ||
| } | ||
|
|
||
| private Process executeWindowsCommand(int port) throws IOException { | ||
| String systemRoot = System.getenv("SystemRoot"); | ||
| if (systemRoot != null && !systemRoot.isBlank()) { | ||
| String netStatPath = systemRoot + "\\System32\\netstat.exe"; | ||
|
||
| String findStrPath = systemRoot + "\\System32\\findstr.exe"; | ||
| String command = netStatPath + " -ano | " + findStrPath + " :" + port; | ||
|
||
| return new ProcessBuilder("cmd.exe", "/c", command) | ||
| .redirectErrorStream(true).start(); | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| private long extractPidFromOutput(BufferedReader reader) throws IOException { | ||
|
||
| String line; | ||
| while ((line = reader.readLine()) != null) { | ||
| if (OS.LINUX || OS.OS_X) { | ||
| Long pid = parsePidFromLine(line); | ||
| if (pid != null) { | ||
| return pid; | ||
| } | ||
| } else if (OS.WINDOWS) { | ||
| Long pid = parseWindowsPidFromLine(line); | ||
| if (pid != null) { | ||
| return pid; | ||
| } | ||
| } | ||
| } | ||
| return -1; | ||
| } | ||
|
|
||
| private Long parsePidFromLine(String line) { | ||
| String[] parts = line.trim().split("\\s+"); | ||
| if (parts.length > 1 && parts[1].matches("\\d+")) { | ||
| return Long.parseLong(parts[1]); | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| private Long parseWindowsPidFromLine(String line) { | ||
| String[] parts = line.trim().split("\\s+"); | ||
| if (parts.length >= 5 && parts[parts.length - 1].matches("\\d+")) { | ||
| return Long.parseLong(parts[parts.length - 1]); | ||
| } | ||
| return null; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| 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; | ||
|
|
||
| public class PostgresMetadataWriter { | ||
| private static final Logger LOGGER = LoggerFactory.getLogger(PostgresMetadataWriter.class); | ||
| private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); | ||
|
|
||
| private PostgresMetadataWriter() { | ||
| } | ||
|
|
||
| public static Path getMetadataFilePath() { | ||
| return Path.of(System.getProperty("java.io.tmpdir"), | ||
|
||
| "jabref-postgres-info-" + ProcessHandle.current().pid() + ".json"); | ||
| } | ||
|
|
||
| public static void write(int port) { | ||
InAnYan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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); | ||
| } | ||
| } | ||
|
|
||
| private static Map<String, Object> createMetadata(int port) { | ||
| Map<String, Object> meta = new HashMap<>(); | ||
| meta.put("javaPid", ProcessHandle.current().pid()); | ||
|
||
| meta.put("postgresPort", port); | ||
|
||
| meta.put("startedBy", "jabref"); | ||
|
||
| meta.put("startedAt", DateTimeFormatter.ISO_INSTANT.format(Instant.now())); | ||
|
||
| return meta; | ||
| } | ||
| } | ||

Uh oh!
There was an error while loading. Please reload this page.