1const std = @import("../std.zig"); 2const builtin = @import("builtin"); 3const assert = std.debug.assert; 4const testing = std.testing; 5const Lock = std.event.Lock; 6 7/// This is a value that starts out unavailable, until resolve() is called 8/// While it is unavailable, functions suspend when they try to get() it, 9/// and then are resumed when resolve() is called. 10/// At this point the value remains forever available, and another resolve() is not allowed. 11pub fn Future(comptime T: type) type { 12 return struct { 13 lock: Lock, 14 data: T, 15 available: Available, 16 17 const Available = enum(u8) { 18 NotStarted, 19 Started, 20 Finished, 21 }; 22 23 const Self = @This(); 24 const Queue = std.atomic.Queue(anyframe); 25 26 pub fn init() Self { 27 return Self{ 28 .lock = Lock.initLocked(), 29 .available = .NotStarted, 30 .data = undefined, 31 }; 32 } 33 34 /// Obtain the value. If it's not available, wait until it becomes 35 /// available. 36 /// Thread-safe. 37 pub fn get(self: *Self) callconv(.Async) *T { 38 if (@atomicLoad(Available, &self.available, .SeqCst) == .Finished) { 39 return &self.data; 40 } 41 const held = self.lock.acquire(); 42 held.release(); 43 44 return &self.data; 45 } 46 47 /// Gets the data without waiting for it. If it's available, a pointer is 48 /// returned. Otherwise, null is returned. 49 pub fn getOrNull(self: *Self) ?*T { 50 if (@atomicLoad(Available, &self.available, .SeqCst) == .Finished) { 51 return &self.data; 52 } else { 53 return null; 54 } 55 } 56 57 /// If someone else has started working on the data, wait for them to complete 58 /// and return a pointer to the data. Otherwise, return null, and the caller 59 /// should start working on the data. 60 /// It's not required to call start() before resolve() but it can be useful since 61 /// this method is thread-safe. 62 pub fn start(self: *Self) callconv(.Async) ?*T { 63 const state = @cmpxchgStrong(Available, &self.available, .NotStarted, .Started, .SeqCst, .SeqCst) orelse return null; 64 switch (state) { 65 .Started => { 66 const held = self.lock.acquire(); 67 held.release(); 68 return &self.data; 69 }, 70 .Finished => return &self.data, 71 else => unreachable, 72 } 73 } 74 75 /// Make the data become available. May be called only once. 76 /// Before calling this, modify the `data` property. 77 pub fn resolve(self: *Self) void { 78 const prev = @atomicRmw(Available, &self.available, .Xchg, .Finished, .SeqCst); 79 assert(prev != .Finished); // resolve() called twice 80 Lock.Held.release(Lock.Held{ .lock = &self.lock }); 81 } 82 }; 83} 84 85test "std.event.Future" { 86 // https://github.com/ziglang/zig/issues/1908 87 if (builtin.single_threaded) return error.SkipZigTest; 88 // https://github.com/ziglang/zig/issues/3251 89 if (builtin.os.tag == .freebsd) return error.SkipZigTest; 90 // TODO provide a way to run tests in evented I/O mode 91 if (!std.io.is_async) return error.SkipZigTest; 92 93 testFuture(); 94} 95 96fn testFuture() void { 97 var future = Future(i32).init(); 98 99 var a = async waitOnFuture(&future); 100 var b = async waitOnFuture(&future); 101 resolveFuture(&future); 102 103 const result = (await a) + (await b); 104 105 try testing.expect(result == 12); 106} 107 108fn waitOnFuture(future: *Future(i32)) i32 { 109 return future.get().*; 110} 111 112fn resolveFuture(future: *Future(i32)) void { 113 future.data = 6; 114 future.resolve(); 115} 116