diff --git a/src/LightningDB.Tests/CursorTests.cs b/src/LightningDB.Tests/CursorTests.cs index 1ced1af..c8bc79a 100644 --- a/src/LightningDB.Tests/CursorTests.cs +++ b/src/LightningDB.Tests/CursorTests.cs @@ -60,8 +60,6 @@ public void CursorShouldPutValues() _env.RunCursorScenario((tx, _, c) => { PopulateCursorValues(c); - c.Dispose(); - //TODO evaluate how not to require this Dispose likely due to #155 var result = tx.Commit(); Assert.Equal(MDBResultCode.Success, result); }); @@ -362,4 +360,41 @@ public void ShouldDeleteDuplicates() Assert.Equal(MDBResultCode.NotFound, result); }, DatabaseOpenFlags.DuplicatesFixed | DatabaseOpenFlags.Create); } + + [Fact] + public void CanPutBatchesViaCursorIssue155() + { + static LightningDatabase OpenDatabase(LightningEnvironment environment) + { + using var tx = environment.BeginTransaction(); + var db = tx.OpenDatabase(configuration: new DatabaseConfiguration { Flags = DatabaseOpenFlags.Create }); + tx.Commit(); + return db; + } + + void ReproduceCoreIteration(LightningEnvironment environment, LightningDatabase db) + { + using var tx = environment.BeginTransaction(); //auto-disposed at end of scope + using var cursor = tx.CreateCursor(db); //auto-disposed at end of scope + + var guid = Guid.NewGuid().ToString(); + var guidBytes = UTF8.GetBytes(guid); + + _ = cursor.Put( + guidBytes, + guidBytes, + CursorPutOptions.None + ); + + tx.Commit().ThrowOnError(); + } + + using var db = OpenDatabase(_env); + + for (var i = 0; i < 5000; i++) + { + ReproduceCoreIteration(_env, db); + } + Assert.True(true, "Code would be unreachable otherwise."); + } } \ No newline at end of file diff --git a/src/LightningDB/LightningCursor.cs b/src/LightningDB/LightningCursor.cs index d5c5dda..e409572 100644 --- a/src/LightningDB/LightningCursor.cs +++ b/src/LightningDB/LightningCursor.cs @@ -29,6 +29,7 @@ internal LightningCursor(LightningDatabase db, LightningTransaction txn) mdb_cursor_open(txn.Handle(), db.Handle(), out _handle).ThrowOnError(); Transaction = txn; + Transaction.ShouldCloseCursor = true; Transaction.Disposing += Dispose; } @@ -503,14 +504,17 @@ public MDBResultCode Renew(LightningTransaction txn) /// True if called from Dispose. private void Dispose(bool disposing) { - if (_handle == 0) + if (_handle == default) return; if (!disposing) throw new InvalidOperationException("The LightningCursor was not disposed and cannot be reliably dealt with from the finalizer"); - mdb_cursor_close(_handle); - _handle = 0; + if (Transaction.ShouldCloseCursor) + { + mdb_cursor_close(_handle); + } + _handle = default; Transaction.Disposing -= Dispose; diff --git a/src/LightningDB/LightningDatabase.cs b/src/LightningDB/LightningDatabase.cs index d5b49c1..0f4f2a4 100644 --- a/src/LightningDB/LightningDatabase.cs +++ b/src/LightningDB/LightningDatabase.cs @@ -86,6 +86,7 @@ public Stats DatabaseStats public MDBResultCode Drop(LightningTransaction transaction) { var result = mdb_drop(transaction.Handle(), _handle, true); + _transaction.ShouldCloseCursor = false; IsOpened = false; _handle = default; return result; diff --git a/src/LightningDB/LightningTransaction.cs b/src/LightningDB/LightningTransaction.cs index 7e4ee43..cf08a70 100644 --- a/src/LightningDB/LightningTransaction.cs +++ b/src/LightningDB/LightningTransaction.cs @@ -59,6 +59,8 @@ private void OnParentStateChanging(LightningTransactionState state) public event Action Disposing; private event Action StateChanging; + + internal bool ShouldCloseCursor { get; set; } /// /// Current transaction state. @@ -295,6 +297,7 @@ public MDBResultCode Renew() public MDBResultCode Commit() { State = LightningTransactionState.Committed; + ShouldCloseCursor = false; StateChanging?.Invoke(State); return mdb_txn_commit(_handle); }