Skip to content

Commit 9bdcd2a

Browse files
committedJul 11, 2018
add std.event.Future
This is like a promise, but it's for multiple getters, and uses an event loop.
1 parent 5954c94 commit 9bdcd2a

File tree

7 files changed

+132
-11
lines changed

7 files changed

+132
-11
lines changed
 

‎CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -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
Original file line numberDiff line numberDiff line change
@@ -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
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,15 @@ 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
}
2619

2720
const file1 = "1.zig";
2821
const allocator = std.heap.c_allocator;
2922

30-
const TestContext = struct {
23+
pub const TestContext = struct {
3124
loop: std.event.Loop,
3225
zig_lib_dir: []u8,
3326
zig_cache_dir: []u8,

‎std/event.zig

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pub const Lock = @import("event/lock.zig").Lock;
44
pub const tcp = @import("event/tcp.zig");
55
pub const Channel = @import("event/channel.zig").Channel;
66
pub const Group = @import("event/group.zig").Group;
7+
pub const Future = @import("event/future.zig").Group;
78

89
test "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

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
const std = @import("../index.zig");
2+
const assert = std.debug.assert;
3+
const builtin = @import("builtin");
4+
const AtomicRmwOp = builtin.AtomicRmwOp;
5+
const AtomicOrder = builtin.AtomicOrder;
6+
const Lock = std.event.Lock;
7+
const 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.
13+
pub 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+
53+
test "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+
69+
async 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+
81+
async fn waitOnFuture(future: *Future(i32)) i32 {
82+
return await (async future.get() catch @panic("memory"));
83+
}
84+
85+
async fn resolveFuture(future: *Future(i32)) void {
86+
future.put(6);
87+
}

‎std/event/lock.zig

+10-1
Original file line numberDiff line numberDiff line change
@@ -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

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const TestContext = @import("../../src-self-hosted/test.zig").TestContext;
2+
3+
pub 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+
}

0 commit comments

Comments
 (0)
Please sign in to comment.