Skip to content

Commit

Permalink
Clean up after JVMs unable to delete temporary shared library files
Browse files Browse the repository at this point in the history
On Windows, File.deleteOnExit appears to be unable to delete our
temporary native library DLL files -- most likely because at the point
of deletion the process still has a reference to the loaded DLL, and
Windows doesn't allow deleting DLLs that are still referenced by a live
process.

Work around this limitation by deleting after the fact, i.e., clean up
old temp files when we know they should be deleted. Keep marker files in
the event that the library file cannot be deleted upon shutdown. Check
for these marker files upon starting a new VM using junixsocket, and
delete both the marker file and the associated shared library file.

The code is written in a way that it should also do the right thing on
other platforms, or at least fail silently.

#160
  • Loading branch information
kohlschuetter committed Aug 26, 2024
1 parent 173cc01 commit 0f4fb9e
Showing 1 changed file with 78 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -52,7 +53,8 @@ final class NativeLibraryLoader implements Closeable {
private static final boolean IS_ANDROID = checkAndroid();

static {
String dir = System.getProperty(PROP_LIBRARY_TMPDIR, null);
String dir = System.getProperty(PROP_LIBRARY_TMPDIR, System.getProperty("java.io.tmpdir",
null));
TEMP_DIR = (dir == null) ? null : new File(dir);
}

Expand Down Expand Up @@ -167,13 +169,64 @@ private static final class ClasspathLibraryCandidate extends LibraryCandidate {
this.library = library;
}

/**
* Even though we ask the JVM to delete the library file upon VM exit, this may not be honored
* in all cases (crash, Windows, etc.)
*
* Therefore, we attempt to delete these files whenever another JVM using junixsocket starts up.
* This is simplified by keeping empty marker files next to the temporary shared library file.
*
* @param libDir The directory to check.
*/
private void deleteLibTmpDelFiles(File libDir) {
if (libDir == null) {
try {
File tempFile = File.createTempFile("libtmp", ".del");
libDir = tempFile.getParentFile();
tryDelete(tempFile);
} catch (IOException e) {
return;
}
}
File[] filesToDelete = libDir.listFiles((File f) -> {
if (!f.isFile()) {
return false;
}
String name = f.getName();
return name.startsWith("libtmp") && name.endsWith(".del");
});
if (filesToDelete == null || filesToDelete.length == 0) {
return;
}

for (File f : filesToDelete) {
tryDelete(f);
String n = f.getName();
n = n.substring(0, n.length() - ".del".length());
File libFile = new File(f.getParentFile(), n);
tryDelete(libFile);
}
}

@Override
@SuppressWarnings("PMD.CognitiveComplexity")
synchronized String load() throws IOException, LinkageError {
if (libraryNameAndVersion == null) {
return null;
}

File libDir = TEMP_DIR;
File userHomeDir = new File(System.getProperty("user.home", "."));
File userDirOrNull = new File(System.getProperty("user.dir", "."));
if (userHomeDir.equals(userDirOrNull)) {
userDirOrNull = null;
}

deleteLibTmpDelFiles(libDir);
deleteLibTmpDelFiles(userHomeDir);
if (userDirOrNull != null) {
deleteLibTmpDelFiles(userDirOrNull);
}

for (int attempt = 0; attempt < 3; attempt++) {
File libFile;
Expand All @@ -200,19 +253,34 @@ synchronized String load() throws IOException, LinkageError {

switch (attempt) {
case 0:
libDir = new File(System.getProperty("user.home", "."));
libDir = userHomeDir;
break;
case 1:
libDir = new File(System.getProperty("user.dir", "."));
break;
if (userDirOrNull != null) {
libDir = userDirOrNull;
break;
}
// fall-through
default:
throw e;
}

continue;
} finally {
if (!libFile.delete()) {
if (!libFile.delete() && libFile.exists()) {
libFile.deleteOnExit();

File markerFile = new File(libFile.getParentFile(), libFile.getName() + ".del");
try {
Files.createFile(markerFile.toPath());
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
if (!libFile.exists() || libFile.delete()) {
tryDelete(markerFile);
}
}));
} catch (IOException | UnsupportedOperationException e) {
// ignore
}
}
}

Expand All @@ -222,6 +290,11 @@ synchronized String load() throws IOException, LinkageError {
return artifactName + "/" + libraryNameAndVersion;
}

@SuppressFBWarnings("RV_RETURN_VALUE_IGNORED_BAD_PRACTICE")
private static void tryDelete(File f) {
f.delete(); // NOPMD
}

@Override
public void close() {
}
Expand Down

0 comments on commit 0f4fb9e

Please sign in to comment.