📁 Sangam Dhakal - Portfolio v2.0 - Netscape Navigator 4.0
_
□
×

πŸ“š Interview question

"The process cannot access the file because it is being used by another process." How do file locks work on Windows and Linux, how does FileShare affect them, and how do you debug and fix this error?

Share modes, mandatory vs advisory locking, byte-range locks, how .NET maps FileShare on each OS, finding the process holding a file, and safe cross-process coordination.

Mid-Level Debugging Intermediate concurrency file-locks file-system fileshare linux troubleshooting windows

🎯 Key points

  • FileAccess is what you will do; FileShare is what you allow others to do; every opener's access must be allowed by every other opener's share mode.
  • Windows enforces share modes (mandatory): a conflicting open fails with a sharing violation, Win32 error 32 (HResult 0x80070020), or 33 for a byte-range lock violation.
  • Linux has no share modes; .NET emulates FileShare.None with an advisory flock that only cooperating processes respect (it can be disabled with DOTNET_SYSTEM_IO_DISABLEFILELOCKING).
  • On Windows, delete and rename of an open file fail unless every current opener passed FileShare.Delete; on Linux unlink and rename always succeed and open handles keep the old inode.
  • Common holders: your own undisposed streams or Image.FromFile, antivirus and the search indexer, Excel, backup agents and IIS locking deployed DLLs.
  • Find the holder before guessing: Process Explorer, handle.exe or Resource Monitor on Windows; lsof, fuser and lslocks on Linux.
  • Network file systems (SMB, NFS, Azure Files) have their own lock semantics, leases and caching, and can leave stale locks after a client crash, so file locks are unreliable for cross-server coordination.
  • Retry only transient sharing violations with bounded backoff, and coordinate writers with a single-writer design, atomic rename, or a database lock or blob lease.

⏱ 30-second answer

When you open a file you choose an access (Read, Write) and a share mode (FileShare.None, Read, ReadWrite, Delete) saying what others may do while you hold it.

  • Windows enforces share modes (mandatory). A conflicting open fails with a sharing violation IOException (Win32 error 32).
  • Linux has no share modes. .NET emulates FileShare.None with an advisory flock, which only other cooperating processes respect.

Debug by finding the holder (Process Explorer / handle.exe on Windows, lsof / fuser on Linux). Fix by opening with the right FileShare, disposing handles promptly, retrying transient locks (antivirus, indexers) with backoff, and coordinating writers explicitly instead of relying on lock errors.

πŸ—£ Full answer (senior)

Share modes in .NET

new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
//                                  ↑ what I do       ↑ what I allow others to do

Both sides must agree. If process A opened with FileShare.Read and process B asks for FileAccess.Write, B fails. If A is writing and B opens for read with FileShare.Read (the default for File.OpenRead), B fails too, because B is saying "nobody else may write" while A already is.

Opener A holds B wants Windows Linux (.NET)
Write, FileShare.None Read Fails Fails only if B is .NET (advisory flock)
Write, FileShare.Read Read, FileShare.Read Fails (B disallows the writer) Succeeds
Write, FileShare.Read Read, FileShare.ReadWrite Succeeds Succeeds
Any Delete/rename Fails unless A allowed FileShare.Delete Succeeds (unlink)

Byte-range locks

FileStream.Lock(position, length) locks part of a file. On Windows it is mandatory for other writers; on Linux it maps to advisory fcntl locks.

Production example

A Windows service tailed a log file written by a third-party app and failed every few minutes with the sharing violation. It used File.ReadAllLines, which opens with FileShare.Read, so it refused to share with the writer. Changing to new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete) fixed it. A second, rarer failure came from antivirus scanning the file right after rotation; a short retry with backoff (3 tries, 100–400 ms) handled that.

πŸ” What the interviewer is checking

A very common real-world error. Checks understanding of share modes, OS differences, and practical debugging.

Green flags

  • Explains FileAccess vs FileShare and that both sides must be compatible.
  • Knows Windows is mandatory and Linux advisory.
  • Names tools to find the holder (Process Explorer, handle.exe, lsof, fuser).
  • Suggests retries for transient locks and better coordination designs.

Red flags

  • "Just restart the server" or catch {}.
  • Thinks FileShare.None protects data on Linux.

πŸ“š Learn in depth

1. Mandatory vs advisory

