tigerbeetle-node 0.11.8 → 0.11.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/dist/.client.node.sha256 +1 -1
  2. package/package.json +4 -3
  3. package/scripts/build_lib.sh +29 -0
  4. package/src/node.zig +1 -1
  5. package/src/tigerbeetle/scripts/validate_docs.sh +7 -1
  6. package/src/tigerbeetle/src/benchmark.zig +3 -3
  7. package/src/tigerbeetle/src/config.zig +29 -16
  8. package/src/tigerbeetle/src/constants.zig +30 -9
  9. package/src/tigerbeetle/src/ewah.zig +5 -5
  10. package/src/tigerbeetle/src/ewah_fuzz.zig +1 -1
  11. package/src/tigerbeetle/src/lsm/binary_search.zig +1 -1
  12. package/src/tigerbeetle/src/lsm/bloom_filter.zig +1 -1
  13. package/src/tigerbeetle/src/lsm/compaction.zig +34 -21
  14. package/src/tigerbeetle/src/lsm/forest_fuzz.zig +85 -103
  15. package/src/tigerbeetle/src/lsm/grid.zig +19 -13
  16. package/src/tigerbeetle/src/lsm/manifest_log.zig +8 -10
  17. package/src/tigerbeetle/src/lsm/manifest_log_fuzz.zig +12 -8
  18. package/src/tigerbeetle/src/lsm/merge_iterator.zig +1 -1
  19. package/src/tigerbeetle/src/lsm/segmented_array.zig +17 -17
  20. package/src/tigerbeetle/src/lsm/segmented_array_fuzz.zig +1 -1
  21. package/src/tigerbeetle/src/lsm/set_associative_cache.zig +1 -1
  22. package/src/tigerbeetle/src/lsm/table.zig +8 -20
  23. package/src/tigerbeetle/src/lsm/table_immutable.zig +1 -1
  24. package/src/tigerbeetle/src/lsm/table_iterator.zig +3 -3
  25. package/src/tigerbeetle/src/lsm/table_mutable.zig +14 -2
  26. package/src/tigerbeetle/src/lsm/tree.zig +31 -5
  27. package/src/tigerbeetle/src/lsm/tree_fuzz.zig +86 -114
  28. package/src/tigerbeetle/src/message_bus.zig +4 -4
  29. package/src/tigerbeetle/src/message_pool.zig +7 -10
  30. package/src/tigerbeetle/src/ring_buffer.zig +22 -12
  31. package/src/tigerbeetle/src/simulator.zig +360 -214
  32. package/src/tigerbeetle/src/state_machine/auditor.zig +5 -5
  33. package/src/tigerbeetle/src/state_machine/workload.zig +3 -3
  34. package/src/tigerbeetle/src/state_machine.zig +190 -178
  35. package/src/tigerbeetle/src/{util.zig → stdx.zig} +2 -0
  36. package/src/tigerbeetle/src/storage.zig +13 -6
  37. package/src/tigerbeetle/src/{test → testing/cluster}/message_bus.zig +3 -3
  38. package/src/tigerbeetle/src/{test → testing/cluster}/network.zig +46 -22
  39. package/src/tigerbeetle/src/testing/cluster/state_checker.zig +169 -0
  40. package/src/tigerbeetle/src/testing/cluster/storage_checker.zig +202 -0
  41. package/src/tigerbeetle/src/testing/cluster.zig +537 -0
  42. package/src/tigerbeetle/src/{test → testing}/fuzz.zig +0 -0
  43. package/src/tigerbeetle/src/testing/hash_log.zig +66 -0
  44. package/src/tigerbeetle/src/{test → testing}/id.zig +0 -0
  45. package/src/tigerbeetle/src/testing/packet_simulator.zig +365 -0
  46. package/src/tigerbeetle/src/{test → testing}/priority_queue.zig +1 -1
  47. package/src/tigerbeetle/src/testing/reply_sequence.zig +139 -0
  48. package/src/tigerbeetle/src/{test → testing}/state_machine.zig +3 -1
  49. package/src/tigerbeetle/src/testing/storage.zig +754 -0
  50. package/src/tigerbeetle/src/{test → testing}/table.zig +21 -0
  51. package/src/tigerbeetle/src/{test → testing}/time.zig +0 -0
  52. package/src/tigerbeetle/src/tigerbeetle.zig +2 -0
  53. package/src/tigerbeetle/src/tracer.zig +3 -3
  54. package/src/tigerbeetle/src/unit_tests.zig +4 -4
  55. package/src/tigerbeetle/src/vopr.zig +2 -2
  56. package/src/tigerbeetle/src/vsr/client.zig +5 -2
  57. package/src/tigerbeetle/src/vsr/clock.zig +93 -53
  58. package/src/tigerbeetle/src/vsr/journal.zig +29 -14
  59. package/src/tigerbeetle/src/vsr/journal_format_fuzz.zig +2 -2
  60. package/src/tigerbeetle/src/vsr/replica.zig +1383 -774
  61. package/src/tigerbeetle/src/vsr/replica_format.zig +2 -2
  62. package/src/tigerbeetle/src/vsr/superblock.zig +59 -43
  63. package/src/tigerbeetle/src/vsr/superblock_client_table.zig +7 -7
  64. package/src/tigerbeetle/src/vsr/superblock_free_set.zig +1 -1
  65. package/src/tigerbeetle/src/vsr/superblock_free_set_fuzz.zig +1 -1
  66. package/src/tigerbeetle/src/vsr/superblock_fuzz.zig +15 -7
  67. package/src/tigerbeetle/src/vsr/superblock_manifest.zig +38 -19
  68. package/src/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +1 -1
  69. package/src/tigerbeetle/src/vsr.zig +6 -4
  70. package/src/tigerbeetle/src/demo.zig +0 -132
  71. package/src/tigerbeetle/src/demo_01_create_accounts.zig +0 -35
  72. package/src/tigerbeetle/src/demo_02_lookup_accounts.zig +0 -7
  73. package/src/tigerbeetle/src/demo_03_create_transfers.zig +0 -37
  74. package/src/tigerbeetle/src/demo_04_create_pending_transfers.zig +0 -61
  75. package/src/tigerbeetle/src/demo_05_post_pending_transfers.zig +0 -37
  76. package/src/tigerbeetle/src/demo_06_void_pending_transfers.zig +0 -24
  77. package/src/tigerbeetle/src/demo_07_lookup_transfers.zig +0 -7
  78. package/src/tigerbeetle/src/test/cluster.zig +0 -352
  79. package/src/tigerbeetle/src/test/conductor.zig +0 -366
  80. package/src/tigerbeetle/src/test/packet_simulator.zig +0 -398
  81. package/src/tigerbeetle/src/test/state_checker.zig +0 -169
  82. package/src/tigerbeetle/src/test/storage.zig +0 -864
  83. package/src/tigerbeetle/src/test/storage_checker.zig +0 -204
