tigerbeetle-node 0.10.0 → 0.11.0

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 (92) hide show
  1. package/README.md +302 -101
  2. package/dist/index.d.ts +70 -72
  3. package/dist/index.js +70 -72
  4. package/dist/index.js.map +1 -1
  5. package/package.json +6 -6
  6. package/scripts/download_node_headers.sh +14 -7
  7. package/src/index.ts +6 -10
  8. package/src/node.zig +6 -3
  9. package/src/tigerbeetle/scripts/benchmark.sh +4 -4
  10. package/src/tigerbeetle/scripts/confirm_image.sh +44 -0
  11. package/src/tigerbeetle/scripts/fuzz_loop.sh +15 -0
  12. package/src/tigerbeetle/scripts/fuzz_unique_errors.sh +7 -0
  13. package/src/tigerbeetle/scripts/install.sh +19 -4
  14. package/src/tigerbeetle/scripts/install_zig.bat +5 -1
  15. package/src/tigerbeetle/scripts/install_zig.sh +24 -14
  16. package/src/tigerbeetle/scripts/pre-commit.sh +9 -0
  17. package/src/tigerbeetle/scripts/shellcheck.sh +5 -0
  18. package/src/tigerbeetle/scripts/tests_on_alpine.sh +10 -0
  19. package/src/tigerbeetle/scripts/tests_on_ubuntu.sh +14 -0
  20. package/src/tigerbeetle/src/benchmark.zig +4 -2
  21. package/src/tigerbeetle/src/benchmark_array_search.zig +3 -3
  22. package/src/tigerbeetle/src/c/tb_client/thread.zig +8 -9
  23. package/src/tigerbeetle/src/c/tb_client.h +100 -80
  24. package/src/tigerbeetle/src/c/tb_client.zig +4 -1
  25. package/src/tigerbeetle/src/cli.zig +1 -1
  26. package/src/tigerbeetle/src/config.zig +48 -16
  27. package/src/tigerbeetle/src/demo.zig +3 -1
  28. package/src/tigerbeetle/src/eytzinger_benchmark.zig +3 -3
  29. package/src/tigerbeetle/src/io/linux.zig +1 -1
  30. package/src/tigerbeetle/src/lsm/README.md +214 -0
  31. package/src/tigerbeetle/src/lsm/binary_search.zig +137 -10
  32. package/src/tigerbeetle/src/lsm/bloom_filter.zig +43 -0
  33. package/src/tigerbeetle/src/lsm/compaction.zig +352 -398
  34. package/src/tigerbeetle/src/lsm/composite_key.zig +2 -0
  35. package/src/tigerbeetle/src/lsm/eytzinger.zig +1 -1
  36. package/src/tigerbeetle/src/lsm/forest.zig +21 -447
  37. package/src/tigerbeetle/src/lsm/forest_fuzz.zig +412 -0
  38. package/src/tigerbeetle/src/lsm/grid.zig +145 -69
  39. package/src/tigerbeetle/src/lsm/groove.zig +196 -133
  40. package/src/tigerbeetle/src/lsm/k_way_merge.zig +40 -18
  41. package/src/tigerbeetle/src/lsm/level_iterator.zig +28 -9
  42. package/src/tigerbeetle/src/lsm/manifest.zig +81 -181
  43. package/src/tigerbeetle/src/lsm/manifest_level.zig +210 -454
  44. package/src/tigerbeetle/src/lsm/manifest_log.zig +77 -28
  45. package/src/tigerbeetle/src/lsm/posted_groove.zig +64 -76
  46. package/src/tigerbeetle/src/lsm/segmented_array.zig +561 -241
  47. package/src/tigerbeetle/src/lsm/segmented_array_benchmark.zig +148 -0
  48. package/src/tigerbeetle/src/lsm/segmented_array_fuzz.zig +9 -0
  49. package/src/tigerbeetle/src/lsm/set_associative_cache.zig +62 -12
  50. package/src/tigerbeetle/src/lsm/table.zig +83 -48
  51. package/src/tigerbeetle/src/lsm/table_immutable.zig +30 -23
  52. package/src/tigerbeetle/src/lsm/table_iterator.zig +25 -14
  53. package/src/tigerbeetle/src/lsm/table_mutable.zig +63 -12
  54. package/src/tigerbeetle/src/lsm/test.zig +49 -55
  55. package/src/tigerbeetle/src/lsm/tree.zig +407 -402
  56. package/src/tigerbeetle/src/lsm/tree_fuzz.zig +457 -0
  57. package/src/tigerbeetle/src/main.zig +28 -6
  58. package/src/tigerbeetle/src/message_bus.zig +2 -2
  59. package/src/tigerbeetle/src/message_pool.zig +14 -17
  60. package/src/tigerbeetle/src/simulator.zig +145 -112
  61. package/src/tigerbeetle/src/state_machine.zig +338 -228
  62. package/src/tigerbeetle/src/static_allocator.zig +65 -0
  63. package/src/tigerbeetle/src/storage.zig +3 -7
  64. package/src/tigerbeetle/src/test/accounting/auditor.zig +577 -0
  65. package/src/tigerbeetle/src/test/accounting/workload.zig +819 -0
  66. package/src/tigerbeetle/src/test/cluster.zig +18 -48
  67. package/src/tigerbeetle/src/test/conductor.zig +365 -0
  68. package/src/tigerbeetle/src/test/fuzz.zig +121 -0
  69. package/src/tigerbeetle/src/test/id.zig +89 -0
  70. package/src/tigerbeetle/src/test/priority_queue.zig +645 -0
  71. package/src/tigerbeetle/src/test/state_checker.zig +93 -69
  72. package/src/tigerbeetle/src/test/state_machine.zig +11 -35
  73. package/src/tigerbeetle/src/test/storage.zig +29 -8
  74. package/src/tigerbeetle/src/tigerbeetle.zig +14 -16
  75. package/src/tigerbeetle/src/unit_tests.zig +7 -0
  76. package/src/tigerbeetle/src/vopr.zig +494 -0
  77. package/src/tigerbeetle/src/vopr_hub/README.md +58 -0
  78. package/src/tigerbeetle/src/vopr_hub/SETUP.md +199 -0
  79. package/src/tigerbeetle/src/vopr_hub/go.mod +3 -0
  80. package/src/tigerbeetle/src/vopr_hub/main.go +1022 -0
  81. package/src/tigerbeetle/src/vopr_hub/scheduler/go.mod +3 -0
  82. package/src/tigerbeetle/src/vopr_hub/scheduler/main.go +403 -0
  83. package/src/tigerbeetle/src/vsr/client.zig +13 -0
  84. package/src/tigerbeetle/src/vsr/journal.zig +16 -13
  85. package/src/tigerbeetle/src/vsr/replica.zig +924 -491
  86. package/src/tigerbeetle/src/vsr/superblock.zig +55 -37
  87. package/src/tigerbeetle/src/vsr/superblock_client_table.zig +7 -10
  88. package/src/tigerbeetle/src/vsr/superblock_free_set.zig +2 -2
  89. package/src/tigerbeetle/src/vsr/superblock_manifest.zig +18 -3
  90. package/src/tigerbeetle/src/vsr.zig +75 -55
  91. package/src/tigerbeetle/scripts/vopr.bat +0 -48
  92. package/src/tigerbeetle/scripts/vopr.sh +0 -33