Windows (mandatory)                    Linux (advisory)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  open(W)    kernel     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  flock(EX)
β”‚ Process A   │──share=None──►│     β”‚ Process A   │─────────────► lock table
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜               β”‚     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  open(R)      β”‚     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  open(R) ── no flock call ──► OK!
β”‚ Process B   β”‚β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Ίβœ˜     β”‚ cat / vim   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  sharing        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                 violation

On Linux, any program that does not call flock simply ignores the lock. .NET applies the emulation to its own FileStreams, and it can be turned off with the DOTNET_SYSTEM_IO_DISABLEFILELOCKING environment variable (useful on some network file systems where flock misbehaves).

2. Deleting and renaming open files

  • Linux: you can delete or rename a file someone has open. The open handle keeps working on the old inode.
  • Windows: delete/rename fails unless every opener passed FileShare.Delete. This is why log rotation and deployments on Windows hit "file in use" errors.

3. Common holders on Windows

  • Your own process: a forgotten FileStream, Image.FromFile (keeps the file open until disposed), XmlReader without using.
  • Antivirus and Windows Search indexer: brief locks right after a file is written.
  • Explorer preview pane, Excel (opens CSVs exclusively), backup agents.
  • IIS / the app itself locking its DLLs during deployment (use app_offline.htm or shadow copying).

4. Finding the holder

OS Tool Command
Windows Process Explorer Find β†’ Find Handle or DLL β†’ file name
Windows Sysinternals handle handle.exe C:\data\report.csv
Windows Resource Monitor CPU β†’ Associated Handles β†’ search
Linux lsof lsof /data/report.csv
Linux fuser fuser -v /data/report.csv
Linux locks cat /proc/locks, lslocks

5. Coordinating writers properly

Locks on files are a weak coordination tool, especially on network shares (SMB/NFS lock semantics vary). Better options:

  • One writer process; others send it messages.
  • A named Mutex or Semaphore on one machine.
  • Distributed: a database row lock, a blob lease, or a lock service.
  • Write to unique temp names and atomically rename, so readers never see partial files.

6. Try it yourself

  1. Open a file in one console app with FileShare.None and keep it open. In a second app, try File.ReadAllText. Run on Windows and Linux; then on Linux try cat on the same file.
  2. On Windows, open the file in Excel and try to write it from .NET; use handle.exe to see who holds it.

πŸ’» Code

// Read a file that another process is actively writing (log tailing)
await using var fs = new FileStream(
    path,
    FileMode.Open,
    FileAccess.Read,
    FileShare.ReadWrite | FileShare.Delete); // allow writer and rotation
using var reader = new StreamReader(fs);
while (await reader.ReadLineAsync(ct) is { } line)
    Process(line);
// Retry transient sharing violations (antivirus, indexer) with backoff
const int SharingViolation = unchecked((int)0x80070020);
const int LockViolation = unchecked((int)0x80070021);

static async Task<T> WithFileRetryAsync<T>(Func<Task<T>> action, CancellationToken ct)
{
    for (int attempt = 1; ; attempt++)
    {
        try { return await action(); }
        catch (IOException ex) when (attempt < 4 &&
            (ex.HResult == SharingViolation || ex.HResult == LockViolation))
        {
            await Task.Delay(TimeSpan.FromMilliseconds(100 * Math.Pow(2, attempt - 1)), ct);
        }
    }
}
// Release image files immediately: copy into memory instead of Image.FromFile holding the file
byte[] bytes = await File.ReadAllBytesAsync(path, ct);
using var ms = new MemoryStream(bytes);
using var image = SixLabors.ImageSharp.Image.Load(ms);

⚠ Common mistakes

Mistake: Reading a file with the default share mode while another process writes it. Why it is wrong: File.OpenRead and File.ReadAllText use FileShare.Read, which means "nobody may write", so the open fails while a writer holds the file. Instead: Open with FileShare.ReadWrite, plus FileShare.Delete if the file is rotated.

Mistake: Assuming Linux enforces the same locks as Windows. Why it is wrong: Linux locks are advisory, so cat, vim or a Python script will read and write a file your app holds with FileShare.None. Instead: Do not rely on file locks for correctness on Linux; coordinate writers explicitly.

Mistake: Blaming another process when your own code holds the handle. Why it is wrong: Undisposed streams, readers or Image.FromFile keep the file open inside your own process. Instead: Check which PID holds it with Process Explorer or lsof, and wrap every stream in using.

