diff --git a/readme.md b/readme.md index 9b458b5..f048dd6 100755 --- a/readme.md +++ b/readme.md @@ -75,10 +75,16 @@ Some sample code to use the terminal: ## Changes +### 0.2 + +Fixes to make native library extraction multi-process safe. + ### 0.1 Initial release +# Development + ## Building You will need to use the Gradle wrapper. Just run `gradlew` in the root directory. @@ -174,7 +180,6 @@ You can run `$INSTALL_DIR/bin/native-platform-test` to run the test application. * String names for errno values. * Split into multiple projects. * Convert to c. -* Make native library extraction multi-process safe. * Use fully decomposed form for unicode file names on hfs+ filesystems. * Extend FileSystem to deal with removable media. diff --git a/src/main/java/net/rubygrapefruit/platform/Native.java b/src/main/java/net/rubygrapefruit/platform/Native.java index c9a5bfd..85efa23 100755 --- a/src/main/java/net/rubygrapefruit/platform/Native.java +++ b/src/main/java/net/rubygrapefruit/platform/Native.java @@ -19,22 +19,17 @@ public class Native { private Native() { } - /** - * Returns the version of the native library implementations used by this class. - */ - @ThreadSafe - static public String getNativeVersion() { - return String.valueOf(NativeLibraryFunctions.VERSION); - } - /** * Initialises the native integration, if not already initialized. * * @param extractDir The directory to extract native resources into. May be null, in which case a default is * selected. + * + * @throws NativeIntegrationUnavailableException When native integration is not available on the current machine. + * @throws NativeException On failure to load the native integration. */ @ThreadSafe - static public void init(File extractDir) { + static public void init(File extractDir) throws NativeIntegrationUnavailableException, NativeException { synchronized (Native.class) { if (!loaded) { Platform platform = Platform.current(); diff --git a/src/main/java/net/rubygrapefruit/platform/internal/NativeLibraryLocator.java b/src/main/java/net/rubygrapefruit/platform/internal/NativeLibraryLocator.java index 63f415a..e1ce674 100644 --- a/src/main/java/net/rubygrapefruit/platform/internal/NativeLibraryLocator.java +++ b/src/main/java/net/rubygrapefruit/platform/internal/NativeLibraryLocator.java @@ -1,9 +1,11 @@ package net.rubygrapefruit.platform.internal; import net.rubygrapefruit.platform.NativeException; +import net.rubygrapefruit.platform.internal.jni.NativeLibraryFunctions; import java.io.*; import java.net.URL; +import java.nio.channels.FileLock; public class NativeLibraryLocator { private final File extractDir; @@ -13,22 +15,47 @@ public class NativeLibraryLocator { } public File find(String libraryName) throws IOException { - File libFile; - URL resource = getClass().getClassLoader().getResource(libraryName); - if (resource != null) { - File libDir = extractDir; - if (libDir == null) { - libDir = File.createTempFile("native-platform", "dir"); + if (extractDir != null) { + File libFile = new File(extractDir, String.format("%s/%s", NativeLibraryFunctions.VERSION, libraryName)); + File lockFile = new File(libFile.getParentFile(), libFile.getName() + ".lock"); + lockFile.getParentFile().mkdirs(); + lockFile.createNewFile(); + RandomAccessFile lockFileAccess = new RandomAccessFile(lockFile, "rw"); + try { + // Take exclusive lock on lock file + FileLock lock = lockFileAccess.getChannel().lock(); + if (lockFile.length() > 0 && lockFileAccess.readBoolean()) { + // Library has been extracted + return libFile; + } + URL resource = getClass().getClassLoader().getResource(libraryName); + if (resource != null) { + // Extract library and write marker to lock file + libFile.getParentFile().mkdirs(); + copy(resource, libFile); + lockFileAccess.seek(0); + lockFileAccess.writeBoolean(true); + return libFile; + } + } finally { + // Also releases lock + lockFileAccess.close(); + } + } else { + URL resource = getClass().getClassLoader().getResource(libraryName); + if (resource != null) { + File libFile; + File libDir = File.createTempFile("native-platform", "dir"); libDir.delete(); libDir.mkdirs(); + libFile = new File(libDir, libraryName); + libFile.deleteOnExit(); + copy(resource, libFile); + return libFile; } - libFile = new File(libDir, libraryName); - libFile.deleteOnExit(); - copy(resource, libFile); - return libFile; } - libFile = new File("build/binaries/" + libraryName); + File libFile = new File("build/binaries/" + libraryName); if (libFile.isFile()) { return libFile; } diff --git a/test-app/build.gradle b/test-app/build.gradle index a341b78..28013c1 100644 --- a/test-app/build.gradle +++ b/test-app/build.gradle @@ -17,6 +17,10 @@ if (project.hasProperty('release')) { } } +dependencies { + compile 'net.sf.jopt-simple:jopt-simple:4.2' +} + configurations.archives.artifacts.clear() artifacts { archives distZip diff --git a/test-app/src/main/java/net/rubygrapefruit/platform/test/Main.java b/test-app/src/main/java/net/rubygrapefruit/platform/test/Main.java index 1a9a652..2bfe5ee 100755 --- a/test-app/src/main/java/net/rubygrapefruit/platform/test/Main.java +++ b/test-app/src/main/java/net/rubygrapefruit/platform/test/Main.java @@ -1,10 +1,33 @@ package net.rubygrapefruit.platform.test; +import joptsimple.OptionException; +import joptsimple.OptionParser; +import joptsimple.OptionSet; import net.rubygrapefruit.platform.*; import net.rubygrapefruit.platform.Process; +import java.io.File; +import java.io.IOException; + public class Main { - public static void main(String[] args) { + public static void main(String[] args) throws IOException { + OptionParser optionParser = new OptionParser(); + optionParser.accepts("cache-dir", "The directory to cache native libraries in").withRequiredArg(); + + OptionSet result = null; + try { + result = optionParser.parse(args); + } catch (OptionException e) { + System.err.println(e.getMessage()); + System.err.println(); + optionParser.printHelpOn(System.err); + System.exit(1); + } + + if (result.has("cache-dir")) { + Native.init(new File(result.valueOf("cache-dir").toString())); + } + System.out.println(); System.out.println("* OS: " + System.getProperty("os.name") + ' ' + System.getProperty("os.version") + ' ' + System.getProperty("os.arch")); System.out.println("* JVM: " + System.getProperty("java.vm.vendor") + ' ' + System.getProperty("java.version"));