diff --git a/src/DotJEM.Json.Index2.Management.Test/JsonIndexManagerTest.cs b/src/DotJEM.Json.Index2.Management.Test/JsonIndexManagerTest.cs index 95c7c13..b7634a9 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,6 +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; @@ -44,6 +45,7 @@ public async Task IndexWriterShouldNotBeDisposed() .Where(@event => @event.Exception is ObjectDisposedException) .Subscribe(@event => { + Debug.WriteLine($"Event {@event.Message};"); disposedEvent = @event; }); manager.InfoStream @@ -51,6 +53,7 @@ public async Task IndexWriterShouldNotBeDisposed() .Where(@event => @event.Exception.Message != "Can't write to an existing snapshot.") .Subscribe(@event => { + Debug.WriteLine($"Event {@event.Message};"); exceptionEvent = @event; }); @@ -59,7 +62,7 @@ public async Task IndexWriterShouldNotBeDisposed() await manager.RunAsync(); 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 { @@ -73,7 +76,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(); + Debug.WriteLine($"Calling {action.Method.Name};"); + try + { + await action(); + } + catch (Exception e) + { + Console.WriteLine(e.Message); + throw; + } } await manager.StopAsync(); @@ -100,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.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..3f423ef 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,7 +12,9 @@ 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) { inner = writer; @@ -30,11 +34,15 @@ public int NumDeletedDocs(SegmentCommitInfo info) public void Dispose() { + whoDisposed = new StackTrace(); + Debug.WriteLine($"Dispose IndexWriter[{id}]"); inner.Dispose(); } public void Dispose(bool waitForMerges) { + whoDisposed = new StackTrace(); + Debug.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..f824297 100644 --- a/src/DotJEM.Json.Index2/IO/JsonIndexWriterManager.cs +++ b/src/DotJEM.Json.Index2/IO/JsonIndexWriterManager.cs @@ -1,5 +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; @@ -10,9 +13,11 @@ namespace DotJEM.Json.Index2.IO; public interface IIndexWriterManager : IDisposable { event EventHandler OnClose; - ILease Lease(); void Close(); + + void Lock(); + void Unlock(); } @@ -28,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 @@ -43,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; } } } @@ -52,7 +73,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,19 +98,25 @@ public void Close() lock (writerPadLock) { - leaseManager.RecallAll(); if (writer == null) return; - writer.Dispose(); + IIndexWriter copy = writer; writer = null; + leaseManager.RecallAll(); + copy.Dispose(); 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) { 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 b5a5d75..b52920a 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,18 +33,26 @@ 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(); + IRecallableLease[] copy = CopyLeases(); + T[] values = Array.ConvertAll(copy, x => x.Value); + foreach (IRecallableLease lease in copy) + lease.Terminate(); + return values; - foreach (IRecallableLease lease in copy) - lease.Terminate(); + IRecallableLease[] CopyLeases() + { + lock (leasesPadLock) + { + IRecallableLease[] copy = leases.ToArray(); + leases.Clear(); + return copy; + } } } + private ILease Add(IRecallableLease lease) { lock (leasesPadLock) @@ -72,6 +81,7 @@ private class Lease : Disposable, IRecallableLease private readonly T value; private readonly Action onReturned; + private readonly ManualResetEventSlim returned = new ManualResetEventSlim(); public bool IsExpired => IsDisposed; public bool IsTerminated { get; private set; } @@ -109,14 +119,21 @@ public bool TryRenew() public void Terminate() { + returned.Wait(500); IsTerminated = true; Terminated?.Invoke(this, EventArgs.Empty); - Dispose(); + Dispose(false); + onReturned(this); } protected override void Dispose(bool disposing) { - onReturned(this); + if (disposing) + { + returned.Set(); + onReturned(this); + } + returned.Dispose(); base.Dispose(disposing); } } @@ -129,7 +146,7 @@ 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 AutoResetEvent returned = new(false); public bool IsExpired => (ElapsedMs > timeLimitMilliseconds) || IsDisposed; public bool IsTerminated { get; private set; } @@ -163,7 +180,7 @@ public TimeLimitedLease(T value, Action onReturned, TimeSpan t this.onReturned = onReturned; this.timeLimitMilliseconds = (long)timeLimit.TotalMilliseconds; } - + public bool TryRenew() { return false; @@ -171,23 +188,31 @@ public bool TryRenew() public void Terminate() { + Wait(); IsTerminated = true; Terminated?.Invoke(this, EventArgs.Empty); - Wait(); - Dispose(); + Dispose(false); + onReturned(this); } - public void Wait() + private void Wait() { if (IsExpired) return; - handle.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) { - onReturned(this); + if (disposing) + { + returned.Set(); + onReturned(this); + } + returned.Dispose(); base.Dispose(disposing); } } diff --git a/src/DotJEM.Json.Index2/Storage/IJsonIndexStorageManager.cs b/src/DotJEM.Json.Index2/Storage/IJsonIndexStorageManager.cs index 8728e4e..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(); @@ -73,19 +73,22 @@ public void Delete() if (directory == null) return; - lock (padlock) { if (directory == null) return; 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