···5757to another for handling.
58585959```zig
6060+// Spawn a thread with a queue of 16 entries. When this function returns, the
6161+// the thread is idle and waiting to receive tasks via msgRing
6262+const thread = main_rt.spawnThread(16);
6063const target_task = try main_rt.getTask();
6164target_task.* {
6265 .userdata = &foo,
···6568 .req = .{ .userfd = fd },
6669};
67706868-// Send target_task from the main_rt thread to the thread_rt Ring. The
7171+// Send target_task from the main_rt thread to the thread Ring. The
6972// thread_rt Ring will then // process the task as a completion, ie
7070-// Worker.onCompletion will be called with // this task. That thread can then
7171-// schedule a recv, a write, etc on the file // descriptor it just received.
7272-_ = try main_rt.msgRing(thread_rt, target_task, .{});
7373+// Worker.onCompletion will be called with this task. That thread can then
7474+// schedule a recv, a write, etc on the file descriptor it just received. Or do
7575+// arbitrary work
7676+_ = try main_rt.msgRing(&thread.ring, target_task, .{});
7377```
74787579### Multiple Rings on the same thread
···8488 .cb = onCompletion,
8589 .msg = @intFromEnum(Msg.rt1_has_completions)}
8690);
9191+8792```
88938994## Example
+14-12
src/Uring.zig
···346346 .userfd, .userptr => unreachable,
347347 };
348348349349- try task.callback(rt, task.*);
350350-351351- if (cqe.flags & msg_ring_received_cqe != 0) {
352352- // This message was received from another ring. We don't decrement inflight for this.
353353- // But we do need to set the task as free because we will add it to our free list
354354- rt.free_q.push(task);
355355- } else if (cqe.flags & linux.IORING_CQE_F_MORE == 0) {
356356- // If the cqe doesn't have IORING_CQE_F_MORE set, then this task is complete and free to
357357- // be rescheduled
358358- task.state = .complete;
359359- self.in_flight.remove(task);
360360- rt.free_q.push(task);
349349+ defer {
350350+ if (cqe.flags & msg_ring_received_cqe != 0) {
351351+ // This message was received from another ring. We don't decrement inflight for this.
352352+ // But we do need to set the task as free because we will add it to our free list
353353+ rt.free_q.push(task);
354354+ } else if (cqe.flags & linux.IORING_CQE_F_MORE == 0) {
355355+ // If the cqe doesn't have IORING_CQE_F_MORE set, then this task is complete and free to
356356+ // be rescheduled
357357+ task.state = .complete;
358358+ self.in_flight.remove(task);
359359+ rt.free_q.push(task);
360360+ }
361361 }
362362+363363+ try task.callback(rt, task.*);
362364 }
363365}
364366
+119
src/main.zig
···394394 self.submission_q.push(task);
395395 return task;
396396 }
397397+398398+ /// Spawns a thread with a Ring instance. The thread will be idle and waiting to receive work
399399+ /// via msgRing when this function returns. Call kill on the returned thread to signal it to
400400+ /// shutdown.
401401+ pub fn spawnThread(self: *Ring, entries: u16) !*Thread {
402402+ const thread = try self.gpa.create(Thread);
403403+ errdefer self.gpa.destroy(thread);
404404+405405+ var wg: std.Thread.WaitGroup = .{};
406406+ wg.start();
407407+ thread.thread = try std.Thread.spawn(.{}, Thread.run, .{ thread, self, &wg, entries });
408408+ wg.wait();
409409+410410+ return thread;
411411+ }
412412+};
413413+414414+pub const Thread = struct {
415415+ thread: std.Thread,
416416+ ring: io.Ring = undefined,
417417+418418+ pub const Msg = enum {
419419+ kill,
420420+ };
421421+422422+ pub fn run(self: *Thread, parent: *io.Ring, wg: *std.Thread.WaitGroup, entries: u16) !void {
423423+ self.ring = try parent.initChild(entries);
424424+ wg.finish();
425425+426426+ defer self.ring.deinit();
427427+428428+ // Run forever, because we may not start with a task. Inter-thread messaging means we could
429429+ // receive work at any time
430430+ self.ring.run(.forever) catch |err| {
431431+ switch (err) {
432432+ error.ThreadKilled => return,
433433+ else => return err,
434434+ }
435435+ };
436436+ }
437437+438438+ /// Kill sends a message to the thread telling it to exit. Callers of this thread can safely
439439+ /// join and deinit the Thread in the Context callback
440440+ pub fn kill(self: *Thread, rt: *io.Ring, ctx: Context) Allocator.Error!*io.Task {
441441+ const target_task = try rt.getTask();
442442+ target_task.* = .{
443443+ .userdata = self,
444444+ .msg = @intFromEnum(Thread.Msg.kill),
445445+ .callback = Thread.onCompletion,
446446+ .result = .noop,
447447+ };
448448+449449+ return rt.msgRing(&self.ring, target_task, ctx);
450450+ }
451451+452452+ pub fn join(self: Thread) void {
453453+ self.thread.join();
454454+ }
455455+456456+ fn onCompletion(_: *io.Ring, task: Task) anyerror!void {
457457+ switch (task.msgToEnum(Thread.Msg)) {
458458+ .kill => return error.ThreadKilled,
459459+ }
460460+ }
397461};
398462399463pub const Op = enum {
···715779 try std.testing.expect(foo.rt1);
716780 try std.testing.expect(foo.rt2);
717781}
782782+783783+test "runtime: spawnThread" {
784784+ const gpa = std.testing.allocator;
785785+ var rt = try io.Ring.init(gpa, 16);
786786+ defer rt.deinit();
787787+788788+ const thread = try rt.spawnThread(4);
789789+790790+ const Foo2 = struct {
791791+ kill: bool = false,
792792+ did_work: bool = false,
793793+794794+ gpa: Allocator,
795795+ thread: *Thread,
796796+797797+ const Msg = enum { kill, work };
798798+799799+ fn callback(_: *io.Ring, task: io.Task) anyerror!void {
800800+ const self = task.userdataCast(@This());
801801+ const msg = task.msgToEnum(Msg);
802802+ switch (msg) {
803803+ .kill => {
804804+ self.kill = true;
805805+ self.thread.join();
806806+ self.gpa.destroy(self.thread);
807807+ },
808808+ .work => self.did_work = true,
809809+ }
810810+ }
811811+ };
812812+813813+ var foo: Foo2 = .{ .thread = thread, .gpa = gpa };
814814+815815+ // Send work to the thread
816816+ const target_task = try rt.getTask();
817817+ target_task.* = .{
818818+ .userdata = &foo,
819819+ .callback = Foo2.callback,
820820+ .msg = @intFromEnum(Foo2.Msg.work),
821821+ .result = .{ .usermsg = 0 },
822822+ };
823823+824824+ _ = try rt.msgRing(&thread.ring, target_task, .{});
825825+826826+ try rt.run(.until_done);
827827+ _ = try thread.kill(&rt, .{
828828+ .ptr = &foo,
829829+ .cb = Foo2.callback,
830830+ .msg = @intFromEnum(Foo2.Msg.kill),
831831+ });
832832+ try rt.run(.until_done);
833833+834834+ try std.testing.expect(foo.did_work);
835835+ try std.testing.expect(foo.kill);
836836+}