stride3d/stride · error · NotImplementedException

GlobalMutex.Wait() is implemented only for…

Error message

GlobalMutex.Wait() is implemented only for millisecondsTimeout 0 or -1

What it means

FileLock.Wait only supports millisecondsTimeout of 0 (single try) or -1 (infinite wait) because it maps directly onto GlobalMutex.Wait semantics. Any other timeout throws NotImplementedException from the Wait method (also reachable via TryLock).

Solutions

  1. Use 0 for a single non-blocking attempt and poll in your own loop with Thread.Sleep
  2. Use -1 to block indefinitely until the lock is acquired
  3. Wrap the call site with your own deadline logic around repeated 0-timeout attempts
  4. Check library version/upstream for bounded-timeout support before using other values

Example fix

// before
var l = FileLock.Wait(name, 5000);
// after
var l = FileLock.Wait(name, -1); // or poll with 0 in your own loop
Defensive patterns

Strategy: try-catch

Validate before calling

if (timeout != 0 && timeout != -1)
    throw new InvalidOperationException("FileLock.Wait only supports 0 or -1");

Try / catch

try { l = FileLock.Wait(name, 0); }
catch (NotImplementedException) { /* fall back to manual polling loop */ }

Prevention

When it happens

Trigger: Calling FileLock.Wait(name, timeout) with any value other than 0 or -1, e.g. Wait("mylock", 5000). Also via TryLock if it forwards a non-{0,-1} timeout.

Common situations: Developers assuming a standard bounded-timeout locking API; migrating from Mutex.WaitOne(TimeSpan) style code; adding retry loops with finite timeouts.

Understand the failure class

Related errors


AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/52bc84c659a8c4d7. Report an issue: GitHub.

Appendix: source

Thrown at sources/core/Stride.Core.Design/Windows/FileLock.cs:87

    /// <summary>
    /// Tries to take ownership of the file lock within a given delay.
    /// </summary>
    /// <param name="name">A unique name identifying the file lock.</param>
    /// <param name="millisecondsTimeout">The maximum delay to wait before returning, in milliseconds.</param>
    /// <returns>A new instance of <see cref="FileLock"/> if the ownership could be taken, <c>null</c> otherwise.</returns>
    /// <remarks>
    /// The returned <see cref="FileLock"/> must be disposed to release the file lock.
    /// Calling this method with 0 for <see paramref="millisecondsTimeout"/> is equivalent to call <see cref="TryLock"/>.
    /// Calling this method with a negative value for <see paramref="millisecondsTimeout"/> is equivalent to call <see cref="Wait(string)"/>.
    /// </remarks>
    public static FileLock? Wait(string name, int millisecondsTimeout)
    {
        var fileLock = BuildFileLock(name);
        try
        {
            if (millisecondsTimeout != 0 && millisecondsTimeout != -1)
                throw new NotImplementedException("GlobalMutex.Wait() is implemented only for millisecondsTimeout 0 or -1");

            bool hasHandle = NativeLockFile.TryLockFile(fileLock, 0, uint.MaxValue, true, millisecondsTimeout == 0);
            return hasHandle ? new FileLock(fileLock) : null;
        }
        catch (AbandonedMutexException)
        {
            return new FileLock(fileLock);
        }
    }

    private static FileStream BuildFileLock(string name)
    {
        // We open with FileShare.ReadWrite mode so that we can implement `Wait`.
        return new FileStream(name, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite);
    }
}

View on GitHub (pinned to 96fad776d2)