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.