diff --git a/YKanBan.Tests/Storage/Workspace/WorkspaceLockTests.cs b/YKanBan.Tests/Storage/Workspace/WorkspaceLockTests.cs index 5806c23..04d4b41 100644 --- a/YKanBan.Tests/Storage/Workspace/WorkspaceLockTests.cs +++ b/YKanBan.Tests/Storage/Workspace/WorkspaceLockTests.cs @@ -5,7 +5,7 @@ using YKanBan.Tests.TestUtilities; namespace YKanBan.Tests.Storage.Workspace; /// -/// Tests for exclusive lock acquisition, diagnostics and release. +/// Tests for exclusive lock acquisition and release. /// [TestClass] public class WorkspaceLockTests @@ -42,7 +42,7 @@ public class WorkspaceLockTests } [TestMethod] - public void LockFileContainsHolderDiagnosticsAfterRelease() + public void LockFileIsEmptyAfterRelease() { using var directory = new TempDirectory(); WorkspaceInitializer.Initialize(directory.FullPath); @@ -51,9 +51,7 @@ public class WorkspaceLockTests { } - // The file survives release and carries PID / machine name / time for humans. - string content = File.ReadAllText(WorkspacePaths.LockFile(directory.FullPath)); - StringAssert.Contains(content, "pid="); - StringAssert.Contains(content, $"machine={Environment.MachineName}"); + // The lock file survives release but carries no content; only the OS handle matters. + Assert.AreEqual(0L, new FileInfo(WorkspacePaths.LockFile(directory.FullPath)).Length); } } diff --git a/YKanBan/Storage/StorageExceptions.cs b/YKanBan/Storage/StorageExceptions.cs index 4e6b69d..ce7bfb0 100644 --- a/YKanBan/Storage/StorageExceptions.cs +++ b/YKanBan/Storage/StorageExceptions.cs @@ -72,39 +72,15 @@ public sealed class WorkspaceLockException : YKanBanException /// public string LockFilePath { get; } - /// - /// Gets the best-effort holder diagnostics read from the lock file, or - /// when they could not be read. - /// - public string? HolderDiagnostics { get; } - /// /// Initializes the exception for a failed lock acquisition. /// /// The lock file that is already held. - /// Diagnostics of the current holder, when readable. /// The underlying I/O error. - public WorkspaceLockException(string lockFilePath, string? holderDiagnostics, Exception innerException) - : base(BuildMessage(lockFilePath, holderDiagnostics), innerException) + public WorkspaceLockException(string lockFilePath, Exception innerException) + : base($"The workspace is locked by another instance: {lockFilePath}", innerException) { LockFilePath = lockFilePath; - HolderDiagnostics = holderDiagnostics; - } - - /// - /// Composes the diagnostic message, appending holder details when they were readable. - /// - /// The lock file that is already held. - /// Diagnostics of the current holder, when readable. - /// The English diagnostic message. - private static string BuildMessage(string lockFilePath, string? holderDiagnostics) - { - string message = $"The workspace is locked by another instance: {lockFilePath}"; - if (!string.IsNullOrEmpty(holderDiagnostics)) - { - message += $" Lock holder: {holderDiagnostics}"; - } - return message; } } diff --git a/YKanBan/Storage/Workspace/WorkspaceLock.cs b/YKanBan/Storage/Workspace/WorkspaceLock.cs index b421770..c689b94 100644 --- a/YKanBan/Storage/Workspace/WorkspaceLock.cs +++ b/YKanBan/Storage/Workspace/WorkspaceLock.cs @@ -1,11 +1,9 @@ -using System.Text; - namespace YKanBan.Storage.Workspace; /// /// Exclusive, OS-enforced lock over a workspace's ykanban.lock. The handle is -/// held until disposal, so crashes and power loss release it automatically. -/// The lock file also carries PID / machine name / time for human diagnostics. +/// held until disposal, so crashes and power loss release it automatically. The +/// lock file is kept empty; only its exclusive OS handle carries meaning. /// public sealed class WorkspaceLock : IDisposable { @@ -28,7 +26,7 @@ public sealed class WorkspaceLock : IDisposable public string LockFilePath { get; } /// - /// Acquires the workspace lock, writing this process's diagnostics into it. + /// Acquires the workspace lock, leaving the lock file empty. /// /// The workspace folder to lock. /// The held lock; dispose it to release. @@ -40,7 +38,7 @@ public sealed class WorkspaceLock : IDisposable FileStream stream; try { - // FileShare.None gives the exclusive semantics; FileMode.Create truncates stale content. + // FileShare.None gives the exclusive semantics; FileMode.Create empties any stale file. stream = new FileStream(lockFilePath, FileMode.Create, FileAccess.Write, FileShare.None); } catch (DirectoryNotFoundException) @@ -49,40 +47,12 @@ public sealed class WorkspaceLock : IDisposable } catch (IOException ex) { - throw new WorkspaceLockException(lockFilePath, ReadDiagnosticsBestEffort(lockFilePath), ex); + throw new WorkspaceLockException(lockFilePath, ex); } - // Write holder diagnostics so humans can identify the owning instance from the file alone. - string diagnostics = $"pid={Environment.ProcessId};machine={Environment.MachineName};time={DateTimeOffset.UtcNow:O}"; - byte[] payload = Encoding.UTF8.GetBytes(diagnostics); - stream.Write(payload, 0, payload.Length); - stream.Flush(); return new WorkspaceLock(stream, lockFilePath); } - /// - /// Attempts to read holder diagnostics from a lock file we failed to - /// acquire. The read is best-effort: a live FileShare.None holder makes the - /// file unreadable, in which case is returned. - /// - /// The lock file to read. - /// The diagnostics text, or when unreadable. - private static string? ReadDiagnosticsBestEffort(string lockFilePath) - { - try - { - return File.ReadAllText(lockFilePath); - } - catch (IOException) - { - return null; - } - catch (UnauthorizedAccessException) - { - return null; - } - } - /// /// Releases the exclusive handle. The lock file itself is intentionally /// left on disk; the OS releases the handle even on a crash.