Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,19 @@
/** Creates and deletes lock files. */
public class LockFile implements Closeable {

private static final ConcurrentHashMap<Path, Lock> lockMap = new ConcurrentHashMap<>();
// In Gradle composite builds with independent buildSrc directories, each included build loads
// jib-core in its own classloader. A normal static field creates separate lockMap instances per
// classloader, failing to prevent concurrent FileChannel.lock() calls on the same file and
// causing OverlappingFileLockException (#3347). This is admittedly a hack, but there is no
// clean way to share state across classloaders in the same JVM. System.getProperties() returns
// the same Hashtable regardless of classloader, so it serves as a JVM-global namespace.
// The map's key/value types (Path, Lock) are bootstrap-loaded, so they're safe to cast.
@SuppressWarnings("unchecked")
private static final ConcurrentHashMap<Path, Lock> lockMap =
(ConcurrentHashMap<Path, Lock>)
System.getProperties()
.computeIfAbsent(
"jib.lockFile.lockMap", key -> new ConcurrentHashMap<Path, Lock>());
Comment on lines +42 to +46
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using System.getProperties() as a global registry is an effective workaround for cross-classloader state sharing in Gradle composite builds, but it introduces a few side effects:

  1. Properties.store() side effect: java.util.Properties is designed for String keys and values. Calling store() or save() on System.getProperties() will now throw a ClassCastException because it iterates over all entries and expects values to be strings. Any other plugin or tool in the same JVM that attempts to persist system properties (e.g., for build reporting or environment capturing) will crash.
  2. Thread Safety on older Java 8: Hashtable (the parent of Properties) did not override computeIfAbsent until Java 8u40. On older updates, it uses a non-atomic default implementation from the Map interface, which could theoretically lead to multiple map instances being created if multiple classloaders initialize this class simultaneously, defeating the purpose of the fix.

While these are likely acceptable risks given the constraints of Gradle's classloader isolation, it is worth noting these global side effects in the code comments.


private final Path lockFilePath;
private final FileLock fileLock;
Expand Down
Loading