tigerbeetle-node 0.11.0 → 0.11.1

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 (80) hide show
  1. package/package.json +4 -3
  2. package/src/tigerbeetle/scripts/fuzz_loop.sh +1 -1
  3. package/src/tigerbeetle/scripts/pre-commit.sh +2 -2
  4. package/src/tigerbeetle/scripts/validate_docs.sh +17 -0
  5. package/src/tigerbeetle/src/benchmark.zig +25 -11
  6. package/src/tigerbeetle/src/c/tb_client/context.zig +248 -47
  7. package/src/tigerbeetle/src/c/tb_client/echo_client.zig +108 -0
  8. package/src/tigerbeetle/src/c/tb_client/packet.zig +2 -2
  9. package/src/tigerbeetle/src/c/tb_client/signal.zig +2 -4
  10. package/src/tigerbeetle/src/c/tb_client/thread.zig +17 -256
  11. package/src/tigerbeetle/src/c/tb_client.h +18 -4
  12. package/src/tigerbeetle/src/c/tb_client.zig +88 -26
  13. package/src/tigerbeetle/src/c/tb_client_header_test.zig +135 -0
  14. package/src/tigerbeetle/src/c/test.zig +371 -1
  15. package/src/tigerbeetle/src/cli.zig +36 -6
  16. package/src/tigerbeetle/src/config.zig +10 -1
  17. package/src/tigerbeetle/src/demo.zig +2 -1
  18. package/src/tigerbeetle/src/demo_01_create_accounts.zig +1 -1
  19. package/src/tigerbeetle/src/demo_03_create_transfers.zig +13 -0
  20. package/src/tigerbeetle/src/ewah.zig +11 -33
  21. package/src/tigerbeetle/src/ewah_benchmark.zig +8 -9
  22. package/src/tigerbeetle/src/lsm/README.md +97 -3
  23. package/src/tigerbeetle/src/lsm/compaction.zig +32 -7
  24. package/src/tigerbeetle/src/{eytzinger_benchmark.zig → lsm/eytzinger_benchmark.zig} +34 -21
  25. package/src/tigerbeetle/src/lsm/forest_fuzz.zig +34 -32
  26. package/src/tigerbeetle/src/lsm/grid.zig +39 -21
  27. package/src/tigerbeetle/src/lsm/groove.zig +1 -0
  28. package/src/tigerbeetle/src/lsm/k_way_merge.zig +3 -3
  29. package/src/tigerbeetle/src/lsm/level_iterator.zig +1 -1
  30. package/src/tigerbeetle/src/lsm/manifest.zig +13 -0
  31. package/src/tigerbeetle/src/lsm/manifest_level.zig +0 -49
  32. package/src/tigerbeetle/src/lsm/manifest_log.zig +173 -335
  33. package/src/tigerbeetle/src/lsm/manifest_log_fuzz.zig +665 -0
  34. package/src/tigerbeetle/src/lsm/node_pool.zig +4 -0
  35. package/src/tigerbeetle/src/lsm/posted_groove.zig +1 -0
  36. package/src/tigerbeetle/src/lsm/segmented_array.zig +24 -15
  37. package/src/tigerbeetle/src/lsm/table.zig +32 -20
  38. package/src/tigerbeetle/src/lsm/table_immutable.zig +1 -1
  39. package/src/tigerbeetle/src/lsm/table_iterator.zig +4 -5
  40. package/src/tigerbeetle/src/lsm/test.zig +13 -2
  41. package/src/tigerbeetle/src/lsm/tree.zig +45 -7
  42. package/src/tigerbeetle/src/lsm/tree_fuzz.zig +36 -32
  43. package/src/tigerbeetle/src/main.zig +55 -2
  44. package/src/tigerbeetle/src/message_bus.zig +18 -7
  45. package/src/tigerbeetle/src/message_pool.zig +8 -2
  46. package/src/tigerbeetle/src/ring_buffer.zig +7 -3
  47. package/src/tigerbeetle/src/simulator.zig +38 -11
  48. package/src/tigerbeetle/src/state_machine.zig +47 -22
  49. package/src/tigerbeetle/src/test/accounting/workload.zig +9 -5
  50. package/src/tigerbeetle/src/test/cluster.zig +15 -33
  51. package/src/tigerbeetle/src/test/conductor.zig +2 -1
  52. package/src/tigerbeetle/src/test/network.zig +45 -19
  53. package/src/tigerbeetle/src/test/packet_simulator.zig +40 -29
  54. package/src/tigerbeetle/src/test/state_checker.zig +5 -7
  55. package/src/tigerbeetle/src/test/storage.zig +453 -110
  56. package/src/tigerbeetle/src/test/storage_checker.zig +204 -0
  57. package/src/tigerbeetle/src/tigerbeetle.zig +1 -0
  58. package/src/tigerbeetle/src/unit_tests.zig +6 -1
  59. package/src/tigerbeetle/src/util.zig +97 -11
  60. package/src/tigerbeetle/src/vopr.zig +2 -1
  61. package/src/tigerbeetle/src/vsr/client.zig +8 -3
  62. package/src/tigerbeetle/src/vsr/journal.zig +280 -202
  63. package/src/tigerbeetle/src/vsr/replica.zig +169 -31
  64. package/src/tigerbeetle/src/vsr/superblock.zig +356 -629
  65. package/src/tigerbeetle/src/vsr/superblock_client_table.zig +7 -6
  66. package/src/tigerbeetle/src/vsr/superblock_free_set.zig +414 -151
  67. package/src/tigerbeetle/src/vsr/superblock_free_set_fuzz.zig +332 -0
  68. package/src/tigerbeetle/src/vsr/superblock_fuzz.zig +349 -0
  69. package/src/tigerbeetle/src/vsr/superblock_manifest.zig +44 -9
  70. package/src/tigerbeetle/src/vsr/superblock_quorums.zig +394 -0
  71. package/src/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +312 -0
  72. package/src/tigerbeetle/src/vsr.zig +19 -5
  73. package/src/tigerbeetle/src/benchmark_array_search.zig +0 -317
  74. package/src/tigerbeetle/src/benchmarks/perf.zig +0 -299
  75. package/src/tigerbeetle/src/vopr_hub/README.md +0 -58
  76. package/src/tigerbeetle/src/vopr_hub/SETUP.md +0 -199
  77. package/src/tigerbeetle/src/vopr_hub/go.mod +0 -3
  78. package/src/tigerbeetle/src/vopr_hub/main.go +0 -1022
  79. package/src/tigerbeetle/src/vopr_hub/scheduler/go.mod +0 -3
  80. package/src/tigerbeetle/src/vopr_hub/scheduler/main.go +0 -403
@@ -1,181 +1,44 @@
1
1
  const std = @import("std");
2
- const os = std.os;
3
2
  const assert = std.debug.assert;
4
3
 
5
4
  const config = @import("../../config.zig");
6
- const log = std.log.scoped(.tb_client);
7
-
8
- const vsr = @import("../../vsr.zig");
9
- const Header = vsr.Header;
10
-
11
- const IO = @import("../../io.zig").IO;
12
- const message_pool = @import("../../message_pool.zig");
13
-
14
- const MessagePool = message_pool.MessagePool;
15
- const Message = MessagePool.Message;
5
+ const log = std.log.scoped(.tb_client_thread);
16
6
 
17
7
  const Packet = @import("packet.zig").Packet;
18
8
  const Signal = @import("signal.zig").Signal;
19
9
 
20
10
  pub fn ThreadType(
21
- comptime StateMachine: type,
22
- comptime MessageBus: type,
11
+ comptime Context: type,
23
12
  ) type {
24
13
  return struct {
25
- pub const Client = vsr.Client(StateMachine, MessageBus);
26
- pub const Operation = StateMachine.Operation;
27
-
28
- fn operation_size_of(op: u8) ?usize {
29
- const allowed_operations = [_]Operation{
30
- .create_accounts,
31
- .create_transfers,
32
- .lookup_accounts,
33
- .lookup_transfers,
34
- };
35
-
36
- inline for (allowed_operations) |operation| {
37
- if (op == @enumToInt(operation)) {
38
- return @sizeOf(StateMachine.Event(operation));
39
- }
40
- }
41
-
42
- return null;
43
- }
44
-
45
- /////////////////////////////////////////////////////////////////////////
46
-
47
14
  const Self = @This();
48
15
 
49
- allocator: std.mem.Allocator,
50
- client_id: u128,
51
- packets: []Packet,
52
-
53
- addresses: []std.net.Address,
54
- io: IO,
55
- message_pool: MessagePool,
56
- message_bus: MessageBus,
57
- client: Client,
16
+ context: *Context,
58
17
 
59
18
  retry: Packet.List,
60
19
  submitted: Packet.Stack,
61
- available_messages: usize,
62
- on_completion_fn: CompletionFn,
63
20
 
64
21
  signal: Signal,
65
22
  thread: std.Thread,
66
23
 
67
- pub const CompletionFn = fn (
68
- client_thread: *Self,
69
- packet: *Packet,
70
- result: ?[]const u8,
71
- ) void;
72
-
73
- pub const Error = error{
74
- Unexpected,
75
- OutOfMemory,
76
- InvalidAddress,
77
- SystemResources,
78
- NetworkSubsystemFailed,
79
- };
80
-
81
24
  pub fn init(
82
25
  self: *Self,
83
- allocator: std.mem.Allocator,
84
- cluster_id: u32,
85
- addresses: []const u8,
86
- num_packets: u32,
87
- on_completion_fn: CompletionFn,
88
- ) Error!void {
89
- self.allocator = allocator;
90
- self.client_id = std.crypto.random.int(u128);
91
- log.debug("init: initializing client_id={}.", .{self.client_id});
92
-
93
- log.debug("init: allocating tb_packets.", .{});
94
- self.packets = self.allocator.alloc(Packet, num_packets) catch |err| {
95
- log.err("failed to allocate tb_packets: {}", .{err});
96
- return Error.OutOfMemory;
97
- };
98
- errdefer self.allocator.free(self.packets);
99
-
100
- log.debug("init: parsing vsr addresses.", .{});
101
- const address_limit = std.mem.count(u8, addresses, ",") + 1;
102
- self.addresses = vsr.parse_addresses(self.allocator, addresses, address_limit) catch |err| {
103
- log.err("failed to parse addresses: {}.", .{err});
104
- return Error.InvalidAddress;
105
- };
106
- errdefer self.allocator.free(self.addresses);
107
-
108
- log.debug("init: initializing IO.", .{});
109
- self.io = IO.init(32, 0) catch |err| {
110
- log.err("failed to initialize IO: {}.", .{err});
111
- return switch (err) {
112
- error.ProcessFdQuotaExceeded => error.SystemResources,
113
- error.Unexpected => error.Unexpected,
114
- else => unreachable,
115
- };
116
- };
117
- errdefer self.io.deinit();
118
-
119
- log.debug("init: initializing MessagePool", .{});
120
- self.message_pool = MessagePool.init(allocator, .client) catch |err| {
121
- log.err("failed to initialize MessagePool: {}", .{err});
122
- return err;
123
- };
124
- errdefer self.message_pool.deinit(self.allocator);
125
-
126
- log.debug("init: initializing MessageBus.", .{});
127
- self.message_bus = MessageBus.init(
128
- self.allocator,
129
- cluster_id,
130
- .{ .client = self.client_id },
131
- &self.message_pool,
132
- Client.on_message,
133
- .{
134
- .configuration = self.addresses,
135
- .io = &self.io,
136
- },
137
- ) catch |err| {
138
- log.err("failed to initialize message bus: {}.", .{err});
139
- return err;
140
- };
141
- errdefer self.message_bus.deinit(self.allocator);
142
-
143
- log.debug("init: Initializing client(cluster_id={d}, client_id={d}, addresses={o})", .{
144
- cluster_id,
145
- self.client_id,
146
- self.addresses,
147
- });
148
- self.client = Client.init(
149
- allocator,
150
- self.client_id,
151
- cluster_id,
152
- @intCast(u8, self.addresses.len),
153
- &self.message_pool,
154
- .{
155
- .configuration = self.addresses,
156
- .io = &self.io,
157
- },
158
- ) catch |err| {
159
- log.err("failed to initalize client: {}", .{err});
160
- return err;
161
- };
162
- errdefer self.client.deinit(self.allocator);
163
-
26
+ context: *Context,
27
+ ) !void {
28
+ self.context = context;
164
29
  self.retry = .{};
165
30
  self.submitted = .{};
166
- self.available_messages = message_pool.messages_max_client;
167
- self.on_completion_fn = on_completion_fn;
168
31
 
169
- log.debug("init: initializing Signal.", .{});
170
- self.signal.init(&self.io, Self.on_signal) catch |err| {
171
- log.err("failed to initialize Signal: {}.", .{err});
172
- return err;
173
- };
32
+ log.debug("{}: init: initializing signal", .{context.client_id});
33
+ try self.signal.init(&context.io, Self.on_signal);
174
34
  errdefer self.signal.deinit();
175
35
 
176
- log.debug("init: spawning Context thread.", .{});
177
- self.thread = std.Thread.spawn(.{}, Self.run, .{self}) catch |err| {
178
- log.err("failed to spawn context thread: {}.", .{err});
36
+ log.debug("{}: init: spawning thread", .{context.client_id});
37
+ self.thread = std.Thread.spawn(.{}, Context.run, .{context}) catch |err| {
38
+ log.err("{}: failed to spawn thread: {s}", .{
39
+ context.client_id,
40
+ @errorName(err),
41
+ });
179
42
  return switch (err) {
180
43
  error.Unexpected => error.Unexpected,
181
44
  error.OutOfMemory => error.OutOfMemory,
@@ -189,13 +52,6 @@ pub fn ThreadType(
189
52
  self.thread.join();
190
53
  self.signal.deinit();
191
54
 
192
- self.client.deinit(self.allocator);
193
- self.message_bus.deinit(self.allocator);
194
- self.message_pool.deinit(self.allocator);
195
- self.io.deinit();
196
-
197
- self.allocator.free(self.addresses);
198
- self.allocator.free(self.packets);
199
55
  self.* = undefined;
200
56
  }
201
57
 
@@ -205,19 +61,9 @@ pub fn ThreadType(
205
61
  self.signal.notify();
206
62
  }
207
63
 
208
- fn run(self: *Self) void {
209
- while (!self.signal.is_shutdown()) {
210
- self.client.tick();
211
- self.io.run_for_ns(config.tick_ms * std.time.ns_per_ms) catch |err| {
212
- log.err("IO.run() failed with {}", .{err});
213
- return;
214
- };
215
- }
216
- }
217
-
218
64
  fn on_signal(signal: *Signal) void {
219
65
  const self = @fieldParentPtr(Self, "signal", signal);
220
- self.client.tick();
66
+ self.context.tick();
221
67
 
222
68
  // Consume all of retry here to avoid infinite loop
223
69
  // if the code below pushes to self.retry while we're dequeueing.
@@ -234,95 +80,10 @@ pub fn ThreadType(
234
80
  }
235
81
 
236
82
  // Process packets from either pending or submitted as long as we have messages.
237
- while (self.available_messages > 0) {
83
+ while (self.context.messages_available > 0) {
238
84
  const packet = pending.pop() orelse self.submitted.pop() orelse break;
239
- const message = self.client.get_message();
240
- defer self.client.unref(message);
241
-
242
- self.available_messages -= 1;
243
- self.request(packet, message);
85
+ self.context.request(packet);
244
86
  }
245
87
  }
246
-
247
- fn request(self: *Self, packet: *Packet, message: *Message) void {
248
- // Get the size of each request structure in the packet.data
249
- const request_size: usize = operation_size_of(packet.operation) orelse {
250
- return self.on_complete(packet, error.InvalidOperation);
251
- };
252
-
253
- // Make sure the packet.data size is correct.
254
- const readable = packet.data[0..packet.data_size];
255
- if (readable.len == 0 or readable.len % request_size != 0) {
256
- return self.on_complete(packet, error.InvalidDataSize);
257
- }
258
-
259
- // Make sure the packet.data wouldn't overflow a message.
260
- const writable = message.buffer[@sizeOf(Header)..];
261
- if (readable.len > writable.len) {
262
- return self.on_complete(packet, error.TooMuchData);
263
- }
264
-
265
- // Write the packet data to the message
266
- std.mem.copy(u8, writable, readable);
267
- const wrote = readable.len;
268
-
269
- // .. and submit the message for processing
270
- self.client.request(
271
- @bitCast(u128, UserData{
272
- .self = self,
273
- .packet = packet,
274
- }),
275
- Self.on_result,
276
- @intToEnum(Operation, packet.operation),
277
- message,
278
- wrote,
279
- );
280
- }
281
-
282
- const UserData = packed struct {
283
- self: *Self,
284
- packet: *Packet,
285
- };
286
-
287
- fn on_result(raw_user_data: u128, op: Operation, results: Client.Error![]const u8) void {
288
- const user_data = @bitCast(UserData, raw_user_data);
289
- const self = user_data.self;
290
- const packet = user_data.packet;
291
-
292
- assert(packet.operation == @enumToInt(op));
293
- self.on_complete(packet, results);
294
- }
295
-
296
- const PacketError = Client.Error || error{
297
- TooMuchData,
298
- InvalidOperation,
299
- InvalidDataSize,
300
- };
301
-
302
- fn on_complete(
303
- self: *Self,
304
- packet: *Packet,
305
- result: PacketError![]const u8,
306
- ) void {
307
- assert(self.available_messages < message_pool.messages_max_client);
308
- self.available_messages += 1;
309
-
310
- const bytes = result catch |err| {
311
- packet.status = switch (err) {
312
- // If there's too many requests, (re)try submitting the packet later
313
- error.TooManyOutstandingRequests => {
314
- return self.retry.push(Packet.List.from(packet));
315
- },
316
- error.TooMuchData => .too_much_data,
317
- error.InvalidOperation => .invalid_operation,
318
- error.InvalidDataSize => .invalid_data_size,
319
- };
320
- return self.on_completion_fn(self, packet, null);
321
- };
322
-
323
- // The packet completed normally
324
- packet.status = .ok;
325
- self.on_completion_fn(self, packet, bytes);
326
- }
327
88
  };
328
89
  }
@@ -120,6 +120,7 @@ typedef enum TB_CREATE_TRANSFER_RESULT {
120
120
  TB_CREATE_TRANSFER_OVERFLOWS_CREDITS_POSTED,
121
121
  TB_CREATE_TRANSFER_OVERFLOWS_DEBITS,
122
122
  TB_CREATE_TRANSFER_OVERFLOWS_CREDITS,
123
+ TB_CREATE_TRANSFER_OVERFLOWS_TIMEOUT,
123
124
 
124
125
  TB_CREATE_TRANSFER_EXCEEDS_CREDITS,
125
126
  TB_CREATE_TRANSFER_EXCEEDS_DEBITS,
@@ -193,9 +194,11 @@ typedef enum TB_STATUS {
193
194
  TB_STATUS_SUCCESS = 0,
194
195
  TB_STATUS_UNEXPECTED = 1,
195
196
  TB_STATUS_OUT_OF_MEMORY = 2,
196
- TB_STATUS_INVALID_ADDRESS = 3,
197
- TB_STATUS_SYSTEM_RESOURCES = 4,
198
- TB_STATUS_NETWORK_SUBSYSTEM = 5,
197
+ TB_STATUS_ADDRESS_INVALID = 3,
198
+ TB_STATUS_ADDRESS_LIMIT_EXCEEDED = 4,
199
+ TB_STATUS_PACKETS_COUNT_INVALID = 5,
200
+ TB_STATUS_SYSTEM_RESOURCES = 6,
201
+ TB_STATUS_NETWORK_SUBSYSTEM = 7,
199
202
  } TB_STATUS;
200
203
 
201
204
  TB_STATUS tb_client_init(
@@ -204,7 +207,18 @@ TB_STATUS tb_client_init(
204
207
  uint32_t cluster_id,
205
208
  const char* address_ptr,
206
209
  uint32_t address_len,
207
- uint32_t num_packets,
210
+ uint32_t packets_count,
211
+ uintptr_t on_completion_ctx,
212
+ void (*on_completion_fn)(uintptr_t, tb_client_t, tb_packet_t*, const uint8_t*, uint32_t)
213
+ );
214
+
215
+ TB_STATUS tb_client_init_echo(
216
+ tb_client_t* out_client,
217
+ tb_packet_list_t* out_packets,
218
+ uint32_t cluster_id,
219
+ const char* address_ptr,
220
+ uint32_t address_len,
221
+ uint32_t packets_count,
208
222
  uintptr_t on_completion_ctx,
209
223
  void (*on_completion_fn)(uintptr_t, tb_client_t, tb_packet_t*, const uint8_t*, uint32_t)
210
224
  );
@@ -10,7 +10,9 @@ pub const tb_status_t = enum(c_int) {
10
10
  success = 0,
11
11
  unexpected,
12
12
  out_of_memory,
13
- invalid_address,
13
+ address_invalid,
14
+ address_limit_exceeded,
15
+ packets_count_invalid,
14
16
  system_resources,
15
17
  network_subsystem,
16
18
  };
@@ -23,9 +25,26 @@ pub const tb_completion_t = fn (
23
25
  result_len: u32,
24
26
  ) callconv(.C) void;
25
27
 
28
+ const config = @import("../config.zig");
29
+ const Storage = @import("../storage.zig").Storage;
30
+ const MessageBus = @import("../message_bus.zig").MessageBusClient;
31
+ const StateMachine = @import("../state_machine.zig").StateMachineType(Storage, .{
32
+ .message_body_size_max = config.message_body_size_max,
33
+ });
34
+
26
35
  const ContextType = @import("tb_client/context.zig").ContextType;
27
36
  const ContextImplementation = @import("tb_client/context.zig").ContextImplementation;
28
37
 
38
+ const DefaultContext = blk: {
39
+ const Client = @import("../vsr/client.zig").Client(StateMachine, MessageBus);
40
+ break :blk ContextType(Client);
41
+ };
42
+
43
+ const TestingContext = blk: {
44
+ const EchoClient = @import("tb_client/echo_client.zig").EchoClient(StateMachine, MessageBus);
45
+ break :blk ContextType(EchoClient);
46
+ };
47
+
29
48
  pub fn context_to_client(implementation: *ContextImplementation) tb_client_t {
30
49
  return @ptrCast(tb_client_t, implementation);
31
50
  }
@@ -34,24 +53,10 @@ fn client_to_context(tb_client: tb_client_t) *ContextImplementation {
34
53
  return @ptrCast(*ContextImplementation, @alignCast(@alignOf(ContextImplementation), tb_client));
35
54
  }
36
55
 
37
- const DefaultContext = blk: {
38
- const config = @import("../config.zig");
39
- const Storage = @import("../storage.zig").Storage;
40
- const MessageBus = @import("../message_bus.zig").MessageBusClient;
41
- const StateMachine = @import("../state_machine.zig").StateMachineType(Storage, .{
42
- .message_body_size_max = config.message_body_size_max,
43
- });
44
- break :blk ContextType(StateMachine, MessageBus);
45
- };
46
-
47
- // const TestingContext = blk: {
48
- // const MessageBus = @import("test_message_bus.zig").MessageBusClient;
49
- // const StateMachine = @import("../state_machine.zig").StateMachine;
50
- // break :blk ContextType(StateMachine, MessageBus);
51
- // };
52
-
53
56
  // Pick the most suitable allocator
54
- const global_allocator = if (builtin.link_libc)
57
+ const global_allocator = if (builtin.is_test)
58
+ std.testing.allocator
59
+ else if (builtin.link_libc)
55
60
  std.heap.c_allocator
56
61
  else if (builtin.target.os.tag == .windows)
57
62
  (struct {
@@ -66,28 +71,85 @@ pub export fn tb_client_init(
66
71
  cluster_id: u32,
67
72
  addresses_ptr: [*:0]const u8,
68
73
  addresses_len: u32,
69
- num_packets: u32,
74
+ packets_count: u32,
70
75
  on_completion_ctx: usize,
71
76
  on_completion_fn: tb_completion_t,
72
77
  ) tb_status_t {
73
- var init_fn = DefaultContext.init;
74
- // if (addresses_len == 0) {
75
- // init_fn = TestingContext.init;
76
- // }
78
+ return init(
79
+ DefaultContext,
80
+ out_client,
81
+ out_packets,
82
+ cluster_id,
83
+ addresses_ptr,
84
+ addresses_len,
85
+ packets_count,
86
+ on_completion_ctx,
87
+ on_completion_fn,
88
+ );
89
+ }
77
90
 
78
- return (init_fn)(
79
- global_allocator,
91
+ pub export fn tb_client_init_echo(
92
+ out_client: *tb_client_t,
93
+ out_packets: *tb_packet_list_t,
94
+ cluster_id: u32,
95
+ addresses_ptr: [*:0]const u8,
96
+ addresses_len: u32,
97
+ packets_count: u32,
98
+ on_completion_ctx: usize,
99
+ on_completion_fn: tb_completion_t,
100
+ ) tb_status_t {
101
+ return init(
102
+ TestingContext,
80
103
  out_client,
81
104
  out_packets,
82
105
  cluster_id,
83
106
  addresses_ptr,
84
107
  addresses_len,
85
- num_packets,
108
+ packets_count,
86
109
  on_completion_ctx,
87
110
  on_completion_fn,
88
111
  );
89
112
  }
90
113
 
114
+ fn init(
115
+ comptime Context: type,
116
+ out_client: *tb_client_t,
117
+ out_packets: *tb_packet_list_t,
118
+ cluster_id: u32,
119
+ addresses_ptr: [*:0]const u8,
120
+ addresses_len: u32,
121
+ packets_count: u32,
122
+ on_completion_ctx: usize,
123
+ on_completion_fn: tb_completion_t,
124
+ ) tb_status_t {
125
+ const addresses = @ptrCast([*]const u8, addresses_ptr)[0..addresses_len];
126
+ const context = Context.init(
127
+ global_allocator,
128
+ cluster_id,
129
+ addresses,
130
+ packets_count,
131
+ on_completion_ctx,
132
+ on_completion_fn,
133
+ ) catch |err| switch (err) {
134
+ error.Unexpected => return .unexpected,
135
+ error.OutOfMemory => return .out_of_memory,
136
+ error.AddressInvalid => return .address_invalid,
137
+ error.AddressLimitExceeded => return .address_limit_exceeded,
138
+ error.PacketsCountInvalid => return .packets_count_invalid,
139
+ error.SystemResources => return .system_resources,
140
+ error.NetworkSubsystemFailed => return .network_subsystem,
141
+ };
142
+
143
+ out_client.* = context_to_client(&context.implementation);
144
+ var list = tb_packet_list_t{};
145
+ for (context.packets) |*packet| {
146
+ list.push(tb_packet_list_t.from(packet));
147
+ }
148
+
149
+ out_packets.* = list;
150
+ return .success;
151
+ }
152
+
91
153
  pub export fn tb_client_submit(
92
154
  client: tb_client_t,
93
155
  packets: *tb_packet_list_t,
@@ -0,0 +1,135 @@
1
+ const std = @import("std");
2
+ const assert = std.debug.assert;
3
+
4
+ const tb = @import("../tigerbeetle.zig");
5
+ const tb_client = @import("./tb_client.zig");
6
+ const c = @cImport(@cInclude("tb_client.h"));
7
+
8
+ fn to_lowercase(comptime input: []const u8) []const u8 {
9
+ comptime var lowercase: [input.len]u8 = undefined;
10
+ inline for (input) |char, i| {
11
+ const is_uppercase = (char >= 'A') and (char <= 'Z');
12
+ lowercase[i] = char + (@as(u8, @boolToInt(is_uppercase)) * 32);
13
+ }
14
+ return &lowercase;
15
+ }
16
+
17
+ fn to_uppercase(comptime input: []const u8) []const u8 {
18
+ comptime var uppercase: [input.len]u8 = undefined;
19
+ inline for (input) |char, i| {
20
+ const is_lowercase = (char >= 'a') and (char <= 'z');
21
+ uppercase[i] = char - (@as(u8, @boolToInt(is_lowercase)) * 32);
22
+ }
23
+ return &uppercase;
24
+ }
25
+
26
+ fn to_snakecase(comptime input: []const u8) []const u8 {
27
+ comptime var output: []const u8 = &.{};
28
+ inline for (input) |char, i| {
29
+ const is_uppercase = (char >= 'A') and (char <= 'Z');
30
+ if (is_uppercase and i > 0) output = "_" ++ output;
31
+ output = output ++ &[_]u8{char};
32
+ }
33
+ return output;
34
+ }
35
+
36
+ test "valid tb_client.h" {
37
+ @setEvalBranchQuota(10_000);
38
+
39
+ inline for (.{
40
+ .{ tb.Account, "tb_account_t" },
41
+ .{ tb.Transfer, "tb_transfer_t" },
42
+ .{ tb.AccountFlags, "TB_ACCOUNT_FLAGS" },
43
+ .{ tb.TransferFlags, "TB_TRANSFER_FLAGS" },
44
+ .{ tb.CreateAccountResult, "TB_CREATE_ACCOUNT_RESULT" },
45
+ .{ tb.CreateTransferResult, "TB_CREATE_TRANSFER_RESULT" },
46
+ .{ tb.CreateAccountsResult, "tb_create_accounts_result_t" },
47
+ .{ tb.CreateTransfersResult, "tb_create_transfers_result_t" },
48
+
49
+ .{ u128, "tb_uint128_t" },
50
+ .{ tb_client.tb_status_t, "TB_STATUS" },
51
+ .{ tb_client.tb_client_t, "tb_client_t" },
52
+ .{ tb_client.tb_packet_t, "tb_packet_t" },
53
+ .{ tb_client.tb_packet_list_t, "tb_packet_list_t" },
54
+ .{ tb_client.tb_packet_status_t, "TB_PACKET_STATUS" },
55
+ }) |c_export| {
56
+ const ty: type = c_export[0];
57
+ const c_type_name = @as([]const u8, c_export[1]);
58
+ const c_type: type = @field(c, c_type_name);
59
+
60
+ switch (@typeInfo(ty)) {
61
+ .Int => comptime assert(ty == c_type),
62
+ .Pointer => comptime assert(@sizeOf(ty) == @sizeOf(c_type)),
63
+ .Enum => {
64
+ const prefix_offset = comptime std.mem.lastIndexOf(u8, c_type_name, "_").?;
65
+ comptime var c_enum_prefix: []const u8 = c_type_name[0 .. prefix_offset + 1];
66
+ comptime assert(c_type == c_uint);
67
+
68
+ // TB_STATUS is a special case in naming
69
+ if (comptime std.mem.eql(u8, c_type_name, "TB_STATUS")) {
70
+ c_enum_prefix = c_type_name ++ "_";
71
+ }
72
+
73
+ // Compare the enum int values in C to the enum int values in Zig.
74
+ inline for (std.meta.fields(ty)) |field| {
75
+ const c_enum_field = comptime to_uppercase(to_snakecase(field.name));
76
+ const c_value = @field(c, c_enum_prefix ++ c_enum_field);
77
+
78
+ const zig_value = @enumToInt(@field(ty, field.name));
79
+ comptime assert(zig_value == c_value);
80
+ }
81
+ },
82
+ .Struct => |type_info| switch (type_info.layout) {
83
+ .Auto => @compileError("struct must be extern or packed to be used in C"),
84
+ .Packed => {
85
+ const prefix_offset = comptime std.mem.lastIndexOf(u8, c_type_name, "_").?;
86
+ const c_enum_prefix = c_type_name[0 .. prefix_offset + 1];
87
+ comptime assert(c_type == c_uint);
88
+
89
+ inline for (std.meta.fields(ty)) |field| {
90
+ if (comptime !std.mem.eql(u8, field.name, "padding")) {
91
+ // Get the bit value in the C enum.
92
+ const c_enum_field = comptime to_uppercase(to_snakecase(field.name));
93
+ const c_value = @field(c, c_enum_prefix ++ c_enum_field);
94
+
95
+ // Compare the bit value to the packed struct's field.
96
+ comptime var instance = std.mem.zeroes(ty);
97
+ @field(instance, field.name) = true;
98
+ comptime assert(@bitCast(u16, instance) == c_value);
99
+ }
100
+ }
101
+ },
102
+ .Extern => {
103
+ // Ensure structs are effectively the same.
104
+ comptime assert(@sizeOf(ty) == @sizeOf(c_type));
105
+ comptime assert(@alignOf(ty) == @alignOf(c_type));
106
+
107
+ inline for (std.meta.fields(ty)) |field| {
108
+ // In C, packed structs and enums are replaced with integers.
109
+ comptime var field_type = field.field_type;
110
+ switch (@typeInfo(field_type)) {
111
+ .Struct => |info| {
112
+ comptime assert(info.layout == .Packed);
113
+ comptime assert(@sizeOf(field_type) <= @sizeOf(u128));
114
+ field_type = std.meta.Int(.unsigned, @bitSizeOf(field_type));
115
+ },
116
+ .Enum => |info| field_type = info.tag_type,
117
+ else => {},
118
+ }
119
+
120
+ // In C, pointers are opaque so we compare only the field sizes,
121
+ comptime var c_field_type = @TypeOf(@field(@as(c_type, undefined), field.name));
122
+ switch (@typeInfo(c_field_type)) {
123
+ .Pointer => |info| {
124
+ comptime assert(info.size == .C);
125
+ comptime assert(@sizeOf(c_field_type) == @sizeOf(field_type));
126
+ },
127
+ else => comptime assert(c_field_type == field_type),
128
+ }
129
+ }
130
+ },
131
+ },
132
+ else => |i| @compileLog("TODO", i),
133
+ }
134
+ }
135
+ }