tigerbeetle-node 0.4.2 → 0.5.2

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 (58) hide show
  1. package/README.md +19 -5
  2. package/dist/benchmark.js.map +1 -1
  3. package/dist/index.d.ts +18 -16
  4. package/dist/index.js +35 -13
  5. package/dist/index.js.map +1 -1
  6. package/dist/test.js +12 -0
  7. package/dist/test.js.map +1 -1
  8. package/package.json +2 -2
  9. package/scripts/postinstall.sh +2 -2
  10. package/src/benchmark.ts +2 -2
  11. package/src/index.ts +29 -4
  12. package/src/node.zig +120 -17
  13. package/src/test.ts +14 -0
  14. package/src/tigerbeetle/scripts/install.sh +1 -1
  15. package/src/tigerbeetle/scripts/install_zig.bat +109 -0
  16. package/src/tigerbeetle/scripts/install_zig.sh +4 -2
  17. package/src/tigerbeetle/scripts/lint.zig +8 -2
  18. package/src/tigerbeetle/scripts/vopr.bat +48 -0
  19. package/src/tigerbeetle/src/benchmark.zig +10 -8
  20. package/src/tigerbeetle/src/cli.zig +6 -4
  21. package/src/tigerbeetle/src/config.zig +2 -2
  22. package/src/tigerbeetle/src/demo.zig +119 -89
  23. package/src/tigerbeetle/src/demo_01_create_accounts.zig +5 -3
  24. package/src/tigerbeetle/src/demo_02_lookup_accounts.zig +2 -3
  25. package/src/tigerbeetle/src/demo_03_create_transfers.zig +5 -3
  26. package/src/tigerbeetle/src/demo_04_create_transfers_two_phase_commit.zig +5 -3
  27. package/src/tigerbeetle/src/demo_05_accept_transfers.zig +5 -3
  28. package/src/tigerbeetle/src/demo_06_reject_transfers.zig +5 -3
  29. package/src/tigerbeetle/src/demo_07_lookup_transfers.zig +7 -0
  30. package/src/tigerbeetle/src/io/benchmark.zig +238 -0
  31. package/src/tigerbeetle/src/{io_darwin.zig → io/darwin.zig} +89 -124
  32. package/src/tigerbeetle/src/io/linux.zig +933 -0
  33. package/src/tigerbeetle/src/io/test.zig +621 -0
  34. package/src/tigerbeetle/src/io.zig +7 -1328
  35. package/src/tigerbeetle/src/main.zig +18 -10
  36. package/src/tigerbeetle/src/message_bus.zig +43 -60
  37. package/src/tigerbeetle/src/message_pool.zig +3 -2
  38. package/src/tigerbeetle/src/ring_buffer.zig +135 -68
  39. package/src/tigerbeetle/src/simulator.zig +41 -37
  40. package/src/tigerbeetle/src/state_machine.zig +851 -26
  41. package/src/tigerbeetle/src/storage.zig +49 -46
  42. package/src/tigerbeetle/src/test/cluster.zig +2 -2
  43. package/src/tigerbeetle/src/test/message_bus.zig +6 -6
  44. package/src/tigerbeetle/src/test/network.zig +3 -3
  45. package/src/tigerbeetle/src/test/packet_simulator.zig +32 -29
  46. package/src/tigerbeetle/src/test/state_checker.zig +2 -2
  47. package/src/tigerbeetle/src/test/state_machine.zig +4 -0
  48. package/src/tigerbeetle/src/test/storage.zig +39 -19
  49. package/src/tigerbeetle/src/test/time.zig +2 -2
  50. package/src/tigerbeetle/src/tigerbeetle.zig +6 -129
  51. package/src/tigerbeetle/src/time.zig +6 -5
  52. package/src/tigerbeetle/src/vsr/client.zig +11 -11
  53. package/src/tigerbeetle/src/vsr/clock.zig +26 -43
  54. package/src/tigerbeetle/src/vsr/journal.zig +7 -6
  55. package/src/tigerbeetle/src/vsr/marzullo.zig +6 -3
  56. package/src/tigerbeetle/src/vsr/replica.zig +51 -48
  57. package/src/tigerbeetle/src/vsr.zig +24 -20
  58. package/src/translate.zig +55 -55
@@ -1,1001 +1,25 @@
1
1
  const std = @import("std");
2
+ const builtin = @import("builtin");
2
3
  const assert = std.debug.assert;
3
4
  const os = std.os;
4
- const linux = os.linux;
5
- const IO_Uring = linux.IO_Uring;
6
- const io_uring_cqe = linux.io_uring_cqe;
7
- const io_uring_sqe = linux.io_uring_sqe;
8
5
 
9
6
  const FIFO = @import("fifo.zig").FIFO;
10
- const IO_Darwin = @import("io_darwin.zig").IO;
7
+ const IO_Linux = @import("io/linux.zig").IO;
8
+ const IO_Darwin = @import("io/darwin.zig").IO;
11
9
 
12
- pub const IO = switch (std.Target.current.os.tag) {
10
+ pub const IO = switch (builtin.target.os.tag) {
13
11
  .linux => IO_Linux,
14
12
  .macos, .tvos, .watchos, .ios => IO_Darwin,
15
13
  else => @compileError("IO is not supported for platform"),
16
14
  };
17
15
 
18
- const IO_Linux = struct {
19
- ring: IO_Uring,
20
-
21
- /// Operations not yet submitted to the kernel and waiting on available space in the
22
- /// submission queue.
23
- unqueued: FIFO(Completion) = .{},
24
-
25
- /// Completions that are ready to have their callbacks run.
26
- completed: FIFO(Completion) = .{},
27
-
28
- pub fn init(entries: u12, flags: u32) !IO {
29
- return IO{ .ring = try IO_Uring.init(entries, flags) };
30
- }
31
-
32
- pub fn deinit(self: *IO) void {
33
- self.ring.deinit();
34
- }
35
-
36
- /// Pass all queued submissions to the kernel and peek for completions.
37
- pub fn tick(self: *IO) !void {
38
- // We assume that all timeouts submitted by `run_for_ns()` will be reaped by `run_for_ns()`
39
- // and that `tick()` and `run_for_ns()` cannot be run concurrently.
40
- // Therefore `timeouts` here will never be decremented and `etime` will always be false.
41
- var timeouts: usize = 0;
42
- var etime = false;
43
-
44
- try self.flush(0, &timeouts, &etime);
45
- assert(etime == false);
46
-
47
- // Flush any SQEs that were queued while running completion callbacks in `flush()`:
48
- // This is an optimization to avoid delaying submissions until the next tick.
49
- // At the same time, we do not flush any ready CQEs since SQEs may complete synchronously.
50
- // We guard against an io_uring_enter() syscall if we know we do not have any queued SQEs.
51
- // We cannot use `self.ring.sq_ready()` here since this counts flushed and unflushed SQEs.
52
- const queued = self.ring.sq.sqe_tail -% self.ring.sq.sqe_head;
53
- if (queued > 0) {
54
- try self.flush_submissions(0, &timeouts, &etime);
55
- assert(etime == false);
56
- }
57
- }
58
-
59
- /// Pass all queued submissions to the kernel and run for `nanoseconds`.
60
- /// The `nanoseconds` argument is a u63 to allow coercion to the i64 used
61
- /// in the __kernel_timespec struct.
62
- pub fn run_for_ns(self: *IO, nanoseconds: u63) !void {
63
- // We must use the same clock source used by io_uring (CLOCK_MONOTONIC) since we specify the
64
- // timeout below as an absolute value. Otherwise, we may deadlock if the clock sources are
65
- // dramatically different. Any kernel that supports io_uring will support CLOCK_MONOTONIC.
66
- var current_ts: os.timespec = undefined;
67
- os.clock_gettime(os.CLOCK_MONOTONIC, &current_ts) catch unreachable;
68
- // The absolute CLOCK_MONOTONIC time after which we may return from this function:
69
- const timeout_ts: os.__kernel_timespec = .{
70
- .tv_sec = current_ts.tv_sec,
71
- .tv_nsec = current_ts.tv_nsec + nanoseconds,
72
- };
73
- var timeouts: usize = 0;
74
- var etime = false;
75
- while (!etime) {
76
- const timeout_sqe = self.ring.get_sqe() catch blk: {
77
- // The submission queue is full, so flush submissions to make space:
78
- try self.flush_submissions(0, &timeouts, &etime);
79
- break :blk self.ring.get_sqe() catch unreachable;
80
- };
81
- // Submit an absolute timeout that will be canceled if any other SQE completes first:
82
- linux.io_uring_prep_timeout(timeout_sqe, &timeout_ts, 1, os.IORING_TIMEOUT_ABS);
83
- timeout_sqe.user_data = 0;
84
- timeouts += 1;
85
- // The amount of time this call will block is bounded by the timeout we just submitted:
86
- try self.flush(1, &timeouts, &etime);
87
- }
88
- // Reap any remaining timeouts, which reference the timespec in the current stack frame.
89
- // The busy loop here is required to avoid a potential deadlock, as the kernel determines
90
- // when the timeouts are pushed to the completion queue, not us.
91
- while (timeouts > 0) _ = try self.flush_completions(0, &timeouts, &etime);
92
- }
93
-
94
- fn flush(self: *IO, wait_nr: u32, timeouts: *usize, etime: *bool) !void {
95
- // Flush any queued SQEs and reuse the same syscall to wait for completions if required:
96
- try self.flush_submissions(wait_nr, timeouts, etime);
97
- // We can now just peek for any CQEs without waiting and without another syscall:
98
- try self.flush_completions(0, timeouts, etime);
99
- // Run completions only after all completions have been flushed:
100
- // Loop on a copy of the linked list, having reset the list first, so that any synchronous
101
- // append on running a completion is executed only the next time round the event loop,
102
- // without creating an infinite loop.
103
- {
104
- var copy = self.completed;
105
- self.completed = .{};
106
- while (copy.pop()) |completion| completion.complete();
107
- }
108
- // Again, loop on a copy of the list to avoid an infinite loop:
109
- {
110
- var copy = self.unqueued;
111
- self.unqueued = .{};
112
- while (copy.pop()) |completion| self.enqueue(completion);
113
- }
114
- }
115
-
116
- fn flush_completions(self: *IO, wait_nr: u32, timeouts: *usize, etime: *bool) !void {
117
- var cqes: [256]io_uring_cqe = undefined;
118
- var wait_remaining = wait_nr;
119
- while (true) {
120
- // Guard against waiting indefinitely (if there are too few requests inflight),
121
- // especially if this is not the first time round the loop:
122
- const completed = self.ring.copy_cqes(&cqes, wait_remaining) catch |err| switch (err) {
123
- error.SignalInterrupt => continue,
124
- else => return err,
125
- };
126
- if (completed > wait_remaining) wait_remaining = 0 else wait_remaining -= completed;
127
- for (cqes[0..completed]) |cqe| {
128
- if (cqe.user_data == 0) {
129
- timeouts.* -= 1;
130
- // We are only done if the timeout submitted was completed due to time, not if
131
- // it was completed due to the completion of an event, in which case `cqe.res`
132
- // would be 0. It is possible for multiple timeout operations to complete at the
133
- // same time if the nanoseconds value passed to `run_for_ns()` is very short.
134
- if (-cqe.res == os.ETIME) etime.* = true;
135
- continue;
136
- }
137
- const completion = @intToPtr(*Completion, @intCast(usize, cqe.user_data));
138
- completion.result = cqe.res;
139
- // We do not run the completion here (instead appending to a linked list) to avoid:
140
- // * recursion through `flush_submissions()` and `flush_completions()`,
141
- // * unbounded stack usage, and
142
- // * confusing stack traces.
143
- self.completed.push(completion);
144
- }
145
- if (completed < cqes.len) break;
146
- }
147
- }
148
-
149
- fn flush_submissions(self: *IO, wait_nr: u32, timeouts: *usize, etime: *bool) !void {
150
- while (true) {
151
- _ = self.ring.submit_and_wait(wait_nr) catch |err| switch (err) {
152
- error.SignalInterrupt => continue,
153
- // Wait for some completions and then try again:
154
- // See https://github.com/axboe/liburing/issues/281 re: error.SystemResources.
155
- // Be careful also that copy_cqes() will flush before entering to wait (it does):
156
- // https://github.com/axboe/liburing/commit/35c199c48dfd54ad46b96e386882e7ac341314c5
157
- error.CompletionQueueOvercommitted, error.SystemResources => {
158
- try self.flush_completions(1, timeouts, etime);
159
- continue;
160
- },
161
- else => return err,
162
- };
163
- break;
164
- }
165
- }
166
-
167
- fn enqueue(self: *IO, completion: *Completion) void {
168
- const sqe = self.ring.get_sqe() catch |err| switch (err) {
169
- error.SubmissionQueueFull => {
170
- self.unqueued.push(completion);
171
- return;
172
- },
173
- };
174
- completion.prep(sqe);
175
- }
176
-
177
- /// This struct holds the data needed for a single io_uring operation
178
- pub const Completion = struct {
179
- io: *IO,
180
- result: i32 = undefined,
181
- next: ?*Completion = null,
182
- operation: Operation,
183
- // This is one of the usecases for c_void outside of C code and as such c_void will
184
- // be replaced with anyopaque eventually: https://github.com/ziglang/zig/issues/323
185
- context: ?*c_void,
186
- callback: fn (context: ?*c_void, completion: *Completion, result: *const c_void) void,
187
-
188
- fn prep(completion: *Completion, sqe: *io_uring_sqe) void {
189
- switch (completion.operation) {
190
- .accept => |*op| {
191
- linux.io_uring_prep_accept(
192
- sqe,
193
- op.socket,
194
- &op.address,
195
- &op.address_size,
196
- op.flags,
197
- );
198
- },
199
- .close => |op| {
200
- linux.io_uring_prep_close(sqe, op.fd);
201
- },
202
- .connect => |*op| {
203
- linux.io_uring_prep_connect(
204
- sqe,
205
- op.socket,
206
- &op.address.any,
207
- op.address.getOsSockLen(),
208
- );
209
- },
210
- .fsync => |op| {
211
- linux.io_uring_prep_fsync(sqe, op.fd, op.flags);
212
- },
213
- .openat => |op| {
214
- linux.io_uring_prep_openat(sqe, op.fd, op.path, op.flags, op.mode);
215
- },
216
- .read => |op| {
217
- linux.io_uring_prep_read(
218
- sqe,
219
- op.fd,
220
- op.buffer[0..buffer_limit(op.buffer.len)],
221
- op.offset,
222
- );
223
- },
224
- .recv => |op| {
225
- linux.io_uring_prep_recv(sqe, op.socket, op.buffer, op.flags);
226
- },
227
- .send => |op| {
228
- linux.io_uring_prep_send(sqe, op.socket, op.buffer, op.flags);
229
- },
230
- .timeout => |*op| {
231
- linux.io_uring_prep_timeout(sqe, &op.timespec, 0, 0);
232
- },
233
- .write => |op| {
234
- linux.io_uring_prep_write(
235
- sqe,
236
- op.fd,
237
- op.buffer[0..buffer_limit(op.buffer.len)],
238
- op.offset,
239
- );
240
- },
241
- }
242
- sqe.user_data = @ptrToInt(completion);
243
- }
244
-
245
- fn complete(completion: *Completion) void {
246
- switch (completion.operation) {
247
- .accept => {
248
- const result = if (completion.result < 0) switch (-completion.result) {
249
- os.EINTR => {
250
- completion.io.enqueue(completion);
251
- return;
252
- },
253
- os.EAGAIN => error.WouldBlock,
254
- os.EBADF => error.FileDescriptorInvalid,
255
- os.ECONNABORTED => error.ConnectionAborted,
256
- os.EFAULT => unreachable,
257
- os.EINVAL => error.SocketNotListening,
258
- os.EMFILE => error.ProcessFdQuotaExceeded,
259
- os.ENFILE => error.SystemFdQuotaExceeded,
260
- os.ENOBUFS => error.SystemResources,
261
- os.ENOMEM => error.SystemResources,
262
- os.ENOTSOCK => error.FileDescriptorNotASocket,
263
- os.EOPNOTSUPP => error.OperationNotSupported,
264
- os.EPERM => error.PermissionDenied,
265
- os.EPROTO => error.ProtocolFailure,
266
- else => |errno| os.unexpectedErrno(@intCast(usize, errno)),
267
- } else @intCast(os.socket_t, completion.result);
268
- completion.callback(completion.context, completion, &result);
269
- },
270
- .close => {
271
- const result = if (completion.result < 0) switch (-completion.result) {
272
- os.EINTR => {}, // A success, see https://github.com/ziglang/zig/issues/2425
273
- os.EBADF => error.FileDescriptorInvalid,
274
- os.EDQUOT => error.DiskQuota,
275
- os.EIO => error.InputOutput,
276
- os.ENOSPC => error.NoSpaceLeft,
277
- else => |errno| os.unexpectedErrno(@intCast(usize, errno)),
278
- } else assert(completion.result == 0);
279
- completion.callback(completion.context, completion, &result);
280
- },
281
- .connect => {
282
- const result = if (completion.result < 0) switch (-completion.result) {
283
- os.EINTR => {
284
- completion.io.enqueue(completion);
285
- return;
286
- },
287
- os.EACCES => error.AccessDenied,
288
- os.EADDRINUSE => error.AddressInUse,
289
- os.EADDRNOTAVAIL => error.AddressNotAvailable,
290
- os.EAFNOSUPPORT => error.AddressFamilyNotSupported,
291
- os.EAGAIN, os.EINPROGRESS => error.WouldBlock,
292
- os.EALREADY => error.OpenAlreadyInProgress,
293
- os.EBADF => error.FileDescriptorInvalid,
294
- os.ECONNREFUSED => error.ConnectionRefused,
295
- os.ECONNRESET => error.ConnectionResetByPeer,
296
- os.EFAULT => unreachable,
297
- os.EISCONN => error.AlreadyConnected,
298
- os.ENETUNREACH => error.NetworkUnreachable,
299
- os.ENOENT => error.FileNotFound,
300
- os.ENOTSOCK => error.FileDescriptorNotASocket,
301
- os.EPERM => error.PermissionDenied,
302
- os.EPROTOTYPE => error.ProtocolNotSupported,
303
- os.ETIMEDOUT => error.ConnectionTimedOut,
304
- else => |errno| os.unexpectedErrno(@intCast(usize, errno)),
305
- } else assert(completion.result == 0);
306
- completion.callback(completion.context, completion, &result);
307
- },
308
- .fsync => {
309
- const result = if (completion.result < 0) switch (-completion.result) {
310
- os.EINTR => {
311
- completion.io.enqueue(completion);
312
- return;
313
- },
314
- os.EBADF => error.FileDescriptorInvalid,
315
- os.EDQUOT => error.DiskQuota,
316
- os.EINVAL => error.ArgumentsInvalid,
317
- os.EIO => error.InputOutput,
318
- os.ENOSPC => error.NoSpaceLeft,
319
- os.EROFS => error.ReadOnlyFileSystem,
320
- else => |errno| os.unexpectedErrno(@intCast(usize, errno)),
321
- } else assert(completion.result == 0);
322
- completion.callback(completion.context, completion, &result);
323
- },
324
- .openat => {
325
- const result = if (completion.result < 0) switch (-completion.result) {
326
- os.EINTR => {
327
- completion.io.enqueue(completion);
328
- return;
329
- },
330
- os.EACCES => error.AccessDenied,
331
- os.EBADF => error.FileDescriptorInvalid,
332
- os.EBUSY => error.DeviceBusy,
333
- os.EEXIST => error.PathAlreadyExists,
334
- os.EFAULT => unreachable,
335
- os.EFBIG => error.FileTooBig,
336
- os.EINVAL => error.ArgumentsInvalid,
337
- os.EISDIR => error.IsDir,
338
- os.ELOOP => error.SymLinkLoop,
339
- os.EMFILE => error.ProcessFdQuotaExceeded,
340
- os.ENAMETOOLONG => error.NameTooLong,
341
- os.ENFILE => error.SystemFdQuotaExceeded,
342
- os.ENODEV => error.NoDevice,
343
- os.ENOENT => error.FileNotFound,
344
- os.ENOMEM => error.SystemResources,
345
- os.ENOSPC => error.NoSpaceLeft,
346
- os.ENOTDIR => error.NotDir,
347
- os.EOPNOTSUPP => error.FileLocksNotSupported,
348
- os.EOVERFLOW => error.FileTooBig,
349
- os.EPERM => error.AccessDenied,
350
- os.EWOULDBLOCK => error.WouldBlock,
351
- else => |errno| os.unexpectedErrno(@intCast(usize, errno)),
352
- } else @intCast(os.fd_t, completion.result);
353
- completion.callback(completion.context, completion, &result);
354
- },
355
- .read => {
356
- const result = if (completion.result < 0) switch (-completion.result) {
357
- os.EINTR => {
358
- completion.io.enqueue(completion);
359
- return;
360
- },
361
- os.EAGAIN => error.WouldBlock,
362
- os.EBADF => error.NotOpenForReading,
363
- os.ECONNRESET => error.ConnectionResetByPeer,
364
- os.EFAULT => unreachable,
365
- os.EINVAL => error.Alignment,
366
- os.EIO => error.InputOutput,
367
- os.EISDIR => error.IsDir,
368
- os.ENOBUFS => error.SystemResources,
369
- os.ENOMEM => error.SystemResources,
370
- os.ENXIO => error.Unseekable,
371
- os.EOVERFLOW => error.Unseekable,
372
- os.ESPIPE => error.Unseekable,
373
- else => |errno| os.unexpectedErrno(@intCast(usize, errno)),
374
- } else @intCast(usize, completion.result);
375
- completion.callback(completion.context, completion, &result);
376
- },
377
- .recv => {
378
- const result = if (completion.result < 0) switch (-completion.result) {
379
- os.EINTR => {
380
- completion.io.enqueue(completion);
381
- return;
382
- },
383
- os.EAGAIN => error.WouldBlock,
384
- os.EBADF => error.FileDescriptorInvalid,
385
- os.ECONNREFUSED => error.ConnectionRefused,
386
- os.EFAULT => unreachable,
387
- os.EINVAL => unreachable,
388
- os.ENOMEM => error.SystemResources,
389
- os.ENOTCONN => error.SocketNotConnected,
390
- os.ENOTSOCK => error.FileDescriptorNotASocket,
391
- os.ECONNRESET => error.ConnectionResetByPeer,
392
- else => |errno| os.unexpectedErrno(@intCast(usize, errno)),
393
- } else @intCast(usize, completion.result);
394
- completion.callback(completion.context, completion, &result);
395
- },
396
- .send => {
397
- const result = if (completion.result < 0) switch (-completion.result) {
398
- os.EINTR => {
399
- completion.io.enqueue(completion);
400
- return;
401
- },
402
- os.EACCES => error.AccessDenied,
403
- os.EAGAIN => error.WouldBlock,
404
- os.EALREADY => error.FastOpenAlreadyInProgress,
405
- os.EAFNOSUPPORT => error.AddressFamilyNotSupported,
406
- os.EBADF => error.FileDescriptorInvalid,
407
- os.ECONNRESET => error.ConnectionResetByPeer,
408
- os.EDESTADDRREQ => unreachable,
409
- os.EFAULT => unreachable,
410
- os.EINVAL => unreachable,
411
- os.EISCONN => unreachable,
412
- os.EMSGSIZE => error.MessageTooBig,
413
- os.ENOBUFS => error.SystemResources,
414
- os.ENOMEM => error.SystemResources,
415
- os.ENOTCONN => error.SocketNotConnected,
416
- os.ENOTSOCK => error.FileDescriptorNotASocket,
417
- os.EOPNOTSUPP => error.OperationNotSupported,
418
- os.EPIPE => error.BrokenPipe,
419
- else => |errno| os.unexpectedErrno(@intCast(usize, errno)),
420
- } else @intCast(usize, completion.result);
421
- completion.callback(completion.context, completion, &result);
422
- },
423
- .timeout => {
424
- const result = if (completion.result < 0) switch (-completion.result) {
425
- os.EINTR => {
426
- completion.io.enqueue(completion);
427
- return;
428
- },
429
- os.ECANCELED => error.Canceled,
430
- os.ETIME => {}, // A success.
431
- else => |errno| os.unexpectedErrno(@intCast(usize, errno)),
432
- } else unreachable;
433
- completion.callback(completion.context, completion, &result);
434
- },
435
- .write => {
436
- const result = if (completion.result < 0) switch (-completion.result) {
437
- os.EINTR => {
438
- completion.io.enqueue(completion);
439
- return;
440
- },
441
- os.EAGAIN => error.WouldBlock,
442
- os.EBADF => error.NotOpenForWriting,
443
- os.EDESTADDRREQ => error.NotConnected,
444
- os.EDQUOT => error.DiskQuota,
445
- os.EFAULT => unreachable,
446
- os.EFBIG => error.FileTooBig,
447
- os.EINVAL => error.Alignment,
448
- os.EIO => error.InputOutput,
449
- os.ENOSPC => error.NoSpaceLeft,
450
- os.ENXIO => error.Unseekable,
451
- os.EOVERFLOW => error.Unseekable,
452
- os.EPERM => error.AccessDenied,
453
- os.EPIPE => error.BrokenPipe,
454
- os.ESPIPE => error.Unseekable,
455
- else => |errno| os.unexpectedErrno(@intCast(usize, errno)),
456
- } else @intCast(usize, completion.result);
457
- completion.callback(completion.context, completion, &result);
458
- },
459
- }
460
- }
461
- };
462
-
463
- /// This union encodes the set of operations supported as well as their arguments.
464
- const Operation = union(enum) {
465
- accept: struct {
466
- socket: os.socket_t,
467
- address: os.sockaddr = undefined,
468
- address_size: os.socklen_t = @sizeOf(os.sockaddr),
469
- flags: u32,
470
- },
471
- close: struct {
472
- fd: os.fd_t,
473
- },
474
- connect: struct {
475
- socket: os.socket_t,
476
- address: std.net.Address,
477
- },
478
- fsync: struct {
479
- fd: os.fd_t,
480
- flags: u32,
481
- },
482
- openat: struct {
483
- fd: os.fd_t,
484
- path: [*:0]const u8,
485
- flags: u32,
486
- mode: os.mode_t,
487
- },
488
- read: struct {
489
- fd: os.fd_t,
490
- buffer: []u8,
491
- offset: u64,
492
- },
493
- recv: struct {
494
- socket: os.socket_t,
495
- buffer: []u8,
496
- flags: u32,
497
- },
498
- send: struct {
499
- socket: os.socket_t,
500
- buffer: []const u8,
501
- flags: u32,
502
- },
503
- timeout: struct {
504
- timespec: os.__kernel_timespec,
505
- },
506
- write: struct {
507
- fd: os.fd_t,
508
- buffer: []const u8,
509
- offset: u64,
510
- },
511
- };
512
-
513
- pub const AcceptError = error{
514
- WouldBlock,
515
- FileDescriptorInvalid,
516
- ConnectionAborted,
517
- SocketNotListening,
518
- ProcessFdQuotaExceeded,
519
- SystemFdQuotaExceeded,
520
- SystemResources,
521
- FileDescriptorNotASocket,
522
- OperationNotSupported,
523
- PermissionDenied,
524
- ProtocolFailure,
525
- } || os.UnexpectedError;
526
-
527
- pub fn accept(
528
- self: *IO,
529
- comptime Context: type,
530
- context: Context,
531
- comptime callback: fn (
532
- context: Context,
533
- completion: *Completion,
534
- result: AcceptError!os.socket_t,
535
- ) void,
536
- completion: *Completion,
537
- socket: os.socket_t,
538
- flags: u32,
539
- ) void {
540
- completion.* = .{
541
- .io = self,
542
- .context = context,
543
- .callback = struct {
544
- fn wrapper(ctx: ?*c_void, comp: *Completion, res: *const c_void) void {
545
- callback(
546
- @intToPtr(Context, @ptrToInt(ctx)),
547
- comp,
548
- @intToPtr(*const AcceptError!os.socket_t, @ptrToInt(res)).*,
549
- );
550
- }
551
- }.wrapper,
552
- .operation = .{
553
- .accept = .{
554
- .socket = socket,
555
- .address = undefined,
556
- .address_size = @sizeOf(os.sockaddr),
557
- .flags = flags,
558
- },
559
- },
560
- };
561
- self.enqueue(completion);
562
- }
563
-
564
- pub const CloseError = error{
565
- FileDescriptorInvalid,
566
- DiskQuota,
567
- InputOutput,
568
- NoSpaceLeft,
569
- } || os.UnexpectedError;
570
-
571
- pub fn close(
572
- self: *IO,
573
- comptime Context: type,
574
- context: Context,
575
- comptime callback: fn (
576
- context: Context,
577
- completion: *Completion,
578
- result: CloseError!void,
579
- ) void,
580
- completion: *Completion,
581
- fd: os.fd_t,
582
- ) void {
583
- completion.* = .{
584
- .io = self,
585
- .context = context,
586
- .callback = struct {
587
- fn wrapper(ctx: ?*c_void, comp: *Completion, res: *const c_void) void {
588
- callback(
589
- @intToPtr(Context, @ptrToInt(ctx)),
590
- comp,
591
- @intToPtr(*const CloseError!void, @ptrToInt(res)).*,
592
- );
593
- }
594
- }.wrapper,
595
- .operation = .{
596
- .close = .{ .fd = fd },
597
- },
598
- };
599
- self.enqueue(completion);
600
- }
601
-
602
- pub const ConnectError = error{
603
- AccessDenied,
604
- AddressInUse,
605
- AddressNotAvailable,
606
- AddressFamilyNotSupported,
607
- WouldBlock,
608
- OpenAlreadyInProgress,
609
- FileDescriptorInvalid,
610
- ConnectionRefused,
611
- AlreadyConnected,
612
- NetworkUnreachable,
613
- FileNotFound,
614
- FileDescriptorNotASocket,
615
- PermissionDenied,
616
- ProtocolNotSupported,
617
- ConnectionTimedOut,
618
- } || os.UnexpectedError;
619
-
620
- pub fn connect(
621
- self: *IO,
622
- comptime Context: type,
623
- context: Context,
624
- comptime callback: fn (
625
- context: Context,
626
- completion: *Completion,
627
- result: ConnectError!void,
628
- ) void,
629
- completion: *Completion,
630
- socket: os.socket_t,
631
- address: std.net.Address,
632
- ) void {
633
- completion.* = .{
634
- .io = self,
635
- .context = context,
636
- .callback = struct {
637
- fn wrapper(ctx: ?*c_void, comp: *Completion, res: *const c_void) void {
638
- callback(
639
- @intToPtr(Context, @ptrToInt(ctx)),
640
- comp,
641
- @intToPtr(*const ConnectError!void, @ptrToInt(res)).*,
642
- );
643
- }
644
- }.wrapper,
645
- .operation = .{
646
- .connect = .{
647
- .socket = socket,
648
- .address = address,
649
- },
650
- },
651
- };
652
- self.enqueue(completion);
653
- }
654
-
655
- pub const FsyncError = error{
656
- FileDescriptorInvalid,
657
- DiskQuota,
658
- ArgumentsInvalid,
659
- InputOutput,
660
- NoSpaceLeft,
661
- ReadOnlyFileSystem,
662
- } || os.UnexpectedError;
663
-
664
- pub fn fsync(
665
- self: *IO,
666
- comptime Context: type,
667
- context: Context,
668
- comptime callback: fn (
669
- context: Context,
670
- completion: *Completion,
671
- result: FsyncError!void,
672
- ) void,
673
- completion: *Completion,
674
- fd: os.fd_t,
675
- flags: u32,
676
- ) void {
677
- completion.* = .{
678
- .io = self,
679
- .context = context,
680
- .callback = struct {
681
- fn wrapper(ctx: ?*c_void, comp: *Completion, res: *const c_void) void {
682
- callback(
683
- @intToPtr(Context, @ptrToInt(ctx)),
684
- comp,
685
- @intToPtr(*const FsyncError!void, @ptrToInt(res)).*,
686
- );
687
- }
688
- }.wrapper,
689
- .operation = .{
690
- .fsync = .{
691
- .fd = fd,
692
- .flags = flags,
693
- },
694
- },
695
- };
696
- self.enqueue(completion);
697
- }
698
-
699
- pub const OpenatError = error{
700
- AccessDenied,
701
- FileDescriptorInvalid,
702
- DeviceBusy,
703
- PathAlreadyExists,
704
- FileTooBig,
705
- ArgumentsInvalid,
706
- IsDir,
707
- SymLinkLoop,
708
- ProcessFdQuotaExceeded,
709
- NameTooLong,
710
- SystemFdQuotaExceeded,
711
- NoDevice,
712
- FileNotFound,
713
- SystemResources,
714
- NoSpaceLeft,
715
- NotDir,
716
- FileLocksNotSupported,
717
- WouldBlock,
718
- } || os.UnexpectedError;
719
-
720
- pub fn openat(
721
- self: *IO,
722
- comptime Context: type,
723
- context: Context,
724
- comptime callback: fn (
725
- context: Context,
726
- completion: *Completion,
727
- result: OpenatError!os.fd_t,
728
- ) void,
729
- completion: *Completion,
730
- fd: os.fd_t,
731
- path: [*:0]const u8,
732
- flags: u32,
733
- mode: os.mode_t,
734
- ) void {
735
- completion.* = .{
736
- .io = self,
737
- .context = context,
738
- .callback = struct {
739
- fn wrapper(ctx: ?*c_void, comp: *Completion, res: *const c_void) void {
740
- callback(
741
- @intToPtr(Context, @ptrToInt(ctx)),
742
- comp,
743
- @intToPtr(*const OpenatError!os.fd_t, @ptrToInt(res)).*,
744
- );
745
- }
746
- }.wrapper,
747
- .operation = .{
748
- .openat = .{
749
- .fd = fd,
750
- .path = path,
751
- .flags = flags,
752
- .mode = mode,
753
- },
754
- },
755
- };
756
- self.enqueue(completion);
757
- }
758
-
759
- pub const ReadError = error{
760
- WouldBlock,
761
- NotOpenForReading,
762
- ConnectionResetByPeer,
763
- Alignment,
764
- InputOutput,
765
- IsDir,
766
- SystemResources,
767
- Unseekable,
768
- } || os.UnexpectedError;
769
-
770
- pub fn read(
771
- self: *IO,
772
- comptime Context: type,
773
- context: Context,
774
- comptime callback: fn (
775
- context: Context,
776
- completion: *Completion,
777
- result: ReadError!usize,
778
- ) void,
779
- completion: *Completion,
780
- fd: os.fd_t,
781
- buffer: []u8,
782
- offset: u64,
783
- ) void {
784
- completion.* = .{
785
- .io = self,
786
- .context = context,
787
- .callback = struct {
788
- fn wrapper(ctx: ?*c_void, comp: *Completion, res: *const c_void) void {
789
- callback(
790
- @intToPtr(Context, @ptrToInt(ctx)),
791
- comp,
792
- @intToPtr(*const ReadError!usize, @ptrToInt(res)).*,
793
- );
794
- }
795
- }.wrapper,
796
- .operation = .{
797
- .read = .{
798
- .fd = fd,
799
- .buffer = buffer,
800
- .offset = offset,
801
- },
802
- },
803
- };
804
- self.enqueue(completion);
805
- }
806
-
807
- pub const RecvError = error{
808
- WouldBlock,
809
- FileDescriptorInvalid,
810
- ConnectionRefused,
811
- SystemResources,
812
- SocketNotConnected,
813
- FileDescriptorNotASocket,
814
- } || os.UnexpectedError;
815
-
816
- pub fn recv(
817
- self: *IO,
818
- comptime Context: type,
819
- context: Context,
820
- comptime callback: fn (
821
- context: Context,
822
- completion: *Completion,
823
- result: RecvError!usize,
824
- ) void,
825
- completion: *Completion,
826
- socket: os.socket_t,
827
- buffer: []u8,
828
- flags: u32,
829
- ) void {
830
- completion.* = .{
831
- .io = self,
832
- .context = context,
833
- .callback = struct {
834
- fn wrapper(ctx: ?*c_void, comp: *Completion, res: *const c_void) void {
835
- callback(
836
- @intToPtr(Context, @ptrToInt(ctx)),
837
- comp,
838
- @intToPtr(*const RecvError!usize, @ptrToInt(res)).*,
839
- );
840
- }
841
- }.wrapper,
842
- .operation = .{
843
- .recv = .{
844
- .socket = socket,
845
- .buffer = buffer,
846
- .flags = flags,
847
- },
848
- },
849
- };
850
- self.enqueue(completion);
851
- }
852
-
853
- pub const SendError = error{
854
- AccessDenied,
855
- WouldBlock,
856
- FastOpenAlreadyInProgress,
857
- AddressFamilyNotSupported,
858
- FileDescriptorInvalid,
859
- ConnectionResetByPeer,
860
- MessageTooBig,
861
- SystemResources,
862
- SocketNotConnected,
863
- FileDescriptorNotASocket,
864
- OperationNotSupported,
865
- BrokenPipe,
866
- } || os.UnexpectedError;
867
-
868
- pub fn send(
869
- self: *IO,
870
- comptime Context: type,
871
- context: Context,
872
- comptime callback: fn (
873
- context: Context,
874
- completion: *Completion,
875
- result: SendError!usize,
876
- ) void,
877
- completion: *Completion,
878
- socket: os.socket_t,
879
- buffer: []const u8,
880
- flags: u32,
881
- ) void {
882
- completion.* = .{
883
- .io = self,
884
- .context = context,
885
- .callback = struct {
886
- fn wrapper(ctx: ?*c_void, comp: *Completion, res: *const c_void) void {
887
- callback(
888
- @intToPtr(Context, @ptrToInt(ctx)),
889
- comp,
890
- @intToPtr(*const SendError!usize, @ptrToInt(res)).*,
891
- );
892
- }
893
- }.wrapper,
894
- .operation = .{
895
- .send = .{
896
- .socket = socket,
897
- .buffer = buffer,
898
- .flags = flags,
899
- },
900
- },
901
- };
902
- self.enqueue(completion);
903
- }
904
-
905
- pub const TimeoutError = error{Canceled} || os.UnexpectedError;
906
-
907
- pub fn timeout(
908
- self: *IO,
909
- comptime Context: type,
910
- context: Context,
911
- comptime callback: fn (
912
- context: Context,
913
- completion: *Completion,
914
- result: TimeoutError!void,
915
- ) void,
916
- completion: *Completion,
917
- nanoseconds: u63,
918
- ) void {
919
- completion.* = .{
920
- .io = self,
921
- .context = context,
922
- .callback = struct {
923
- fn wrapper(ctx: ?*c_void, comp: *Completion, res: *const c_void) void {
924
- callback(
925
- @intToPtr(Context, @ptrToInt(ctx)),
926
- comp,
927
- @intToPtr(*const TimeoutError!void, @ptrToInt(res)).*,
928
- );
929
- }
930
- }.wrapper,
931
- .operation = .{
932
- .timeout = .{
933
- .timespec = .{ .tv_sec = 0, .tv_nsec = nanoseconds },
934
- },
935
- },
936
- };
937
- self.enqueue(completion);
938
- }
939
-
940
- pub const WriteError = error{
941
- WouldBlock,
942
- NotOpenForWriting,
943
- NotConnected,
944
- DiskQuota,
945
- FileTooBig,
946
- Alignment,
947
- InputOutput,
948
- NoSpaceLeft,
949
- Unseekable,
950
- AccessDenied,
951
- BrokenPipe,
952
- } || os.UnexpectedError;
953
-
954
- pub fn write(
955
- self: *IO,
956
- comptime Context: type,
957
- context: Context,
958
- comptime callback: fn (
959
- context: Context,
960
- completion: *Completion,
961
- result: WriteError!usize,
962
- ) void,
963
- completion: *Completion,
964
- fd: os.fd_t,
965
- buffer: []const u8,
966
- offset: u64,
967
- ) void {
968
- completion.* = .{
969
- .io = self,
970
- .context = context,
971
- .callback = struct {
972
- fn wrapper(ctx: ?*c_void, comp: *Completion, res: *const c_void) void {
973
- callback(
974
- @intToPtr(Context, @ptrToInt(ctx)),
975
- comp,
976
- @intToPtr(*const WriteError!usize, @ptrToInt(res)).*,
977
- );
978
- }
979
- }.wrapper,
980
- .operation = .{
981
- .write = .{
982
- .fd = fd,
983
- .buffer = buffer,
984
- .offset = offset,
985
- },
986
- },
987
- };
988
- self.enqueue(completion);
989
- }
990
- };
991
-
992
16
  pub fn buffer_limit(buffer_len: usize) usize {
993
17
  // Linux limits how much may be written in a `pwrite()/pread()` call, which is `0x7ffff000` on
994
18
  // both 64-bit and 32-bit systems, due to using a signed C int as the return value, as well as
995
19
  // stuffing the errno codes into the last `4096` values.
996
20
  // Darwin limits writes to `0x7fffffff` bytes, more than that returns `EINVAL`.
997
21
  // The corresponding POSIX limit is `std.math.maxInt(isize)`.
998
- const limit = switch (std.Target.current.os.tag) {
22
+ const limit = switch (builtin.target.os.tag) {
999
23
  .linux => 0x7ffff000,
1000
24
  .macos, .ios, .watchos, .tvos => std.math.maxInt(i32),
1001
25
  else => std.math.maxInt(isize),
@@ -1003,351 +27,6 @@ pub fn buffer_limit(buffer_len: usize) usize {
1003
27
  return std.math.min(limit, buffer_len);
1004
28
  }
1005
29
 
1006
- test "ref all decls" {
1007
- std.testing.refAllDecls(IO);
1008
- }
1009
-
1010
- test "write/fsync/read" {
1011
- const testing = std.testing;
1012
-
1013
- try struct {
1014
- const Context = @This();
1015
-
1016
- io: IO,
1017
- done: bool = false,
1018
- fd: os.fd_t,
1019
-
1020
- write_buf: [20]u8 = [_]u8{97} ** 20,
1021
- read_buf: [20]u8 = [_]u8{98} ** 20,
1022
-
1023
- written: usize = 0,
1024
- fsynced: bool = false,
1025
- read: usize = 0,
1026
-
1027
- fn run_test() !void {
1028
- const path = "test_io_write_fsync_read";
1029
- const file = try std.fs.cwd().createFile(path, .{ .read = true, .truncate = true });
1030
- defer file.close();
1031
- defer std.fs.cwd().deleteFile(path) catch {};
1032
-
1033
- var self: Context = .{
1034
- .io = try IO.init(32, 0),
1035
- .fd = file.handle,
1036
- };
1037
- defer self.io.deinit();
1038
-
1039
- var completion: IO.Completion = undefined;
1040
-
1041
- self.io.write(
1042
- *Context,
1043
- &self,
1044
- write_callback,
1045
- &completion,
1046
- self.fd,
1047
- &self.write_buf,
1048
- 10,
1049
- );
1050
- while (!self.done) try self.io.tick();
1051
-
1052
- try testing.expectEqual(self.write_buf.len, self.written);
1053
- try testing.expect(self.fsynced);
1054
- try testing.expectEqual(self.read_buf.len, self.read);
1055
- try testing.expectEqualSlices(u8, &self.write_buf, &self.read_buf);
1056
- }
1057
-
1058
- fn write_callback(
1059
- self: *Context,
1060
- completion: *IO.Completion,
1061
- result: IO.WriteError!usize,
1062
- ) void {
1063
- self.written = result catch @panic("write error");
1064
- self.io.fsync(*Context, self, fsync_callback, completion, self.fd, 0);
1065
- }
1066
-
1067
- fn fsync_callback(
1068
- self: *Context,
1069
- completion: *IO.Completion,
1070
- result: IO.FsyncError!void,
1071
- ) void {
1072
- result catch @panic("fsync error");
1073
- self.fsynced = true;
1074
- self.io.read(*Context, self, read_callback, completion, self.fd, &self.read_buf, 10);
1075
- }
1076
-
1077
- fn read_callback(
1078
- self: *Context,
1079
- completion: *IO.Completion,
1080
- result: IO.ReadError!usize,
1081
- ) void {
1082
- self.read = result catch @panic("read error");
1083
- self.done = true;
1084
- }
1085
- }.run_test();
1086
- }
1087
-
1088
- test "openat/close" {
1089
- const testing = std.testing;
1090
-
1091
- try struct {
1092
- const Context = @This();
1093
-
1094
- io: IO,
1095
- done: bool = false,
1096
- fd: os.fd_t = 0,
1097
-
1098
- fn run_test() !void {
1099
- const path = "test_io_openat_close";
1100
- defer std.fs.cwd().deleteFile(path) catch {};
1101
-
1102
- var self: Context = .{ .io = try IO.init(32, 0) };
1103
- defer self.io.deinit();
1104
-
1105
- var completion: IO.Completion = undefined;
1106
- self.io.openat(
1107
- *Context,
1108
- &self,
1109
- openat_callback,
1110
- &completion,
1111
- linux.AT_FDCWD,
1112
- path,
1113
- os.O_CLOEXEC | os.O_RDWR | os.O_CREAT,
1114
- 0o666,
1115
- );
1116
- while (!self.done) try self.io.tick();
1117
-
1118
- try testing.expect(self.fd > 0);
1119
- }
1120
-
1121
- fn openat_callback(
1122
- self: *Context,
1123
- completion: *IO.Completion,
1124
- result: IO.OpenatError!os.fd_t,
1125
- ) void {
1126
- self.fd = result catch @panic("openat error");
1127
- self.io.close(*Context, self, close_callback, completion, self.fd);
1128
- }
1129
-
1130
- fn close_callback(
1131
- self: *Context,
1132
- completion: *IO.Completion,
1133
- result: IO.CloseError!void,
1134
- ) void {
1135
- result catch @panic("close error");
1136
- self.done = true;
1137
- }
1138
- }.run_test();
1139
- }
1140
-
1141
- test "accept/connect/send/receive" {
1142
- const testing = std.testing;
1143
-
1144
- try struct {
1145
- const Context = @This();
1146
-
1147
- io: IO,
1148
- done: bool = false,
1149
- server: os.socket_t,
1150
- client: os.socket_t,
1151
-
1152
- accepted_sock: os.socket_t = undefined,
1153
-
1154
- send_buf: [10]u8 = [_]u8{ 1, 0, 1, 0, 1, 0, 1, 0, 1, 0 },
1155
- recv_buf: [5]u8 = [_]u8{ 0, 1, 0, 1, 0 },
1156
-
1157
- sent: usize = 0,
1158
- received: usize = 0,
1159
-
1160
- fn run_test() !void {
1161
- const address = try std.net.Address.parseIp4("127.0.0.1", 3131);
1162
- const kernel_backlog = 1;
1163
- const server = try os.socket(address.any.family, os.SOCK_STREAM | os.SOCK_CLOEXEC, 0);
1164
- defer os.close(server);
1165
-
1166
- const client = try os.socket(address.any.family, os.SOCK_STREAM | os.SOCK_CLOEXEC, 0);
1167
- defer os.close(client);
1168
-
1169
- try os.setsockopt(
1170
- server,
1171
- os.SOL_SOCKET,
1172
- os.SO_REUSEADDR,
1173
- &std.mem.toBytes(@as(c_int, 1)),
1174
- );
1175
- try os.bind(server, &address.any, address.getOsSockLen());
1176
- try os.listen(server, kernel_backlog);
1177
-
1178
- var self: Context = .{
1179
- .io = try IO.init(32, 0),
1180
- .server = server,
1181
- .client = client,
1182
- };
1183
- defer self.io.deinit();
1184
-
1185
- var client_completion: IO.Completion = undefined;
1186
- self.io.connect(
1187
- *Context,
1188
- &self,
1189
- connect_callback,
1190
- &client_completion,
1191
- client,
1192
- address,
1193
- );
1194
-
1195
- var server_completion: IO.Completion = undefined;
1196
- self.io.accept(*Context, &self, accept_callback, &server_completion, server, 0);
1197
-
1198
- while (!self.done) try self.io.tick();
1199
-
1200
- try testing.expectEqual(self.send_buf.len, self.sent);
1201
- try testing.expectEqual(self.recv_buf.len, self.received);
1202
-
1203
- try testing.expectEqualSlices(u8, self.send_buf[0..self.received], &self.recv_buf);
1204
- }
1205
-
1206
- fn connect_callback(
1207
- self: *Context,
1208
- completion: *IO.Completion,
1209
- result: IO.ConnectError!void,
1210
- ) void {
1211
- result catch @panic("connect error");
1212
- self.io.send(
1213
- *Context,
1214
- self,
1215
- send_callback,
1216
- completion,
1217
- self.client,
1218
- &self.send_buf,
1219
- os.MSG_NOSIGNAL,
1220
- );
1221
- }
1222
-
1223
- fn send_callback(
1224
- self: *Context,
1225
- completion: *IO.Completion,
1226
- result: IO.SendError!usize,
1227
- ) void {
1228
- self.sent = result catch @panic("send error");
1229
- }
1230
-
1231
- fn accept_callback(
1232
- self: *Context,
1233
- completion: *IO.Completion,
1234
- result: IO.AcceptError!os.socket_t,
1235
- ) void {
1236
- self.accepted_sock = result catch @panic("accept error");
1237
- self.io.recv(
1238
- *Context,
1239
- self,
1240
- recv_callback,
1241
- completion,
1242
- self.accepted_sock,
1243
- &self.recv_buf,
1244
- os.MSG_NOSIGNAL,
1245
- );
1246
- }
1247
-
1248
- fn recv_callback(
1249
- self: *Context,
1250
- completion: *IO.Completion,
1251
- result: IO.RecvError!usize,
1252
- ) void {
1253
- self.received = result catch @panic("recv error");
1254
- self.done = true;
1255
- }
1256
- }.run_test();
1257
- }
1258
-
1259
- test "timeout" {
1260
- const testing = std.testing;
1261
-
1262
- const ms = 20;
1263
- const margin = 5;
1264
- const count = 10;
1265
-
1266
- try struct {
1267
- const Context = @This();
1268
-
1269
- io: IO,
1270
- count: u32 = 0,
1271
- stop_time: i64 = 0,
1272
-
1273
- fn run_test() !void {
1274
- const start_time = std.time.milliTimestamp();
1275
- var self: Context = .{ .io = try IO.init(32, 0) };
1276
- defer self.io.deinit();
1277
-
1278
- var completions: [count]IO.Completion = undefined;
1279
- for (completions) |*completion| {
1280
- self.io.timeout(
1281
- *Context,
1282
- &self,
1283
- timeout_callback,
1284
- completion,
1285
- ms * std.time.ns_per_ms,
1286
- );
1287
- }
1288
- while (self.count < count) try self.io.tick();
1289
-
1290
- try self.io.tick();
1291
- try testing.expectEqual(@as(u32, count), self.count);
1292
-
1293
- try testing.expectApproxEqAbs(
1294
- @as(f64, ms),
1295
- @intToFloat(f64, self.stop_time - start_time),
1296
- margin,
1297
- );
1298
- }
1299
-
1300
- fn timeout_callback(
1301
- self: *Context,
1302
- completion: *IO.Completion,
1303
- result: IO.TimeoutError!void,
1304
- ) void {
1305
- result catch @panic("timeout error");
1306
- if (self.stop_time == 0) self.stop_time = std.time.milliTimestamp();
1307
- self.count += 1;
1308
- }
1309
- }.run_test();
1310
- }
1311
-
1312
- test "submission queue full" {
1313
- const testing = std.testing;
1314
-
1315
- const ms = 20;
1316
- const count = 10;
1317
-
1318
- try struct {
1319
- const Context = @This();
1320
-
1321
- io: IO,
1322
- count: u32 = 0,
1323
-
1324
- fn run_test() !void {
1325
- var self: Context = .{ .io = try IO.init(1, 0) };
1326
- defer self.io.deinit();
1327
-
1328
- var completions: [count]IO.Completion = undefined;
1329
- for (completions) |*completion| {
1330
- self.io.timeout(
1331
- *Context,
1332
- &self,
1333
- timeout_callback,
1334
- completion,
1335
- ms * std.time.ns_per_ms,
1336
- );
1337
- }
1338
- while (self.count < count) try self.io.tick();
1339
-
1340
- try self.io.tick();
1341
- try testing.expectEqual(@as(u32, count), self.count);
1342
- }
1343
-
1344
- fn timeout_callback(
1345
- self: *Context,
1346
- completion: *IO.Completion,
1347
- result: IO.TimeoutError!void,
1348
- ) void {
1349
- result catch @panic("timeout error");
1350
- self.count += 1;
1351
- }
1352
- }.run_test();
30
+ test "I/O" {
31
+ _ = @import("io/test.zig");
1353
32
  }