Skip to content
Merged
Show file tree
Hide file tree
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
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
## [Unreleased]

### Fixed

- **Android deploys no longer crash concurrent Gradle builds (MOB-468).**
Before every `assembleDebug`, mob_dev deleted `~/.gradle/native/**/*.lock`
as "stale locks". native-platform holds those only while it extracts (they
are never left stale), and their content marks a finished extraction: without one, the next Gradle JVM rewrites
`libnative-platform.so` in place, over the inode every running Gradle JVM
(another project's build, a `--no-daemon` wrapper and its daemon) has
mapped, and those JVMs die with SIGSEGV (`ld-linux-x86-64.so.2+0x10f2` in
the wrapper, a JNI `stat`/`getPid` in the daemon). The markers are kept now;
the wrapper, daemon-registry and cache locks are still cleared.

## [0.7.22] - 2026-10-09

### Fixed
Expand Down
19 changes: 13 additions & 6 deletions lib/mob_dev/native_build.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1574,15 +1574,22 @@ defmodule MobDev.NativeBuild do
# Remove stale Gradle lock files left behind when a build is interrupted
# (Ctrl+C, kill, etc.). These cause the next run to hang indefinitely while
# the wrapper waits to acquire the lock.
defp clear_stale_gradle_locks do
gradle_home =
System.get_env("GRADLE_USER_HOME") ||
Path.join(System.user_home!(), ".gradle")

#
# Never `<gradle_home>/native/**/*.lock` (MOB-468): native-platform holds
# each only while it extracts (never left stale), and its 1-byte content
# marks a finished extraction. Without one, the next Gradle JVM re-extracts
# `libnative-platform.so` (and friends) in place, over the inode every
# running Gradle JVM has mapped, and those JVMs SIGSEGV (the wrapper in
# `ld-linux` at exit, the daemon in a JNI `stat`/`getPid`).
@doc false
@spec clear_stale_gradle_locks(Path.t()) :: :ok
def clear_stale_gradle_locks(
gradle_home \\ System.get_env("GRADLE_USER_HOME") ||
Path.join(System.user_home!(), ".gradle")
) do
patterns = [
"#{gradle_home}/daemon/*/registry.bin.lock",
"#{gradle_home}/wrapper/dists/**/*.lck",
"#{gradle_home}/native/**/*.lock",
"#{gradle_home}/caches/**/*.lock",
"#{gradle_home}/caches/**/*.lck"
]
Expand Down
28 changes: 28 additions & 0 deletions test/mob_dev/native_build_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2400,6 +2400,34 @@ defmodule MobDev.NativeBuildTest do
end
end

describe "clear_stale_gradle_locks/1" do
@describetag :tmp_dir

# MOB-468: native-platform's `<lib>.so.lock` marks a finished extraction.
# Deleting it made the next Gradle JVM rewrite the mapped .so in place and
# crashed every concurrent Gradle JVM (SIGSEGV in ld-linux / JNI).
test "clears stale wrapper, daemon and cache locks but keeps native-platform's extraction markers",
%{tmp_dir: home} do
marker = Path.join(home, "native/68d5/linux-amd64/libnative-platform.so.lock")
lib = Path.join(home, "native/68d5/linux-amd64/libnative-platform.so")

stale = [
Path.join(home, "daemon/8.2.1/registry.bin.lock"),
Path.join(home, "caches/8.2.1/fileHashes/fileHashes.lock")
]

for f <- [marker, lib | stale] do
File.mkdir_p!(Path.dirname(f))
File.write!(f, <<1>>)
end

assert NativeBuild.clear_stale_gradle_locks(home) == :ok
assert File.exists?(marker)
assert File.exists?(lib)
for f <- stale, do: refute(File.exists?(f), f)
end
end

# ── ios_bundle_id/1 ───────────────────────────────────────────────────────────
#
# The sim bundle, the device bundle, and code signing must all stamp the
Expand Down
Loading