tigerbeetle-node 0.5.0 → 0.5.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 (46) hide show
  1. package/package.json +2 -2
  2. package/scripts/postinstall.sh +2 -2
  3. package/src/node.zig +17 -15
  4. package/src/tigerbeetle/scripts/install.sh +1 -1
  5. package/src/tigerbeetle/scripts/install_zig.bat +3 -3
  6. package/src/tigerbeetle/scripts/install_zig.sh +4 -2
  7. package/src/tigerbeetle/scripts/lint.zig +8 -2
  8. package/src/tigerbeetle/src/benchmark.zig +8 -6
  9. package/src/tigerbeetle/src/cli.zig +6 -4
  10. package/src/tigerbeetle/src/config.zig +2 -2
  11. package/src/tigerbeetle/src/demo.zig +119 -97
  12. package/src/tigerbeetle/src/demo_01_create_accounts.zig +5 -3
  13. package/src/tigerbeetle/src/demo_02_lookup_accounts.zig +2 -3
  14. package/src/tigerbeetle/src/demo_03_create_transfers.zig +5 -3
  15. package/src/tigerbeetle/src/demo_04_create_transfers_two_phase_commit.zig +5 -3
  16. package/src/tigerbeetle/src/demo_05_accept_transfers.zig +5 -3
  17. package/src/tigerbeetle/src/demo_06_reject_transfers.zig +5 -3
  18. package/src/tigerbeetle/src/demo_07_lookup_transfers.zig +2 -3
  19. package/src/tigerbeetle/src/io/benchmark.zig +238 -0
  20. package/src/tigerbeetle/src/{io_darwin.zig → io/darwin.zig} +88 -127
  21. package/src/tigerbeetle/src/io/linux.zig +933 -0
  22. package/src/tigerbeetle/src/io/test.zig +621 -0
  23. package/src/tigerbeetle/src/io.zig +7 -1328
  24. package/src/tigerbeetle/src/main.zig +18 -10
  25. package/src/tigerbeetle/src/message_bus.zig +43 -54
  26. package/src/tigerbeetle/src/message_pool.zig +3 -2
  27. package/src/tigerbeetle/src/simulator.zig +41 -37
  28. package/src/tigerbeetle/src/state_machine.zig +58 -27
  29. package/src/tigerbeetle/src/storage.zig +49 -46
  30. package/src/tigerbeetle/src/test/cluster.zig +2 -2
  31. package/src/tigerbeetle/src/test/message_bus.zig +6 -6
  32. package/src/tigerbeetle/src/test/network.zig +3 -3
  33. package/src/tigerbeetle/src/test/packet_simulator.zig +32 -29
  34. package/src/tigerbeetle/src/test/state_checker.zig +1 -1
  35. package/src/tigerbeetle/src/test/state_machine.zig +4 -0
  36. package/src/tigerbeetle/src/test/storage.zig +23 -19
  37. package/src/tigerbeetle/src/test/time.zig +2 -2
  38. package/src/tigerbeetle/src/tigerbeetle.zig +6 -128
  39. package/src/tigerbeetle/src/time.zig +6 -5
  40. package/src/tigerbeetle/src/vsr/client.zig +7 -7
  41. package/src/tigerbeetle/src/vsr/clock.zig +26 -43
  42. package/src/tigerbeetle/src/vsr/journal.zig +7 -6
  43. package/src/tigerbeetle/src/vsr/marzullo.zig +6 -3
  44. package/src/tigerbeetle/src/vsr/replica.zig +49 -46
  45. package/src/tigerbeetle/src/vsr.zig +24 -20
  46. package/src/translate.zig +55 -55