@@ -4,8 +4,8 @@ const assert = std.debug.assert;
4
4
  const math = std.math;
5
5
  const mem = std.mem;
6
6
 
7
- const util = @import("../util.zig");
8
- const div_ceil = @import("../util.zig").div_ceil;
7
+ const stdx = @import("../stdx.zig");
8
+ const div_ceil = @import("../stdx.zig").div_ceil;
9
9
  const binary_search_values_raw = @import("binary_search.zig").binary_search_values_raw;
10
10
  const binary_search_keys = @import("binary_search.zig").binary_search_keys;
11
11
  const Direction = @import("direction.zig").Direction;
@@ -280,13 +280,13 @@ fn SegmentedArrayType(
280
280
 
281
281
  const total = array.count(a) + @intCast(u32, elements.len);
282
282
  if (total <= node_capacity) {
283
- util.copy_right(
283
+ stdx.copy_right(
284
284
  .inexact,
285
285
  T,
286
286
  a_pointer[cursor.relative_index + elements.len ..],
287
287
  a_pointer[cursor.relative_index..array.count(a)],
288
288
  );
289
- util.copy_disjoint(.inexact, T, a_pointer[cursor.relative_index..], elements);
289
+ stdx.copy_disjoint(.inexact, T, a_pointer[cursor.relative_index..], elements);
290
290
 
291
291
  array.increment_indexes_after(a, @intCast(u32, elements.len));
292
292
  return;
@@ -348,7 +348,7 @@ fn SegmentedArrayType(
348
348
 
349
349
  if (a_half < cursor.relative_index) {
350
350
  // Move the part of `a` that is past the half-way point into `b`.
351
- util.copy_right(
351
+ stdx.copy_right(
352
352
  .inexact,
353
353
  T,
354
354
  b_half_pointer,
@@ -383,10 +383,10 @@ fn SegmentedArrayType(
383
383
  const source_a = source[0..target_a.len];
384
384
  const source_b = source[target_a.len..];
385
385
  if (target_b.ptr != source_b.ptr) {
386
- util.copy_right(.exact, T, target_b, source_b);
386
+ stdx.copy_right(.exact, T, target_b, source_b);
387
387
  }
388
388
  if (target_a.ptr != source_a.ptr) {
389
- util.copy_right(.exact, T, target_a, source_a);
389
+ stdx.copy_right(.exact, T, target_a, source_a);
390
390
  }
391
391
  }
392
392
 
@@ -395,13 +395,13 @@ fn SegmentedArrayType(
395
395
  assert(node <= array.node_count);
396
396
  assert(array.node_count + 1 <= node_count_max);
397
397
 
398
- util.copy_right(
398
+ stdx.copy_right(
399
399
  .exact,
400
400
  ?*[node_capacity]T,
401
401
  array.nodes[node + 1 .. array.node_count + 1],
402
402
  array.nodes[node..array.node_count],
403
403
  );
404
- util.copy_right(
404
+ stdx.copy_right(
405
405
  .exact,
406
406
  u32,
407
407
  array.indexes[node + 1 .. array.node_count + 2],
@@ -470,7 +470,7 @@ fn SegmentedArrayType(
470
470
 
471
471
  // Remove elements from exactly one node:
472
472
  if (a_remaining + remove_count <= array.count(a)) {
473
- util.copy_left(
473
+ stdx.copy_left(
474
474
  .inexact,
475
475
  T,
476
476
  a_pointer[a_remaining..],
@@ -497,7 +497,7 @@ fn SegmentedArrayType(
497
497
  assert(a_remaining > 0 or b_remaining.len > 0);
498
498
 
499
499
  if (a_remaining >= half) {
500
- util.copy_left(.inexact, T, b_pointer, b_remaining);
500
+ stdx.copy_left(.inexact, T, b_pointer, b_remaining);
501
501
 
502
502
  array.indexes[b] = array.indexes[a] + a_remaining;
503
503
  array.decrement_indexes_after(b, remove_count);
@@ -514,7 +514,7 @@ fn SegmentedArrayType(
514
514
  assert(a_remaining < half and b_remaining.len < half);
515
515
  assert(a_remaining + b_remaining.len <= node_capacity);
516
516
 
517
- util.copy_disjoint(.inexact, T, a_pointer[a_remaining..], b_remaining);
517
+ stdx.copy_disjoint(.inexact, T, a_pointer[a_remaining..], b_remaining);
518
518
 
519
519
  array.indexes[b] = array.indexes[a] + a_remaining + @intCast(u32, b_remaining.len);
520
520
  array.decrement_indexes_after(b, remove_count);
@@ -576,7 +576,7 @@ fn SegmentedArrayType(
576
576
 
577
577
  const total = array.count(a) + @intCast(u32, b_elements.len);
578
578
  if (total <= node_capacity) {
579
- util.copy_disjoint(.inexact, T, a_pointer[array.count(a)..], b_elements);
579
+ stdx.copy_disjoint(.inexact, T, a_pointer[array.count(a)..], b_elements);
580
580
 
581
581
  array.indexes[b] = array.indexes[b + 1];
582
582
  array.remove_empty_node_at(node_pool, b);
@@ -588,13 +588,13 @@ fn SegmentedArrayType(
588
588
  assert(a_half >= b_half);
589
589
  assert(a_half + b_half == total);
590
590
 
591
- util.copy_disjoint(
591
+ stdx.copy_disjoint(
592
592
  .exact,
593
593
  T,
594
594
  a_pointer[array.count(a)..a_half],
595
595
  b_elements[0 .. a_half - array.count(a)],
596
596
  );
597
- util.copy_left(.inexact, T, b_pointer, b_elements[a_half - array.count(a) ..]);
597
+ stdx.copy_left(.inexact, T, b_pointer, b_elements[a_half - array.count(a) ..]);
598
598
 
599
599
  array.indexes[b] = array.indexes[a] + a_half;
600
600
 
@@ -616,13 +616,13 @@ fn SegmentedArrayType(
616
616
  @ptrCast(NodePool.Node, @alignCast(NodePool.node_alignment, array.nodes[node].?)),
617
617
  );
618
618
 
619
- util.copy_left(
619
+ stdx.copy_left(
620
620
  .exact,
621
621
  ?*[node_capacity]T,
622
622
  array.nodes[node .. array.node_count - 1],
623
623
  array.nodes[node + 1 .. array.node_count],
624
624
  );
625
- util.copy_left(
625
+ stdx.copy_left(
626
626
  .exact,
627
627
  u32,
628
628
  array.indexes[node..array.node_count],
@@ -1,6 +1,6 @@
1
1
  const std = @import("std");
2
2
 
3
- const fuzz = @import("../test/fuzz.zig");
3
+ const fuzz = @import("../testing/fuzz.zig");
4
4
  const segmented_array = @import("segmented_array.zig");
5
5
 
6
6
  pub fn main() !void {
@@ -8,7 +8,7 @@ const meta = std.meta;
8
8
  const Vector = meta.Vector;
9
9
 
10
10
  const constants = @import("../constants.zig");
11
- const div_ceil = @import("../util.zig").div_ceil;
11
+ const div_ceil = @import("../stdx.zig").div_ceil;
12
12
  const verify = constants.verify;
13
13
 
14
14
  pub const Layout = struct {
@@ -8,12 +8,13 @@ const vsr = @import("../vsr.zig");
8
8
  const binary_search = @import("binary_search.zig");
9
9
  const bloom_filter = @import("bloom_filter.zig");
10
10
 
11
- const util = @import("../util.zig");
12
- const div_ceil = util.div_ceil;
11
+ const stdx = @import("../stdx.zig");
12
+ const div_ceil = stdx.div_ceil;
13
13
  const eytzinger = @import("eytzinger.zig").eytzinger;
14
14
  const snapshot_latest = @import("tree.zig").snapshot_latest;
15
15
 
16
16
  const BlockType = @import("grid.zig").BlockType;
17
+ const alloc_block = @import("grid.zig").alloc_block;
17
18
  const TableInfoType = @import("manifest.zig").TableInfoType;
18
19
 
19
20
  pub const TableUsage = enum {
@@ -484,13 +485,13 @@ pub fn TableType(
484
485
  data_blocks_in_filter: u32 = 0,
485
486
 
486
487
  pub fn init(allocator: mem.Allocator) !Builder {
487
- const index_block = try allocator.alignedAlloc(u8, constants.sector_size, block_size);
488
+ const index_block = try alloc_block(allocator);
488
489
  errdefer allocator.free(index_block);
489
490
 
490
- const filter_block = try allocator.alignedAlloc(u8, constants.sector_size, block_size);
491
+ const filter_block = try alloc_block(allocator);
491
492
  errdefer allocator.free(filter_block);
492
493
 
493
- const data_block = try allocator.alignedAlloc(u8, constants.sector_size, block_size);
494
+ const data_block = try alloc_block(allocator);
494
495
  errdefer allocator.free(data_block);
495
496
 
496
497
  return Builder{
@@ -527,7 +528,7 @@ pub fn TableType(
527
528
  const values_max = data_block_values(builder.data_block);
528
529
  assert(values_max.len == data.value_count_max);
529
530
 
530
- util.copy_disjoint(.inexact, Value, values_max[builder.value..], values);
531
+ stdx.copy_disjoint(.inexact, Value, values_max[builder.value..], values);
531
532
  builder.value += @intCast(u32, values.len);
532
533
 
533
534
  for (values) |*value| {
@@ -596,8 +597,6 @@ pub fn TableType(
596
597
 
597
598
  const values_padding = mem.sliceAsBytes(values_max[builder.value..]);
598
599
  const block_padding = block[data.padding_offset..][0..data.padding_size];
599
- mem.set(u8, values_padding, 0);
600
- mem.set(u8, block_padding, 0);
601
600
  assert(compare_keys(key_from_value(&values[values.len - 1]), key_max) == .eq);
602
601
 
603
602
  const header_bytes = block[0..@sizeOf(vsr.Header)];
@@ -712,17 +711,6 @@ pub fn TableType(
712
711
 
713
712
  const index_block = builder.index_block;
714
713
 
715
- const index_data_keys_padding = index_data_keys(index_block)[builder.data_block_count..];
716
- const index_data_keys_padding_bytes = mem.sliceAsBytes(index_data_keys_padding);
717
- mem.set(u8, index_data_keys_padding_bytes, 0);
718
- mem.set(u64, index_data_addresses(index_block)[builder.data_block_count..], 0);
719
- mem.set(u128, index_data_checksums(index_block)[builder.data_block_count..], 0);
720
-
721
- mem.set(u64, index_filter_addresses(index_block)[builder.filter_block_count..], 0);
722
- mem.set(u128, index_filter_checksums(index_block)[builder.filter_block_count..], 0);
723
-
724
- mem.set(u8, index_block[index.padding_offset..][0..index.padding_size], 0);
725
-
726
714
  const header_bytes = index_block[0..@sizeOf(vsr.Header)];
727
715
  const header = mem.bytesAsValue(vsr.Header, header_bytes);
728
716
 
@@ -992,7 +980,7 @@ pub fn TableType(
992
980
  key_min: ?Key,
993
981
  key_max: ?Key,
994
982
  ) void {
995
- if (Storage != @import("../test/storage.zig").Storage)
983
+ if (Storage != @import("../testing/storage.zig").Storage)
996
984
  // Too complicated to do async verification
997
985
  return;
998
986
 
@@ -4,7 +4,7 @@ const math = std.math;
4
4
  const assert = std.debug.assert;
5
5
 
6
6
  const constants = @import("../constants.zig");
7
- const div_ceil = @import("../util.zig").div_ceil;
7
+ const div_ceil = @import("../stdx.zig").div_ceil;
8
8
  const binary_search = @import("binary_search.zig");
9
9
  const snapshot_latest = @import("tree.zig").snapshot_latest;
10
10
 
@@ -5,7 +5,7 @@ const assert = std.debug.assert;
5
5
 
6
6
  const constants = @import("../constants.zig");
7
7
 
8
- const util = @import("../util.zig");
8
+ const stdx = @import("../stdx.zig");
9
9
  const RingBuffer = @import("../ring_buffer.zig").RingBuffer;
10
10
  const ManifestType = @import("manifest.zig").ManifestType;
11
11
  const GridType = @import("grid.zig").GridType;
@@ -208,7 +208,7 @@ pub fn TableIteratorType(comptime Table: type, comptime Storage: type) type {
208
208
 
209
209
  // Copy the bytes read into a buffer owned by the iterator since the Grid
210
210
  // only guarantees the provided pointer to be valid in this callback.
211
- util.copy_disjoint(.exact, u8, it.index_block, block);
211
+ stdx.copy_disjoint(.exact, u8, it.index_block, block);
212
212
 
213
213
  if (it.index_block_callback) |callback| {
214
214
  it.index_block_callback = null;
@@ -240,7 +240,7 @@ pub fn TableIteratorType(comptime Table: type, comptime Storage: type) type {
240
240
 
241
241
  // Copy the bytes read into a buffer owned by the iterator since the Grid
242
242
  // only guarantees the provided pointer to be valid in this callback.
243
- util.copy_disjoint(.exact, u8, it.data_blocks.next_tail().?, block);
243
+ stdx.copy_disjoint(.exact, u8, it.data_blocks.next_tail().?, block);
244
244
 
245
245
  it.data_blocks.advance_tail();
246
246
  it.data_block_index += 1;
@@ -4,7 +4,7 @@ const math = std.math;
4
4
  const assert = std.debug.assert;
5
5
 
6
6
  const constants = @import("../constants.zig");
7
- const div_ceil = @import("../util.zig").div_ceil;
7
+ const div_ceil = @import("../stdx.zig").div_ceil;
8
8
  const SetAssociativeCache = @import("set_associative_cache.zig").SetAssociativeCache;
9
9
 
10
10
  /// Range queries are not supported on the TableMutable, it must first be made immutable.
@@ -43,6 +43,13 @@ pub fn TableMutableType(comptime Table: type) type {
43
43
  value_count_max: u32,
44
44
  values: Values = .{},
45
45
 
46
+ /// Rather than using values.count(), we count how many values we could have had if every
47
+ /// operation had been on a different key. This means that mistakes in calculating
48
+ /// commit_entries_max are much easier to catch when fuzzing, rather than requiring very
49
+ /// specific workloads.
50
+ /// Invariant: value_count_worst_case <= value_count_max
51
+ value_count_worst_case: u32 = 0,
52
+
46
53
  /// This is used to accelerate point lookups and is not used for range queries.
47
54
  /// Secondary index trees used only for range queries can therefore set this to null.
48
55
  ///
@@ -98,6 +105,8 @@ pub fn TableMutableType(comptime Table: type) type {
98
105
  }
99
106
 
100
107
  pub fn put(table: *TableMutable, value: *const Value) void {
108
+ assert(table.value_count_worst_case < table.value_count_max);
109
+ table.value_count_worst_case += 1;
101
110
  switch (usage) {
102
111
  .secondary_index => {
103
112
  const existing = table.values.fetchRemove(value.*);
@@ -123,6 +132,8 @@ pub fn TableMutableType(comptime Table: type) type {
123
132
  }
124
133
 
125
134
  pub fn remove(table: *TableMutable, value: *const Value) void {
135
+ assert(table.value_count_worst_case < table.value_count_max);
136
+ table.value_count_worst_case += 1;
126
137
  switch (usage) {
127
138
  .secondary_index => {
128
139
  const existing = table.values.fetchRemove(value.*);
@@ -152,11 +163,12 @@ pub fn TableMutableType(comptime Table: type) type {
152
163
  /// assumes that none of the batch's keys are already in `table.values`.
153
164
  pub fn can_commit_batch(table: *TableMutable, batch_count: u32) bool {
154
165
  assert(batch_count <= table.value_count_max);
155
- return (table.count() + batch_count) <= table.value_count_max;
166
+ return (table.value_count_worst_case + batch_count) <= table.value_count_max;
156
167
  }
157
168
 
158
169
  pub fn clear(table: *TableMutable) void {
159
170
  assert(table.values.count() > 0);
171
+ table.value_count_worst_case = 0;
160
172
  table.values.clearRetainingCapacity();
161
173
  assert(table.values.count() == 0);
162
174
  }
@@ -10,7 +10,7 @@ const log = std.log.scoped(.tree);
10
10
  const tracer = @import("../tracer.zig");
11
11
 
12
12
  const constants = @import("../constants.zig");
13
- const div_ceil = @import("../util.zig").div_ceil;
13
+ const div_ceil = @import("../stdx.zig").div_ceil;
14
14
  const eytzinger = @import("eytzinger.zig").eytzinger;
15
15
  const vsr = @import("../vsr.zig");
16
16
  const bloom_filter = @import("bloom_filter.zig");
@@ -145,6 +145,7 @@ pub fn TreeType(comptime TreeTable: type, comptime Storage: type, comptime tree_
145
145
 
146
146
  compaction_io_pending: usize,
147
147
  compaction_callback: ?fn (*Tree) void,
148
+ compaction_next_tick: Grid.NextTick = undefined,
148
149
 
149
150
  checkpoint_callback: ?fn (*Tree) void,
150
151
  open_callback: ?fn (*Tree) void,
@@ -336,7 +337,8 @@ pub fn TreeType(comptime TreeTable: type, comptime Storage: type, comptime tree_
336
337
  }
337
338
 
338
339
  if (index_block_count == 0) {
339
- callback(context, null);
340
+ context.callback = callback;
341
+ tree.grid.on_next_tick(lookup_invalid_tick_callback, &context.next_tick);
340
342
  return;
341
343
  }
342
344
 
@@ -346,6 +348,7 @@ pub fn TreeType(comptime TreeTable: type, comptime Storage: type, comptime tree_
346
348
  context.* = .{
347
349
  .tree = tree,
348
350
  .completion = undefined,
351
+ .next_tick = undefined,
349
352
 
350
353
  .key = key,
351
354
  .fingerprint = fingerprint,
@@ -360,12 +363,18 @@ pub fn TreeType(comptime TreeTable: type, comptime Storage: type, comptime tree_
360
363
  context.read_index_block();
361
364
  }
362
365
 
366
+ fn lookup_invalid_tick_callback(next_tick: *Grid.NextTick) void {
367
+ const context = @fieldParentPtr(LookupContext, "next_tick", next_tick);
368
+ context.callback(context, null);
369
+ }
370
+
363
371
  pub const LookupContext = struct {
364
372
  const Read = Grid.Read;
365
373
  const BlockPtrConst = Grid.BlockPtrConst;
366
374
 
367
375
  tree: *Tree,
368
376
  completion: Read,
377
+ next_tick: Grid.NextTick,
369
378
 
370
379
  key: Key,
371
380
  fingerprint: bloom_filter.Fingerprint,
@@ -560,8 +569,8 @@ pub fn TreeType(comptime TreeTable: type, comptime Storage: type, comptime tree_
560
569
  tree.compact_mutable_table_into_immutable();
561
570
  }
562
571
 
563
- // TODO Defer this callback until tick() to avoid stack growth.
564
- callback(tree);
572
+ tree.compaction_callback = callback;
573
+ tree.grid.on_next_tick(compact_skip_tick_callback, &tree.compaction_next_tick);
565
574
  return;
566
575
  }
567
576
 
@@ -589,6 +598,13 @@ pub fn TreeType(comptime TreeTable: type, comptime Storage: type, comptime tree_
589
598
  tree.compact_drive();
590
599
  }
591
600
 
601
+ fn compact_skip_tick_callback(next_tick: *Grid.NextTick) void {
602
+ const tree = @fieldParentPtr(Tree, "compaction_next_tick", next_tick);
603
+ const callback = tree.compaction_callback.?;
604
+ tree.compaction_callback = null;
605
+ callback(tree);
606
+ }
607
+
592
608
  fn compact_start(tree: *Tree, callback: fn (*Tree) void) void {
593
609
  assert(tree.compaction_io_pending == 0);
594
610
  assert(tree.compaction_callback == null);
@@ -858,7 +874,7 @@ pub fn TreeType(comptime TreeTable: type, comptime Storage: type, comptime tree_
858
874
  // We are at the end of a half-bar, but the compactions have not finished.
859
875
  // We keep ticking them until they finish.
860
876
  log.debug(tree_name ++ ": compact_done: driving outstanding compactions", .{});
861
- tree.compact_drive();
877
+ tree.grid.on_next_tick(compact_drive_tick_callback, &tree.compaction_next_tick);
862
878
  return;
863
879
  }
864
880
 
@@ -936,6 +952,16 @@ pub fn TreeType(comptime TreeTable: type, comptime Storage: type, comptime tree_
936
952
  tree.manifest.compact(compact_manifest_callback);
937
953
  }
938
954
 
955
+ /// Asynchronously continue to drive the compactions when they haven't finished at the time
956
+ /// they were supposed to at the end of a half-bar.
957
+ fn compact_drive_tick_callback(next_tick: *Grid.NextTick) void {
958
+ const tree = @fieldParentPtr(Tree, "compaction_next_tick", next_tick);
959
+ assert(tree.compaction_io_pending == 0);
960
+ assert(tree.compaction_callback != null);
961
+ assert(tree.compaction_op == tree.lookup_snapshot_max);
962
+ tree.compact_drive();
963
+ }
964
+
939
965
  /// Called after the last beat of a full compaction bar.
940
966
  fn compact_mutable_table_into_immutable(tree: *Tree) void {
941
967
  assert(tree.table_immutable.free);