Skip to content

Commit cd845d4

Browse files
authored
fix: fix daemon.lock race on mutagen startup (#101)
I found the source of the issue where mutagen would fail to acquire the lock on `daemon.lock` at startup. The MutagenClient attempts to lock the `daemon.lock` file while it is starting, so that it can fail fast if the daemon is not running. While well meaning, this creates a race condition because as soon as we start the daemon process we create a MutagenClient so that we can talk to the daemon over its API. The MutagenClient might be holding the lock or have the lockfile open at the exact moment the daemon itself attempts to acquire and lock the file. The daemon immediately exits in that case and doesn't retry locking the file. I've just removed the preflight checks on the `daemon.lock`, since we don't want Coder Desktop to ever mess with that file (outside of tests).
1 parent 6b3851d commit cd845d4

File tree

2 files changed

+8
-20
lines changed

2 files changed

+8
-20
lines changed

App/Services/MutagenController.cs

+8
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,7 @@ private async Task<SyncSessionControllerStateModel> UpdateState(MutagenClient cl
441441
/// </summary>
442442
private async Task<MutagenClient> EnsureDaemon(CancellationToken ct)
443443
{
444+
_logger.LogDebug("EnsureDaemon called");
444445
ObjectDisposedException.ThrowIf(_disposing, typeof(MutagenController));
445446
if (_mutagenClient != null && _daemonProcess != null)
446447
return _mutagenClient;
@@ -479,12 +480,14 @@ private async Task<MutagenClient> EnsureDaemon(CancellationToken ct)
479480
/// </summary>
480481
private async Task<MutagenClient> StartDaemon(CancellationToken ct)
481482
{
483+
_logger.LogDebug("StartDaemon called");
482484
// Stop the running daemon
483485
if (_daemonProcess != null) await StopDaemon(ct);
484486

485487
// Attempt to stop any orphaned daemon
486488
try
487489
{
490+
_logger.LogDebug("creating MutagenClient to stop orphan");
488491
var client = new MutagenClient(_mutagenDataDirectory);
489492
await client.Daemon.TerminateAsync(new DaemonTerminateRequest(), cancellationToken: ct);
490493
}
@@ -496,6 +499,10 @@ private async Task<MutagenClient> StartDaemon(CancellationToken ct)
496499
{
497500
// Mainline; no daemon running.
498501
}
502+
finally
503+
{
504+
_logger.LogDebug("finished with orphan mutagen client");
505+
}
499506

500507
// If we get some failure while creating the log file or starting the process, we'll retry
501508
// it up to 5 times x 100ms. Those issues should resolve themselves quickly if they are
@@ -528,6 +535,7 @@ private async Task<MutagenClient> StartDaemon(CancellationToken ct)
528535
ct.ThrowIfCancellationRequested();
529536
try
530537
{
538+
_logger.LogDebug("creating mainline mutagen client");
531539
var client = new MutagenClient(_mutagenDataDirectory);
532540
_ = await client.Daemon.VersionAsync(new VersionRequest(), cancellationToken: ct);
533541
_mutagenClient = client;

MutagenSdk/MutagenClient.cs

-20
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,6 @@ public class MutagenClient : IDisposable
1616

1717
public MutagenClient(string dataDir)
1818
{
19-
// Check for the lock file first, since it should exist if it's running.
20-
var daemonLockFile = Path.Combine(dataDir, "daemon", "daemon.lock");
21-
if (!File.Exists(daemonLockFile))
22-
throw new FileNotFoundException(
23-
"Mutagen daemon lock file not found, did the mutagen daemon start successfully?", daemonLockFile);
24-
25-
// We should not be able to open the lock file.
26-
try
27-
{
28-
using var _ = File.Open(daemonLockFile, FileMode.Open, FileAccess.Write, FileShare.None);
29-
// We throw a FileNotFoundException if we could open the file because
30-
// it means the same thing and allows us to return the path nicely.
31-
throw new InvalidOperationException(
32-
$"Mutagen daemon lock file '{daemonLockFile}' is unlocked, did the mutagen daemon start successfully?");
33-
}
34-
catch (IOException)
35-
{
36-
// this is what we expect
37-
}
38-
3919
// Read the IPC named pipe address from the sock file.
4020
var daemonSockFile = Path.Combine(dataDir, "daemon", "daemon.sock");
4121
if (!File.Exists(daemonSockFile))

0 commit comments

Comments
 (0)