stride3d/stride · error · IOException
Couldn't unlock file.
Error message
Couldn't unlock file.
What it means
TryUnlockFile releases a byte-range lock previously taken on a file. On Windows it calls UnlockFileEx; if the OS reports the unlock failed (e.g. the range was not locked by this handle), an IOException is thrown.
Solutions
- Ensure every unlock uses the exact offset and count used when locking, on the same FileStream instance
- Guard against double-unlock with a flag or try/finally pairing lock/unlock
- Verify the handle still owns the lock (no intermediate close/reopen)
- Check the underlying Win32 error via the inner details; treat EINVAL-style 'not locked' as a logic bug in the caller
Example fix
// before
lockFile.TryLockFile(0, len);
// ... exception path skips unlock ...
lockFile.TryUnlockFile(0, len); // may throw if never locked
// after
lockFile.TryLockFile(0, len);
try { /* work */ }
finally { if (isLocked) lockFile.TryUnlockFile(0, len); } Defensive patterns
Strategy: try-catch
Validate before calling
// track locked ranges in a HashSet<(long offset,long count)> owned by the same FileStream bool owned = lockedRanges.Contains((offset, count));
Try / catch
try { lockFile.TryUnlockFile(offset, count); }
catch (IOException) { /* range not locked by this handle — log and continue */ }
finally { lockedRanges.Remove((offset, count)); } Prevention
- Pair every TryLockFile with exactly one unlock using identical offset/count
- Use try/finally so exceptions never skip unlock
- Never unlock on a different FileStream instance than the one that locked
When it happens
Trigger: Calling TryUnlockFile on a range (offset/count) that was never locked via the matching TryLockFile on the same FileStream/SafeFileHandle, or with an offset/count that doesn't exactly match the locked region.
Common situations: Double-unlock after an exception, unlocking after the stream was reopened (locks are per-handle), mismatched offset/count values between lock and unlock calls.
Understand the failure class
Background: "open() failed", "failed to open file", "cannot create file" — what a file open error means and how to fix it — this error's family across 42 libraries.
Related errors
- A file path cannot end with with directory char '\' or '/'…
- Base path must be absolute, got
- Base path must be non-empty (use null for passthrough).
- Build manifest [ ] must exist
- Can't read beyond end of stream.
AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14).
Data as JSON: /api/errors/5257068ece643a94.
Report an issue: GitHub.
Appendix: source
Thrown at sources/core/Stride.Core.IO/NativeLockFile.cs:154
if (Platform.Type == PlatformType.Android)
{
count = (count + offset > int.MaxValue) ? int.MaxValue - offset : count;
}
if (OperatingSystem.IsWindows())
{
var countLow = (uint)count;
var countHigh = (uint)(count >> 32);
var overlapped = new NativeOverlapped()
{
OffsetLow = (int)(offset & uint.MaxValue),
OffsetHigh = (int)(offset >> 32),
};
if (!UnlockFileEx(fileStream.SafeFileHandle, 0, countLow, countHigh, ref overlapped))
{
throw new IOException("Couldn't unlock file.");
}
}
else
{
UnixUnlock(fileStream, offset, count);
}
}
/// <summary>
/// Lock on Unix: on Linux/Android, tries OFD locks first (per-handle, like Windows LockFileEx),
/// falls back to standard fcntl (per-process) if OFD is not supported.
/// On macOS/iOS, uses standard fcntl directly (OFD not available on XNU).
/// </summary>
private static FileLockResult UnixLock(FileStream fileStream, long offset, long count, bool exclusive)
{
int fd = fileStream.SafeFileHandle.DangerousGetHandle().ToInt32();
var lockInfo = new FlockView on GitHub (pinned to 96fad776d2)