// Native-target primitives and `with_lock` impl. The heavy lifting
// (open(O_EXCL) / stat / unlink / utimes / setenv) happens in C via
// native_stub.c; this file provides the extern "C" bindings and
// wraps release in a MoonBit `defer` so it fires regardless of how
// `f` exits.
///| O_EXCL sentinel acquire. Returns true on fresh create, false if
///| the sentinel already exists.
fn _try_acquire(path : String) -> Bool {
let bytes = @utf8.encode(path.view())
mnemo_lock_try_acquire(bytes) == 1
}
///| stat(2) mtime in epoch ms, or -1 on any error / missing file.
fn _stat_mtime_ms(path : String) -> Int64 {
let bytes = @utf8.encode(path.view())
mnemo_stat_mtime_ms(bytes)
}
#borrow(path)
extern "C" fn mnemo_lock_try_acquire(path : Bytes) -> Int = "mnemo_lock_try_acquire"
#borrow(path)
extern "C" fn mnemo_stat_mtime_ms(path : Bytes) -> Int64 = "mnemo_stat_mtime_ms"
#borrow(path)
extern "C" fn mnemo_lock_force_remove(path : Bytes) -> Int = "mnemo_lock_force_remove"
#borrow(path, content)
extern "C" fn mnemo_atomic_write_c(path : Bytes, content : Bytes) -> Int = "mnemo_atomic_write"
///| Acquires a lock on `path`, runs `f`, then releases via `defer` so
///| the sentinel is cleaned up even if `f` raises.
pub async fn[T] with_lock(path : String, f : async () -> T) -> T {
let ttl_ms = _effective_stale_ttl_ms()
let mut attempts = 0
while !_try_acquire(path) {
attempts = attempts + 1
if attempts % 100 == 0 {
let mtime = _stat_mtime_ms(path)
if mtime > 0L {
let age = _now_ms_lock() - mtime
if age > ttl_ms.to_int64() {
let _ = _force_remove(path)
continue
}
}
}
let max_attempts = (ttl_ms / 10) + 100
if attempts > max_attempts {
abort("lock timeout: " + path)
}
sleep_ms(10)
}
defer _release(path)
f()
}
// Test-helper stubs for native. They're unused from tests that actually
// require JS semantics (utimes backdating + env manipulation); if a
// native test run needs them they can be implemented in native_stub.c.
///| utimes(2) shim — not yet implemented for native; the stale-sentinel
///| test is gated to JS. Stubs out to a no-op so the file compiles.
fn ffi_set_mtime(_path : String, _mtime_ms : Int64) -> Unit {
()
}
///| setenv(3) shim — no-op on native. The stale-sentinel test reads
///| MNEMO_LOCK_STALE_TTL_MS via @env.get_env_var, so if the test is
///| enabled on native it should be set through the environment
///| before launching the test binary rather than mutated in-process.
fn ffi_set_env_lock(_key : String, _value : String) -> Unit {
()
}
///| unsetenv(3) shim — no-op on native. Paired with ffi_set_env_lock.
fn ffi_unset_env_lock(_key : String) -> Unit {
()
}