@@ -0,0 +1,3 @@
1
+ module github.com/coilhq/tigerbeetle
2
+
3
+ go 1.17
@@ -0,0 +1,403 @@
1
+ /*
2
+ The scheduler checks out the correct commit on each VOPR to test the latest code on main and
3
+ on pull requests with the `vopr` label.
4
+ Note: The scheduler must run in a directory which is separate from the VOPRs it manages to
5
+ prevent it from checking out a commit that could change the organizer itself.
6
+ To run the scheduler, four environmental variables are required:
7
+ 1. TIGERBEETLE_DIRECTORY the location of the VOPR's tigerbeetle directory
8
+ 2. REPOSITORY_URL to access TigerBeetle's GitHub repository
9
+ 3. DEVELOPER_TOKEN required for making GitHub API calls
10
+ 4. NUM_VOPRS specifies the number of VOPRs the system is running
11
+ 5. CURRENT_VOPR specifies which VOPR is currently starting up and requiring updated Git commit info
12
+ */
13
+
14
+ package main
15
+
16
+ import (
17
+ "encoding/json"
18
+ "flag"
19
+ "fmt"
20
+ "io"
21
+ "net/http"
22
+ "os"
23
+ "os/exec"
24
+ "regexp"
25
+ "strconv"
26
+ "strings"
27
+ "time"
28
+ )
29
+
30
+ const usageFmt = `usage: %s [flags]
31
+
32
+ environment:
33
+ TIGERBEETLE_DIRECTORY the location of the VOPR's tigerbeetle directory
34
+ REPOSITORY_URL TigerBeetle's GitHub repository URL
35
+ DEVELOPER_TOKEN required for making GitHub API calls
36
+ NUM_VOPRS the number of VOPRs the system is running
37
+ CURRENT_VOPR the number of the VOPR which is currently starting up (counting from 0)
38
+
39
+ flags:
40
+ -debug enabled debug logging
41
+ `
42
+
43
+ var (
44
+ debug_mode bool
45
+ developer_token string
46
+ tigerbeetle_directory string
47
+ repository_url string
48
+ num_voprs int
49
+ current_vopr int
50
+ )
51
+
52
+ type Label struct {
53
+ Name string `json:"name"`
54
+ }
55
+
56
+ type Head struct {
57
+ Label string `json:"label"`
58
+ }
59
+
60
+ type Issue struct {
61
+ Labels []Label `json:"labels"`
62
+ Head Head `json:"head"`
63
+ }
64
+
65
+ type Commit struct {
66
+ Sha string `json:"sha"`
67
+ }
68
+
69
+ func usage() {
70
+ fmt.Fprintf(os.Stderr, usageFmt, os.Args[0])
71
+ os.Exit(1)
72
+ }
73
+
74
+ func load_environment_variable(name string) string {
75
+ val, _ := os.LookupEnv(name)
76
+ if val == "" {
77
+ log_error(fmt.Sprintf("env %s not set", name))
78
+ usage()
79
+ os.Exit(1)
80
+ } else if name == "DEVELOPER_TOKEN" {
81
+ log_debug(fmt.Sprintf("env %s=*****", name))
82
+ } else {
83
+ log_debug(fmt.Sprintf("env %s=%q", name, val))
84
+ }
85
+ return val
86
+ }
87
+
88
+ // Checks out the appropriate commit for the specified tigerbeetle directory if it exists.
89
+ func checkout_commit(commit string, tigerbeetle_directory string) error {
90
+ // Git commands need to be run with the particular TigerBeetle directory as their
91
+ // working_directory.
92
+ fetch_command := exec.Command("git", "fetch", "--all")
93
+ fetch_command.Dir = tigerbeetle_directory
94
+ error := fetch_command.Run()
95
+ if error != nil {
96
+ error_message := fmt.Sprintf("Failed to run git fetch: %s", error.Error())
97
+ log_error(error_message)
98
+ return error
99
+ }
100
+
101
+ // Ensures commit is all hexadecimal.
102
+ commit_valid, error := regexp.MatchString(`^([0-9a-f]){40}$`, commit)
103
+ if error != nil {
104
+ panic(error.Error())
105
+ } else if !commit_valid {
106
+ error = fmt.Errorf("The GitHub commit contained unexpected characters")
107
+ log_error(error.Error())
108
+ return error
109
+ }
110
+
111
+ // Checkout the commit.
112
+ checkout_command := exec.Command("git", "checkout", commit)
113
+ checkout_command.Dir = tigerbeetle_directory
114
+ error = checkout_command.Run()
115
+ if error != nil {
116
+ error_message := fmt.Sprintf("Failed to run git checkout: %s", error.Error())
117
+ log_error(error_message)
118
+ return error
119
+ }
120
+
121
+ // Inspect the git logs.
122
+ log_command := exec.Command("git", "log", "-1")
123
+ log_command.Dir = tigerbeetle_directory
124
+ log_output := make([]byte, 47)
125
+ log_output, error = log_command.Output()
126
+ if error != nil {
127
+ error_message := fmt.Sprintf("Failed to run git log: %s", error.Error())
128
+ log_error(error_message)
129
+ return error
130
+ }
131
+
132
+ // Check the log to determine if the commit has been successfully checked out.
133
+ current_commit := string(log_output[0:47])
134
+ checkout_successful, error := regexp.MatchString("^commit "+commit, current_commit)
135
+ if error != nil {
136
+ error_message := fmt.Sprintf(
137
+ "Regular expression failure while checking the git logs: %s",
138
+ error.Error(),
139
+ )
140
+ log_error(error_message)
141
+ return error
142
+ }
143
+
144
+ if !checkout_successful {
145
+ error = fmt.Errorf("Checkout failed")
146
+ return error
147
+ }
148
+
149
+ return nil
150
+ }
151
+
152
+ func get_pull_requests(num_posts int, page_number int) []Issue {
153
+ pull_requests := []Issue{}
154
+ get_request, err := http.NewRequest(
155
+ "GET",
156
+ fmt.Sprintf("%s/pulls?per_page=%d&page=%d", repository_url, num_posts, page_number),
157
+ nil,
158
+ )
159
+ if err != nil {
160
+ log_error("unable to create get request")
161
+ panic(err.Error())
162
+ }
163
+ get_request.Header.Set("Authorization", "token "+developer_token)
164
+ res, err := http.DefaultClient.Do(get_request)
165
+ if err != nil {
166
+ log_error("Failed to send the HTTP request for the GitHub API")
167
+ panic(err.Error())
168
+ }
169
+ defer res.Body.Close()
170
+
171
+ body, err := io.ReadAll(res.Body)
172
+
173
+ if res.StatusCode > 299 {
174
+ log_error(
175
+ fmt.Sprintf(
176
+ "Response failed with status code: %d and\nbody: %s\n",
177
+ res.StatusCode,
178
+ body,
179
+ ),
180
+ )
181
+ panic(err.Error())
182
+ }
183
+ if err != nil {
184
+ log_error("unable to receive a response from GitHub")
185
+ panic(err.Error())
186
+ }
187
+
188
+ err = json.Unmarshal(body, &pull_requests)
189
+ if err != nil {
190
+ log_error("unable to unmarshall json")
191
+ panic(err.Error())
192
+ }
193
+
194
+ return pull_requests
195
+ }
196
+
197
+ func get_commits(branch_name string) string {
198
+ commits := []Commit{}
199
+ get_request, err := http.NewRequest(
200
+ "GET",
201
+ fmt.Sprintf("%s/commits?per_page=1&sha=%s", repository_url, branch_name),
202
+ nil,
203
+ )
204
+ if err != nil {
205
+ log_error("unable to create get request")
206
+ panic(err.Error())
207
+ }
208
+ get_request.Header.Set("Authorization", "token "+developer_token)
209
+ res, err := http.DefaultClient.Do(get_request)
210
+ if err != nil {
211
+ log_error("Failed to send the HTTP request for the GitHub API")
212
+ panic(err.Error())
213
+ }
214
+ defer res.Body.Close()
215
+
216
+ body, err := io.ReadAll(res.Body)
217
+
218
+ if res.StatusCode > 299 {
219
+ log_error(
220
+ fmt.Sprintf(
221
+ "Response failed with status code: %d and\nbody: %s\n",
222
+ res.StatusCode,
223
+ body,
224
+ ),
225
+ )
226
+ panic(err.Error())
227
+ }
228
+ if err != nil {
229
+ log_error("unable to receive a response from GitHub")
230
+ panic(err.Error())
231
+ }
232
+
233
+ err = json.Unmarshal(body, &commits)
234
+ if err != nil {
235
+ log_error("unable to unmarshall json")
236
+ panic(err.Error())
237
+ }
238
+
239
+ if len(commits) > 0 && len(commits[0].Sha) > 0 {
240
+ return commits[0].Sha
241
+ } else {
242
+ error := fmt.Errorf("unable to access commit associated with branch %s", branch_name)
243
+ panic(error.Error())
244
+ }
245
+ }
246
+
247
+ func get_commit_hashes() []string {
248
+ const num_posts int = 30 // This is the GitHub API default.
249
+ var pull_requests []Issue
250
+ var vopr_commits []string
251
+
252
+ page_number := 1
253
+
254
+ // Require at most (num_voprs-1) pull requests.
255
+ // The first VOPR always runs on main's latest commit.
256
+ for len(vopr_commits) < num_voprs-1 {
257
+ // Pull requests will be ordered newest to oldest by default.
258
+ pull_requests = get_pull_requests(num_posts, page_number)
259
+
260
+ for _, element := range pull_requests {
261
+ for _, label := range element.Labels {
262
+ if label.Name == "vopr" {
263
+ // Branches are returned in the format owner:branch_name.
264
+ _, branch_name, found := strings.Cut(element.Head.Label, ":")
265
+ if found && branch_name != "" {
266
+ commit := get_commits(branch_name)
267
+ vopr_commits = append(vopr_commits, commit)
268
+ }
269
+ break
270
+ }
271
+ }
272
+
273
+ if len(vopr_commits) == num_voprs-1 {
274
+ break
275
+ }
276
+ }
277
+ // Exit the loop if there are no more pages of pull requests to be fetched from GitHub.
278
+ if len(pull_requests) < num_posts {
279
+ break
280
+ }
281
+ page_number++
282
+ }
283
+
284
+ return vopr_commits
285
+ }
286
+
287
+ func get_vopr_assignments(vopr_commits []string) []string {
288
+ var num_pull_requests = len(vopr_commits)
289
+ var vopr_assignments []string
290
+ main_commit := get_commits("main")
291
+
292
+ if num_pull_requests > 0 {
293
+ // The first VOPR always runs main
294
+ vopr_assignments = append(vopr_assignments, main_commit)
295
+
296
+ // This calculates how many times each PR branch will be assigned to a VOPR.
297
+ var repeats = int((num_voprs - 1) / num_pull_requests)
298
+ // This calculates how many branches will have an additional assignment.
299
+ var remainders = (num_voprs - 1) % num_pull_requests
300
+ i := 1
301
+ commit_index := 0
302
+ for i < num_voprs {
303
+ for j := 0; j < repeats; j++ {
304
+ vopr_assignments = append(
305
+ vopr_assignments,
306
+ fmt.Sprintf("%s", vopr_commits[commit_index]),
307
+ )
308
+ i++
309
+ }
310
+ if remainders > 0 {
311
+ vopr_assignments = append(
312
+ vopr_assignments,
313
+ fmt.Sprintf("%s", vopr_commits[commit_index]),
314
+ )
315
+ remainders--
316
+ i++
317
+ }
318
+ commit_index++
319
+ }
320
+ } else {
321
+ i := 0
322
+ for i < num_voprs {
323
+ vopr_assignments = append(vopr_assignments, main_commit)
324
+ i++
325
+ }
326
+ }
327
+ return vopr_assignments
328
+ }
329
+
330
+ func log_error(message string) {
331
+ log_message("error: ", message)
332
+ }
333
+
334
+ func log_debug(message string) {
335
+ if debug_mode {
336
+ log_message("debug: ", message)
337
+ }
338
+ }
339
+
340
+ // Formats all the log messages and adds a timestamp to them.
341
+ func log_message(log_level string, message string) {
342
+ // Gets the current time in UTC and rounds to the nearest second.
343
+ timestamp := time.Now().UTC().Format(time.RFC3339)
344
+ fmt.Printf("%s %s%s\n", timestamp, log_level, message)
345
+ }
346
+
347
+ func main() {
348
+ // Determine the mode in which to run the VOPR Hub.
349
+ flags := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
350
+ flags.BoolVar(&debug_mode, "debug", false, "runs with debugging logs enabled")
351
+ if err := flags.Parse(os.Args[1:]); err != nil {
352
+ usage()
353
+ }
354
+
355
+ tigerbeetle_directory = load_environment_variable("TIGERBEETLE_DIRECTORY")
356
+ repository_url = load_environment_variable("REPOSITORY_URL")
357
+ developer_token = load_environment_variable("DEVELOPER_TOKEN")
358
+ num_voprs_str := load_environment_variable("NUM_VOPRS")
359
+ // string to int
360
+ var err error
361
+ num_voprs, err = strconv.Atoi(num_voprs_str)
362
+ if err != nil {
363
+ log_error("unable to convert num_voprs to a an integer value")
364
+ panic(err.Error())
365
+ } else if num_voprs <= 0 {
366
+ log_error("NUM_VOPRS must be an integer greater than 0")
367
+ os.Exit(1)
368
+ }
369
+ current_vopr_str := load_environment_variable("CURRENT_VOPR")
370
+ // string to int
371
+ current_vopr, err = strconv.Atoi(current_vopr_str)
372
+ if err != nil {
373
+ log_error("unable to convert current_vopr to a an integer value")
374
+ panic(err.Error())
375
+ } else if current_vopr < 0 {
376
+ log_error("CURRENT_VOPR must be a positive integer")
377
+ os.Exit(1)
378
+ }
379
+
380
+ // Gets commit hashes for main and up to (NUM_VOPRS -1) PR branches that have the `vopr` label.
381
+ vopr_commits := get_commit_hashes()
382
+
383
+ // Assigns one commit for each VOPR to run on.
384
+ vopr_assignments := get_vopr_assignments(vopr_commits)
385
+
386
+ if current_vopr < len(vopr_assignments) && current_vopr >= 0 {
387
+ error := checkout_commit(
388
+ vopr_assignments[current_vopr],
389
+ fmt.Sprintf("%s%d", tigerbeetle_directory, current_vopr),
390
+ )
391
+ if error == nil {
392
+ log_debug("Successfully checked out commit " + vopr_assignments[current_vopr])
393
+ } else {
394
+ error_message := fmt.Sprintf(
395
+ "Failed to checkout commit %s: %s",
396
+ vopr_assignments[current_vopr],
397
+ error.Error(),
398
+ )
399
+ log_error(error_message)
400
+ panic(error.Error())
401
+ }
402
+ }
403
+ }
@@ -81,6 +81,15 @@ pub fn Client(comptime StateMachine: type, comptime MessageBus: type) type {
81
81
  /// Seeded with the client's ID.
82
82
  prng: std.rand.DefaultPrng,
83
83
 
84
+ on_reply_context: ?*anyopaque = null,
85
+ /// Used for testing. Called for replies to all operations (including `register`).
86
+ on_reply_callback: ?fn (
87
+ context: ?*anyopaque,
88
+ client: *Self,
89
+ request: *Message,
90
+ reply: *Message,
91
+ ) void = null,
92
+
84
93
  pub fn init(
85
94
  allocator: mem.Allocator,
86
95
  id: u128,
@@ -352,6 +361,10 @@ pub fn Client(comptime StateMachine: type, comptime MessageBus: type) type {
352
361
  self.send_request_for_the_first_time(next_request.message);
353
362
  }
354
363
 
364
+ if (self.on_reply_callback) |on_reply_callback| {
365
+ on_reply_callback(self.on_reply_context, self, inflight.message, reply);
366
+ }
367
+
355
368
  if (inflight.message.header.operation != .register) {
356
369
  inflight.callback(
357
370
  inflight.user_data,
@@ -380,14 +380,15 @@ pub fn Journal(comptime Replica: type, comptime Storage: type) type {
380
380
  return self.slot_with_op(header.op);
381
381
  }
382
382
 
383
- /// Returns any existing entry at the location indicated by header.op.
384
- /// This existing entry may have an older or newer op number.
385
- pub fn header_for_entry(self: *const Self, header: *const Header) ?*const Header {
383
+ /// Returns any existing header at the location indicated by header.op.
384
+ /// The existing header may have an older or newer op number.
385
+ pub fn header_for_prepare(self: *const Self, header: *const Header) ?*const Header {
386
386
  assert(header.command == .prepare);
387
387
  return self.header_for_op(header.op);
388
388
  }
389
389
 
390
390
  /// We use `op` directly to index into the headers array and locate ops without a scan.
391
+ /// The existing header may have an older or newer op number.
391
392
  pub fn header_for_op(self: *const Self, op: u64) ?*const Header {
392
393
  // TODO Snapshots
393
394
  const slot = self.slot_for_op(op);
@@ -510,7 +511,8 @@ pub fn Journal(comptime Replica: type, comptime Storage: type) type {
510
511
 
511
512
  /// Copies latest headers between `op_min` and `op_max` (both inclusive) as fit in `dest`.
512
513
  /// Reverses the order when copying so that latest headers are copied first, which protects
513
- /// against the callsite slicing the buffer the wrong way and incorrectly.
514
+ /// against the callsite slicing the buffer the wrong way and incorrectly, and which is
515
+ /// required by message handlers that use the hash chain for repairs.
514
516
  /// Skips .reserved headers (gaps between headers).
515
517
  /// Zeroes the `dest` buffer in case the copy would underflow and leave a buffer bleed.
516
518
  /// Returns the number of headers actually copied.
@@ -725,6 +727,9 @@ pub fn Journal(comptime Replica: type, comptime Storage: type) type {
725
727
  if (self.header_with_op_and_checksum(op, checksum)) |exact| {
726
728
  if (exact.size == @sizeOf(Header)) {
727
729
  message.header.* = exact.*;
730
+ // Normally the message's padding would have been zeroed by the MessageBus,
731
+ // but we are copying (only) a message header into a new buffer.
732
+ std.mem.set(u8, message.buffer[@sizeOf(Header)..config.sector_size], 0);
728
733
  callback(replica, message, destination_replica);
729
734
  return;
730
735
  }
@@ -981,7 +986,7 @@ pub fn Journal(comptime Replica: type, comptime Storage: type) type {
981
986
  const max = std.math.min(message.buffer.len, headers_size - offset);
982
987
  assert(max % config.sector_size == 0);
983
988
  assert(max % @sizeOf(Header) == 0);
984
- return @alignCast(@alignOf(Header), message.buffer[0..max]);
989
+ return message.buffer[0..max];
985
990
  }
986
991
 
987
992
  fn recover_prepares(self: *Self, slot: Slot) void {
@@ -1441,6 +1446,7 @@ pub fn Journal(comptime Replica: type, comptime Storage: type) type {
1441
1446
  header.op,
1442
1447
  header.checksum,
1443
1448
  });
1449
+
1444
1450
  const slot = self.slot_for_header(header);
1445
1451
 
1446
1452
  if (self.has(header)) {
@@ -1827,10 +1833,7 @@ pub fn Journal(comptime Replica: type, comptime Storage: type) type {
1827
1833
  self.lock_sectors(@fieldParentPtr(Self.Write, "range", waiting));
1828
1834
  }
1829
1835
 
1830
- // The callback may set range, so we can't set range to undefined after the callback.
1831
- const callback = range.callback;
1832
- range.* = undefined;
1833
- callback(write);
1836
+ range.callback(write);
1834
1837
  }
1835
1838
 
1836
1839
  pub fn writing(self: *Self, op: u64, checksum: u128) bool {
@@ -2236,14 +2239,14 @@ test "format_journal" {
2236
2239
  };
2237
2240
 
2238
2241
  for (write_sizes) |write_size_max| {
2239
- var wal_data = try std.testing.allocator.alloc(u8, config.journal_size_max);
2242
+ const wal_data = try std.testing.allocator.alignedAlloc(u8, @alignOf(Header), config.journal_size_max);
2240
2243
  defer std.testing.allocator.free(wal_data);
2241
2244
 
2242
- var write_data = try std.testing.allocator.alloc(u8, write_size_max);
2245
+ const write_data = try std.testing.allocator.alloc(u8, write_size_max);
2243
2246
  defer std.testing.allocator.free(write_data);
2244
2247
 
2245
- var headers_ring = std.mem.bytesAsSlice(Header, @alignCast(@alignOf(Header), wal_data[0..config.journal_size_headers]));
2246
- var prepare_ring = std.mem.bytesAsSlice([config.message_size_max]u8, wal_data[config.journal_size_headers..]);
2248
+ const headers_ring = std.mem.bytesAsSlice(Header, wal_data[0..config.journal_size_headers]);
2249
+ const prepare_ring = std.mem.bytesAsSlice([config.message_size_max]u8, wal_data[config.journal_size_headers..]);
2247
2250
  try std.testing.expectEqual(@as(usize, config.journal_slot_count), headers_ring.len);
2248
2251
  try std.testing.expectEqual(@as(usize, config.journal_slot_count), prepare_ring.len);
2249
2252