From 33fdcc1ac4ad014ec43837ff7a3bc354b59cd772 Mon Sep 17 00:00:00 2001 From: jmd Date: Sat, 3 May 2025 16:30:04 +0200 Subject: [PATCH 1/7] spoike --- .../JsonIndexManagerTest.cs | 17 ++++- .../Writer/IJsonIndexWriter.cs | 3 +- src/DotJEM.Json.Index2.Test/JsonIndexTest.cs | 1 + .../IO/IndexWriterSafeProxy.cs | 28 +++++++- .../IO/JsonIndexWriterManager.cs | 14 +++- src/DotJEM.Json.Index2/Leases/Lease.cs | 3 + src/DotJEM.Json.Index2/Leases/LeaseManager.cs | 69 ++++++++++++++++--- .../Storage/IJsonIndexStorageManager.cs | 3 +- 8 files changed, 117 insertions(+), 21 deletions(-) diff --git a/src/DotJEM.Json.Index2.Management.Test/JsonIndexManagerTest.cs b/src/DotJEM.Json.Index2.Management.Test/JsonIndexManagerTest.cs index 95c7c13..ae78d5b 100644 --- a/src/DotJEM.Json.Index2.Management.Test/JsonIndexManagerTest.cs +++ b/src/DotJEM.Json.Index2.Management.Test/JsonIndexManagerTest.cs @@ -21,7 +21,7 @@ namespace DotJEM.Json.Index2.Management.Test; [TestFixture] public class JsonIndexManagerTest { - [Test, Explicit] + [Test, Explicit, MaxTime(1000*60*12)] public async Task IndexWriterShouldNotBeDisposed() { using TestDirectory dir = new(); @@ -36,7 +36,7 @@ public async Task IndexWriterShouldNotBeDisposed() ISnapshotStrategy strategy = new ZipSnapshotStrategy(dir.Info.CreateSubdirectory("snapshot").FullName); IJsonIndexSnapshotManager snapshots = new JsonIndexSnapshotManager(index, strategy, scheduler, "60h"); IJsonIndexManager manager = new JsonIndexManager(source, snapshots, index); - + index.Commit(); InfoStreamExceptionEvent? disposedEvent = null; InfoStreamExceptionEvent? exceptionEvent = null; manager.InfoStream @@ -44,6 +44,7 @@ public async Task IndexWriterShouldNotBeDisposed() .Where(@event => @event.Exception is ObjectDisposedException) .Subscribe(@event => { + Console.WriteLine($"Event {@event.Message};"); disposedEvent = @event; }); manager.InfoStream @@ -51,6 +52,7 @@ public async Task IndexWriterShouldNotBeDisposed() .Where(@event => @event.Exception.Message != "Can't write to an existing snapshot.") .Subscribe(@event => { + Console.WriteLine($"Event {@event.Message};"); exceptionEvent = @event; }); @@ -73,7 +75,16 @@ public async Task IndexWriterShouldNotBeDisposed() async Task DoAfterDelay(Func action, TimeSpan? delay = null) { await Task.Delay(delay ?? Random.Shared.Next(1, 5).Seconds()); - await action(); + Console.WriteLine($"Calling {action.Method.Name};"); + try + { + await action(); + } + catch (Exception e) + { + Console.WriteLine(e.Message); + throw; + } } await manager.StopAsync(); diff --git a/src/DotJEM.Json.Index2.Management/Writer/IJsonIndexWriter.cs b/src/DotJEM.Json.Index2.Management/Writer/IJsonIndexWriter.cs index d153925..9104610 100644 --- a/src/DotJEM.Json.Index2.Management/Writer/IJsonIndexWriter.cs +++ b/src/DotJEM.Json.Index2.Management/Writer/IJsonIndexWriter.cs @@ -151,7 +151,8 @@ private void Commit() long start = Stopwatch.GetTimestamp(); try { - lease.Value.Commit(); + lease.WithLock(v => v.Commit()); + //lease.Value.Commit(); } catch (LeaseTerminatedException e) { diff --git a/src/DotJEM.Json.Index2.Test/JsonIndexTest.cs b/src/DotJEM.Json.Index2.Test/JsonIndexTest.cs index da7641d..2c9795d 100644 --- a/src/DotJEM.Json.Index2.Test/JsonIndexTest.cs +++ b/src/DotJEM.Json.Index2.Test/JsonIndexTest.cs @@ -83,6 +83,7 @@ public async Task Search_Booleans() int count = searcher.Search(new TermQuery(new Term("inStock", "true"))).Count(); Assert.AreEqual(3, count); } + [Test] public async Task FindBeforeCommit_AddsDocument() { diff --git a/src/DotJEM.Json.Index2/IO/IndexWriterSafeProxy.cs b/src/DotJEM.Json.Index2/IO/IndexWriterSafeProxy.cs index dccebce..fef2cfc 100644 --- a/src/DotJEM.Json.Index2/IO/IndexWriterSafeProxy.cs +++ b/src/DotJEM.Json.Index2/IO/IndexWriterSafeProxy.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Diagnostics; using Lucene.Net.Analysis; using Lucene.Net.Index; using Lucene.Net.Search; @@ -10,6 +12,8 @@ namespace DotJEM.Json.Index2.IO; public class IndexWriterSafeProxy : IIndexWriter { private readonly IndexWriter inner; + private readonly Guid id = Guid.NewGuid(); + private StackTrace whoDisposed; public IndexWriterSafeProxy(IndexWriter writer) { @@ -30,11 +34,15 @@ public int NumDeletedDocs(SegmentCommitInfo info) public void Dispose() { + whoDisposed = new StackTrace(); + Console.WriteLine($"Dispose IndexWriter[{id}]"); inner.Dispose(); } public void Dispose(bool waitForMerges) { + whoDisposed = new StackTrace(); + Console.WriteLine($"Dispose IndexWriter[{id}]"); inner.Dispose(waitForMerges); } @@ -190,7 +198,23 @@ public void SetCommitData(IDictionary commitUserData) public void Commit() { - inner.Commit(); + Console.WriteLine($"Commit IndexWriter[{id}]"); + try + { + inner.Commit(); + } + catch (ObjectDisposedException e) + { + Console.WriteLine("Who disposed me:"); + Console.WriteLine(whoDisposed); + Console.WriteLine(); + + Console.WriteLine("Who then called me:"); + Console.WriteLine(new StackTrace()); + Console.WriteLine(); + + throw; + } } public bool HasUncommittedChanges() diff --git a/src/DotJEM.Json.Index2/IO/JsonIndexWriterManager.cs b/src/DotJEM.Json.Index2/IO/JsonIndexWriterManager.cs index fbab680..9c36c1b 100644 --- a/src/DotJEM.Json.Index2/IO/JsonIndexWriterManager.cs +++ b/src/DotJEM.Json.Index2/IO/JsonIndexWriterManager.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using DotJEM.Json.Index2.Leases; using DotJEM.Json.Index2.Util; @@ -52,7 +53,13 @@ public IndexWriterManager(IJsonIndex index) { this.index = index; } - public ILease Lease() => leaseManager.Create(Writer, TimeSpan.FromSeconds(10)); + public ILease Lease() + { + lock (writerPadLock) + { + return leaseManager.Create(Writer, TimeSpan.FromSeconds(10)); + } + } private static IIndexWriter Open(IJsonIndex index) { @@ -71,7 +78,10 @@ public void Close() lock (writerPadLock) { - leaseManager.RecallAll(); + IEnumerable recalled = leaseManager.RecallAll(); + foreach (IIndexWriter leasedValue in recalled) + leasedValue.Dispose(); + if (writer == null) return; diff --git a/src/DotJEM.Json.Index2/Leases/Lease.cs b/src/DotJEM.Json.Index2/Leases/Lease.cs index cdc083b..de1b73f 100644 --- a/src/DotJEM.Json.Index2/Leases/Lease.cs +++ b/src/DotJEM.Json.Index2/Leases/Lease.cs @@ -10,6 +10,9 @@ public interface ILease : IDisposable bool IsExpired { get; } bool IsTerminated { get; } bool TryRenew(); + + TOut WithLock(Func func); + void WithLock(Action action); } diff --git a/src/DotJEM.Json.Index2/Leases/LeaseManager.cs b/src/DotJEM.Json.Index2/Leases/LeaseManager.cs index b5a5d75..f4e886e 100644 --- a/src/DotJEM.Json.Index2/Leases/LeaseManager.cs +++ b/src/DotJEM.Json.Index2/Leases/LeaseManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Threading; using DotJEM.Json.Index2.Util; @@ -11,7 +12,7 @@ public interface ILeaseManager int Count { get; } ILease Create(T value); ILease Create(T value, TimeSpan limit); - void RecallAll(); + IEnumerable RecallAll(); } public class LeaseManager : ILeaseManager @@ -32,15 +33,19 @@ public ILease Create(T value, TimeSpan limit) return Add(new TimeLimitedLease(value, OnReturned, limit)); } - public void RecallAll() + public IEnumerable RecallAll() { lock (leasesPadLock) { IRecallableLease[] copy = leases.ToArray(); leases.Clear(); + T[] values = Array.ConvertAll(copy, x => x.Value); + foreach (IRecallableLease lease in copy) lease.Terminate(); + + return values; } } @@ -72,6 +77,8 @@ private class Lease : Disposable, IRecallableLease private readonly T value; private readonly Action onReturned; + private readonly object padlock = new (); + private readonly ManualResetEventSlim returned = new ManualResetEventSlim(); public bool IsExpired => IsDisposed; public bool IsTerminated { get; private set; } @@ -102,6 +109,20 @@ public Lease(T value, Action onReturned) this.onReturned = onReturned; } + public TOut WithLock(Func func) + { + lock (padlock) { + return func(Value); + } + } + + public void WithLock(Action action) + { + lock (padlock) { + action(Value); + } + } + public bool TryRenew() { return false; @@ -109,13 +130,18 @@ public bool TryRenew() public void Terminate() { - IsTerminated = true; - Terminated?.Invoke(this, EventArgs.Empty); - Dispose(); + lock (padlock) + { + returned.Wait(5000); + IsTerminated = true; + Terminated?.Invoke(this, EventArgs.Empty); + Dispose(); + } } protected override void Dispose(bool disposing) { + returned.Set(); onReturned(this); base.Dispose(disposing); } @@ -130,6 +156,7 @@ private class TimeLimitedLease : Disposable, IRecallableLease private readonly long timeLimitMilliseconds; private readonly long leaseTime = Stopwatch.GetTimestamp(); private readonly AutoResetEvent handle = new(false); + private readonly object padlock = new(); public bool IsExpired => (ElapsedMs > timeLimitMilliseconds) || IsDisposed; public bool IsTerminated { get; private set; } @@ -164,6 +191,20 @@ public TimeLimitedLease(T value, Action onReturned, TimeSpan t this.timeLimitMilliseconds = (long)timeLimit.TotalMilliseconds; } + public TOut WithLock(Func func) + { + lock (padlock) { + return func(Value); + } + } + + public void WithLock(Action action) + { + lock (padlock) { + action(Value); + } + } + public bool TryRenew() { return false; @@ -171,10 +212,13 @@ public bool TryRenew() public void Terminate() { - IsTerminated = true; - Terminated?.Invoke(this, EventArgs.Empty); - Wait(); - Dispose(); + lock (padlock) + { + IsTerminated = true; + Terminated?.Invoke(this, EventArgs.Empty); + Wait(); + Dispose(); + } } public void Wait() @@ -187,8 +231,11 @@ public void Wait() protected override void Dispose(bool disposing) { - onReturned(this); - base.Dispose(disposing); + lock (padlock) + { + onReturned(this); + base.Dispose(disposing); + } } } } diff --git a/src/DotJEM.Json.Index2/Storage/IJsonIndexStorageManager.cs b/src/DotJEM.Json.Index2/Storage/IJsonIndexStorageManager.cs index 8728e4e..cb2d722 100644 --- a/src/DotJEM.Json.Index2/Storage/IJsonIndexStorageManager.cs +++ b/src/DotJEM.Json.Index2/Storage/IJsonIndexStorageManager.cs @@ -73,14 +73,13 @@ public void Delete() if (directory == null) return; - lock (padlock) { if (directory == null) return; leaseManager.RecallAll(); - + Close(); Unlock(); foreach (string file in directory.ListAll()) From bfaaa4c09ecd7d43b56805b82f2706e64fe18536 Mon Sep 17 00:00:00 2001 From: Jens Melgaard Date: Mon, 5 May 2025 08:24:16 +0200 Subject: [PATCH 2/7] Found a deadlock, working it out. --- .../JsonIndexManagerTest.cs | 5 ++- .../IJsonIndexManager.cs | 1 + src/DotJEM.Json.Index2/Leases/LeaseManager.cs | 41 +++++++++++-------- .../Storage/IIndexStorageProvider.cs | 10 ++--- 4 files changed, 34 insertions(+), 23 deletions(-) diff --git a/src/DotJEM.Json.Index2.Management.Test/JsonIndexManagerTest.cs b/src/DotJEM.Json.Index2.Management.Test/JsonIndexManagerTest.cs index ae78d5b..9a9a1e4 100644 --- a/src/DotJEM.Json.Index2.Management.Test/JsonIndexManagerTest.cs +++ b/src/DotJEM.Json.Index2.Management.Test/JsonIndexManagerTest.cs @@ -37,6 +37,7 @@ public async Task IndexWriterShouldNotBeDisposed() IJsonIndexSnapshotManager snapshots = new JsonIndexSnapshotManager(index, strategy, scheduler, "60h"); IJsonIndexManager manager = new JsonIndexManager(source, snapshots, index); index.Commit(); + InfoStreamExceptionEvent? disposedEvent = null; InfoStreamExceptionEvent? exceptionEvent = null; manager.InfoStream @@ -59,7 +60,7 @@ public async Task IndexWriterShouldNotBeDisposed() try { await manager.RunAsync(); - Debug.WriteLine("TEST STARTED"); + Console.WriteLine("TEST STARTED"); Stopwatch sw = Stopwatch.StartNew(); while (sw.Elapsed < 10.Minutes() && disposedEvent == null && exceptionEvent == null) { @@ -111,7 +112,7 @@ public class TestDirectory : IDisposable public TestDirectory() { - Info = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), $"TEST-{Guid.NewGuid():N}")); + Info = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), "DOTNET_TEST", $"TEST-{Guid.NewGuid():N}")); Debug.WriteLine("TEST DIR: " + Info.FullName); } diff --git a/src/DotJEM.Json.Index2.Management/IJsonIndexManager.cs b/src/DotJEM.Json.Index2.Management/IJsonIndexManager.cs index a9c829c..d0865d8 100644 --- a/src/DotJEM.Json.Index2.Management/IJsonIndexManager.cs +++ b/src/DotJEM.Json.Index2.Management/IJsonIndexManager.cs @@ -121,6 +121,7 @@ public async Task ResetIndexAsync() { await jsonDocumentSource.StopAsync().ConfigureAwait(false); index.Storage.Delete(); + index.Commit(); await jsonDocumentSource.ResetAsync().ConfigureAwait(false); await jsonDocumentSource.StartAsync().ConfigureAwait(false); } diff --git a/src/DotJEM.Json.Index2/Leases/LeaseManager.cs b/src/DotJEM.Json.Index2/Leases/LeaseManager.cs index f4e886e..1be55b7 100644 --- a/src/DotJEM.Json.Index2/Leases/LeaseManager.cs +++ b/src/DotJEM.Json.Index2/Leases/LeaseManager.cs @@ -35,20 +35,25 @@ public ILease Create(T value, TimeSpan limit) public IEnumerable RecallAll() { - lock (leasesPadLock) - { - IRecallableLease[] copy = leases.ToArray(); - leases.Clear(); + IRecallableLease[] copy = CopyLeases(); - T[] values = Array.ConvertAll(copy, x => x.Value); + T[] values = Array.ConvertAll(copy, x => x.Value); + foreach (IRecallableLease lease in copy) + lease.Terminate(); + return values; - foreach (IRecallableLease lease in copy) - lease.Terminate(); - - return values; + IRecallableLease[] CopyLeases() + { + lock (leasesPadLock) + { + IRecallableLease[] copy = leases.ToArray(); + leases.Clear(); + return copy; + } } } + private ILease Add(IRecallableLease lease) { lock (leasesPadLock) @@ -77,7 +82,7 @@ private class Lease : Disposable, IRecallableLease private readonly T value; private readonly Action onReturned; - private readonly object padlock = new (); + private readonly object padlock = new(); private readonly ManualResetEventSlim returned = new ManualResetEventSlim(); public bool IsExpired => IsDisposed; @@ -111,14 +116,16 @@ public Lease(T value, Action onReturned) public TOut WithLock(Func func) { - lock (padlock) { + lock (padlock) + { return func(Value); } } public void WithLock(Action action) { - lock (padlock) { + lock (padlock) + { action(Value); } } @@ -130,9 +137,9 @@ public bool TryRenew() public void Terminate() { + returned.Wait(500); lock (padlock) { - returned.Wait(5000); IsTerminated = true; Terminated?.Invoke(this, EventArgs.Empty); Dispose(); @@ -193,14 +200,16 @@ public TimeLimitedLease(T value, Action onReturned, TimeSpan t public TOut WithLock(Func func) { - lock (padlock) { + lock (padlock) + { return func(Value); } } public void WithLock(Action action) { - lock (padlock) { + lock (padlock) + { action(Value); } } @@ -212,11 +221,11 @@ public bool TryRenew() public void Terminate() { + Wait(); lock (padlock) { IsTerminated = true; Terminated?.Invoke(this, EventArgs.Empty); - Wait(); Dispose(); } } diff --git a/src/DotJEM.Json.Index2/Storage/IIndexStorageProvider.cs b/src/DotJEM.Json.Index2/Storage/IIndexStorageProvider.cs index 395f19b..0c00347 100644 --- a/src/DotJEM.Json.Index2/Storage/IIndexStorageProvider.cs +++ b/src/DotJEM.Json.Index2/Storage/IIndexStorageProvider.cs @@ -35,11 +35,11 @@ public SimpleFsIndexStorageProvider(string path) public void Delete() { //TODO: For now. But maybe there is cases where this actually makes sense to always have. - DirectoryInfo dir = new DirectoryInfo(path); - foreach (FileInfo file in dir.EnumerateFiles()) - file.Delete(); + //DirectoryInfo dir = new DirectoryInfo(path); + //foreach (FileInfo file in dir.EnumerateFiles()) + // file.Delete(); - foreach (DirectoryInfo directory in dir.EnumerateDirectories()) - directory.Delete(true); + //foreach (DirectoryInfo directory in dir.EnumerateDirectories()) + // directory.Delete(true); } } \ No newline at end of file From c6c102dca6521673037e4d3a16cd255edb059074 Mon Sep 17 00:00:00 2001 From: jmd Date: Wed, 7 May 2025 10:11:46 +0200 Subject: [PATCH 3/7] Temporary fix for reset and write race condition, needs a better solution. --- .../JsonIndexManagerTest.cs | 10 +-- .../IJsonIndexManager.cs | 1 - .../IO/IndexWriterSafeProxy.cs | 6 +- .../IO/JsonIndexWriterManager.cs | 37 ++++++++--- src/DotJEM.Json.Index2/Leases/LeaseManager.cs | 62 +++++++------------ .../Storage/IIndexStorageProvider.cs | 30 ++++++--- .../Storage/IJsonIndexStorageManager.cs | 6 +- 7 files changed, 86 insertions(+), 66 deletions(-) diff --git a/src/DotJEM.Json.Index2.Management.Test/JsonIndexManagerTest.cs b/src/DotJEM.Json.Index2.Management.Test/JsonIndexManagerTest.cs index 9a9a1e4..b7634a9 100644 --- a/src/DotJEM.Json.Index2.Management.Test/JsonIndexManagerTest.cs +++ b/src/DotJEM.Json.Index2.Management.Test/JsonIndexManagerTest.cs @@ -45,7 +45,7 @@ public async Task IndexWriterShouldNotBeDisposed() .Where(@event => @event.Exception is ObjectDisposedException) .Subscribe(@event => { - Console.WriteLine($"Event {@event.Message};"); + Debug.WriteLine($"Event {@event.Message};"); disposedEvent = @event; }); manager.InfoStream @@ -53,16 +53,16 @@ public async Task IndexWriterShouldNotBeDisposed() .Where(@event => @event.Exception.Message != "Can't write to an existing snapshot.") .Subscribe(@event => { - Console.WriteLine($"Event {@event.Message};"); + Debug.WriteLine($"Event {@event.Message};"); exceptionEvent = @event; }); try { await manager.RunAsync(); - Console.WriteLine("TEST STARTED"); + Debug.WriteLine("TEST STARTED"); Stopwatch sw = Stopwatch.StartNew(); - while (sw.Elapsed < 10.Minutes() && disposedEvent == null && exceptionEvent == null) + while (sw.Elapsed < 1.Minutes() && disposedEvent == null && exceptionEvent == null) { Task result = Random.Shared.Next(100) switch { @@ -76,7 +76,7 @@ public async Task IndexWriterShouldNotBeDisposed() async Task DoAfterDelay(Func action, TimeSpan? delay = null) { await Task.Delay(delay ?? Random.Shared.Next(1, 5).Seconds()); - Console.WriteLine($"Calling {action.Method.Name};"); + Debug.WriteLine($"Calling {action.Method.Name};"); try { await action(); diff --git a/src/DotJEM.Json.Index2.Management/IJsonIndexManager.cs b/src/DotJEM.Json.Index2.Management/IJsonIndexManager.cs index d0865d8..a9c829c 100644 --- a/src/DotJEM.Json.Index2.Management/IJsonIndexManager.cs +++ b/src/DotJEM.Json.Index2.Management/IJsonIndexManager.cs @@ -121,7 +121,6 @@ public async Task ResetIndexAsync() { await jsonDocumentSource.StopAsync().ConfigureAwait(false); index.Storage.Delete(); - index.Commit(); await jsonDocumentSource.ResetAsync().ConfigureAwait(false); await jsonDocumentSource.StartAsync().ConfigureAwait(false); } diff --git a/src/DotJEM.Json.Index2/IO/IndexWriterSafeProxy.cs b/src/DotJEM.Json.Index2/IO/IndexWriterSafeProxy.cs index fef2cfc..3f423ef 100644 --- a/src/DotJEM.Json.Index2/IO/IndexWriterSafeProxy.cs +++ b/src/DotJEM.Json.Index2/IO/IndexWriterSafeProxy.cs @@ -14,7 +14,7 @@ public class IndexWriterSafeProxy : IIndexWriter private readonly IndexWriter inner; private readonly Guid id = Guid.NewGuid(); private StackTrace whoDisposed; - + public IndexWriterSafeProxy(IndexWriter writer) { inner = writer; @@ -35,14 +35,14 @@ public int NumDeletedDocs(SegmentCommitInfo info) public void Dispose() { whoDisposed = new StackTrace(); - Console.WriteLine($"Dispose IndexWriter[{id}]"); + Debug.WriteLine($"Dispose IndexWriter[{id}]"); inner.Dispose(); } public void Dispose(bool waitForMerges) { whoDisposed = new StackTrace(); - Console.WriteLine($"Dispose IndexWriter[{id}]"); + Debug.WriteLine($"Dispose IndexWriter[{id}]"); inner.Dispose(waitForMerges); } diff --git a/src/DotJEM.Json.Index2/IO/JsonIndexWriterManager.cs b/src/DotJEM.Json.Index2/IO/JsonIndexWriterManager.cs index 9c36c1b..963d574 100644 --- a/src/DotJEM.Json.Index2/IO/JsonIndexWriterManager.cs +++ b/src/DotJEM.Json.Index2/IO/JsonIndexWriterManager.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; +using System.Threading; using DotJEM.Json.Index2.Leases; using DotJEM.Json.Index2.Util; using Lucene.Net.Index; @@ -11,9 +13,11 @@ namespace DotJEM.Json.Index2.IO; public interface IIndexWriterManager : IDisposable { event EventHandler OnClose; - ILease Lease(); void Close(); + + void Lock(); + void Unlock(); } @@ -29,9 +33,21 @@ public class IndexWriterManager : Disposable, IIndexWriterManager private readonly object writerPadLock = new(); private readonly LeaseManager leaseManager = new(); + private readonly ManualResetEventSlim reset = new(true); //TODO: With leases, this should not be needed. public event EventHandler OnClose; + private static List> writers = new(); + + public void Lock() + { + reset.Reset(); + } + public void Unlock() + { + reset.Set(); + } + private IIndexWriter Writer { get @@ -44,7 +60,11 @@ private IIndexWriter Writer if (writer != null) return writer; - return writer = Open(index); + reset.Wait(); + + IIndexWriter newWriter = Open(index); + writers.Add(new(newWriter)); + return writer = newWriter; } } } @@ -78,17 +98,20 @@ public void Close() lock (writerPadLock) { - IEnumerable recalled = leaseManager.RecallAll(); - foreach (IIndexWriter leasedValue in recalled) - leasedValue.Dispose(); - if (writer == null) return; - writer.Dispose(); + IIndexWriter copy = writer; writer = null; + copy.Dispose(); + + leaseManager.RecallAll(); RaiseOnClose(); } + + int writersOpenend = writers.Count; + int writersAlive = writers.Count(w => w.TryGetTarget(out _)); + Debug.WriteLine($"Number of opened writers: {writersOpenend} where {writersAlive} are still alive"); } protected override void Dispose(bool disposing) diff --git a/src/DotJEM.Json.Index2/Leases/LeaseManager.cs b/src/DotJEM.Json.Index2/Leases/LeaseManager.cs index 1be55b7..3a49cf0 100644 --- a/src/DotJEM.Json.Index2/Leases/LeaseManager.cs +++ b/src/DotJEM.Json.Index2/Leases/LeaseManager.cs @@ -82,7 +82,6 @@ private class Lease : Disposable, IRecallableLease private readonly T value; private readonly Action onReturned; - private readonly object padlock = new(); private readonly ManualResetEventSlim returned = new ManualResetEventSlim(); public bool IsExpired => IsDisposed; @@ -116,18 +115,12 @@ public Lease(T value, Action onReturned) public TOut WithLock(Func func) { - lock (padlock) - { - return func(Value); - } + return func(Value); } public void WithLock(Action action) { - lock (padlock) - { - action(Value); - } + action(Value); } public bool TryRenew() @@ -138,18 +131,18 @@ public bool TryRenew() public void Terminate() { returned.Wait(500); - lock (padlock) - { - IsTerminated = true; - Terminated?.Invoke(this, EventArgs.Empty); - Dispose(); - } + IsTerminated = true; + Terminated?.Invoke(this, EventArgs.Empty); + onReturned(this); } protected override void Dispose(bool disposing) { - returned.Set(); - onReturned(this); + if (disposing) + { + returned.Set(); + onReturned(this); + } base.Dispose(disposing); } } @@ -162,8 +155,8 @@ private class TimeLimitedLease : Disposable, IRecallableLease private readonly Action onReturned; private readonly long timeLimitMilliseconds; private readonly long leaseTime = Stopwatch.GetTimestamp(); - private readonly AutoResetEvent handle = new(false); - private readonly object padlock = new(); + private readonly AutoResetEvent returned = new(false); + //private readonly object padlock = new(); public bool IsExpired => (ElapsedMs > timeLimitMilliseconds) || IsDisposed; public bool IsTerminated { get; private set; } @@ -200,18 +193,12 @@ public TimeLimitedLease(T value, Action onReturned, TimeSpan t public TOut WithLock(Func func) { - lock (padlock) - { - return func(Value); - } + return func(Value); } public void WithLock(Action action) { - lock (padlock) - { - action(Value); - } + action(Value); } public bool TryRenew() @@ -222,29 +209,24 @@ public bool TryRenew() public void Terminate() { Wait(); - lock (padlock) - { - IsTerminated = true; - Terminated?.Invoke(this, EventArgs.Empty); - Dispose(); - } + IsTerminated = true; + Terminated?.Invoke(this, EventArgs.Empty); + Dispose(); } - public void Wait() + private void Wait() { if (IsExpired) return; - handle.WaitOne(TimeSpan.FromSeconds(6) - TimeSpan.FromMilliseconds(ElapsedMs)); + returned.WaitOne(TimeSpan.FromSeconds(6) - TimeSpan.FromMilliseconds(ElapsedMs)); } protected override void Dispose(bool disposing) { - lock (padlock) - { - onReturned(this); - base.Dispose(disposing); - } + returned.Set(); + onReturned(this); + base.Dispose(disposing); } } } diff --git a/src/DotJEM.Json.Index2/Storage/IIndexStorageProvider.cs b/src/DotJEM.Json.Index2/Storage/IIndexStorageProvider.cs index 0c00347..090f5a2 100644 --- a/src/DotJEM.Json.Index2/Storage/IIndexStorageProvider.cs +++ b/src/DotJEM.Json.Index2/Storage/IIndexStorageProvider.cs @@ -1,4 +1,6 @@ -using System.IO; +using System; +using System.Diagnostics; +using System.IO; using Lucene.Net.Store; using Directory = Lucene.Net.Store.Directory; @@ -6,13 +8,14 @@ namespace DotJEM.Json.Index2.Storage; public interface IIndexStorageProvider { + string Path { get; } Directory Get(); void Delete(); } public class RamIndexStorageProvider : IIndexStorageProvider { - + public string Path => "N/A"; public Directory Get() => new RAMDirectory(); public void Delete() @@ -24,22 +27,31 @@ public void Delete() public class SimpleFsIndexStorageProvider : IIndexStorageProvider { private readonly string path; + public string Path => path; public SimpleFsIndexStorageProvider(string path) { this.path = path; } - + public Directory Get() => new SimpleFSDirectory(path); public void Delete() { + Debug.WriteLine("DELETE FILES"); //TODO: For now. But maybe there is cases where this actually makes sense to always have. - //DirectoryInfo dir = new DirectoryInfo(path); - //foreach (FileInfo file in dir.EnumerateFiles()) - // file.Delete(); - - //foreach (DirectoryInfo directory in dir.EnumerateDirectories()) - // directory.Delete(true); + try + { + DirectoryInfo dir = new DirectoryInfo(path); + foreach (FileInfo file in dir.EnumerateFiles()) + file.Delete(); + + foreach (DirectoryInfo directory in dir.EnumerateDirectories()) + directory.Delete(true); + } + catch (Exception e) + { + Debug.WriteLine(e); + } } } \ No newline at end of file diff --git a/src/DotJEM.Json.Index2/Storage/IJsonIndexStorageManager.cs b/src/DotJEM.Json.Index2/Storage/IJsonIndexStorageManager.cs index cb2d722..df779da 100644 --- a/src/DotJEM.Json.Index2/Storage/IJsonIndexStorageManager.cs +++ b/src/DotJEM.Json.Index2/Storage/IJsonIndexStorageManager.cs @@ -20,8 +20,8 @@ public interface IJsonIndexStorageManager public class JsonIndexStorageManager: IJsonIndexStorageManager { - private readonly IIndexStorageProvider provider; private readonly object padlock = new (); + private readonly IIndexStorageProvider provider; private volatile Directory directory; private readonly LeaseManager leaseManager = new(); @@ -80,11 +80,15 @@ public void Delete() leaseManager.RecallAll(); + WriterManager.Lock(); + Close(); Unlock(); foreach (string file in directory.ListAll()) directory.DeleteFile(file); provider.Delete(); + + WriterManager.Unlock(); } } } \ No newline at end of file From 6871a6b6d9dddaeb696fe6c5fbffa1bd8b0d1c80 Mon Sep 17 00:00:00 2001 From: jmd Date: Wed, 7 May 2025 10:35:54 +0200 Subject: [PATCH 4/7] Remove WithLock methods on lease, they did not promise anything in the end anyways. --- .../Writer/IJsonIndexWriter.cs | 3 +-- src/DotJEM.Json.Index2/Leases/Lease.cs | 3 --- src/DotJEM.Json.Index2/Leases/LeaseManager.cs | 23 +------------------ 3 files changed, 2 insertions(+), 27 deletions(-) diff --git a/src/DotJEM.Json.Index2.Management/Writer/IJsonIndexWriter.cs b/src/DotJEM.Json.Index2.Management/Writer/IJsonIndexWriter.cs index 9104610..d153925 100644 --- a/src/DotJEM.Json.Index2.Management/Writer/IJsonIndexWriter.cs +++ b/src/DotJEM.Json.Index2.Management/Writer/IJsonIndexWriter.cs @@ -151,8 +151,7 @@ private void Commit() long start = Stopwatch.GetTimestamp(); try { - lease.WithLock(v => v.Commit()); - //lease.Value.Commit(); + lease.Value.Commit(); } catch (LeaseTerminatedException e) { diff --git a/src/DotJEM.Json.Index2/Leases/Lease.cs b/src/DotJEM.Json.Index2/Leases/Lease.cs index de1b73f..cdc083b 100644 --- a/src/DotJEM.Json.Index2/Leases/Lease.cs +++ b/src/DotJEM.Json.Index2/Leases/Lease.cs @@ -10,9 +10,6 @@ public interface ILease : IDisposable bool IsExpired { get; } bool IsTerminated { get; } bool TryRenew(); - - TOut WithLock(Func func); - void WithLock(Action action); } diff --git a/src/DotJEM.Json.Index2/Leases/LeaseManager.cs b/src/DotJEM.Json.Index2/Leases/LeaseManager.cs index 3a49cf0..e87817e 100644 --- a/src/DotJEM.Json.Index2/Leases/LeaseManager.cs +++ b/src/DotJEM.Json.Index2/Leases/LeaseManager.cs @@ -113,16 +113,6 @@ public Lease(T value, Action onReturned) this.onReturned = onReturned; } - public TOut WithLock(Func func) - { - return func(Value); - } - - public void WithLock(Action action) - { - action(Value); - } - public bool TryRenew() { return false; @@ -156,7 +146,6 @@ private class TimeLimitedLease : Disposable, IRecallableLease private readonly long timeLimitMilliseconds; private readonly long leaseTime = Stopwatch.GetTimestamp(); private readonly AutoResetEvent returned = new(false); - //private readonly object padlock = new(); public bool IsExpired => (ElapsedMs > timeLimitMilliseconds) || IsDisposed; public bool IsTerminated { get; private set; } @@ -190,17 +179,7 @@ public TimeLimitedLease(T value, Action onReturned, TimeSpan t this.onReturned = onReturned; this.timeLimitMilliseconds = (long)timeLimit.TotalMilliseconds; } - - public TOut WithLock(Func func) - { - return func(Value); - } - - public void WithLock(Action action) - { - action(Value); - } - + public bool TryRenew() { return false; From 2638441053cb8e6eac9a0890913551e22d310c23 Mon Sep 17 00:00:00 2001 From: jmd Date: Wed, 7 May 2025 10:39:08 +0200 Subject: [PATCH 5/7] Fixes --- src/DotJEM.Json.Index2/Leases/LeaseManager.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/DotJEM.Json.Index2/Leases/LeaseManager.cs b/src/DotJEM.Json.Index2/Leases/LeaseManager.cs index e87817e..3e459c0 100644 --- a/src/DotJEM.Json.Index2/Leases/LeaseManager.cs +++ b/src/DotJEM.Json.Index2/Leases/LeaseManager.cs @@ -36,7 +36,6 @@ public ILease Create(T value, TimeSpan limit) public IEnumerable RecallAll() { IRecallableLease[] copy = CopyLeases(); - T[] values = Array.ConvertAll(copy, x => x.Value); foreach (IRecallableLease lease in copy) lease.Terminate(); @@ -123,6 +122,7 @@ public void Terminate() returned.Wait(500); IsTerminated = true; Terminated?.Invoke(this, EventArgs.Empty); + Dispose(false); onReturned(this); } @@ -190,7 +190,8 @@ public void Terminate() Wait(); IsTerminated = true; Terminated?.Invoke(this, EventArgs.Empty); - Dispose(); + Dispose(false); + onReturned(this); } private void Wait() @@ -198,13 +199,18 @@ private void Wait() if (IsExpired) return; - returned.WaitOne(TimeSpan.FromSeconds(6) - TimeSpan.FromMilliseconds(ElapsedMs)); + TimeSpan remaining = TimeSpan.FromSeconds(6) - TimeSpan.FromMilliseconds(ElapsedMs); + if(remaining > TimeSpan.Zero) + returned.WaitOne(TimeSpan.FromSeconds(6) - TimeSpan.FromMilliseconds(ElapsedMs)); } protected override void Dispose(bool disposing) { - returned.Set(); - onReturned(this); + if (disposing) + { + returned.Set(); + onReturned(this); + } base.Dispose(disposing); } } From 4a1e7981c2ac77ef075de044908e3b2ee48cd2e7 Mon Sep 17 00:00:00 2001 From: jmd Date: Wed, 7 May 2025 10:42:11 +0200 Subject: [PATCH 6/7] reorder dispose and recall --- src/DotJEM.Json.Index2/IO/JsonIndexWriterManager.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/DotJEM.Json.Index2/IO/JsonIndexWriterManager.cs b/src/DotJEM.Json.Index2/IO/JsonIndexWriterManager.cs index 963d574..c490f28 100644 --- a/src/DotJEM.Json.Index2/IO/JsonIndexWriterManager.cs +++ b/src/DotJEM.Json.Index2/IO/JsonIndexWriterManager.cs @@ -103,9 +103,8 @@ public void Close() IIndexWriter copy = writer; writer = null; - copy.Dispose(); - leaseManager.RecallAll(); + copy.Dispose(); RaiseOnClose(); } From ce91bbda9eaaafc4a468cfdddb81dd503ecd87a3 Mon Sep 17 00:00:00 2001 From: jmd Date: Wed, 7 May 2025 10:47:18 +0200 Subject: [PATCH 7/7] dispose WaitHandles --- src/DotJEM.Json.Index2/IO/JsonIndexWriterManager.cs | 1 + src/DotJEM.Json.Index2/Leases/LeaseManager.cs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/DotJEM.Json.Index2/IO/JsonIndexWriterManager.cs b/src/DotJEM.Json.Index2/IO/JsonIndexWriterManager.cs index c490f28..f824297 100644 --- a/src/DotJEM.Json.Index2/IO/JsonIndexWriterManager.cs +++ b/src/DotJEM.Json.Index2/IO/JsonIndexWriterManager.cs @@ -116,6 +116,7 @@ public void Close() protected override void Dispose(bool disposing) { Debug.WriteLine($"DISPOSE WRITER: {disposing}"); + reset.Dispose(); if (disposing) Close(); base.Dispose(disposing); diff --git a/src/DotJEM.Json.Index2/Leases/LeaseManager.cs b/src/DotJEM.Json.Index2/Leases/LeaseManager.cs index 3e459c0..b52920a 100644 --- a/src/DotJEM.Json.Index2/Leases/LeaseManager.cs +++ b/src/DotJEM.Json.Index2/Leases/LeaseManager.cs @@ -133,6 +133,7 @@ protected override void Dispose(bool disposing) returned.Set(); onReturned(this); } + returned.Dispose(); base.Dispose(disposing); } } @@ -211,6 +212,7 @@ protected override void Dispose(bool disposing) returned.Set(); onReturned(this); } + returned.Dispose(); base.Dispose(disposing); } }