authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-03-29 20:58:07-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-20 10:38:38-07:00
log988f58341b60d61ec5be86f3d7dec5738c68d16a
treeee4ca2efffaf255b1cf659191674f50d50a74228
parentdab5dd286f289510985802d4cc5954227232ac44

std.Io: introduce cancellation


3 files changed, 239 insertions(+), 117 deletions(-)

lib/std/Io.zig+53-10
...@@ -922,6 +922,8 @@ vtable: *const VTable,...@@ -922,6 +922,8 @@ vtable: *const VTable,
922pub const VTable = struct {922pub const VTable = struct {
923 /// If it returns `null` it means `result` has been already populated and923 /// If it returns `null` it means `result` has been already populated and
924 /// `await` will be a no-op.924 /// `await` will be a no-op.
925 ///
926 /// Thread-safe.
925 async: *const fn (927 async: *const fn (
926 /// Corresponds to `Io.userdata`.928 /// Corresponds to `Io.userdata`.
927 userdata: ?*anyopaque,929 userdata: ?*anyopaque,
...@@ -937,6 +939,8 @@ pub const VTable = struct {...@@ -937,6 +939,8 @@ pub const VTable = struct {
937 ) ?*AnyFuture,939 ) ?*AnyFuture,
938940
939 /// This function is only called when `async` returns a non-null value.941 /// This function is only called when `async` returns a non-null value.
942 ///
943 /// Thread-safe.
940 await: *const fn (944 await: *const fn (
941 /// Corresponds to `Io.userdata`.945 /// Corresponds to `Io.userdata`.
942 userdata: ?*anyopaque,946 userdata: ?*anyopaque,
...@@ -947,13 +951,41 @@ pub const VTable = struct {...@@ -947,13 +951,41 @@ pub const VTable = struct {
947 result: []u8,951 result: []u8,
948 ) void,952 ) void,
949953
950 createFile: *const fn (?*anyopaque, dir: fs.Dir, sub_path: []const u8, flags: fs.File.CreateFlags) fs.File.OpenError!fs.File,954 /// Equivalent to `await` but initiates cancel request.
951 openFile: *const fn (?*anyopaque, dir: fs.Dir, sub_path: []const u8, flags: fs.File.OpenFlags) fs.File.OpenError!fs.File,955 ///
956 /// This function is only called when `async` returns a non-null value.
957 ///
958 /// Thread-safe.
959 cancel: *const fn (
960 /// Corresponds to `Io.userdata`.
961 userdata: ?*anyopaque,
962 /// The same value that was returned from `async`.
963 any_future: *AnyFuture,
964 /// Points to a buffer where the result is written.
965 /// The length is equal to size in bytes of result type.
966 result: []u8,
967 ) void,
968
969 /// Returns whether the current thread of execution is known to have
970 /// been requested to cancel.
971 ///
972 /// Thread-safe.
973 cancelRequested: *const fn (?*anyopaque) bool,
974
975 createFile: *const fn (?*anyopaque, dir: fs.Dir, sub_path: []const u8, flags: fs.File.CreateFlags) FileOpenError!fs.File,
976 openFile: *const fn (?*anyopaque, dir: fs.Dir, sub_path: []const u8, flags: fs.File.OpenFlags) FileOpenError!fs.File,
952 closeFile: *const fn (?*anyopaque, fs.File) void,977 closeFile: *const fn (?*anyopaque, fs.File) void,
953 read: *const fn (?*anyopaque, file: fs.File, buffer: []u8) fs.File.ReadError!usize,978 read: *const fn (?*anyopaque, file: fs.File, buffer: []u8) FileReadError!usize,
954 write: *const fn (?*anyopaque, file: fs.File, buffer: []const u8) fs.File.WriteError!usize,979 write: *const fn (?*anyopaque, file: fs.File, buffer: []const u8) FileWriteError!usize,
955};980};
956981
982pub const OpenFlags = fs.File.OpenFlags;
983pub const CreateFlags = fs.File.CreateFlags;
984
985pub const FileOpenError = fs.File.OpenError || error{AsyncCancel};
986pub const FileReadError = fs.File.ReadError || error{AsyncCancel};
987pub const FileWriteError = fs.File.WriteError || error{AsyncCancel};
988
957pub const AnyFuture = opaque {};989pub const AnyFuture = opaque {};
958990
959pub fn Future(Result: type) type {991pub fn Future(Result: type) type {
...@@ -961,6 +993,17 @@ pub fn Future(Result: type) type {...@@ -961,6 +993,17 @@ pub fn Future(Result: type) type {
961 any_future: ?*AnyFuture,993 any_future: ?*AnyFuture,
962 result: Result,994 result: Result,
963995
996 /// Equivalent to `await` but sets a flag observable to application
997 /// code that cancellation has been requested.
998 ///
999 /// Idempotent.
1000 pub fn cancel(f: *@This(), io: Io) Result {
1001 const any_future = f.any_future orelse return f.result;
1002 io.vtable.cancel(io.userdata, any_future, @ptrCast((&f.result)[0..1]));
1003 f.any_future = null;
1004 return f.result;
1005 }
1006
964 pub fn await(f: *@This(), io: Io) Result {1007 pub fn await(f: *@This(), io: Io) Result {
965 const any_future = f.any_future orelse return f.result;1008 const any_future = f.any_future orelse return f.result;
966 io.vtable.await(io.userdata, any_future, @ptrCast((&f.result)[0..1]));1009 io.vtable.await(io.userdata, any_future, @ptrCast((&f.result)[0..1]));
...@@ -994,11 +1037,11 @@ pub fn async(io: Io, function: anytype, args: anytype) Future(@typeInfo(@TypeOf(...@@ -994,11 +1037,11 @@ pub fn async(io: Io, function: anytype, args: anytype) Future(@typeInfo(@TypeOf(
994 return future;1037 return future;
995}1038}
9961039
997pub fn openFile(io: Io, dir: fs.Dir, sub_path: []const u8, flags: fs.File.OpenFlags) fs.File.OpenError!fs.File {1040pub fn openFile(io: Io, dir: fs.Dir, sub_path: []const u8, flags: fs.File.OpenFlags) FileOpenError!fs.File {
998 return io.vtable.openFile(io.userdata, dir, sub_path, flags);1041 return io.vtable.openFile(io.userdata, dir, sub_path, flags);
999}1042}
10001043
1001pub fn createFile(io: Io, dir: fs.Dir, sub_path: []const u8, flags: fs.File.CreateFlags) fs.File.OpenError!fs.File {1044pub fn createFile(io: Io, dir: fs.Dir, sub_path: []const u8, flags: fs.File.CreateFlags) FileOpenError!fs.File {
1002 return io.vtable.createFile(io.userdata, dir, sub_path, flags);1045 return io.vtable.createFile(io.userdata, dir, sub_path, flags);
1003}1046}
10041047
...@@ -1006,22 +1049,22 @@ pub fn closeFile(io: Io, file: fs.File) void {...@@ -1006,22 +1049,22 @@ pub fn closeFile(io: Io, file: fs.File) void {
1006 return io.vtable.closeFile(io.userdata, file);1049 return io.vtable.closeFile(io.userdata, file);
1007}1050}
10081051
1009pub fn read(io: Io, file: fs.File, buffer: []u8) fs.File.ReadError!usize {1052pub fn read(io: Io, file: fs.File, buffer: []u8) FileReadError!usize {
1010 return io.vtable.read(io.userdata, file, buffer);1053 return io.vtable.read(io.userdata, file, buffer);
1011}1054}
10121055
1013pub fn write(io: Io, file: fs.File, buffer: []const u8) fs.File.WriteError!usize {1056pub fn write(io: Io, file: fs.File, buffer: []const u8) FileWriteError!usize {
1014 return io.vtable.write(io.userdata, file, buffer);1057 return io.vtable.write(io.userdata, file, buffer);
1015}1058}
10161059
1017pub fn writeAll(io: Io, file: fs.File, bytes: []const u8) fs.File.WriteError!void {1060pub fn writeAll(io: Io, file: fs.File, bytes: []const u8) FileWriteError!void {
1018 var index: usize = 0;1061 var index: usize = 0;
1019 while (index < bytes.len) {1062 while (index < bytes.len) {
1020 index += try io.write(file, bytes[index..]);1063 index += try io.write(file, bytes[index..]);
1021 }1064 }
1022}1065}
10231066
1024pub fn readAll(io: Io, file: fs.File, buffer: []u8) fs.File.ReadError!usize {1067pub fn readAll(io: Io, file: fs.File, buffer: []u8) FileReadError!usize {
1025 var index: usize = 0;1068 var index: usize = 0;
1026 while (index != buffer.len) {1069 while (index != buffer.len) {
1027 const amt = try io.read(file, buffer[index..]);1070 const amt = try io.read(file, buffer[index..]);
lib/std/Io/EventLoop.zig+13-3
...@@ -7,12 +7,13 @@ const EventLoop = @This();...@@ -7,12 +7,13 @@ const EventLoop = @This();
7const Alignment = std.mem.Alignment;7const Alignment = std.mem.Alignment;
8const IoUring = std.os.linux.IoUring;8const IoUring = std.os.linux.IoUring;
99
10/// Must be a thread-safe allocator.
10gpa: Allocator,11gpa: Allocator,
11mutex: std.Thread.Mutex,12mutex: std.Thread.Mutex,
12queue: std.DoublyLinkedList(void),13queue: std.DoublyLinkedList,
13/// Atomic copy of queue.len14/// Atomic copy of queue.len
14queue_len: u32,15queue_len: u32,
15free: std.DoublyLinkedList(void),16free: std.DoublyLinkedList,
16main_fiber: Fiber,17main_fiber: Fiber,
17idle_count: usize,18idle_count: usize,
18threads: std.ArrayListUnmanaged(Thread),19threads: std.ArrayListUnmanaged(Thread),
...@@ -39,7 +40,7 @@ const Thread = struct {...@@ -39,7 +40,7 @@ const Thread = struct {
39const Fiber = struct {40const Fiber = struct {
40 context: Context,41 context: Context,
41 awaiter: ?*Fiber,42 awaiter: ?*Fiber,
42 queue_node: std.DoublyLinkedList(void).Node,43 queue_node: std.DoublyLinkedList.Node,
43 result_align: Alignment,44 result_align: Alignment,
4445
45 const finished: ?*Fiber = @ptrFromInt(std.mem.alignBackward(usize, std.math.maxInt(usize), @alignOf(Fiber)));46 const finished: ?*Fiber = @ptrFromInt(std.mem.alignBackward(usize, std.math.maxInt(usize), @alignOf(Fiber)));
...@@ -447,6 +448,15 @@ pub fn @"await"(userdata: ?*anyopaque, any_future: *std.Io.AnyFuture, result: []...@@ -447,6 +448,15 @@ pub fn @"await"(userdata: ?*anyopaque, any_future: *std.Io.AnyFuture, result: []
447 event_loop.recycle(future_fiber);448 event_loop.recycle(future_fiber);
448}449}
449450
451pub fn cancel(userdata: ?*anyopaque, any_future: *std.Io.AnyFuture, result: []u8) void {
452 const event_loop: *EventLoop = @alignCast(@ptrCast(userdata));
453 const future_fiber: *Fiber = @alignCast(@ptrCast(any_future));
454 // TODO set a flag that makes all IO operations for this fiber return error.Canceled
455 if (@atomicLoad(?*Fiber, &future_fiber.awaiter, .acquire) != Fiber.finished) event_loop.yield(null, .{ .register_awaiter = &future_fiber.awaiter });
456 @memcpy(result, future_fiber.resultPointer());
457 event_loop.recycle(future_fiber);
458}
459
450pub fn createFile(userdata: ?*anyopaque, dir: std.fs.Dir, sub_path: []const u8, flags: std.fs.File.CreateFlags) std.fs.File.OpenError!std.fs.File {460pub fn createFile(userdata: ?*anyopaque, dir: std.fs.Dir, sub_path: []const u8, flags: std.fs.File.CreateFlags) std.fs.File.OpenError!std.fs.File {
451 const el: *EventLoop = @ptrCast(@alignCast(userdata));461 const el: *EventLoop = @ptrCast(@alignCast(userdata));
452462
lib/std/Thread/Pool.zig+173-104
...@@ -1,22 +1,27 @@...@@ -1,22 +1,27 @@
1const builtin = @import("builtin");1const builtin = @import("builtin");
2const std = @import("std");2const std = @import("std");
3const Allocator = std.mem.Allocator;
3const assert = std.debug.assert;4const assert = std.debug.assert;
4const WaitGroup = @import("WaitGroup.zig");5const WaitGroup = @import("WaitGroup.zig");
6const Io = std.Io;
5const Pool = @This();7const Pool = @This();
68
9/// Must be a thread-safe allocator.
10allocator: std.mem.Allocator,
7mutex: std.Thread.Mutex = .{},11mutex: std.Thread.Mutex = .{},
8cond: std.Thread.Condition = .{},12cond: std.Thread.Condition = .{},
9run_queue: std.SinglyLinkedList = .{},13run_queue: std.SinglyLinkedList = .{},
10is_running: bool = true,14is_running: bool = true,
11/// Must be a thread-safe allocator.15threads: std.ArrayListUnmanaged(std.Thread),
12allocator: std.mem.Allocator,
13threads: if (builtin.single_threaded) [0]std.Thread else []std.Thread,
14ids: if (builtin.single_threaded) struct {16ids: if (builtin.single_threaded) struct {
15 inline fn deinit(_: @This(), _: std.mem.Allocator) void {}17 inline fn deinit(_: @This(), _: std.mem.Allocator) void {}
16 fn getIndex(_: @This(), _: std.Thread.Id) usize {18 fn getIndex(_: @This(), _: std.Thread.Id) usize {
17 return 0;19 return 0;
18 }20 }
19} else std.AutoArrayHashMapUnmanaged(std.Thread.Id, void),21} else std.AutoArrayHashMapUnmanaged(std.Thread.Id, void),
22stack_size: usize,
23
24threadlocal var current_closure: ?*AsyncClosure = null;
2025
21pub const Runnable = struct {26pub const Runnable = struct {
22 runFn: RunProto,27 runFn: RunProto,
...@@ -33,48 +38,36 @@ pub const Options = struct {...@@ -33,48 +38,36 @@ pub const Options = struct {
33};38};
3439
35pub fn init(pool: *Pool, options: Options) !void {40pub fn init(pool: *Pool, options: Options) !void {
36 const allocator = options.allocator;41 const gpa = options.allocator;
42 const thread_count = options.n_jobs orelse @max(1, std.Thread.getCpuCount() catch 1);
43 const threads = try gpa.alloc(std.Thread, thread_count);
44 errdefer gpa.free(threads);
3745
38 pool.* = .{46 pool.* = .{
39 .allocator = allocator,47 .allocator = gpa,
40 .threads = if (builtin.single_threaded) .{} else &.{},48 .threads = .initBuffer(threads),
41 .ids = .{},49 .ids = .{},
50 .stack_size = options.stack_size,
42 };51 };
4352
44 if (builtin.single_threaded) {53 if (builtin.single_threaded) return;
45 return;
46 }
4754
48 const thread_count = options.n_jobs orelse @max(1, std.Thread.getCpuCount() catch 1);
49 if (options.track_ids) {55 if (options.track_ids) {
50 try pool.ids.ensureTotalCapacity(allocator, 1 + thread_count);56 try pool.ids.ensureTotalCapacity(gpa, 1 + thread_count);
51 pool.ids.putAssumeCapacityNoClobber(std.Thread.getCurrentId(), {});57 pool.ids.putAssumeCapacityNoClobber(std.Thread.getCurrentId(), {});
52 }58 }
53
54 // kill and join any threads we spawned and free memory on error.
55 pool.threads = try allocator.alloc(std.Thread, thread_count);
56 var spawned: usize = 0;
57 errdefer pool.join(spawned);
58
59 for (pool.threads) |*thread| {
60 thread.* = try std.Thread.spawn(.{
61 .stack_size = options.stack_size,
62 .allocator = allocator,
63 }, worker, .{pool});
64 spawned += 1;
65 }
66}59}
6760
68pub fn deinit(pool: *Pool) void {61pub fn deinit(pool: *Pool) void {
69 pool.join(pool.threads.len); // kill and join all threads.62 const gpa = pool.allocator;
70 pool.ids.deinit(pool.allocator);63 pool.join();
64 pool.threads.deinit(gpa);
65 pool.ids.deinit(gpa);
71 pool.* = undefined;66 pool.* = undefined;
72}67}
7368
74fn join(pool: *Pool, spawned: usize) void {69fn join(pool: *Pool) void {
75 if (builtin.single_threaded) {70 if (builtin.single_threaded) return;
76 return;
77 }
7871
79 {72 {
80 pool.mutex.lock();73 pool.mutex.lock();
...@@ -87,11 +80,7 @@ fn join(pool: *Pool, spawned: usize) void {...@@ -87,11 +80,7 @@ fn join(pool: *Pool, spawned: usize) void {
87 // wake up any sleeping threads (this can be done outside the mutex)80 // wake up any sleeping threads (this can be done outside the mutex)
88 // then wait for all the threads we know are spawned to complete.81 // then wait for all the threads we know are spawned to complete.
89 pool.cond.broadcast();82 pool.cond.broadcast();
90 for (pool.threads[0..spawned]) |thread| {83 for (pool.threads.items) |thread| thread.join();
91 thread.join();
92 }
93
94 pool.allocator.free(pool.threads);
95}84}
9685
97/// Runs `func` in the thread pool, calling `WaitGroup.start` beforehand, and86/// Runs `func` in the thread pool, calling `WaitGroup.start` beforehand, and
...@@ -123,26 +112,34 @@ pub fn spawnWg(pool: *Pool, wait_group: *WaitGroup, comptime func: anytype, args...@@ -123,26 +112,34 @@ pub fn spawnWg(pool: *Pool, wait_group: *WaitGroup, comptime func: anytype, args
123 }112 }
124 };113 };
125114
126 {115 pool.mutex.lock();
127 pool.mutex.lock();
128
129 const closure = pool.allocator.create(Closure) catch {
130 pool.mutex.unlock();
131 @call(.auto, func, args);
132 wait_group.finish();
133 return;
134 };
135 closure.* = .{
136 .arguments = args,
137 .pool = pool,
138 .wait_group = wait_group,
139 };
140116
141 pool.run_queue.prepend(&closure.runnable.node);117 const gpa = pool.allocator;
118 const closure = gpa.create(Closure) catch {
142 pool.mutex.unlock();119 pool.mutex.unlock();
120 @call(.auto, func, args);
121 wait_group.finish();
122 return;
123 };
124 closure.* = .{
125 .arguments = args,
126 .pool = pool,
127 .wait_group = wait_group,
128 };
129
130 pool.run_queue.prepend(&closure.runnable.node);
131
132 if (pool.threads.items.len < pool.threads.capacity) {
133 pool.threads.addOneAssumeCapacity().* = std.Thread.spawn(.{
134 .stack_size = pool.stack_size,
135 .allocator = gpa,
136 }, worker, .{pool}) catch t: {
137 pool.threads.items.len -= 1;
138 break :t undefined;
139 };
143 }140 }
144141
145 // Notify waiting threads outside the lock to try and keep the critical section small.142 pool.mutex.unlock();
146 pool.cond.signal();143 pool.cond.signal();
147}144}
148145
...@@ -179,31 +176,39 @@ pub fn spawnWgId(pool: *Pool, wait_group: *WaitGroup, comptime func: anytype, ar...@@ -179,31 +176,39 @@ pub fn spawnWgId(pool: *Pool, wait_group: *WaitGroup, comptime func: anytype, ar
179 }176 }
180 };177 };
181178
182 {179 pool.mutex.lock();
183 pool.mutex.lock();
184
185 const closure = pool.allocator.create(Closure) catch {
186 const id: ?usize = pool.ids.getIndex(std.Thread.getCurrentId());
187 pool.mutex.unlock();
188 @call(.auto, func, .{id.?} ++ args);
189 wait_group.finish();
190 return;
191 };
192 closure.* = .{
193 .arguments = args,
194 .pool = pool,
195 .wait_group = wait_group,
196 };
197180
198 pool.run_queue.prepend(&closure.runnable.node);181 const gpa = pool.allocator;
182 const closure = gpa.create(Closure) catch {
183 const id: ?usize = pool.ids.getIndex(std.Thread.getCurrentId());
199 pool.mutex.unlock();184 pool.mutex.unlock();
185 @call(.auto, func, .{id.?} ++ args);
186 wait_group.finish();
187 return;
188 };
189 closure.* = .{
190 .arguments = args,
191 .pool = pool,
192 .wait_group = wait_group,
193 };
194
195 pool.run_queue.prepend(&closure.runnable.node);
196
197 if (pool.threads.items.len < pool.threads.capacity) {
198 pool.threads.addOneAssumeCapacity().* = std.Thread.spawn(.{
199 .stack_size = pool.stack_size,
200 .allocator = gpa,
201 }, worker, .{pool}) catch t: {
202 pool.threads.items.len -= 1;
203 break :t undefined;
204 };
200 }205 }
201206
202 // Notify waiting threads outside the lock to try and keep the critical section small.207 pool.mutex.unlock();
203 pool.cond.signal();208 pool.cond.signal();
204}209}
205210
206pub fn spawn(pool: *Pool, comptime func: anytype, args: anytype) !void {211pub fn spawn(pool: *Pool, comptime func: anytype, args: anytype) void {
207 if (builtin.single_threaded) {212 if (builtin.single_threaded) {
208 @call(.auto, func, args);213 @call(.auto, func, args);
209 return;214 return;
...@@ -222,20 +227,32 @@ pub fn spawn(pool: *Pool, comptime func: anytype, args: anytype) !void {...@@ -222,20 +227,32 @@ pub fn spawn(pool: *Pool, comptime func: anytype, args: anytype) !void {
222 }227 }
223 };228 };
224229
225 {230 pool.mutex.lock();
226 pool.mutex.lock();
227 defer pool.mutex.unlock();
228231
229 const closure = try pool.allocator.create(Closure);232 const gpa = pool.allocator;
230 closure.* = .{233 const closure = gpa.create(Closure) catch {
231 .arguments = args,234 pool.mutex.unlock();
232 .pool = pool,235 @call(.auto, func, args);
233 };236 return;
237 };
238 closure.* = .{
239 .arguments = args,
240 .pool = pool,
241 };
242
243 pool.run_queue.prepend(&closure.runnable.node);
234244
235 pool.run_queue.prepend(&closure.runnable.node);245 if (pool.threads.items.len < pool.threads.capacity) {
246 pool.threads.addOneAssumeCapacity().* = std.Thread.spawn(.{
247 .stack_size = pool.stack_size,
248 .allocator = gpa,
249 }, worker, .{pool}) catch t: {
250 pool.threads.items.len -= 1;
251 break :t undefined;
252 };
236 }253 }
237254
238 // Notify waiting threads outside the lock to try and keep the critical section small.255 pool.mutex.unlock();
239 pool.cond.signal();256 pool.cond.signal();
240}257}
241258
...@@ -254,7 +271,7 @@ test spawn {...@@ -254,7 +271,7 @@ test spawn {
254 .allocator = std.testing.allocator,271 .allocator = std.testing.allocator,
255 });272 });
256 defer pool.deinit();273 defer pool.deinit();
257 try pool.spawn(TestFn.checkRun, .{&completed});274 pool.spawn(TestFn.checkRun, .{&completed});
258 }275 }
259276
260 try std.testing.expectEqual(true, completed);277 try std.testing.expectEqual(true, completed);
...@@ -306,15 +323,17 @@ pub fn waitAndWork(pool: *Pool, wait_group: *WaitGroup) void {...@@ -306,15 +323,17 @@ pub fn waitAndWork(pool: *Pool, wait_group: *WaitGroup) void {
306}323}
307324
308pub fn getIdCount(pool: *Pool) usize {325pub fn getIdCount(pool: *Pool) usize {
309 return @intCast(1 + pool.threads.len);326 return @intCast(1 + pool.threads.items.len);
310}327}
311328
312pub fn io(pool: *Pool) std.Io {329pub fn io(pool: *Pool) Io {
313 return .{330 return .{
314 .userdata = pool,331 .userdata = pool,
315 .vtable = &.{332 .vtable = &.{
316 .@"async" = @"async",333 .@"async" = @"async",
317 .@"await" = @"await",334 .@"await" = @"await",
335 .cancel = cancel,
336 .cancelRequested = cancelRequested,
318 .createFile = createFile,337 .createFile = createFile,
319 .openFile = openFile,338 .openFile = openFile,
320 .closeFile = closeFile,339 .closeFile = closeFile,
...@@ -326,15 +345,17 @@ pub fn io(pool: *Pool) std.Io {...@@ -326,15 +345,17 @@ pub fn io(pool: *Pool) std.Io {
326345
327const AsyncClosure = struct {346const AsyncClosure = struct {
328 func: *const fn (context: *anyopaque, result: *anyopaque) void,347 func: *const fn (context: *anyopaque, result: *anyopaque) void,
329 run_node: std.Thread.Pool.RunQueue.Node = .{ .data = .{ .runFn = runFn } },348 runnable: Runnable = .{ .runFn = runFn },
330 reset_event: std.Thread.ResetEvent,349 reset_event: std.Thread.ResetEvent,
350 cancel_flag: bool,
331 context_offset: usize,351 context_offset: usize,
332 result_offset: usize,352 result_offset: usize,
333353
334 fn runFn(runnable: *std.Thread.Pool.Runnable, _: ?usize) void {354 fn runFn(runnable: *std.Thread.Pool.Runnable, _: ?usize) void {
335 const run_node: *std.Thread.Pool.RunQueue.Node = @fieldParentPtr("data", runnable);355 const closure: *AsyncClosure = @alignCast(@fieldParentPtr("runnable", runnable));
336 const closure: *AsyncClosure = @alignCast(@fieldParentPtr("run_node", run_node));356 current_closure = closure;
337 closure.func(closure.contextPointer(), closure.resultPointer());357 closure.func(closure.contextPointer(), closure.resultPointer());
358 current_closure = null;
338 closure.reset_event.set();359 closure.reset_event.set();
339 }360 }
340361
...@@ -359,16 +380,23 @@ const AsyncClosure = struct {...@@ -359,16 +380,23 @@ const AsyncClosure = struct {
359 const base: [*]u8 = @ptrCast(closure);380 const base: [*]u8 = @ptrCast(closure);
360 return base + closure.context_offset;381 return base + closure.context_offset;
361 }382 }
383
384 fn waitAndFree(closure: *AsyncClosure, gpa: Allocator, result: []u8) void {
385 closure.reset_event.wait();
386 const base: [*]align(@alignOf(AsyncClosure)) u8 = @ptrCast(closure);
387 @memcpy(result, closure.resultPointer()[0..result.len]);
388 gpa.free(base[0 .. closure.result_offset + result.len]);
389 }
362};390};
363391
364pub fn @"async"(392fn @"async"(
365 userdata: ?*anyopaque,393 userdata: ?*anyopaque,
366 result: []u8,394 result: []u8,
367 result_alignment: std.mem.Alignment,395 result_alignment: std.mem.Alignment,
368 context: []const u8,396 context: []const u8,
369 context_alignment: std.mem.Alignment,397 context_alignment: std.mem.Alignment,
370 start: *const fn (context: *const anyopaque, result: *anyopaque) void,398 start: *const fn (context: *const anyopaque, result: *anyopaque) void,
371) ?*std.Io.AnyFuture {399) ?*Io.AnyFuture {
372 const pool: *std.Thread.Pool = @alignCast(@ptrCast(userdata));400 const pool: *std.Thread.Pool = @alignCast(@ptrCast(userdata));
373 pool.mutex.lock();401 pool.mutex.lock();
374402
...@@ -386,46 +414,87 @@ pub fn @"async"(...@@ -386,46 +414,87 @@ pub fn @"async"(
386 .context_offset = context_offset,414 .context_offset = context_offset,
387 .result_offset = result_offset,415 .result_offset = result_offset,
388 .reset_event = .{},416 .reset_event = .{},
417 .cancel_flag = false,
389 };418 };
390 @memcpy(closure.contextPointer()[0..context.len], context);419 @memcpy(closure.contextPointer()[0..context.len], context);
391 pool.run_queue.prepend(&closure.run_node);420 pool.run_queue.prepend(&closure.runnable.node);
392 pool.mutex.unlock();421
422 if (pool.threads.items.len < pool.threads.capacity) {
423 pool.threads.addOneAssumeCapacity().* = std.Thread.spawn(.{
424 .stack_size = pool.stack_size,
425 .allocator = gpa,
426 }, worker, .{pool}) catch t: {
427 pool.threads.items.len -= 1;
428 break :t undefined;
429 };
430 }
393431
432 pool.mutex.unlock();
394 pool.cond.signal();433 pool.cond.signal();
395434
396 return @ptrCast(closure);435 return @ptrCast(closure);
397}436}
398437
399pub fn @"await"(userdata: ?*anyopaque, any_future: *std.Io.AnyFuture, result: []u8) void {438fn @"await"(userdata: ?*anyopaque, any_future: *Io.AnyFuture, result: []u8) void {
400 const thread_pool: *std.Thread.Pool = @alignCast(@ptrCast(userdata));439 const pool: *std.Thread.Pool = @alignCast(@ptrCast(userdata));
401 const closure: *AsyncClosure = @ptrCast(@alignCast(any_future));440 const closure: *AsyncClosure = @ptrCast(@alignCast(any_future));
402 closure.reset_event.wait();441 closure.waitAndFree(pool.allocator, result);
403 const base: [*]align(@alignOf(AsyncClosure)) u8 = @ptrCast(closure);442}
404 @memcpy(result, closure.resultPointer()[0..result.len]);443
405 thread_pool.allocator.free(base[0 .. closure.result_offset + result.len]);444fn cancel(userdata: ?*anyopaque, any_future: *Io.AnyFuture, result: []u8) void {
445 const pool: *std.Thread.Pool = @alignCast(@ptrCast(userdata));
446 const closure: *AsyncClosure = @ptrCast(@alignCast(any_future));
447 @atomicStore(bool, &closure.cancel_flag, true, .seq_cst);
448 closure.waitAndFree(pool.allocator, result);
449}
450
451fn cancelRequested(userdata: ?*anyopaque) bool {
452 const pool: *std.Thread.Pool = @alignCast(@ptrCast(userdata));
453 _ = pool;
454 const closure = current_closure orelse return false;
455 return @atomicLoad(bool, &closure.cancel_flag, .unordered);
456}
457
458fn checkCancel(pool: *Pool) error{AsyncCancel}!void {
459 if (cancelRequested(pool)) return error.AsyncCancel;
406}460}
407461
408pub fn createFile(userdata: ?*anyopaque, dir: std.fs.Dir, sub_path: []const u8, flags: std.fs.File.CreateFlags) std.fs.File.OpenError!std.fs.File {462pub fn createFile(
409 _ = userdata;463 userdata: ?*anyopaque,
464 dir: std.fs.Dir,
465 sub_path: []const u8,
466 flags: std.fs.File.CreateFlags,
467) Io.FileOpenError!std.fs.File {
468 const pool: *std.Thread.Pool = @alignCast(@ptrCast(userdata));
469 try pool.checkCancel();
410 return dir.createFile(sub_path, flags);470 return dir.createFile(sub_path, flags);
411}471}
412472
413pub fn openFile(userdata: ?*anyopaque, dir: std.fs.Dir, sub_path: []const u8, flags: std.fs.File.OpenFlags) std.fs.File.OpenError!std.fs.File {473pub fn openFile(
414 _ = userdata;474 userdata: ?*anyopaque,
475 dir: std.fs.Dir,
476 sub_path: []const u8,
477 flags: std.fs.File.OpenFlags,
478) Io.FileOpenError!std.fs.File {
479 const pool: *std.Thread.Pool = @alignCast(@ptrCast(userdata));
480 try pool.checkCancel();
415 return dir.openFile(sub_path, flags);481 return dir.openFile(sub_path, flags);
416}482}
417483
418pub fn closeFile(userdata: ?*anyopaque, file: std.fs.File) void {484pub fn closeFile(userdata: ?*anyopaque, file: std.fs.File) void {
419 _ = userdata;485 const pool: *std.Thread.Pool = @alignCast(@ptrCast(userdata));
486 _ = pool;
420 return file.close();487 return file.close();
421}488}
422489
423pub fn read(userdata: ?*anyopaque, file: std.fs.File, buffer: []u8) std.fs.File.ReadError!usize {490pub fn read(userdata: ?*anyopaque, file: std.fs.File, buffer: []u8) Io.FileReadError!usize {
424 _ = userdata;491 const pool: *std.Thread.Pool = @alignCast(@ptrCast(userdata));
492 try pool.checkCancel();
425 return file.read(buffer);493 return file.read(buffer);
426}494}
427495
428pub fn write(userdata: ?*anyopaque, file: std.fs.File, buffer: []const u8) std.fs.File.WriteError!usize {496pub fn write(userdata: ?*anyopaque, file: std.fs.File, buffer: []const u8) Io.FileWriteError!usize {
429 _ = userdata;497 const pool: *std.Thread.Pool = @alignCast(@ptrCast(userdata));
498 try pool.checkCancel();
430 return file.write(buffer);499 return file.write(buffer);
431}500}