Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 44 additions & 43 deletions lib/src/main/java/org/automerge/LoadLibrary.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
import java.nio.file.*;
import java.util.Locale;
import java.util.Optional;
import java.util.Properties;
import java.util.UUID;
import java.util.stream.Stream;

class LoadLibrary {
public class LoadLibrary {

private static class Library {

public String target;
public String prefix;
public String suffix;
Expand Down Expand Up @@ -124,16 +124,44 @@ public boolean isAndroid() {
// The extension added to the library name to create a lockfile for it
private static final String LIBRARY_LOCK_EXT = ".lck";

static boolean loaded = false;
static volatile boolean loaded = false;
static volatile boolean versionChecked = false;

public static synchronized void initialize() {
// Only do this on first load and _dont_ do it on android, where libraries
// are provided by jniLibs and the version.properties file is removed from the
// jar by the build process
if (!loaded && !CURRENT_PLATFORM.isAndroid()) {
cleanup();
public static void initialize() {
// Early return if already loaded and version checked
if (loaded && versionChecked) {
return;
}

synchronized (LoadLibrary.class) {
// Double-check after acquiring lock
if (loaded && versionChecked) {
return;
}

// Load the library if not already loaded
if (!loaded) {
// Only do this on first load and _dont_ do it on android, where libraries
// are provided by jniLibs
if (!CURRENT_PLATFORM.isAndroid()) {
cleanup();
}
loadAutomergeJniLib();
loaded = true;
}
}

// Perform version check OUTSIDE the synchronized block to avoid deadlock
// when AutomergeSys static initializer calls back to initialize()
if (!versionChecked) {
String expectedLibVersion = BuildInfo.getExpectedRustLibVersion();
String actualLibVersion = AutomergeSys.rustLibVersion();
if (!expectedLibVersion.equals(actualLibVersion)) {
throw new RuntimeException("Automerge native library version mismatch. Expected " + expectedLibVersion
+ " but got " + actualLibVersion);
}
versionChecked = true;
}
loadAutomergeJniLib();
}

private static File getTempDir() {
Expand All @@ -152,7 +180,7 @@ private static File getTempDir() {
* instances of the JVM which have loaded the library.
*/
static void cleanup() {
String searchPattern = "automerge-jni-" + getVersion();
String searchPattern = "automerge-jni-" + BuildInfo.getExpectedRustLibVersion();

try (Stream<Path> dirList = Files.list(getTempDir().toPath())) {
dirList.filter(path -> !path.getFileName().toString().endsWith(LIBRARY_LOCK_EXT)
Expand All @@ -173,7 +201,7 @@ static void cleanup() {

private static void extractAndLoadLibraryFile(Library library, String targetFolder) {
String uuid = UUID.randomUUID().toString();
String extractedLibFileName = String.format("automerge-jni-%s-%s-%s.%s", getVersion(), uuid, library.target,
String extractedLibFileName = String.format("automerge-jni-%s-%s-%s.%s", BuildInfo.getExpectedRustLibVersion(), uuid, library.target,
library.suffix);
String extractedLckFileName = extractedLibFileName + LIBRARY_LOCK_EXT;

Expand Down Expand Up @@ -230,17 +258,16 @@ private static InputStream getResourceAsStream(String name) {
* <p>
* We first try the system library path, then if that fails we try and load one
* of the bundled libraries from this jar.
*
* <p>
* Note: This method does NOT set the loaded flag. That is the responsibility
* of the caller (initialize() method) to ensure proper synchronization.
*/
private static void loadAutomergeJniLib() {
if (loaded) {
return;
}

String libName = "automerge_jni_" + BuildInfo.getExpectedRustLibVersion().replace('.', '_');
// Try System.loadLibrary first
try {
System.loadLibrary(libName);
loaded = true;
return;
} catch (UnsatisfiedLinkError e) {
if (CURRENT_PLATFORM.isAndroid()) {
Expand All @@ -259,31 +286,5 @@ private static void loadAutomergeJniLib() {
Library lib = CURRENT_PLATFORM.library().get();
// Try extracting the library from jar
extractAndLoadLibraryFile(lib, tempFolder);

// Check that we have the correct version of the library (BuildInfo is
// generated by gradle and contains the version of the rust library we
// were built against)
String expectedLibVersion = BuildInfo.getExpectedRustLibVersion();
String actualLibVersion = AutomergeSys.rustLibVersion();
if (!expectedLibVersion.equals(actualLibVersion)) {
throw new RuntimeException("Automerge native library version mismatch. Expected " + expectedLibVersion
+ " but got " + actualLibVersion);
}

loaded = true;
}

public static String getVersion() {
InputStream input = getResourceAsStream("version.properties");

String version = "unknown";
try {
Properties versionData = new Properties();
versionData.load(input);
version = versionData.getProperty("version", version);
return version;
} catch (IOException e) {
throw new RuntimeException("unable to load version properties", e);
}
}
}
Loading