Skip to content

[hooks_runner] TimeoutException when file locking is unsupported by the filesystem (e.g. SMB shares on macOS) #3651

Closed

Description

@bkonyi

Problem Description

When running builds (such as flutter build ios or flutter run) on a project hosted on a filesystem where advisory byte-range file locking is unsupported (such as macOS SMB/smbfs network shares), hooks_runner fails after 5 minutes with a TimeoutException:

Could not acquire the lock to /Volumes/APP/.dart_tool/hooks_runner/shared/objective_c/.lock.
Target build_hooks failed: TimeoutException after 0:05:00.000000: Could not acquire the lock to /Volumes/APP/.dart_tool/hooks_runner/shared/objective_c/.lock.
Failed to package /Volumes/APP.
Command PhaseScriptExecution failed with a nonzero exit code

Root Cause

In pkgs/hooks_runner/lib/src/locking/locking.dart, _runUnderFileLock attempts to lock .lock:

while (timeout == null || stopwatch.elapsed < timeout) {
  try {
    await randomAccessFile.lock(FileLock.exclusive);
    ...
  } on FileSystemException {
    if (errorFromCallback) {
      rethrow;
    }
    if (!printed) {
      logger?.finer(
        'Waiting to be able to obtain lock of directory: ${file.path}.',
      );
      printed = true;
    }
    // Don't busy wait, give the CPU some rest.
    // Magic constant taken from flutter_tools for startup lock.
    await Future<void>.delayed(const Duration(milliseconds: 50));
  }
}

final message = 'Could not acquire the lock to ${file.path}.';
logger?.severe(message);
throw TimeoutException(message, timeout);
  1. On POSIX systems, randomAccessFile.lock() delegates to fcntl(fd, F_SETLK, &fl).
  2. When the underlying filesystem does not support advisory locking (such as macOS SMB mounts, NFS, or special virtual filesystems), fcntl returns an OS error code such as ENOTSUP (45), ENOLCK (77), or EINVAL (22). Dart surfaces this as a native FileSystemException with an OSError.
  3. _runUnderFileLock catches all FileSystemException instances and unconditionally treats them as lock contention, sleeping for 50ms and retrying until the 5-minute timeout expires.

Proposed Fix

Inspect e.osError?.errorCode in _runUnderFileLock. If the error code indicates that locking is unsupported rather than genuine contention (e.g., outside Linux EAGAIN 11, POSIX EACCES 13, macOS EAGAIN 35, Windows sharing/lock violations 32/33):

  • Log that locking is unsupported on the current filesystem.
  • Proceed to execute callback() without holding the lock rather than timing out.

Related Issues

Activity

  1. dcharkes commented on Sep 16, 2026

    @dcharkes
    Collaborator

    Thanks for the report @bkonyi 🙏

    • Proceed to execute callback() without holding the lock rather than timing out.

    This will lead to race conditions between hook invocations. Hook authors can then no longer rely on that they are not invoked concurrently for the same input configuration. Which would then lead to invoking clang/xcode/msvc/CMake or downloading the same dylib on the same dir concurrently. Pushing the problem to hook authors.

    Some wild ideas to do cross-process mutex without process file locks:

    • Acquiring and holding a port open, e.g. localhost:3843
    • Existence of a file on disk, if you created it you have the lock, and you update the contents each minute. If the file on disk is older than 2 minutes assume the process owning it died and allow to create it.
  2. added this to the Native Assets v1.x milestone on Sep 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions