@@ -8,6 +8,16 @@ internal class RocksDbContext : IDisposable
88 private readonly WriteOptions _writeOptions ;
99 private readonly Dictionary < string , MergeOperatorConfig > _mergeOperators ;
1010 private readonly Cache _cache ;
11+
12+ // ReSharper disable once CollectionNeverQueried.Local
13+ /// <summary>
14+ /// Stores ColumnFamilyOptions instances to prevent garbage collection of merge operator delegates.
15+ /// This is critical because ColumnFamilyOptions holds MergeOperatorRef which contains the delegates
16+ /// (FullMerge and PartialMerge) that RocksDB (native code) references. Without keeping the
17+ /// ColumnFamilyOptions alive, the GC may collect MergeOperatorRef and its delegates, causing the error:
18+ /// "A callback was made on a garbage collected delegate of type 'RocksDbSharp!RocksDbSharp.GetMergeOperator::Invoke'"
19+ /// </summary>
20+ private readonly Dictionary < string , ColumnFamilyOptions > _columnFamilyOptions = new ( ) ;
1121
1222 private const long BlockCacheSize = 50 * 1024 * 1024L ;
1323 private const long BlockSize = 4096L ;
@@ -38,21 +48,21 @@ public RocksDbContext(IOptions<RocksDbOptions> options)
3848 dbOptions . SetWriteBufferSize ( WriteBufferSize ) ;
3949 dbOptions . SetCompression ( Compression . No ) ;
4050 dbOptions . SetCompactionStyle ( Compaction . Universal ) ;
41-
51+
4252 var tableConfig = new BlockBasedTableOptions ( ) ;
4353 tableConfig . SetBlockCache ( _cache ) ;
4454 tableConfig . SetBlockSize ( BlockSize ) ;
45-
55+
4656 var filter = BloomFilterPolicy . Create ( ) ;
4757 tableConfig . SetFilterPolicy ( filter ) ;
48-
58+
4959 dbOptions . SetBlockBasedTableFactory ( tableConfig ) ;
50-
60+
5161 _writeOptions = new WriteOptions ( ) ;
5262 _writeOptions . DisableWal ( 1 ) ;
5363
5464 _mergeOperators = options . Value . MergeOperators ;
55-
65+
5666 var columnFamilies = CreateColumnFamilies ( options . Value . ColumnFamilies ) ;
5767
5868 if ( options . Value . DeleteExistingDatabaseOnStartup )
@@ -75,6 +85,10 @@ private static void DestroyDatabase(string path)
7585
7686 public ColumnFamilyOptions CreateColumnFamilyOptions ( string columnFamilyName )
7787 {
88+ // Remove old options if they exist (e.g., when recreating column family in Clear())
89+ // We don't need to dispose them - just remove the reference and let GC handle it
90+ _columnFamilyOptions . Remove ( columnFamilyName ) ;
91+
7892 var cfOptions = new ColumnFamilyOptions ( ) ;
7993 if ( _mergeOperators . TryGetValue ( columnFamilyName , out var mergeOperatorConfig ) )
8094 {
@@ -86,6 +100,9 @@ public ColumnFamilyOptions CreateColumnFamilyOptions(string columnFamilyName)
86100 cfOptions . SetMergeOperator ( mergeOp ) ;
87101 }
88102
103+ // Store the options to keep MergeOperatorRef (and its delegates) alive
104+ _columnFamilyOptions [ columnFamilyName ] = cfOptions ;
105+
89106 return cfOptions ;
90107 }
91108
@@ -104,6 +121,9 @@ private ColumnFamilies CreateColumnFamilies(IReadOnlyList<string> columnFamilyNa
104121
105122 public void Dispose ( )
106123 {
124+ // Clear column family options to allow garbage collection
125+ _columnFamilyOptions . Clear ( ) ;
126+
107127 Db . Dispose ( ) ;
108128 }
109- }
129+ }
0 commit comments