authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2018-07-11 15:58:48-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2018-07-11 16:00:06-04:00
log9bdcd2a495d4189d6536d43f1294dffb38daa9a5
tree95072bfc6959b2b7efd26381492934855b8c0fbf
parent5954c94d2079c9b378ae14bb4c9c24aed719beec

add std.event.Future

This is like a promise, but it's for multiple getters, and uses an event loop.

7 files changed, 132 insertions(+), 11 deletions(-)

CMakeLists.txt+1
......@@ -460,6 +460,7 @@ set(ZIG_STD_FILES
460460 "empty.zig"
461461 "event.zig"
462462 "event/channel.zig"
463 "event/future.zig"
463464 "event/group.zig"
464465 "event/lock.zig"
465466 "event/locked.zig"
src-self-hosted/module.zig+18-1
......@@ -381,6 +381,7 @@ pub const Module = struct {
381381
382382 if (is_export) {
383383 try self.build_group.call(verifyUniqueSymbol, self, parsed_file, decl);
384 try self.build_group.call(generateDecl, self, parsed_file, decl);
384385 }
385386 }
386387
......@@ -429,6 +430,22 @@ pub const Module = struct {
429430 }
430431 }
431432
433 /// This declaration has been blessed as going into the final code generation.
434 async fn generateDecl(self: *Module, parsed_file: *ParsedFile, decl: *Decl) void {
435 switch (decl.id) {
436 Decl.Id.Var => @panic("TODO"),
437 Decl.Id.Fn => {
438 const fn_decl = @fieldParentPtr(Decl.Fn, "base", decl);
439 return await (async self.generateDeclFn(parsed_file, fn_decl) catch unreachable);
440 },
441 Decl.Id.CompTime => @panic("TODO"),
442 }
443 }
444
445 async fn generateDeclFn(self: *Module, parsed_file: *ParsedFile, fn_decl: *Decl.Fn) void {
446 fn_decl.value = Decl.Fn.Val{ .Ok = Value.Fn{} };
447 }
448
432449 pub fn link(self: *Module, out_file: ?[]const u8) !void {
433450 warn("TODO link");
434451 return error.Todo;
......@@ -589,7 +606,7 @@ pub const Decl = struct {
589606 // TODO https://github.com/ziglang/zig/issues/683 and then make this anonymous
590607 pub const Val = union {
591608 Unresolved: void,
592 Ok: *Value.Fn,
609 Ok: Value.Fn,
593610 };
594611
595612 pub fn externLibName(self: Fn, tree: *ast.Tree) ?[]const u8 {
src-self-hosted/test.zig+2-9
......@@ -12,14 +12,7 @@ test "compile errors" {
1212 try ctx.init();
1313 defer ctx.deinit();
1414
15 try ctx.testCompileError(
16 \\export fn entry() void {}
17 \\export fn entry() void {}
18 , file1, 2, 8, "exported symbol collision: 'entry'");
19
20 try ctx.testCompileError(
21 \\fn() void {}
22 , file1, 1, 1, "missing function name");
15 try @import("../test/stage2/compile_errors.zig").addCases(&ctx);
2316
2417 try ctx.run();
2518}
......@@ -27,7 +20,7 @@ test "compile errors" {
2720const file1 = "1.zig";
2821const allocator = std.heap.c_allocator;
2922
30const TestContext = struct {
23pub const TestContext = struct {
3124 loop: std.event.Loop,
3225 zig_lib_dir: []u8,
3326 zig_cache_dir: []u8,
std/event.zig+2
......@@ -4,6 +4,7 @@ pub const Lock = @import("event/lock.zig").Lock;
44pub const tcp = @import("event/tcp.zig");
55pub const Channel = @import("event/channel.zig").Channel;
66pub const Group = @import("event/group.zig").Group;
7pub const Future = @import("event/future.zig").Group;
78
89test "import event tests" {
910 _ = @import("event/locked.zig");
......@@ -12,4 +13,5 @@ test "import event tests" {
1213 _ = @import("event/tcp.zig");
1314 _ = @import("event/channel.zig");
1415 _ = @import("event/group.zig");
16 _ = @import("event/future.zig");
1517}
std/event/future.zig created+87
......@@ -0,0 +1,87 @@
1const std = @import("../index.zig");
2const assert = std.debug.assert;
3const builtin = @import("builtin");
4const AtomicRmwOp = builtin.AtomicRmwOp;
5const AtomicOrder = builtin.AtomicOrder;
6const Lock = std.event.Lock;
7const Loop = std.event.Loop;
8
9/// This is a value that starts out unavailable, until a value is put().
10/// While it is unavailable, coroutines suspend when they try to get() it,
11/// and then are resumed when the value is put().
12/// At this point the value remains forever available, and another put() is not allowed.
13pub fn Future(comptime T: type) type {
14 return struct {
15 lock: Lock,
16 data: T,
17 available: u8, // TODO make this a bool
18
19 const Self = this;
20 const Queue = std.atomic.QueueMpsc(promise);
21
22 pub fn init(loop: *Loop) Self {
23 return Self{
24 .lock = Lock.initLocked(loop),
25 .available = 0,
26 .data = undefined,
27 };
28 }
29
30 /// Obtain the value. If it's not available, wait until it becomes
31 /// available.
32 /// Thread-safe.
33 pub async fn get(self: *Self) T {
34 if (@atomicLoad(u8, &self.available, AtomicOrder.SeqCst) == 1) {
35 return self.data;
36 }
37 const held = await (async self.lock.acquire() catch unreachable);
38 defer held.release();
39
40 return self.data;
41 }
42
43 /// Make the data become available. May be called only once.
44 pub fn put(self: *Self, value: T) void {
45 self.data = value;
46 const prev = @atomicRmw(u8, &self.available, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst);
47 assert(prev == 0); // put() called twice
48 Lock.Held.release(Lock.Held{ .lock = &self.lock });
49 }
50 };
51}
52
53test "std.event.Future" {
54 var da = std.heap.DirectAllocator.init();
55 defer da.deinit();
56
57 const allocator = &da.allocator;
58
59 var loop: Loop = undefined;
60 try loop.initMultiThreaded(allocator);
61 defer loop.deinit();
62
63 const handle = try async<allocator> testFuture(&loop);
64 defer cancel handle;
65
66 loop.run();
67}
68
69async fn testFuture(loop: *Loop) void {
70 var future = Future(i32).init(loop);
71
72 const a = async waitOnFuture(&future) catch @panic("memory");
73 const b = async waitOnFuture(&future) catch @panic("memory");
74 const c = async resolveFuture(&future) catch @panic("memory");
75
76 const result = (await a) + (await b);
77 cancel c;
78 assert(result == 12);
79}
80
81async fn waitOnFuture(future: *Future(i32)) i32 {
82 return await (async future.get() catch @panic("memory"));
83}
84
85async fn resolveFuture(future: *Future(i32)) void {
86 future.put(6);
87}
std/event/lock.zig+10-1
......@@ -73,6 +73,15 @@ pub const Lock = struct {
7373 };
7474 }
7575
76 pub fn initLocked(loop: *Loop) Lock {
77 return Lock{
78 .loop = loop,
79 .shared_bit = 1,
80 .queue = Queue.init(),
81 .queue_empty_bit = 1,
82 };
83 }
84
7685 /// Must be called when not locked. Not thread safe.
7786 /// All calls to acquire() and release() must complete before calling deinit().
7887 pub fn deinit(self: *Lock) void {
......@@ -81,7 +90,7 @@ pub const Lock = struct {
8190 }
8291
8392 pub async fn acquire(self: *Lock) Held {
84 s: suspend |handle| {
93 suspend |handle| {
8594 // TODO explicitly put this memory in the coroutine frame #1194
8695 var my_tick_node = Loop.NextTickNode{
8796 .data = handle,
test/stage2/compile_errors.zig created+12
......@@ -0,0 +1,12 @@
1const TestContext = @import("../../src-self-hosted/test.zig").TestContext;
2
3pub fn addCases(ctx: *TestContext) !void {
4 try ctx.testCompileError(
5 \\export fn entry() void {}
6 \\export fn entry() void {}
7 , "1.zig", 2, 8, "exported symbol collision: 'entry'");
8
9 try ctx.testCompileError(
10 \\fn() void {}
11 , "1.zig", 1, 1, "missing function name");
12}