@@ -0,0 +1,238 @@
1
+ const std = @import("std");
2
+ const os = std.os;
3
+ const assert = std.debug.assert;
4
+
5
+ const Time = @import("../time.zig").Time;
6
+ const IO = @import("../io.zig").IO;
7
+
8
+ // 1 MB: larger than socket buffer so forces io_pending on darwin
9
+ // Configure this value to smaller amounts to test IO scheduling overhead
10
+ const buffer_size = 1 * 1024 * 1024;
11
+
12
+ // max time for the benchmark to run
13
+ const run_duration = 1 * std.time.ns_per_s;
14
+
15
+ pub fn main() !void {
16
+ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
17
+ const allocator = &gpa.allocator;
18
+ defer {
19
+ const leaks = gpa.deinit();
20
+ assert(!leaks);
21
+ }
22
+
23
+ const buffer = try allocator.alloc(u8, buffer_size * 2);
24
+ defer allocator.free(buffer);
25
+ std.mem.set(u8, buffer, 0);
26
+
27
+ var timer = Time{};
28
+ const started = timer.monotonic();
29
+ var self = Context{
30
+ .io = try IO.init(32, 0),
31
+ .timer = &timer,
32
+ .started = started,
33
+ .current = started,
34
+ .tx = .{ .buffer = buffer[0 * buffer_size ..][0..buffer_size] },
35
+ .rx = .{ .buffer = buffer[1 * buffer_size ..][0..buffer_size] },
36
+ };
37
+
38
+ defer {
39
+ self.io.deinit();
40
+ const elapsed_ns = self.current - started;
41
+ const transferred_mb = @intToFloat(f64, self.transferred) / 1024 / 1024;
42
+
43
+ std.debug.print("IO throughput test: took {}ms @ {d:.2} MB/s\n", .{
44
+ elapsed_ns / std.time.ns_per_ms,
45
+ transferred_mb / (@intToFloat(f64, elapsed_ns) / std.time.ns_per_s),
46
+ });
47
+ }
48
+
49
+ // Setup the server socket
50
+ self.server.fd = try IO.openSocket(os.AF_INET, os.SOCK_STREAM, os.IPPROTO_TCP);
51
+ defer os.closeSocket(self.server.fd);
52
+
53
+ const address = try std.net.Address.parseIp4("127.0.0.1", 3131);
54
+ try os.setsockopt(
55
+ self.server.fd,
56
+ os.SOL_SOCKET,
57
+ os.SO_REUSEADDR,
58
+ &std.mem.toBytes(@as(c_int, 1)),
59
+ );
60
+ try os.bind(self.server.fd, &address.any, address.getOsSockLen());
61
+ try os.listen(self.server.fd, 1);
62
+
63
+ // Start accepting the client
64
+ self.io.accept(
65
+ *Context,
66
+ &self,
67
+ Context.on_accept,
68
+ &self.server.completion,
69
+ self.server.fd,
70
+ );
71
+
72
+ // Setup the client connection
73
+ self.tx.socket.fd = try IO.openSocket(os.AF_INET, os.SOCK_STREAM, os.IPPROTO_TCP);
74
+ defer os.closeSocket(self.tx.socket.fd);
75
+
76
+ self.io.connect(
77
+ *Context,
78
+ &self,
79
+ Context.on_connect,
80
+ &self.tx.socket.completion,
81
+ self.tx.socket.fd,
82
+ address,
83
+ );
84
+
85
+ // Run the IO loop, calling either tick() or run_for_ns() at "pseudo-random"
86
+ // to benchmark each io-driving execution path
87
+ var tick: usize = 0xdeadbeef;
88
+ while (self.is_running()) : (tick +%= 1) {
89
+ if (tick % 61 == 0) {
90
+ const timeout_ns = tick % (10 * std.time.ns_per_ms);
91
+ try self.io.run_for_ns(@intCast(u63, timeout_ns));
92
+ } else {
93
+ try self.io.tick();
94
+ }
95
+ }
96
+
97
+ // Assert that everything is connected
98
+ assert(self.server.fd != -1);
99
+ assert(self.tx.socket.fd != -1);
100
+ assert(self.rx.socket.fd != -1);
101
+
102
+ // Close the accepted client socket.
103
+ // The actual client socket + server socket are closed by defer
104
+ os.closeSocket(self.rx.socket.fd);
105
+ }
106
+
107
+ const Context = struct {
108
+ io: IO,
109
+ tx: Pipe,
110
+ rx: Pipe,
111
+ timer: *Time,
112
+ started: u64,
113
+ current: u64,
114
+ server: Socket = .{},
115
+ transferred: u64 = 0,
116
+
117
+ const Socket = struct {
118
+ fd: os.socket_t = -1,
119
+ completion: IO.Completion = undefined,
120
+ };
121
+ const Pipe = struct {
122
+ socket: Socket = .{},
123
+ buffer: []u8,
124
+ transferred: usize = 0,
125
+ };
126
+
127
+ fn is_running(self: Context) bool {
128
+ // Make sure that we're connected
129
+ if (self.rx.socket.fd == -1) return true;
130
+
131
+ // Make sure that we haven't run too long as configured
132
+ const elapsed = self.current - self.started;
133
+ return elapsed < run_duration;
134
+ }
135
+
136
+ fn on_accept(
137
+ self: *Context,
138
+ completion: *IO.Completion,
139
+ result: IO.AcceptError!os.socket_t,
140
+ ) void {
141
+ assert(self.rx.socket.fd == -1);
142
+ assert(&self.server.completion == completion);
143
+ self.rx.socket.fd = result catch |err| std.debug.panic("accept error {}", .{err});
144
+
145
+ // Start reading data from the accepted client socket
146
+ assert(self.rx.transferred == 0);
147
+ self.do_transfer("rx", .read, 0);
148
+ }
149
+
150
+ fn on_connect(
151
+ self: *Context,
152
+ completion: *IO.Completion,
153
+ result: IO.ConnectError!void,
154
+ ) void {
155
+ assert(self.tx.socket.fd != -1);
156
+ assert(&self.tx.socket.completion == completion);
157
+
158
+ // Start sending data to the server's accepted client
159
+ assert(self.tx.transferred == 0);
160
+ self.do_transfer("tx", .write, 0);
161
+ }
162
+
163
+ const TransferType = enum {
164
+ read = 0,
165
+ write = 1,
166
+ };
167
+
168
+ fn do_transfer(
169
+ self: *Context,
170
+ comptime pipe_name: []const u8,
171
+ comptime transfer_type: TransferType,
172
+ bytes: usize,
173
+ ) void {
174
+ // The type of IO to perform and what type of IO to perform next (after the current one completes).
175
+ const transfer_info = switch (transfer_type) {
176
+ .read => .{
177
+ .IoError = IO.RecvError,
178
+ .io_func = "recv",
179
+ .next = TransferType.write,
180
+ },
181
+ .write => .{
182
+ .IoError = IO.SendError,
183
+ .io_func = "send",
184
+ .next = TransferType.read,
185
+ },
186
+ };
187
+
188
+ assert(bytes <= buffer_size);
189
+ self.transferred += bytes;
190
+
191
+ // Check in with the benchmark timer to stop sending/receiving data
192
+ self.current = self.timer.monotonic();
193
+ if (!self.is_running())
194
+ return;
195
+
196
+ // Select which connection (tx or rx) depending on the type of transfer
197
+ const pipe = &@field(self, pipe_name);
198
+ pipe.transferred += bytes;
199
+ assert(pipe.transferred <= pipe.buffer.len);
200
+
201
+ // There's still more data to transfer on the connection
202
+ if (pipe.transferred < pipe.buffer.len) {
203
+ // Callback which calls this function again when data is transferred.
204
+ // Effectively loops back above.
205
+ const on_transfer = struct {
206
+ fn on_transfer(
207
+ _self: *Context,
208
+ completion: *IO.Completion,
209
+ result: transfer_info.IoError!usize,
210
+ ) void {
211
+ const _bytes = result catch |err| {
212
+ std.debug.panic("{s} error: {}", .{ transfer_info.io_func, err });
213
+ };
214
+ assert(&@field(_self, pipe_name).socket.completion == completion);
215
+ _self.do_transfer(pipe_name, transfer_type, _bytes);
216
+ }
217
+ }.on_transfer;
218
+
219
+ // Perform the IO with the callback for the completion
220
+ return @field(self.io, transfer_info.io_func)(
221
+ *Context,
222
+ self,
223
+ on_transfer,
224
+ &pipe.socket.completion,
225
+ pipe.socket.fd,
226
+ pipe.buffer[pipe.transferred..],
227
+ );
228
+ }
229
+
230
+ // This transfer type completed transferring all the bytes.
231
+ // Now, switch the transfer type (transfer_info.next).
232
+ // This means if we read to the buffer, now we write it out.
233
+ // Inversely, if we wrote the buffer, now we read it back.
234
+ // This is basically a modified echo benchmark.
235
+ pipe.transferred = 0;
236
+ self.do_transfer(pipe_name, transfer_info.next, 0);
237
+ }
238
+ };
@@ -1,19 +1,24 @@
1
1
  const std = @import("std");
