Skip to content

Commit 6334ca4

Browse files
committed
Add ColumnFamilyOptions management to prevent garbage collection of merge operator delegates
1 parent 153a33d commit 6334ca4

1 file changed

Lines changed: 26 additions & 6 deletions

File tree

src/RocksDb.Extensions/RocksDbContext.cs

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)