Summary
dash-spv fails to initialize its storage on Android (target_os = "android") because LockFile::new() calls std::fs::File::try_lock(), which the Rust standard library does not implement on Android — it is a compile-time stub that unconditionally returns ErrorKind::Unsupported. dash-spv treats that error as fatal, so SPV sync can never start on Android.
Observed at runtime on a real device/emulator as:
SPV error: Write failed: Failed to acquire lock: try_lock() not supported
This blocks the entire Core (SPV) layer on Android — no header/filter sync, no balances, no L1 send, and by extension anything that depends on a funded Core wallet (asset-lock funding, etc.). iOS and desktop are unaffected.
Environment
- Target:
aarch64-linux-android (also affects x86_64-linux-android), Android 15 / API 35
- Rust: 1.92.0 (behavior is the same on every std since file-locking stabilized in 1.89)
- Consumer: Android JNI build of
dash-spv (via dashpay/platform rs-unified-sdk-jni → rs-platform-wallet → dash-spv)
Root cause
1. std::fs::File::try_lock() is unsupported on target_os = "android"
The flock-based implementation in std (library/std/src/sys/fs/unix.rs) is gated on a fixed target list that includes linux and Apple, but not android:
#[cfg(any(
target_os = "freebsd",
target_os = "fuchsia",
target_os = "linux", // <-- Android is a SEPARATE target_os, not covered
target_os = "netbsd",
target_os = "openbsd",
target_os = "cygwin",
target_os = "illumos",
target_vendor = "apple", // <-- this is why iOS/macOS work
))]
pub fn try_lock(&self) -> Result<(), TryLockError> { /* real flock(LOCK_EX|LOCK_NB) */ }
Android therefore hits the fallback stub:
#[cfg(not(any(/* ...same list... */)))]
pub fn try_lock(&self) -> Result<(), TryLockError> {
Err(TryLockError::Error(io::const_error!(
io::ErrorKind::Unsupported,
"try_lock() not supported",
)))
}
So on Android this is a compile-time guarantee of failure, not a runtime filesystem quirk. (lock, lock_shared, try_lock_shared are gated identically and are also unsupported on Android.)
2. dash-spv treats the Unsupported error as fatal
dash-spv/src/storage/lockfile.rs:
file.try_lock().map_err(|e| match e {
std::fs::TryLockError::WouldBlock => StorageError::DirectoryLocked(format!(
"Data directory '{}' is already in use by another process",
path.parent().map(|p| p.display().to_string()).unwrap_or_default()
)),
std::fs::TryLockError::Error(io_err) => {
StorageError::WriteFailed(format!("Failed to acquire lock: {}", io_err)) // <-- Android lands here
}
})?;
The TryLockError::Error(io_err) arm does not distinguish ErrorKind::Unsupported from a genuine I/O failure, so storage init returns Err and SPV never starts.
Impact
- Android: Core/SPV completely non-functional — storage cannot be opened.
- iOS, macOS, Linux desktop: unaffected (their targets implement
flock).
The lock file is a best-effort guard against multiple processes sharing one data directory. On mobile the SDK runs as a single app process that owns its data dir, so the advisory lock provides no value there and its absence is safe.
Proposed fix
Treat Unsupported as non-fatal in LockFile::new() — skip the advisory lock (with a warning) and continue:
match file.try_lock() {
Ok(()) => {}
Err(std::fs::TryLockError::WouldBlock) => {
return Err(StorageError::DirectoryLocked(format!(
"Data directory '{}' is already in use by another process",
path.parent().map(|p| p.display().to_string()).unwrap_or_default()
)));
}
// Platforms without advisory file locking (e.g. Android) — single-process,
// so proceeding without the cross-process guard is safe.
Err(std::fs::TryLockError::Error(io_err))
if io_err.kind() == std::io::ErrorKind::Unsupported =>
{
tracing::warn!(
"advisory file lock not supported on this platform ({io_err}); \
continuing without multi-process protection"
);
}
Err(std::fs::TryLockError::Error(io_err)) => {
return Err(StorageError::WriteFailed(format!(
"Failed to acquire lock: {io_err}"
)));
}
}
Alternatives considered:
#[cfg(target_os = "android")]-gate the whole lock — works, but handling ErrorKind::Unsupported generically is more robust (covers any non-flock target and keeps the fast path unchanged on desktop).
Happy to open a PR with the graceful-degradation approach plus a small test if that's the preferred direction.
Repro
- Build
dash-spv for aarch64-linux-android and open a storage instance whose data dir triggers LockFile::new().
- Storage init returns
StorageError::WriteFailed("Failed to acquire lock: try_lock() not supported").
Summary
dash-spvfails to initialize its storage on Android (target_os = "android") becauseLockFile::new()callsstd::fs::File::try_lock(), which the Rust standard library does not implement on Android — it is a compile-time stub that unconditionally returnsErrorKind::Unsupported.dash-spvtreats that error as fatal, so SPV sync can never start on Android.Observed at runtime on a real device/emulator as:
This blocks the entire Core (SPV) layer on Android — no header/filter sync, no balances, no L1 send, and by extension anything that depends on a funded Core wallet (asset-lock funding, etc.). iOS and desktop are unaffected.
Environment
aarch64-linux-android(also affectsx86_64-linux-android), Android 15 / API 35dash-spv(viadashpay/platformrs-unified-sdk-jni→rs-platform-wallet→dash-spv)Root cause
1.
std::fs::File::try_lock()is unsupported ontarget_os = "android"The
flock-based implementation in std (library/std/src/sys/fs/unix.rs) is gated on a fixed target list that includeslinuxand Apple, but notandroid:Android therefore hits the fallback stub:
So on Android this is a compile-time guarantee of failure, not a runtime filesystem quirk. (
lock,lock_shared,try_lock_sharedare gated identically and are also unsupported on Android.)2.
dash-spvtreats theUnsupportederror as fataldash-spv/src/storage/lockfile.rs:The
TryLockError::Error(io_err)arm does not distinguishErrorKind::Unsupportedfrom a genuine I/O failure, so storage init returnsErrand SPV never starts.Impact
flock).The lock file is a best-effort guard against multiple processes sharing one data directory. On mobile the SDK runs as a single app process that owns its data dir, so the advisory lock provides no value there and its absence is safe.
Proposed fix
Treat
Unsupportedas non-fatal inLockFile::new()— skip the advisory lock (with a warning) and continue:Alternatives considered:
#[cfg(target_os = "android")]-gate the whole lock — works, but handlingErrorKind::Unsupportedgenerically is more robust (covers any non-flock target and keeps the fast path unchanged on desktop).Happy to open a PR with the graceful-degradation approach plus a small test if that's the preferred direction.
Repro
dash-spvforaarch64-linux-androidand open a storage instance whose data dir triggersLockFile::new().StorageError::WriteFailed("Failed to acquire lock: try_lock() not supported").