2
2
  const os = std.os;
3
+ const mem = std.mem;
3
4
  const assert = std.debug.assert;
4
5
 
5
- const FIFO = @import("fifo.zig").FIFO;
6
- const Time = @import("time.zig").Time;
7
- const buffer_limit = @import("io.zig").buffer_limit;
6
+ const FIFO = @import("../fifo.zig").FIFO;
7
+ const Time = @import("../time.zig").Time;
8
+ const buffer_limit = @import("../io.zig").buffer_limit;
8
9
 
9
10
  pub const IO = struct {
10
11
  kq: os.fd_t,
11
12
  time: Time = .{},
13
+ io_inflight: usize = 0,
12
14
  timeouts: FIFO(Completion) = .{},
13
15
  completed: FIFO(Completion) = .{},
14
16
  io_pending: FIFO(Completion) = .{},
15
17
 
16
18
  pub fn init(entries: u12, flags: u32) !IO {
19
+ _ = entries;
20
+ _ = flags;
21
+
17
22
  const kq = try os.kqueue();
18
23
  assert(kq > -1);
19
24
  return IO{ .kq = kq };
@@ -27,9 +32,7 @@ pub const IO = struct {
27
32
 
28
33
  /// Pass all queued submissions to the kernel and peek for completions.
29
34
  pub fn tick(self: *IO) !void {
30
- // TODO This is a hack to block 1ms every 10ms tick on macOS while we fix `flush(false)`:
31
- // What we're seeing for the tigerbeetle-node client is that recv() syscalls never complete.
32
- return self.run_for_ns(std.time.ns_per_ms);
35
+ return self.flush(false);
33
36
  }
34
37
 
35
38
  /// Pass all queued submissions to the kernel and run for `nanoseconds`.
@@ -42,8 +45,11 @@ pub const IO = struct {
42
45
  fn callback(
43
46
  timed_out_ptr: *bool,
44
47
  _completion: *Completion,
45
- _result: TimeoutError!void,
48
+ result: TimeoutError!void,
46
49
  ) void {
50
+ _ = _completion;
51
+ _ = result catch unreachable;
52
+
47
53
  timed_out_ptr.* = true;
48
54
  }
49
55
  }.callback;
@@ -84,10 +90,13 @@ pub const IO = struct {
84
90
  // - tick() is non-blocking (wait_for_completions = false)
85
91
  // - run_for_ns() always submits a timeout
86
92
  if (change_events == 0 and self.completed.peek() == null) {
87
- if (!wait_for_completions) return;
88
- const timeout_ns = next_timeout orelse @panic("kevent() blocking forever");
89
- ts.tv_nsec = @intCast(@TypeOf(ts.tv_nsec), timeout_ns % std.time.ns_per_s);
90
- ts.tv_sec = @intCast(@TypeOf(ts.tv_sec), timeout_ns / std.time.ns_per_s);
93
+ if (wait_for_completions) {
94
+ const timeout_ns = next_timeout orelse @panic("kevent() blocking forever");
95
+ ts.tv_nsec = @intCast(@TypeOf(ts.tv_nsec), timeout_ns % std.time.ns_per_s);
96
+ ts.tv_sec = @intCast(@TypeOf(ts.tv_sec), timeout_ns / std.time.ns_per_s);
97
+ } else if (self.io_inflight == 0) {
98
+ return;
99
+ }
91
100
  }
92
101
 
93
102
  const new_events = try os.kevent(
@@ -103,6 +112,9 @@ pub const IO = struct {
103
112
  self.io_pending.in = null;
104
113
  }
105
114
 
115
+ self.io_inflight += change_events;
116
+ self.io_inflight -= new_events;
117
+
106
118
  for (events[0..new_events]) |event| {
107
119
  const completion = @intToPtr(*Completion, event.udata);
108
120
  completion.next = null;
@@ -117,25 +129,25 @@ pub const IO = struct {
117
129
  }
118
130
  }
119
131
 
120
- fn flush_io(self: *IO, events: []os.Kevent, io_pending_top: *?*Completion) usize {
132
+ fn flush_io(_: *IO, events: []os.Kevent, io_pending_top: *?*Completion) usize {
121
133
  for (events) |*event, flushed| {
122
134
  const completion = io_pending_top.* orelse return flushed;
123
135
  io_pending_top.* = completion.next;
124
136
 
125
137
  const event_info = switch (completion.operation) {
126
- .accept => |op| [2]c_int{ op.socket, os.EVFILT_READ },
127
- .connect => |op| [2]c_int{ op.socket, os.EVFILT_WRITE },
128
- .read => |op| [2]c_int{ op.fd, os.EVFILT_READ },
129
- .write => |op| [2]c_int{ op.fd, os.EVFILT_WRITE },
130
- .recv => |op| [2]c_int{ op.socket, os.EVFILT_READ },
131
- .send => |op| [2]c_int{ op.socket, os.EVFILT_WRITE },
138
+ .accept => |op| [2]c_int{ op.socket, os.system.EVFILT_READ },
139
+ .connect => |op| [2]c_int{ op.socket, os.system.EVFILT_WRITE },
140
+ .read => |op| [2]c_int{ op.fd, os.system.EVFILT_READ },
141
+ .write => |op| [2]c_int{ op.fd, os.system.EVFILT_WRITE },
142
+ .recv => |op| [2]c_int{ op.socket, os.system.EVFILT_READ },
143
+ .send => |op| [2]c_int{ op.socket, os.system.EVFILT_WRITE },
132
144
  else => @panic("invalid completion operation queued for io"),
133
145
  };
134
146
 
135
147
  event.* = .{
136
148
  .ident = @intCast(u32, event_info[0]),
137
149
  .filter = @intCast(i16, event_info[1]),
138
- .flags = os.EV_ADD | os.EV_ENABLE | os.EV_ONESHOT,
150
+ .flags = os.system.EV_ADD | os.system.EV_ENABLE | os.system.EV_ONESHOT,
139
151
  .fflags = 0,
140
152
  .data = 0,
141
153
  .udata = @ptrToInt(completion),
@@ -175,7 +187,7 @@ pub const IO = struct {
175
187
  /// This struct holds the data needed for a single IO operation
176
188
  pub const Completion = struct {
177
189
  next: ?*Completion,
178
- context: ?*c_void,
190
+ context: ?*anyopaque,
179
191
  callback: fn (*IO, *Completion) void,
180
192
  operation: Operation,
181
193
  };
@@ -183,7 +195,6 @@ pub const IO = struct {
183
195
  const Operation = union(enum) {
184
196
  accept: struct {
185
197
  socket: os.socket_t,
186
- flags: u32,
187
198
  },
188
199
  close: struct {
189
200
  fd: os.fd_t,
@@ -195,13 +206,6 @@ pub const IO = struct {
195
206
  },
196
207
  fsync: struct {
197
208
  fd: os.fd_t,
198
- flags: u32,
199
- },
200
- openat: struct {
201
- fd: os.fd_t,
202
- path: [*:0]const u8,
203
- flags: u32,
204
- mode: os.mode_t,
205
209
  },
206
210
  read: struct {
207
211
  fd: os.fd_t,
@@ -213,13 +217,11 @@ pub const IO = struct {
213
217
  socket: os.socket_t,
214
218
  buf: [*]u8,
215
219
  len: u32,
216
- flags: u32,
217
220
  },
218
221
  send: struct {
219
222
  socket: os.socket_t,
220
223
  buf: [*]const u8,
221
224
  len: u32,
222
- flags: u32,
223
225
  },
224
226
  timeout: struct {
225
227
  expires: u64,
@@ -285,7 +287,7 @@ pub const IO = struct {
285
287
  }
286
288
  }
287
289
 
288
- pub const AcceptError = os.AcceptError;
290
+ pub const AcceptError = os.AcceptError || os.SetSockOptError;
289
291
 
290
292
  pub fn accept(
291
293
  self: *IO,
@@ -298,7 +300,6 @@ pub const IO = struct {
298
300
  ) void,
299
301
  completion: *Completion,
300
302
  socket: os.socket_t,
301
- flags: u32,
302
303
  ) void {
303
304
  self.submit(
304
305
  context,
@@ -307,11 +308,33 @@ pub const IO = struct {
307
308
  .accept,
308
309
  .{
309
310
  .socket = socket,
310
- .flags = flags,
311
311
  },
312
312
  struct {
313
313
  fn doOperation(op: anytype) AcceptError!os.socket_t {
314
- return os.accept(op.socket, null, null, op.flags);
314
+ const fd = try os.accept(
315
+ op.socket,
316
+ null,
317
+ null,
318
+ os.SOCK.NONBLOCK | os.SOCK.CLOEXEC,
319
+ );
320
+ errdefer os.close(fd);
321
+
322
+ // Darwin doesn't support os.MSG_NOSIGNAL to avoid getting SIGPIPE on socket send().
323
+ // Instead, it uses the SO_NOSIGPIPE socket option which does the same for all send()s.
324
+ os.setsockopt(
325
+ fd,
326
+ os.SOL.SOCKET,
327
+ os.SO.NOSIGPIPE,
328
+ &mem.toBytes(@as(c_int, 1)),
329
+ ) catch |err| return switch (err) {
330
+ error.TimeoutTooBig => unreachable,
331
+ error.PermissionDenied => error.NetworkSubsystemFailed,
332
+ error.AlreadyConnected => error.NetworkSubsystemFailed,
333
+ error.InvalidProtocolOption => error.ProtocolFailure,
334
+ else => |e| e,
335
+ };
336
+
337
+ return fd;
315
338
  }
316
339
  },
317
340
  );
@@ -347,10 +370,10 @@ pub const IO = struct {
347
370
  struct {
348
371
  fn doOperation(op: anytype) CloseError!void {
349
372
  return switch (os.errno(os.system.close(op.fd))) {
350
- 0 => {},
351
- os.EBADF => error.FileDescriptorInvalid,
352
- os.EINTR => {}, // A success, see https://github.com/ziglang/zig/issues/2425
353
- os.EIO => error.InputOutput,
373
+ .SUCCESS => {},
374
+ .BADF => error.FileDescriptorInvalid,
375
+ .INTR => {}, // A success, see https://github.com/ziglang/zig/issues/2425
376
+ .IO => error.InputOutput,
354
377
  else => |errno| os.unexpectedErrno(errno),
355
378
  };
356
379
  }
@@ -399,15 +422,7 @@ pub const IO = struct {
399
422
  );
400
423
  }
401
424
 
402
- pub const FsyncError = error{
403
- FileDescriptorInvalid,
404
- DiskQuota,
405
- ArgumentsInvalid,
406
- InputOutput,
407
- NoSpaceLeft,
408
- ReadOnlyFileSystem,
409
- AccessDenied,
410
- } || os.UnexpectedError;
425
+ pub const FsyncError = os.SyncError;
411
426
 
412
427
  pub fn fsync(
413
428
  self: *IO,
@@ -420,7 +435,6 @@ pub const IO = struct {
420
435
  ) void,
421
436
  completion: *Completion,
422
437
  fd: os.fd_t,
423
- flags: u32,
424
438
  ) void {
425
439
  self.submit(
426
440
  context,
@@ -429,68 +443,10 @@ pub const IO = struct {
429
443
  .fsync,
430
444
  .{
431
445
  .fd = fd,
432
- .flags = flags,
433
446
  },
434
447
  struct {
435
448
  fn doOperation(op: anytype) FsyncError!void {
436
- _ = os.fcntl(op.fd, os.F_FULLFSYNC, 1) catch return os.fsync(op.fd);
437
- }
438
- },
439
- );
440
- }
441
-
442
- pub const OpenatError = error{
443
- AccessDenied,
444
- FileDescriptorInvalid,
445
- DeviceBusy,
446
- PathAlreadyExists,
447
- FileTooBig,
448
- ArgumentsInvalid,
449
- IsDir,
450
- SymLinkLoop,
451
- ProcessFdQuotaExceeded,
452
- NameTooLong,
453
- SystemFdQuotaExceeded,
454
- NoDevice,
455
- FileNotFound,
456
- SystemResources,
457
- NoSpaceLeft,
458
- NotDir,
459
- FileLocksNotSupported,
460
- BadPathName,
461
- InvalidUtf8,
462
- WouldBlock,
463
- } || os.UnexpectedError;
464
-
465
- pub fn openat(
466
- self: *IO,
467
- comptime Context: type,
468
- context: Context,
469
- comptime callback: fn (
470
- context: Context,
471
- completion: *Completion,
472
- result: OpenatError!os.fd_t,
473
- ) void,
474
- completion: *Completion,
475
- fd: os.fd_t,
476
- path: [*:0]const u8,
477
- flags: u32,
478
- mode: os.mode_t,
479
- ) void {
480
- self.submit(
481
- context,
482
- callback,
483
- completion,
484
- .openat,
485
- .{
486
- .fd = fd,
487
- .path = path,
488
- .mode = mode,
489
- .flags = flags,
490
- },
491
- struct {
492
- fn doOperation(op: anytype) OpenatError!os.fd_t {
493
- return os.openatZ(op.fd, op.path, op.flags, op.mode);
449
+ _ = os.fcntl(op.fd, os.F.FULLFSYNC, 1) catch return os.fsync(op.fd);
494
450
  }
495
451
  },
496
452
  );
@@ -542,20 +498,20 @@ pub const IO = struct {
542
498
  @bitCast(isize, op.offset),
543
499
  );
544
500
  return switch (os.errno(rc)) {
545
- 0 => @intCast(usize, rc),
546
- os.EINTR => continue,
547
- os.EAGAIN => error.WouldBlock,
548
- os.EBADF => error.NotOpenForReading,
549
- os.ECONNRESET => error.ConnectionResetByPeer,
550
- os.EFAULT => unreachable,
551
- os.EINVAL => error.Alignment,
552
- os.EIO => error.InputOutput,
553
- os.EISDIR => error.IsDir,
554
- os.ENOBUFS => error.SystemResources,
555
- os.ENOMEM => error.SystemResources,
556
- os.ENXIO => error.Unseekable,
557
- os.EOVERFLOW => error.Unseekable,
558
- os.ESPIPE => error.Unseekable,
501
+ .SUCCESS => @intCast(usize, rc),
502
+ .INTR => continue,
503
+ .AGAIN => error.WouldBlock,
504
+ .BADF => error.NotOpenForReading,
505
+ .CONNRESET => error.ConnectionResetByPeer,
506
+ .FAULT => unreachable,
507
+ .INVAL => error.Alignment,
508
+ .IO => error.InputOutput,
509
+ .ISDIR => error.IsDir,
510
+ .NOBUFS => error.SystemResources,
511
+ .NOMEM => error.SystemResources,
512
+ .NXIO => error.Unseekable,
513
+ .OVERFLOW => error.Unseekable,
514
+ .SPIPE => error.Unseekable,
559
515
  else => |err| os.unexpectedErrno(err),
560
516
  };
561
517
  }
@@ -578,7 +534,6 @@ pub const IO = struct {
578
534
  completion: *Completion,
579
535
  socket: os.socket_t,
580
536
  buffer: []u8,
581
- flags: u32,
582
537
  ) void {
583
538
  self.submit(
584
539
  context,
@@ -589,11 +544,10 @@ pub const IO = struct {
589
544
  .socket = socket,
590
545
  .buf = buffer.ptr,
591
546
  .len = @intCast(u32, buffer_limit(buffer.len)),
592
- .flags = flags,
593
547
  },
594
548
  struct {
595
549
  fn doOperation(op: anytype) RecvError!usize {
596
- return os.recv(op.socket, op.buf[0..op.len], op.flags);
550
+ return os.recv(op.socket, op.buf[0..op.len], 0);
597
551
  }
598
552
  },
599
553
  );
@@ -613,7 +567,6 @@ pub const IO = struct {
613
567
  completion: *Completion,
614
568
  socket: os.socket_t,
615
569
  buffer: []const u8,
616
- flags: u32,
617
570
  ) void {
618
571
  self.submit(
619
572
  context,
@@ -624,11 +577,10 @@ pub const IO = struct {
624
577
  .socket = socket,
625
578
  .buf = buffer.ptr,
626
579
  .len = @intCast(u32, buffer_limit(buffer.len)),
627
- .flags = flags,
628
580
  },
629
581
  struct {
630
582
  fn doOperation(op: anytype) SendError!usize {
631
- return os.send(op.socket, op.buf[0..op.len], op.flags);
583
+ return os.send(op.socket, op.buf[0..op.len], 0);
632
584
  }
633
585
  },
634
586
  );
@@ -698,4 +650,13 @@ pub const IO = struct {
698
650
  },
699
651
  );
700
652
  }
653
+
654
+ pub fn openSocket(family: u32, sock_type: u32, protocol: u32) !os.socket_t {
655
+ const fd = try os.socket(family, sock_type | os.SOCK.NONBLOCK, protocol);
656
+ errdefer os.close(fd);
657
+
658
+ // darwin doesn't support os.MSG_NOSIGNAL, but instead a socket option to avoid SIGPIPE.
659
+ try os.setsockopt(fd, os.SOL.SOCKET, os.SO.NOSIGPIPE, &mem.toBytes(@as(c_int, 1)));
660
+ return fd;
661
+ }
701
662
  };