Mistake: Using file locks on a network share to coordinate several servers. Why it is wrong: SMB and NFS lock behaviour differs by protocol version and client caching, and a crashed client can leave a stale lock that blocks everyone. Instead: Use a database row lock, an Azure Blob lease or a single-writer service.

Mistake: Retrying forever, or treating every IOException as a lock. Why it is wrong: Infinite retries hide real conflicts, and access-denied or disk-full errors will never succeed on retry. Instead: Retry only sharing and lock violations (by HResult) a few times with backoff, then fail with a message naming the file.

πŸ› Senior insight: trade-offs

I treat file locks as a platform detail to work around, not a coordination mechanism to build on. The Windows versus Linux difference means code that works on one platform can silently corrupt data on the other.

Coordination option Scope Reliability Notes
FileShare / flock One machine Mandatory on Windows, advisory on Linux Fine for avoiding accidental clashes
Named Mutex or Semaphore One machine Good Cross-process on the same host
Unique temp file + atomic rename One volume Good Readers never see partial files; no lock needed
Database row lock or sp_getapplock Many servers Good Uses infrastructure you already have
Blob lease (Azure Storage) Many servers Good, with lease expiry Expiry handles crashed holders
Single-writer service or queue Many servers Best Removes contention by design

In cloud and container setups, shared files on Azure Files or NFS are a common source of these errors, so I prefer object storage plus messaging over many instances writing the same file. When diagnosing, I always identify the actual holder process first; guessing wastes hours.

πŸ”— Follow-ups the interviewer may ask

  1. When: Candidate mentions handles kept open Why: Checks descriptor and handle fundamentals
    How to answer

    A file descriptor (Linux, a small integer) or handle (Windows, an opaque value) is the process's reference to a kernel object: an open file, socket, pipe, event, mutex, process or thread. The kernel owns the object, and the process holds an index into its own table.

    They are limited (e.g. ulimit -n often 1,024 soft by default on Linux shells, much higher in many containers and under systemd), so leaks eventually break the app.

    In .NET, SafeHandle wraps the raw value: it releases it in a critical finaliser if you forget, guarantees release even under thread aborts or async exceptions, and prevents handle recycling bugs by reference counting during P/Invoke calls. You still call Dispose (using) to release deterministically.

  2. When: Candidate suggests atomic rename instead of locks Why: Probes durable write patterns
    How to answer

    Data passes through several buffers: your own buffer β†’ StreamWriter/FileStream buffer (4 KiB default for FileStream) β†’ the OS page cache β†’ the disk's own cache β†’ the storage media.

    • Flush() moves data from the .NET buffer into the OS. It survives a process crash, not a power loss or kernel crash.
    • Flush(flushToDisk: true) calls fsync/FlushFileBuffers, asking the OS and device to persist it.
    • For safe updates, write to a temp file, Flush(true), then rename over the target (File.Move(temp, path, overwrite: true)), so readers see either the old or the new file, never half of one.

    Buffers exist for speed: many small writes become a few large system calls and disk operations.

πŸ›  Real-world problems: how you handle them

Log tailer fails with sharing violations on Windows
Situation
[Monitoring agent] on .NET [10] as a Windows Service reading a [vendor app's] log file every [30 s].
Signals
`IOException: The process cannot access the file ... because it is being used by another process` [several] times an hour; gaps in shipped logs.
Investigation
`handle.exe` showed the vendor app holding the file for write. Our code used `File.ReadAllLines`, which opens with `FileShare.Read`. A second, rarer failure lined up with [Defender] scans after rotation.
Resolution
Opened with `FileShare.ReadWrite | FileShare.Delete`, tracked the read offset, and added a 3-attempt backoff for sharing violations.
Prevention
A shared helper for reading files owned by other processes, an alert on repeated IOExceptions, and an integration test that writes and reads concurrently.

How to tell it (STAR)

**S:** Our [monitoring agent] kept missing log lines because it failed to open the vendor's log file. **T:** I needed reliable shipping without changing the vendor app. **A:** `handle.exe` showed the vendor writing the file, and our `ReadAllLines` call refused to share with a writer. I opened the file with `FileShare.ReadWrite | FileShare.Delete`, tracked the offset, and added a short retry for antivirus scans. **R:** Sharing errors dropped to [zero] and we stopped losing log lines.

↩ Usually asked after

More in File System

Last updated 23 Oct 2026.

🌐 Sangam's Webring

βͺ Previous 🏠 Main Next ⏩
πŸ’¬ Chat with me