Skip to content

Commit a67b148

Browse files
authored
Clean up .dll files on Windows (#493)
**ISSUE:** On Windows, the .dll file extracted to the temp directory was never being deleted. And repeated runs of the program were filling the disk with old .dll files. **DESCRIPTION OF CHANGES:** On Windows only, during startup, scan the temp dir for old instances of `AWSCRT_*aws-crt-jni.dll` and try to delete them. **DIAGNOSIS:** We were using [File.deleteOnExit()](https://docs.oracle.com/javase/8/docs/api/java/io/File.html#deleteOnExit--) to clean up the shared lib, which is working OK on other platforms, but not on Windows. On Windows, files cannot be deleted while they're in use, and it appears that Java doesn't try to "unload" the .dll, so it's apparently impossible for a Java process to delete a .dll that it's using. I did an experiment with a dead-simple JNI library. I simply loaded it, and called `File.deleteOnExit()`. That .dll didn't get deleted either. So it's not just a problem with our .dll being very complex. **SIDE-NOTE:** I realized that on non-Windows platforms, the shared-lib won't be deleted on exit if Java dies suddenly (crash in C, OS terminated process, etc). But on non-Windows platforms you can delete the shared-lib from disk immediately after loading it, you don't need to wait until the process exits. I did experiments on Mac and this works. But I left it out of this PR because this is an emergency fix for windows and I don't want to introduce non-necessary changes.
1 parent e7ae730 commit a67b148

File tree

1 file changed

+50
-9
lines changed
  • src/main/java/software/amazon/awssdk/crt

1 file changed

+50
-9
lines changed

src/main/java/software/amazon/awssdk/crt/CRT.java

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import java.io.File;
88
import java.io.FileOutputStream;
9+
import java.io.FilenameFilter;
910
import java.io.IOException;
1011
import java.io.InputStream;
1112
import java.util.*;
@@ -148,17 +149,36 @@ private static void extractAndLoadLibrary(String path) {
148149
throw new IOException(msg, ex);
149150
}
150151

151-
// Prefix the lib
152-
String prefix = "AWSCRT_" + new Date().getTime();
153152
String libraryName = System.mapLibraryName(CRT_LIB_NAME);
154-
String libraryPath = "/" + getOSIdentifier() + "/" + getArchIdentifier() + "/" + libraryName;
155153

156-
File tempSharedLib = File.createTempFile(prefix, libraryName, tmpdirFile);
154+
// Prefix the lib we'll extract to disk
155+
String tempSharedLibPrefix = "AWSCRT_";
156+
157+
File tempSharedLib = File.createTempFile(tempSharedLibPrefix, libraryName, tmpdirFile);
158+
159+
// The temp lib file should be deleted when we're done with it.
160+
// Ask Java to try and delete it on exit. We call this immediately
161+
// so that if anything goes wrong writing the file to disk, or
162+
// loading it as a shared lib, it will still get cleaned up.
163+
tempSharedLib.deleteOnExit();
164+
165+
// Unfortunately File.deleteOnExit() won't work on Windows, where
166+
// files cannot be deleted while they're in use. On Windows, once
167+
// our .dll is loaded, it can't be deleted by this process.
168+
//
169+
// The Windows-only solution to this problem is to scan on startup
170+
// for old instances of the .dll and try to delete them. If another
171+
// process is still using the .dll, the delete will fail, which is fine.
172+
String os = getOSIdentifier();
173+
if (os.equals("windows")) {
174+
tryDeleteOldLibrariesFromTempDir(tmpdirFile, tempSharedLibPrefix, libraryName);
175+
}
157176

158177
// open a stream to read the shared lib contents from this JAR
159-
try (InputStream in = CRT.class.getResourceAsStream(libraryPath)) {
178+
String libResourcePath = "/" + os + "/" + getArchIdentifier() + "/" + libraryName;
179+
try (InputStream in = CRT.class.getResourceAsStream(libResourcePath)) {
160180
if (in == null) {
161-
throw new IOException("Unable to open library in jar for AWS CRT: " + libraryPath);
181+
throw new IOException("Unable to open library in jar for AWS CRT: " + libResourcePath);
162182
}
163183

164184
if (tempSharedLib.exists()) {
@@ -185,9 +205,6 @@ private static void extractAndLoadLibrary(String path) {
185205
throw new CrtRuntimeException("Unable to make shared library readable");
186206
}
187207

188-
// Ensure that the shared lib will be destroyed when java exits
189-
tempSharedLib.deleteOnExit();
190-
191208
// load the shared lib from the temp path
192209
System.load(tempSharedLib.getAbsolutePath());
193210
} catch (CrtRuntimeException crtex) {
@@ -236,6 +253,30 @@ private static void loadLibraryFromJar() {
236253
throw new CrtRuntimeException(failureMessage.toString());
237254
}
238255

256+
// Try to delete old CRT libraries that were extracted to the temp dir by previous runs.
257+
private static void tryDeleteOldLibrariesFromTempDir(File tmpDir, String libNamePrefix, String libNameSuffix) {
258+
try {
259+
File[] oldLibs = tmpDir.listFiles(new FilenameFilter() {
260+
public boolean accept(File dir, String name) {
261+
return name.startsWith(libNamePrefix) && name.endsWith(libNameSuffix);
262+
}
263+
});
264+
265+
// Don't delete files that are too new.
266+
// We don't want to delete another process's lib in the
267+
// millisecond between the file being written to disk,
268+
// and the file being loaded as a shared lib.
269+
long aFewMomentsAgo = System.currentTimeMillis() - 10_000; // 10sec
270+
for (File oldLib : oldLibs) {
271+
try {
272+
if (oldLib.lastModified() < aFewMomentsAgo) {
273+
oldLib.delete();
274+
}
275+
} catch (Exception e) {}
276+
}
277+
} catch (Exception e) {}
278+
}
279+
239280
private static CrtPlatform findPlatformImpl() {
240281
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
241282
String[] platforms = new String[] {

0 commit comments

Comments
 (0)