@@ -193,7 +193,7 @@ pub fn SegmentList(Segment: type) type {
193
193
};
194
194
}
195
195
196
- fn getSegmentSize (comptime T : type ) fn (SharedPtr (T )) usize {
196
+ fn getSizeFn (comptime T : type ) fn (SharedPtr (T )) usize {
197
197
const tmp = struct {
198
198
fn getSize (segment : SharedPtr (T )) usize {
199
199
return segment .value .getSize ();
@@ -202,17 +202,27 @@ fn getSegmentSize(comptime T: type) fn (SharedPtr(T)) usize {
202
202
return tmp .getSize ;
203
203
}
204
204
205
+ fn isFrozenFn (comptime T : type ) fn (SharedPtr (T )) bool {
206
+ const tmp = struct {
207
+ fn isFrozen (segment : SharedPtr (T )) bool {
208
+ return segment .value .status .frozen ;
209
+ }
210
+ };
211
+ return tmp .isFrozen ;
212
+ }
213
+
205
214
pub fn SegmentListManager (Segment : type ) type {
206
215
return struct {
207
216
pub const Self = @This ();
208
217
pub const List = SegmentList (Segment );
209
- pub const MergePolicy = TieredMergePolicy (List .Node , getSegmentSize (Segment ));
218
+ pub const MergePolicy = TieredMergePolicy (List .Node , getSizeFn ( Segment ), isFrozenFn (Segment ));
210
219
211
220
options : Segment.Options ,
212
221
segments : SharedPtr (List ),
213
222
merge_policy : MergePolicy ,
214
223
num_allowed_segments : std .atomic .Value (usize ),
215
224
update_lock : std.Thread.Mutex ,
225
+ status_update_lock : std.Thread.Mutex ,
216
226
217
227
pub fn init (allocator : Allocator , options : Segment.Options , merge_policy : MergePolicy ) ! Self {
218
228
const segments = try SharedPtr (List ).create (allocator , List .initEmpty ());
@@ -222,6 +232,7 @@ pub fn SegmentListManager(Segment: type) type {
222
232
.merge_policy = merge_policy ,
223
233
.num_allowed_segments = std .atomic .Value (usize ).init (0 ),
224
234
.update_lock = .{},
235
+ .status_update_lock = .{},
225
236
};
226
237
}
227
238
@@ -249,16 +260,49 @@ pub fn SegmentListManager(Segment: type) type {
249
260
return self .segments .value .nodes .items .len > self .num_allowed_segments .load (.acquire );
250
261
}
251
262
252
- pub fn prepareMerge (self : * Self , allocator : Allocator ) ! ? Update {
263
+ pub fn prepareCheckpoint (self : * Self , allocator : Allocator ) ? List.Node {
253
264
var segments = self .acquireSegments ();
254
265
defer destroySegments (allocator , & segments );
255
266
256
- self .num_allowed_segments .store (self .merge_policy .calculateBudget (segments .value .nodes .items ), .release );
257
- if (! self .needsMerge ()) {
258
- return null ;
267
+ self .status_update_lock .lock ();
268
+ defer self .status_update_lock .unlock ();
269
+
270
+ if (segments .value .getFirst ()) | node | {
271
+ if (node .value .status .frozen ) {
272
+ return node .acquire ();
273
+ }
259
274
}
275
+ return null ;
276
+ }
260
277
261
- const candidate = self .merge_policy .findSegmentsToMerge (segments .value .nodes .items ) orelse return null ;
278
+ pub fn prepareMerge (self : * Self , allocator : Allocator ) ! ? Update {
279
+ var segments = self .acquireSegments ();
280
+ defer destroySegments (allocator , & segments );
281
+
282
+ const candidate = blk : {
283
+ self .status_update_lock .lock ();
284
+ defer self .status_update_lock .unlock ();
285
+
286
+ // Check for a degenerate case:
287
+ // - we have a small segment in the front of list and then an oversized one right next to it
288
+ // - such a segment could never be merged
289
+ // - but it would also never be considered for checkpoint, so it would be stuck there, blocking checkpoints
290
+ if (segments .value .nodes .items .len >= 2 ) {
291
+ const node0 = segments .value .nodes .items [0 ];
292
+ const node1 = segments .value .nodes .items [1 ];
293
+
294
+ if (node1 .value .status .frozen and ! node0 .value .status .frozen ) {
295
+ node0 .value .status .frozen = true ;
296
+ }
297
+ }
298
+
299
+ self .num_allowed_segments .store (self .merge_policy .calculateBudget (segments .value .nodes .items ), .release );
300
+ if (! self .needsMerge ()) {
301
+ return null ;
302
+ }
303
+
304
+ break :blk self .merge_policy .findSegmentsToMerge (segments .value .nodes .items ) orelse return null ;
305
+ };
262
306
263
307
var target = try List .createSegment (allocator , self .options );
264
308
defer List .destroySegment (allocator , & target );
@@ -274,6 +318,11 @@ pub fn SegmentListManager(Segment: type) type {
274
318
try target .value .merge (& merger );
275
319
errdefer target .value .cleanup ();
276
320
321
+ if (target .value .getSize () > self .merge_policy .max_segment_size ) {
322
+ // we can do this without a lock, because we are the only ones knowing about this new segment
323
+ target .value .status .frozen = true ;
324
+ }
325
+
277
326
var update = try self .beginUpdate (allocator );
278
327
update .replaceMergedSegment (target );
279
328
0 commit comments