| author | |
| committer | |
| log | 4d8d513e16d308131846d98267bc844bf702e9ce |
| tree | 44bf7273c880681aa6193d45a8830510426eff24 |
| parent | af8c6ccb4bcae7baf30f3b1032a98b82f39d9c26 |
| signature |
17 files changed, 240 insertions(+), 381 deletions(-)
BRANCH_TODO+5-1| ... | ... | @@ -1,10 +1,13 @@ |
| 1 | * for loops need to spill the index. other payload captures probably also need to spill | |
| 2 | * compile error (instead of crashing) for trying to get @Frame of generic function | |
| 3 | * compile error (instead of crashing) for trying to async call and passing @Frame of wrong function | |
| 4 | * `const result = (await a) + (await b);` this causes "Instruction does not dominate all uses" - need spill | |
| 1 | 5 | * compile error for error: expected anyframe->T, found 'anyframe' |
| 2 | 6 | * compile error for error: expected anyframe->T, found 'i32' |
| 3 | 7 | * await of a non async function |
| 4 | 8 | * async call on a non async function |
| 5 | 9 | * a test where an async function destroys its own frame in a defer |
| 6 | 10 | * implicit cast of normal function to async function should be allowed when it is inferred to be async |
| 7 | * revive std.event.Loop | |
| 8 | 11 | * @typeInfo for @Frame(func) |
| 9 | 12 | * peer type resolution of *@Frame(func) and anyframe |
| 10 | 13 | * peer type resolution of *@Frame(func) and anyframe->T when the return type matches |
| ... | ... | @@ -36,3 +39,4 @@ |
| 36 | 39 | - it can be assumed that these are always available: the awaiter ptr, return ptr if applicable, |
| 37 | 40 | error return trace ptr if applicable. |
| 38 | 41 | - it can be assumed that it is never cancelled |
| 42 | * fix the debug info for variables of async functions |
doc/docgen.zig+1-1| ... | ... | @@ -770,7 +770,7 @@ fn tokenizeAndPrintRaw(docgen_tokenizer: *Tokenizer, out: var, source_token: Tok |
| 770 | 770 | .Keyword_or, |
| 771 | 771 | .Keyword_orelse, |
| 772 | 772 | .Keyword_packed, |
| 773 | .Keyword_promise, | |
| 773 | .Keyword_anyframe, | |
| 774 | 774 | .Keyword_pub, |
| 775 | 775 | .Keyword_resume, |
| 776 | 776 | .Keyword_return, |
doc/langref.html.in+26-55| ... | ... | @@ -6024,13 +6024,14 @@ const assert = std.debug.assert; |
| 6024 | 6024 | |
| 6025 | 6025 | var x: i32 = 1; |
| 6026 | 6026 | |
| 6027 | test "create a coroutine and cancel it" { | |
| 6028 | const p = try async<std.debug.global_allocator> simpleAsyncFn(); | |
| 6029 | comptime assert(@typeOf(p) == promise->void); | |
| 6030 | cancel p; | |
| 6027 | test "call an async function" { | |
| 6028 | var frame = async simpleAsyncFn(); | |
| 6029 | comptime assert(@typeOf(frame) == @Frame(simpleAsyncFn)); | |
| 6031 | 6030 | assert(x == 2); |
| 6032 | 6031 | } |
| 6033 | async<*std.mem.Allocator> fn simpleAsyncFn() void { | |
| 6032 | fn simpleAsyncFn() void { | |
| 6033 | x += 1; | |
| 6034 | suspend; | |
| 6034 | 6035 | x += 1; |
| 6035 | 6036 | } |
| 6036 | 6037 | {#code_end#} |
| ... | ... | @@ -6041,60 +6042,33 @@ async<*std.mem.Allocator> fn simpleAsyncFn() void { |
| 6041 | 6042 | return to the caller or resumer. The following code demonstrates where control flow |
| 6042 | 6043 | goes: |
| 6043 | 6044 | </p> |
| 6044 | {#code_begin|test#} | |
| 6045 | const std = @import("std"); | |
| 6046 | const assert = std.debug.assert; | |
| 6047 | ||
| 6048 | test "coroutine suspend, resume, cancel" { | |
| 6049 | seq('a'); | |
| 6050 | const p = try async<std.debug.global_allocator> testAsyncSeq(); | |
| 6051 | seq('c'); | |
| 6052 | resume p; | |
| 6053 | seq('f'); | |
| 6054 | cancel p; | |
| 6055 | seq('g'); | |
| 6056 | ||
| 6057 | assert(std.mem.eql(u8, points, "abcdefg")); | |
| 6058 | } | |
| 6059 | async fn testAsyncSeq() void { | |
| 6060 | defer seq('e'); | |
| 6061 | ||
| 6062 | seq('b'); | |
| 6063 | suspend; | |
| 6064 | seq('d'); | |
| 6065 | } | |
| 6066 | var points = [_]u8{0} ** "abcdefg".len; | |
| 6067 | var index: usize = 0; | |
| 6068 | ||
| 6069 | fn seq(c: u8) void { | |
| 6070 | points[index] = c; | |
| 6071 | index += 1; | |
| 6072 | } | |
| 6073 | {#code_end#} | |
| 6045 | <p> | |
| 6046 | TODO another test example here | |
| 6047 | </p> | |
| 6074 | 6048 | <p> |
| 6075 | 6049 | When an async function suspends itself, it must be sure that it will be |
| 6076 | 6050 | resumed or canceled somehow, for example by registering its promise handle |
| 6077 | 6051 | in an event loop. Use a suspend capture block to gain access to the |
| 6078 | promise: | |
| 6052 | promise (TODO this is outdated): | |
| 6079 | 6053 | </p> |
| 6080 | 6054 | {#code_begin|test#} |
| 6081 | 6055 | const std = @import("std"); |
| 6082 | 6056 | const assert = std.debug.assert; |
| 6083 | 6057 | |
| 6058 | var the_frame: anyframe = undefined; | |
| 6059 | var result = false; | |
| 6060 | ||
| 6084 | 6061 | test "coroutine suspend with block" { |
| 6085 | const p = try async<std.debug.global_allocator> testSuspendBlock(); | |
| 6062 | _ = async testSuspendBlock(); | |
| 6086 | 6063 | std.debug.assert(!result); |
| 6087 | resume a_promise; | |
| 6064 | resume the_frame; | |
| 6088 | 6065 | std.debug.assert(result); |
| 6089 | cancel p; | |
| 6090 | 6066 | } |
| 6091 | 6067 | |
| 6092 | var a_promise: promise = undefined; | |
| 6093 | var result = false; | |
| 6094 | async fn testSuspendBlock() void { | |
| 6068 | fn testSuspendBlock() void { | |
| 6095 | 6069 | suspend { |
| 6096 | comptime assert(@typeOf(@handle()) == promise->void); | |
| 6097 | a_promise = @handle(); | |
| 6070 | comptime assert(@typeOf(@frame()) == *@Frame(testSuspendBlock)); | |
| 6071 | the_frame = @frame(); | |
| 6098 | 6072 | } |
| 6099 | 6073 | result = true; |
| 6100 | 6074 | } |
| ... | ... | @@ -6124,16 +6098,13 @@ const std = @import("std"); |
| 6124 | 6098 | const assert = std.debug.assert; |
| 6125 | 6099 | |
| 6126 | 6100 | test "resume from suspend" { |
| 6127 | var buf: [500]u8 = undefined; | |
| 6128 | var a = &std.heap.FixedBufferAllocator.init(buf[0..]).allocator; | |
| 6129 | 6101 | var my_result: i32 = 1; |
| 6130 | const p = try async<a> testResumeFromSuspend(&my_result); | |
| 6131 | cancel p; | |
| 6102 | _ = async testResumeFromSuspend(&my_result); | |
| 6132 | 6103 | std.debug.assert(my_result == 2); |
| 6133 | 6104 | } |
| 6134 | 6105 | async fn testResumeFromSuspend(my_result: *i32) void { |
| 6135 | 6106 | suspend { |
| 6136 | resume @handle(); | |
| 6107 | resume @frame(); | |
| 6137 | 6108 | } |
| 6138 | 6109 | my_result.* += 1; |
| 6139 | 6110 | suspend; |
| ... | ... | @@ -6172,30 +6143,30 @@ async fn testResumeFromSuspend(my_result: *i32) void { |
| 6172 | 6143 | const std = @import("std"); |
| 6173 | 6144 | const assert = std.debug.assert; |
| 6174 | 6145 | |
| 6175 | var a_promise: promise = undefined; | |
| 6146 | var the_frame: anyframe = undefined; | |
| 6176 | 6147 | var final_result: i32 = 0; |
| 6177 | 6148 | |
| 6178 | 6149 | test "coroutine await" { |
| 6179 | 6150 | seq('a'); |
| 6180 | const p = async<std.debug.global_allocator> amain() catch unreachable; | |
| 6151 | _ = async amain(); | |
| 6181 | 6152 | seq('f'); |
| 6182 | resume a_promise; | |
| 6153 | resume the_frame; | |
| 6183 | 6154 | seq('i'); |
| 6184 | 6155 | assert(final_result == 1234); |
| 6185 | 6156 | assert(std.mem.eql(u8, seq_points, "abcdefghi")); |
| 6186 | 6157 | } |
| 6187 | 6158 | async fn amain() void { |
| 6188 | 6159 | seq('b'); |
| 6189 | const p = async another() catch unreachable; | |
| 6160 | var f = async another(); | |
| 6190 | 6161 | seq('e'); |
| 6191 | final_result = await p; | |
| 6162 | final_result = await f; | |
| 6192 | 6163 | seq('h'); |
| 6193 | 6164 | } |
| 6194 | 6165 | async fn another() i32 { |
| 6195 | 6166 | seq('c'); |
| 6196 | 6167 | suspend { |
| 6197 | 6168 | seq('d'); |
| 6198 | a_promise = @handle(); | |
| 6169 | the_frame = @frame(); | |
| 6199 | 6170 | } |
| 6200 | 6171 | seq('g'); |
| 6201 | 6172 | return 1234; |
src/analyze.cpp+1-1| ... | ... | @@ -5325,7 +5325,7 @@ static Error resolve_coro_frame(CodeGen *g, ZigType *frame_type) { |
| 5325 | 5325 | if (*instruction->name_hint == 0) { |
| 5326 | 5326 | name = buf_ptr(buf_sprintf("@local%" ZIG_PRI_usize, alloca_i)); |
| 5327 | 5327 | } else { |
| 5328 | name = instruction->name_hint; | |
| 5328 | name = buf_ptr(buf_sprintf("%s.%" ZIG_PRI_usize, instruction->name_hint, alloca_i)); | |
| 5329 | 5329 | } |
| 5330 | 5330 | field_names.append(name); |
| 5331 | 5331 | field_types.append(child_type); |
src/codegen.cpp+27-22| ... | ... | @@ -535,24 +535,24 @@ static LLVMValueRef make_fn_llvm_value(CodeGen *g, ZigFn *fn) { |
| 535 | 535 | // use the ABI alignment, which is fine. |
| 536 | 536 | } |
| 537 | 537 | |
| 538 | unsigned init_gen_i = 0; | |
| 539 | if (!type_has_bits(return_type)) { | |
| 540 | // nothing to do | |
| 541 | } else if (type_is_nonnull_ptr(return_type)) { | |
| 542 | addLLVMAttr(llvm_fn, 0, "nonnull"); | |
| 543 | } else if (!is_async && want_first_arg_sret(g, &fn_type->data.fn.fn_type_id)) { | |
| 544 | // Sret pointers must not be address 0 | |
| 545 | addLLVMArgAttr(llvm_fn, 0, "nonnull"); | |
| 546 | addLLVMArgAttr(llvm_fn, 0, "sret"); | |
| 547 | if (cc_want_sret_attr(cc)) { | |
| 548 | addLLVMArgAttr(llvm_fn, 0, "noalias"); | |
| 549 | } | |
| 550 | init_gen_i = 1; | |
| 551 | } | |
| 552 | ||
| 553 | 538 | if (is_async) { |
| 554 | 539 | addLLVMArgAttr(llvm_fn, 0, "nonnull"); |
| 555 | 540 | } else { |
| 541 | unsigned init_gen_i = 0; | |
| 542 | if (!type_has_bits(return_type)) { | |
| 543 | // nothing to do | |
| 544 | } else if (type_is_nonnull_ptr(return_type)) { | |
| 545 | addLLVMAttr(llvm_fn, 0, "nonnull"); | |
| 546 | } else if (want_first_arg_sret(g, &fn_type->data.fn.fn_type_id)) { | |
| 547 | // Sret pointers must not be address 0 | |
| 548 | addLLVMArgAttr(llvm_fn, 0, "nonnull"); | |
| 549 | addLLVMArgAttr(llvm_fn, 0, "sret"); | |
| 550 | if (cc_want_sret_attr(cc)) { | |
| 551 | addLLVMArgAttr(llvm_fn, 0, "noalias"); | |
| 552 | } | |
| 553 | init_gen_i = 1; | |
| 554 | } | |
| 555 | ||
| 556 | 556 | // set parameter attributes |
| 557 | 557 | FnWalk fn_walk = {}; |
| 558 | 558 | fn_walk.id = FnWalkIdAttrs; |
| ... | ... | @@ -911,7 +911,7 @@ static Buf *panic_msg_buf(PanicMsgId msg_id) { |
| 911 | 911 | case PanicMsgIdBadResume: |
| 912 | 912 | return buf_create_from_str("resumed an async function which already returned"); |
| 913 | 913 | case PanicMsgIdBadAwait: |
| 914 | return buf_create_from_str("async function awaited/canceled twice"); | |
| 914 | return buf_create_from_str("async function awaited twice"); | |
| 915 | 915 | case PanicMsgIdBadReturn: |
| 916 | 916 | return buf_create_from_str("async function returned twice"); |
| 917 | 917 | case PanicMsgIdResumedAnAwaitingFn: |
| ... | ... | @@ -2350,6 +2350,10 @@ static LLVMValueRef ir_render_return_begin(CodeGen *g, IrExecutable *executable, |
| 2350 | 2350 | return get_handle_value(g, g->cur_ret_ptr, operand_type, get_pointer_to_type(g, operand_type, true)); |
| 2351 | 2351 | } |
| 2352 | 2352 | |
| 2353 | static void set_tail_call_if_appropriate(CodeGen *g, LLVMValueRef call_inst) { | |
| 2354 | LLVMSetTailCall(call_inst, true); | |
| 2355 | } | |
| 2356 | ||
| 2353 | 2357 | static LLVMValueRef ir_render_return(CodeGen *g, IrExecutable *executable, IrInstructionReturn *instruction) { |
| 2354 | 2358 | if (fn_is_async(g->cur_fn)) { |
| 2355 | 2359 | LLVMTypeRef usize_type_ref = g->builtin_types.entry_usize->llvm_type; |
| ... | ... | @@ -2394,7 +2398,7 @@ static LLVMValueRef ir_render_return(CodeGen *g, IrExecutable *executable, IrIns |
| 2394 | 2398 | LLVMValueRef their_frame_ptr = LLVMBuildIntToPtr(g->builder, masked_prev_val, |
| 2395 | 2399 | get_llvm_type(g, any_frame_type), ""); |
| 2396 | 2400 | LLVMValueRef call_inst = gen_resume(g, nullptr, their_frame_ptr, ResumeIdReturn, nullptr); |
| 2397 | LLVMSetTailCall(call_inst, true); | |
| 2401 | set_tail_call_if_appropriate(g, call_inst); | |
| 2398 | 2402 | LLVMBuildRetVoid(g->builder); |
| 2399 | 2403 | |
| 2400 | 2404 | g->cur_is_after_return = false; |
| ... | ... | @@ -4009,7 +4013,7 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutable *executable, IrInstr |
| 4009 | 4013 | LLVMBasicBlockRef call_bb = gen_suspend_begin(g, "CallResume"); |
| 4010 | 4014 | |
| 4011 | 4015 | LLVMValueRef call_inst = gen_resume(g, fn_val, frame_result_loc, ResumeIdCall, nullptr); |
| 4012 | LLVMSetTailCall(call_inst, true); | |
| 4016 | set_tail_call_if_appropriate(g, call_inst); | |
| 4013 | 4017 | LLVMBuildRetVoid(g->builder); |
| 4014 | 4018 | |
| 4015 | 4019 | LLVMPositionBuilderAtEnd(g->builder, call_bb); |
| ... | ... | @@ -5520,7 +5524,7 @@ static LLVMValueRef ir_render_cancel(CodeGen *g, IrExecutable *executable, IrIns |
| 5520 | 5524 | |
| 5521 | 5525 | LLVMPositionBuilderAtEnd(g->builder, early_return_block); |
| 5522 | 5526 | LLVMValueRef call_inst = gen_resume(g, nullptr, target_frame_ptr, ResumeIdAwaitEarlyReturn, awaiter_ored_val); |
| 5523 | LLVMSetTailCall(call_inst, true); | |
| 5527 | set_tail_call_if_appropriate(g, call_inst); | |
| 5524 | 5528 | LLVMBuildRetVoid(g->builder); |
| 5525 | 5529 | |
| 5526 | 5530 | LLVMPositionBuilderAtEnd(g->builder, resume_bb); |
| ... | ... | @@ -5556,8 +5560,9 @@ static LLVMValueRef ir_render_await(CodeGen *g, IrExecutable *executable, IrInst |
| 5556 | 5560 | } |
| 5557 | 5561 | |
| 5558 | 5562 | // supply the error return trace pointer |
| 5559 | LLVMValueRef my_err_ret_trace_val = get_cur_err_ret_trace_val(g, instruction->base.scope); | |
| 5560 | if (my_err_ret_trace_val != nullptr) { | |
| 5563 | if (codegen_fn_has_err_ret_tracing_arg(g, result_type)) { | |
| 5564 | LLVMValueRef my_err_ret_trace_val = get_cur_err_ret_trace_val(g, instruction->base.scope); | |
| 5565 | assert(my_err_ret_trace_val != nullptr); | |
| 5561 | 5566 | LLVMValueRef err_ret_trace_ptr_ptr = LLVMBuildStructGEP(g->builder, target_frame_ptr, |
| 5562 | 5567 | frame_index_trace_arg(g, result_type), ""); |
| 5563 | 5568 | LLVMBuildStore(g->builder, my_err_ret_trace_val, err_ret_trace_ptr_ptr); |
| ... | ... | @@ -5588,7 +5593,7 @@ static LLVMValueRef ir_render_await(CodeGen *g, IrExecutable *executable, IrInst |
| 5588 | 5593 | // Tail resume it now, so that it can complete. |
| 5589 | 5594 | LLVMPositionBuilderAtEnd(g->builder, early_return_block); |
| 5590 | 5595 | LLVMValueRef call_inst = gen_resume(g, nullptr, target_frame_ptr, ResumeIdAwaitEarlyReturn, awaiter_init_val); |
| 5591 | LLVMSetTailCall(call_inst, true); | |
| 5596 | set_tail_call_if_appropriate(g, call_inst); | |
| 5592 | 5597 | LLVMBuildRetVoid(g->builder); |
| 5593 | 5598 | |
| 5594 | 5599 | // Rely on the target to resume us from suspension. |
src/ir.cpp+3| ... | ... | @@ -15064,6 +15064,9 @@ static IrInstruction *ir_analyze_async_call(IrAnalyze *ira, IrInstructionCallSrc |
| 15064 | 15064 | if (result_loc != nullptr && (type_is_invalid(result_loc->value.type) || instr_is_unreachable(result_loc))) { |
| 15065 | 15065 | return result_loc; |
| 15066 | 15066 | } |
| 15067 | result_loc = ir_implicit_cast(ira, result_loc, get_pointer_to_type(ira->codegen, frame_type, false)); | |
| 15068 | if (type_is_invalid(result_loc->value.type)) | |
| 15069 | return ira->codegen->invalid_instruction; | |
| 15067 | 15070 | return &ir_build_call_gen(ira, &call_instruction->base, fn_entry, fn_ref, arg_count, |
| 15068 | 15071 | casted_args, FnInlineAuto, true, nullptr, result_loc, frame_type)->base; |
| 15069 | 15072 | } |
std/event/channel.zig+6-5| ... | ... | @@ -77,18 +77,19 @@ pub fn Channel(comptime T: type) type { |
| 77 | 77 | /// must be called when all calls to put and get have suspended and no more calls occur |
| 78 | 78 | pub fn destroy(self: *SelfChannel) void { |
| 79 | 79 | while (self.getters.get()) |get_node| { |
| 80 | cancel get_node.data.tick_node.data; | |
| 80 | resume get_node.data.tick_node.data; | |
| 81 | 81 | } |
| 82 | 82 | while (self.putters.get()) |put_node| { |
| 83 | cancel put_node.data.tick_node.data; | |
| 83 | resume put_node.data.tick_node.data; | |
| 84 | 84 | } |
| 85 | 85 | self.loop.allocator.free(self.buffer_nodes); |
| 86 | 86 | self.loop.allocator.destroy(self); |
| 87 | 87 | } |
| 88 | 88 | |
| 89 | /// puts a data item in the channel. The promise completes when the value has been added to the | |
| 89 | /// puts a data item in the channel. The function returns when the value has been added to the | |
| 90 | 90 | /// buffer, or in the case of a zero size buffer, when the item has been retrieved by a getter. |
| 91 | pub async fn put(self: *SelfChannel, data: T) void { | |
| 91 | /// Or when the channel is destroyed. | |
| 92 | pub fn put(self: *SelfChannel, data: T) void { | |
| 92 | 93 | var my_tick_node = Loop.NextTickNode.init(@frame()); |
| 93 | 94 | var queue_node = std.atomic.Queue(PutNode).Node.init(PutNode{ |
| 94 | 95 | .tick_node = &my_tick_node, |
| ... | ... | @@ -114,7 +115,7 @@ pub fn Channel(comptime T: type) type { |
| 114 | 115 | } |
| 115 | 116 | } |
| 116 | 117 | |
| 117 | /// await this function to get an item from the channel. If the buffer is empty, the promise will | |
| 118 | /// await this function to get an item from the channel. If the buffer is empty, the frame will | |
| 118 | 119 | /// complete when the next item is put in the channel. |
| 119 | 120 | pub async fn get(self: *SelfChannel) T { |
| 120 | 121 | // TODO integrate this function with named return values |
std/event/fs.zig+24-78| ... | ... | @@ -76,12 +76,8 @@ pub const Request = struct { |
| 76 | 76 | |
| 77 | 77 | pub const PWriteVError = error{OutOfMemory} || File.WriteError; |
| 78 | 78 | |
| 79 | /// data - just the inner references - must live until pwritev promise completes. | |
| 79 | /// data - just the inner references - must live until pwritev frame completes. | |
| 80 | 80 | pub async fn pwritev(loop: *Loop, fd: fd_t, data: []const []const u8, offset: usize) PWriteVError!void { |
| 81 | // workaround for https://github.com/ziglang/zig/issues/1194 | |
| 82 | suspend { | |
| 83 | resume @handle(); | |
| 84 | } | |
| 85 | 81 | switch (builtin.os) { |
| 86 | 82 | .macosx, |
| 87 | 83 | .linux, |
| ... | ... | @@ -109,7 +105,7 @@ pub async fn pwritev(loop: *Loop, fd: fd_t, data: []const []const u8, offset: us |
| 109 | 105 | } |
| 110 | 106 | } |
| 111 | 107 | |
| 112 | /// data must outlive the returned promise | |
| 108 | /// data must outlive the returned frame | |
| 113 | 109 | pub async fn pwritevWindows(loop: *Loop, fd: fd_t, data: []const []const u8, offset: usize) os.WindowsWriteError!void { |
| 114 | 110 | if (data.len == 0) return; |
| 115 | 111 | if (data.len == 1) return await (async pwriteWindows(loop, fd, data[0], offset) catch unreachable); |
| ... | ... | @@ -123,15 +119,10 @@ pub async fn pwritevWindows(loop: *Loop, fd: fd_t, data: []const []const u8, off |
| 123 | 119 | } |
| 124 | 120 | |
| 125 | 121 | pub async fn pwriteWindows(loop: *Loop, fd: fd_t, data: []const u8, offset: u64) os.WindowsWriteError!void { |
| 126 | // workaround for https://github.com/ziglang/zig/issues/1194 | |
| 127 | suspend { | |
| 128 | resume @handle(); | |
| 129 | } | |
| 130 | ||
| 131 | 122 | var resume_node = Loop.ResumeNode.Basic{ |
| 132 | 123 | .base = Loop.ResumeNode{ |
| 133 | 124 | .id = Loop.ResumeNode.Id.Basic, |
| 134 | .handle = @handle(), | |
| 125 | .handle = @frame(), | |
| 135 | 126 | .overlapped = windows.OVERLAPPED{ |
| 136 | 127 | .Internal = 0, |
| 137 | 128 | .InternalHigh = 0, |
| ... | ... | @@ -166,18 +157,13 @@ pub async fn pwriteWindows(loop: *Loop, fd: fd_t, data: []const u8, offset: u64) |
| 166 | 157 | } |
| 167 | 158 | } |
| 168 | 159 | |
| 169 | /// iovecs must live until pwritev promise completes. | |
| 160 | /// iovecs must live until pwritev frame completes. | |
| 170 | 161 | pub async fn pwritevPosix( |
| 171 | 162 | loop: *Loop, |
| 172 | 163 | fd: fd_t, |
| 173 | 164 | iovecs: []const os.iovec_const, |
| 174 | 165 | offset: usize, |
| 175 | 166 | ) os.WriteError!void { |
| 176 | // workaround for https://github.com/ziglang/zig/issues/1194 | |
| 177 | suspend { | |
| 178 | resume @handle(); | |
| 179 | } | |
| 180 | ||
| 181 | 167 | var req_node = RequestNode{ |
| 182 | 168 | .prev = null, |
| 183 | 169 | .next = null, |
| ... | ... | @@ -194,7 +180,7 @@ pub async fn pwritevPosix( |
| 194 | 180 | .TickNode = Loop.NextTickNode{ |
| 195 | 181 | .prev = null, |
| 196 | 182 | .next = null, |
| 197 | .data = @handle(), | |
| 183 | .data = @frame(), | |
| 198 | 184 | }, |
| 199 | 185 | }, |
| 200 | 186 | }, |
| ... | ... | @@ -211,13 +197,8 @@ pub async fn pwritevPosix( |
| 211 | 197 | |
| 212 | 198 | pub const PReadVError = error{OutOfMemory} || File.ReadError; |
| 213 | 199 | |
| 214 | /// data - just the inner references - must live until preadv promise completes. | |
| 200 | /// data - just the inner references - must live until preadv frame completes. | |
| 215 | 201 | pub async fn preadv(loop: *Loop, fd: fd_t, data: []const []u8, offset: usize) PReadVError!usize { |
| 216 | // workaround for https://github.com/ziglang/zig/issues/1194 | |
| 217 | suspend { | |
| 218 | resume @handle(); | |
| 219 | } | |
| 220 | ||
| 221 | 202 | assert(data.len != 0); |
| 222 | 203 | switch (builtin.os) { |
| 223 | 204 | .macosx, |
| ... | ... | @@ -246,7 +227,7 @@ pub async fn preadv(loop: *Loop, fd: fd_t, data: []const []u8, offset: usize) PR |
| 246 | 227 | } |
| 247 | 228 | } |
| 248 | 229 | |
| 249 | /// data must outlive the returned promise | |
| 230 | /// data must outlive the returned frame | |
| 250 | 231 | pub async fn preadvWindows(loop: *Loop, fd: fd_t, data: []const []u8, offset: u64) !usize { |
| 251 | 232 | assert(data.len != 0); |
| 252 | 233 | if (data.len == 1) return await (async preadWindows(loop, fd, data[0], offset) catch unreachable); |
| ... | ... | @@ -272,15 +253,10 @@ pub async fn preadvWindows(loop: *Loop, fd: fd_t, data: []const []u8, offset: u6 |
| 272 | 253 | } |
| 273 | 254 | |
| 274 | 255 | pub async fn preadWindows(loop: *Loop, fd: fd_t, data: []u8, offset: u64) !usize { |
| 275 | // workaround for https://github.com/ziglang/zig/issues/1194 | |
| 276 | suspend { | |
| 277 | resume @handle(); | |
| 278 | } | |
| 279 | ||
| 280 | 256 | var resume_node = Loop.ResumeNode.Basic{ |
| 281 | 257 | .base = Loop.ResumeNode{ |
| 282 | 258 | .id = Loop.ResumeNode.Id.Basic, |
| 283 | .handle = @handle(), | |
| 259 | .handle = @frame(), | |
| 284 | 260 | .overlapped = windows.OVERLAPPED{ |
| 285 | 261 | .Internal = 0, |
| 286 | 262 | .InternalHigh = 0, |
| ... | ... | @@ -314,18 +290,13 @@ pub async fn preadWindows(loop: *Loop, fd: fd_t, data: []u8, offset: u64) !usize |
| 314 | 290 | return usize(bytes_transferred); |
| 315 | 291 | } |
| 316 | 292 | |
| 317 | /// iovecs must live until preadv promise completes | |
| 293 | /// iovecs must live until preadv frame completes | |
| 318 | 294 | pub async fn preadvPosix( |
| 319 | 295 | loop: *Loop, |
| 320 | 296 | fd: fd_t, |
| 321 | 297 | iovecs: []const os.iovec, |
| 322 | 298 | offset: usize, |
| 323 | 299 | ) os.ReadError!usize { |
| 324 | // workaround for https://github.com/ziglang/zig/issues/1194 | |
| 325 | suspend { | |
| 326 | resume @handle(); | |
| 327 | } | |
| 328 | ||
| 329 | 300 | var req_node = RequestNode{ |
| 330 | 301 | .prev = null, |
| 331 | 302 | .next = null, |
| ... | ... | @@ -342,7 +313,7 @@ pub async fn preadvPosix( |
| 342 | 313 | .TickNode = Loop.NextTickNode{ |
| 343 | 314 | .prev = null, |
| 344 | 315 | .next = null, |
| 345 | .data = @handle(), | |
| 316 | .data = @frame(), | |
| 346 | 317 | }, |
| 347 | 318 | }, |
| 348 | 319 | }, |
| ... | ... | @@ -363,11 +334,6 @@ pub async fn openPosix( |
| 363 | 334 | flags: u32, |
| 364 | 335 | mode: File.Mode, |
| 365 | 336 | ) File.OpenError!fd_t { |
| 366 | // workaround for https://github.com/ziglang/zig/issues/1194 | |
| 367 | suspend { | |
| 368 | resume @handle(); | |
| 369 | } | |
| 370 | ||
| 371 | 337 | const path_c = try std.os.toPosixPath(path); |
| 372 | 338 | |
| 373 | 339 | var req_node = RequestNode{ |
| ... | ... | @@ -386,7 +352,7 @@ pub async fn openPosix( |
| 386 | 352 | .TickNode = Loop.NextTickNode{ |
| 387 | 353 | .prev = null, |
| 388 | 354 | .next = null, |
| 389 | .data = @handle(), | |
| 355 | .data = @frame(), | |
| 390 | 356 | }, |
| 391 | 357 | }, |
| 392 | 358 | }, |
| ... | ... | @@ -643,11 +609,6 @@ async fn writeFileWindows(loop: *Loop, path: []const u8, contents: []const u8) ! |
| 643 | 609 | } |
| 644 | 610 | |
| 645 | 611 | async fn writeFileModeThread(loop: *Loop, path: []const u8, contents: []const u8, mode: File.Mode) !void { |
| 646 | // workaround for https://github.com/ziglang/zig/issues/1194 | |
| 647 | suspend { | |
| 648 | resume @handle(); | |
| 649 | } | |
| 650 | ||
| 651 | 612 | const path_with_null = try std.cstr.addNullByte(loop.allocator, path); |
| 652 | 613 | defer loop.allocator.free(path_with_null); |
| 653 | 614 | |
| ... | ... | @@ -667,7 +628,7 @@ async fn writeFileModeThread(loop: *Loop, path: []const u8, contents: []const u8 |
| 667 | 628 | .TickNode = Loop.NextTickNode{ |
| 668 | 629 | .prev = null, |
| 669 | 630 | .next = null, |
| 670 | .data = @handle(), | |
| 631 | .data = @frame(), | |
| 671 | 632 | }, |
| 672 | 633 | }, |
| 673 | 634 | }, |
| ... | ... | @@ -682,7 +643,7 @@ async fn writeFileModeThread(loop: *Loop, path: []const u8, contents: []const u8 |
| 682 | 643 | return req_node.data.msg.WriteFile.result; |
| 683 | 644 | } |
| 684 | 645 | |
| 685 | /// The promise resumes when the last data has been confirmed written, but before the file handle | |
| 646 | /// The frame resumes when the last data has been confirmed written, but before the file handle | |
| 686 | 647 | /// is closed. |
| 687 | 648 | /// Caller owns returned memory. |
| 688 | 649 | pub async fn readFile(loop: *Loop, file_path: []const u8, max_size: usize) ![]u8 { |
| ... | ... | @@ -734,7 +695,7 @@ pub const WatchEventId = enum { |
| 734 | 695 | // |
| 735 | 696 | // const FileTable = std.AutoHashMap([]const u8, *Put); |
| 736 | 697 | // const Put = struct { |
| 737 | // putter: promise, | |
| 698 | // putter: anyframe, | |
| 738 | 699 | // value_ptr: *V, |
| 739 | 700 | // }; |
| 740 | 701 | // }, |
| ... | ... | @@ -748,21 +709,21 @@ pub const WatchEventId = enum { |
| 748 | 709 | // const WindowsOsData = struct { |
| 749 | 710 | // table_lock: event.Lock, |
| 750 | 711 | // dir_table: DirTable, |
| 751 | // all_putters: std.atomic.Queue(promise), | |
| 712 | // all_putters: std.atomic.Queue(anyframe), | |
| 752 | 713 | // ref_count: std.atomic.Int(usize), |
| 753 | 714 | // |
| 754 | 715 | // const DirTable = std.AutoHashMap([]const u8, *Dir); |
| 755 | 716 | // const FileTable = std.AutoHashMap([]const u16, V); |
| 756 | 717 | // |
| 757 | 718 | // const Dir = struct { |
| 758 | // putter: promise, | |
| 719 | // putter: anyframe, | |
| 759 | 720 | // file_table: FileTable, |
| 760 | 721 | // table_lock: event.Lock, |
| 761 | 722 | // }; |
| 762 | 723 | // }; |
| 763 | 724 | // |
| 764 | 725 | // const LinuxOsData = struct { |
| 765 | // putter: promise, | |
| 726 | // putter: anyframe, | |
| 766 | 727 | // inotify_fd: i32, |
| 767 | 728 | // wd_table: WdTable, |
| 768 | 729 | // table_lock: event.Lock, |
| ... | ... | @@ -776,7 +737,7 @@ pub const WatchEventId = enum { |
| 776 | 737 | // }; |
| 777 | 738 | // }; |
| 778 | 739 | // |
| 779 | // const FileToHandle = std.AutoHashMap([]const u8, promise); | |
| 740 | // const FileToHandle = std.AutoHashMap([]const u8, anyframe); | |
| 780 | 741 | // |
| 781 | 742 | // const Self = @This(); |
| 782 | 743 | // |
| ... | ... | @@ -811,7 +772,7 @@ pub const WatchEventId = enum { |
| 811 | 772 | // .table_lock = event.Lock.init(loop), |
| 812 | 773 | // .dir_table = OsData.DirTable.init(loop.allocator), |
| 813 | 774 | // .ref_count = std.atomic.Int(usize).init(1), |
| 814 | // .all_putters = std.atomic.Queue(promise).init(), | |
| 775 | // .all_putters = std.atomic.Queue(anyframe).init(), | |
| 815 | 776 | // }, |
| 816 | 777 | // }; |
| 817 | 778 | // return self; |
| ... | ... | @@ -926,14 +887,9 @@ pub const WatchEventId = enum { |
| 926 | 887 | // } |
| 927 | 888 | // |
| 928 | 889 | // async fn kqPutEvents(self: *Self, close_op: *CloseOperation, value: V, out_put: **OsData.Put) void { |
| 929 | // // TODO https://github.com/ziglang/zig/issues/1194 | |
| 930 | // suspend { | |
| 931 | // resume @handle(); | |
| 932 | // } | |
| 933 | // | |
| 934 | 890 | // var value_copy = value; |
| 935 | 891 | // var put = OsData.Put{ |
| 936 | // .putter = @handle(), | |
| 892 | // .putter = @frame(), | |
| 937 | 893 | // .value_ptr = &value_copy, |
| 938 | 894 | // }; |
| 939 | 895 | // out_put.* = &put; |
| ... | ... | @@ -1091,18 +1047,13 @@ pub const WatchEventId = enum { |
| 1091 | 1047 | // } |
| 1092 | 1048 | // |
| 1093 | 1049 | // async fn windowsDirReader(self: *Self, dir_handle: windows.HANDLE, dir: *OsData.Dir) void { |
| 1094 | // // TODO https://github.com/ziglang/zig/issues/1194 | |
| 1095 | // suspend { | |
| 1096 | // resume @handle(); | |
| 1097 | // } | |
| 1098 | // | |
| 1099 | 1050 | // self.ref(); |
| 1100 | 1051 | // defer self.deref(); |
| 1101 | 1052 | // |
| 1102 | 1053 | // defer os.close(dir_handle); |
| 1103 | 1054 | // |
| 1104 | // var putter_node = std.atomic.Queue(promise).Node{ | |
| 1105 | // .data = @handle(), | |
| 1055 | // var putter_node = std.atomic.Queue(anyframe).Node{ | |
| 1056 | // .data = @frame(), | |
| 1106 | 1057 | // .prev = null, |
| 1107 | 1058 | // .next = null, |
| 1108 | 1059 | // }; |
| ... | ... | @@ -1112,7 +1063,7 @@ pub const WatchEventId = enum { |
| 1112 | 1063 | // var resume_node = Loop.ResumeNode.Basic{ |
| 1113 | 1064 | // .base = Loop.ResumeNode{ |
| 1114 | 1065 | // .id = Loop.ResumeNode.Id.Basic, |
| 1115 | // .handle = @handle(), | |
| 1066 | // .handle = @frame(), | |
| 1116 | 1067 | // .overlapped = windows.OVERLAPPED{ |
| 1117 | 1068 | // .Internal = 0, |
| 1118 | 1069 | // .InternalHigh = 0, |
| ... | ... | @@ -1207,17 +1158,12 @@ pub const WatchEventId = enum { |
| 1207 | 1158 | // } |
| 1208 | 1159 | // |
| 1209 | 1160 | // async fn linuxEventPutter(inotify_fd: i32, channel: *event.Channel(Event.Error!Event), out_watch: **Self) void { |
| 1210 | // // TODO https://github.com/ziglang/zig/issues/1194 | |
| 1211 | // suspend { | |
| 1212 | // resume @handle(); | |
| 1213 | // } | |
| 1214 | // | |
| 1215 | 1161 | // const loop = channel.loop; |
| 1216 | 1162 | // |
| 1217 | 1163 | // var watch = Self{ |
| 1218 | 1164 | // .channel = channel, |
| 1219 | 1165 | // .os_data = OsData{ |
| 1220 | // .putter = @handle(), | |
| 1166 | // .putter = @frame(), | |
| 1221 | 1167 | // .inotify_fd = inotify_fd, |
| 1222 | 1168 | // .wd_table = OsData.WdTable.init(loop.allocator), |
| 1223 | 1169 | // .table_lock = event.Lock.init(loop), |
std/event/future.zig+19-26| ... | ... | @@ -2,8 +2,6 @@ const std = @import("../std.zig"); |
| 2 | 2 | const assert = std.debug.assert; |
| 3 | 3 | const testing = std.testing; |
| 4 | 4 | const builtin = @import("builtin"); |
| 5 | const AtomicRmwOp = builtin.AtomicRmwOp; | |
| 6 | const AtomicOrder = builtin.AtomicOrder; | |
| 7 | 5 | const Lock = std.event.Lock; |
| 8 | 6 | const Loop = std.event.Loop; |
| 9 | 7 | |
| ... | ... | @@ -23,7 +21,7 @@ pub fn Future(comptime T: type) type { |
| 23 | 21 | available: u8, |
| 24 | 22 | |
| 25 | 23 | const Self = @This(); |
| 26 | const Queue = std.atomic.Queue(promise); | |
| 24 | const Queue = std.atomic.Queue(anyframe); | |
| 27 | 25 | |
| 28 | 26 | pub fn init(loop: *Loop) Self { |
| 29 | 27 | return Self{ |
| ... | ... | @@ -37,10 +35,10 @@ pub fn Future(comptime T: type) type { |
| 37 | 35 | /// available. |
| 38 | 36 | /// Thread-safe. |
| 39 | 37 | pub async fn get(self: *Self) *T { |
| 40 | if (@atomicLoad(u8, &self.available, AtomicOrder.SeqCst) == 2) { | |
| 38 | if (@atomicLoad(u8, &self.available, .SeqCst) == 2) { | |
| 41 | 39 | return &self.data; |
| 42 | 40 | } |
| 43 | const held = await (async self.lock.acquire() catch unreachable); | |
| 41 | const held = self.lock.acquire(); | |
| 44 | 42 | held.release(); |
| 45 | 43 | |
| 46 | 44 | return &self.data; |
| ... | ... | @@ -49,7 +47,7 @@ pub fn Future(comptime T: type) type { |
| 49 | 47 | /// Gets the data without waiting for it. If it's available, a pointer is |
| 50 | 48 | /// returned. Otherwise, null is returned. |
| 51 | 49 | pub fn getOrNull(self: *Self) ?*T { |
| 52 | if (@atomicLoad(u8, &self.available, AtomicOrder.SeqCst) == 2) { | |
| 50 | if (@atomicLoad(u8, &self.available, .SeqCst) == 2) { | |
| 53 | 51 | return &self.data; |
| 54 | 52 | } else { |
| 55 | 53 | return null; |
| ... | ... | @@ -62,10 +60,10 @@ pub fn Future(comptime T: type) type { |
| 62 | 60 | /// It's not required to call start() before resolve() but it can be useful since |
| 63 | 61 | /// this method is thread-safe. |
| 64 | 62 | pub async fn start(self: *Self) ?*T { |
| 65 | const state = @cmpxchgStrong(u8, &self.available, 0, 1, AtomicOrder.SeqCst, AtomicOrder.SeqCst) orelse return null; | |
| 63 | const state = @cmpxchgStrong(u8, &self.available, 0, 1, .SeqCst, .SeqCst) orelse return null; | |
| 66 | 64 | switch (state) { |
| 67 | 65 | 1 => { |
| 68 | const held = await (async self.lock.acquire() catch unreachable); | |
| 66 | const held = self.lock.acquire(); | |
| 69 | 67 | held.release(); |
| 70 | 68 | return &self.data; |
| 71 | 69 | }, |
| ... | ... | @@ -77,7 +75,7 @@ pub fn Future(comptime T: type) type { |
| 77 | 75 | /// Make the data become available. May be called only once. |
| 78 | 76 | /// Before calling this, modify the `data` property. |
| 79 | 77 | pub fn resolve(self: *Self) void { |
| 80 | const prev = @atomicRmw(u8, &self.available, AtomicRmwOp.Xchg, 2, AtomicOrder.SeqCst); | |
| 78 | const prev = @atomicRmw(u8, &self.available, .Xchg, 2, .SeqCst); | |
| 81 | 79 | assert(prev == 0 or prev == 1); // resolve() called twice |
| 82 | 80 | Lock.Held.release(Lock.Held{ .lock = &self.lock }); |
| 83 | 81 | } |
| ... | ... | @@ -86,7 +84,7 @@ pub fn Future(comptime T: type) type { |
| 86 | 84 | |
| 87 | 85 | test "std.event.Future" { |
| 88 | 86 | // https://github.com/ziglang/zig/issues/1908 |
| 89 | if (builtin.single_threaded or builtin.os != builtin.Os.linux) return error.SkipZigTest; | |
| 87 | if (builtin.single_threaded) return error.SkipZigTest; | |
| 90 | 88 | |
| 91 | 89 | const allocator = std.heap.direct_allocator; |
| 92 | 90 | |
| ... | ... | @@ -94,38 +92,33 @@ test "std.event.Future" { |
| 94 | 92 | try loop.initMultiThreaded(allocator); |
| 95 | 93 | defer loop.deinit(); |
| 96 | 94 | |
| 97 | const handle = try async<allocator> testFuture(&loop); | |
| 98 | defer cancel handle; | |
| 95 | const handle = async testFuture(&loop); | |
| 99 | 96 | |
| 100 | 97 | loop.run(); |
| 101 | 98 | } |
| 102 | 99 | |
| 103 | 100 | async fn testFuture(loop: *Loop) void { |
| 104 | suspend { | |
| 105 | resume @handle(); | |
| 106 | } | |
| 107 | 101 | var future = Future(i32).init(loop); |
| 108 | 102 | |
| 109 | const a = async waitOnFuture(&future) catch @panic("memory"); | |
| 110 | const b = async waitOnFuture(&future) catch @panic("memory"); | |
| 111 | const c = async resolveFuture(&future) catch @panic("memory"); | |
| 103 | const a = async waitOnFuture(&future); | |
| 104 | const b = async waitOnFuture(&future); | |
| 105 | const c = async resolveFuture(&future); | |
| 106 | ||
| 107 | // TODO make this work: | |
| 108 | //const result = (await a) + (await b); | |
| 109 | const a_result = await a; | |
| 110 | const b_result = await b; | |
| 111 | const result = a_result + b_result; | |
| 112 | 112 | |
| 113 | const result = (await a) + (await b); | |
| 114 | 113 | cancel c; |
| 115 | 114 | testing.expect(result == 12); |
| 116 | 115 | } |
| 117 | 116 | |
| 118 | 117 | async fn waitOnFuture(future: *Future(i32)) i32 { |
| 119 | suspend { | |
| 120 | resume @handle(); | |
| 121 | } | |
| 122 | return (await (async future.get() catch @panic("memory"))).*; | |
| 118 | return future.get().*; | |
| 123 | 119 | } |
| 124 | 120 | |
| 125 | 121 | async fn resolveFuture(future: *Future(i32)) void { |
| 126 | suspend { | |
| 127 | resume @handle(); | |
| 128 | } | |
| 129 | 122 | future.data = 6; |
| 130 | 123 | future.resolve(); |
| 131 | 124 | } |
std/event/group.zig+20-48| ... | ... | @@ -2,8 +2,6 @@ const std = @import("../std.zig"); |
| 2 | 2 | const builtin = @import("builtin"); |
| 3 | 3 | const Lock = std.event.Lock; |
| 4 | 4 | const Loop = std.event.Loop; |
| 5 | const AtomicRmwOp = builtin.AtomicRmwOp; | |
| 6 | const AtomicOrder = builtin.AtomicOrder; | |
| 7 | 5 | const testing = std.testing; |
| 8 | 6 | |
| 9 | 7 | /// ReturnType must be `void` or `E!void` |
| ... | ... | @@ -16,10 +14,10 @@ pub fn Group(comptime ReturnType: type) type { |
| 16 | 14 | const Self = @This(); |
| 17 | 15 | |
| 18 | 16 | const Error = switch (@typeInfo(ReturnType)) { |
| 19 | builtin.TypeId.ErrorUnion => |payload| payload.error_set, | |
| 17 | .ErrorUnion => |payload| payload.error_set, | |
| 20 | 18 | else => void, |
| 21 | 19 | }; |
| 22 | const Stack = std.atomic.Stack(promise->ReturnType); | |
| 20 | const Stack = std.atomic.Stack(anyframe->ReturnType); | |
| 23 | 21 | |
| 24 | 22 | pub fn init(loop: *Loop) Self { |
| 25 | 23 | return Self{ |
| ... | ... | @@ -29,7 +27,7 @@ pub fn Group(comptime ReturnType: type) type { |
| 29 | 27 | }; |
| 30 | 28 | } |
| 31 | 29 | |
| 32 | /// Cancel all the outstanding promises. Can be called even if wait was already called. | |
| 30 | /// Cancel all the outstanding frames. Can be called even if wait was already called. | |
| 33 | 31 | pub fn deinit(self: *Self) void { |
| 34 | 32 | while (self.coro_stack.pop()) |node| { |
| 35 | 33 | cancel node.data; |
| ... | ... | @@ -40,8 +38,8 @@ pub fn Group(comptime ReturnType: type) type { |
| 40 | 38 | } |
| 41 | 39 | } |
| 42 | 40 | |
| 43 | /// Add a promise to the group. Thread-safe. | |
| 44 | pub fn add(self: *Self, handle: promise->ReturnType) (error{OutOfMemory}!void) { | |
| 41 | /// Add a frame to the group. Thread-safe. | |
| 42 | pub fn add(self: *Self, handle: anyframe->ReturnType) (error{OutOfMemory}!void) { | |
| 45 | 43 | const node = try self.lock.loop.allocator.create(Stack.Node); |
| 46 | 44 | node.* = Stack.Node{ |
| 47 | 45 | .next = undefined, |
| ... | ... | @@ -51,7 +49,7 @@ pub fn Group(comptime ReturnType: type) type { |
| 51 | 49 | } |
| 52 | 50 | |
| 53 | 51 | /// Add a node to the group. Thread-safe. Cannot fail. |
| 54 | /// `node.data` should be the promise handle to add to the group. | |
| 52 | /// `node.data` should be the frame handle to add to the group. | |
| 55 | 53 | /// The node's memory should be in the coroutine frame of |
| 56 | 54 | /// the handle that is in the node, or somewhere guaranteed to live |
| 57 | 55 | /// at least as long. |
| ... | ... | @@ -59,40 +57,11 @@ pub fn Group(comptime ReturnType: type) type { |
| 59 | 57 | self.coro_stack.push(node); |
| 60 | 58 | } |
| 61 | 59 | |
| 62 | /// This is equivalent to an async call, but the async function is added to the group, instead | |
| 63 | /// of returning a promise. func must be async and have return type ReturnType. | |
| 64 | /// Thread-safe. | |
| 65 | pub fn call(self: *Self, comptime func: var, args: ...) (error{OutOfMemory}!void) { | |
| 66 | const S = struct { | |
| 67 | async fn asyncFunc(node: **Stack.Node, args2: ...) ReturnType { | |
| 68 | // TODO this is a hack to make the memory following be inside the coro frame | |
| 69 | suspend { | |
| 70 | var my_node: Stack.Node = undefined; | |
| 71 | node.* = &my_node; | |
| 72 | resume @handle(); | |
| 73 | } | |
| 74 | ||
| 75 | // TODO this allocation elision should be guaranteed because we await it in | |
| 76 | // this coro frame | |
| 77 | return await (async func(args2) catch unreachable); | |
| 78 | } | |
| 79 | }; | |
| 80 | var node: *Stack.Node = undefined; | |
| 81 | const handle = try async<self.lock.loop.allocator> S.asyncFunc(&node, args); | |
| 82 | node.* = Stack.Node{ | |
| 83 | .next = undefined, | |
| 84 | .data = handle, | |
| 85 | }; | |
| 86 | self.coro_stack.push(node); | |
| 87 | } | |
| 88 | ||
| 89 | 60 | /// Wait for all the calls and promises of the group to complete. |
| 90 | 61 | /// Thread-safe. |
| 91 | 62 | /// Safe to call any number of times. |
| 92 | 63 | pub async fn wait(self: *Self) ReturnType { |
| 93 | // TODO catch unreachable because the allocation can be grouped with | |
| 94 | // the coro frame allocation | |
| 95 | const held = await (async self.lock.acquire() catch unreachable); | |
| 64 | const held = self.lock.acquire(); | |
| 96 | 65 | defer held.release(); |
| 97 | 66 | |
| 98 | 67 | while (self.coro_stack.pop()) |node| { |
| ... | ... | @@ -131,8 +100,7 @@ test "std.event.Group" { |
| 131 | 100 | try loop.initMultiThreaded(allocator); |
| 132 | 101 | defer loop.deinit(); |
| 133 | 102 | |
| 134 | const handle = try async<allocator> testGroup(&loop); | |
| 135 | defer cancel handle; | |
| 103 | const handle = async testGroup(&loop); | |
| 136 | 104 | |
| 137 | 105 | loop.run(); |
| 138 | 106 | } |
| ... | ... | @@ -140,26 +108,30 @@ test "std.event.Group" { |
| 140 | 108 | async fn testGroup(loop: *Loop) void { |
| 141 | 109 | var count: usize = 0; |
| 142 | 110 | var group = Group(void).init(loop); |
| 143 | group.add(async sleepALittle(&count) catch @panic("memory")) catch @panic("memory"); | |
| 144 | group.call(increaseByTen, &count) catch @panic("memory"); | |
| 145 | await (async group.wait() catch @panic("memory")); | |
| 111 | var sleep_a_little_frame = async sleepALittle(&count); | |
| 112 | group.add(&sleep_a_little_frame) catch @panic("memory"); | |
| 113 | var increase_by_ten_frame = async increaseByTen(&count); | |
| 114 | group.add(&increase_by_ten_frame) catch @panic("memory"); | |
| 115 | group.wait(); | |
| 146 | 116 | testing.expect(count == 11); |
| 147 | 117 | |
| 148 | 118 | var another = Group(anyerror!void).init(loop); |
| 149 | another.add(async somethingElse() catch @panic("memory")) catch @panic("memory"); | |
| 150 | another.call(doSomethingThatFails) catch @panic("memory"); | |
| 151 | testing.expectError(error.ItBroke, await (async another.wait() catch @panic("memory"))); | |
| 119 | var something_else_frame = async somethingElse(); | |
| 120 | another.add(&something_else_frame) catch @panic("memory"); | |
| 121 | var something_that_fails_frame = async doSomethingThatFails(); | |
| 122 | another.add(&something_that_fails_frame) catch @panic("memory"); | |
| 123 | testing.expectError(error.ItBroke, another.wait()); | |
| 152 | 124 | } |
| 153 | 125 | |
| 154 | 126 | async fn sleepALittle(count: *usize) void { |
| 155 | 127 | std.time.sleep(1 * std.time.millisecond); |
| 156 | _ = @atomicRmw(usize, count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); | |
| 128 | _ = @atomicRmw(usize, count, .Add, 1, .SeqCst); | |
| 157 | 129 | } |
| 158 | 130 | |
| 159 | 131 | async fn increaseByTen(count: *usize) void { |
| 160 | 132 | var i: usize = 0; |
| 161 | 133 | while (i < 10) : (i += 1) { |
| 162 | _ = @atomicRmw(usize, count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); | |
| 134 | _ = @atomicRmw(usize, count, .Add, 1, .SeqCst); | |
| 163 | 135 | } |
| 164 | 136 | } |
| 165 | 137 |
std/event/io.zig+9-10| ... | ... | @@ -1,6 +1,5 @@ |
| 1 | 1 | const std = @import("../std.zig"); |
| 2 | 2 | const builtin = @import("builtin"); |
| 3 | const Allocator = std.mem.Allocator; | |
| 4 | 3 | const assert = std.debug.assert; |
| 5 | 4 | const mem = std.mem; |
| 6 | 5 | |
| ... | ... | @@ -12,13 +11,13 @@ pub fn InStream(comptime ReadError: type) type { |
| 12 | 11 | /// Return the number of bytes read. It may be less than buffer.len. |
| 13 | 12 | /// If the number of bytes read is 0, it means end of stream. |
| 14 | 13 | /// End of stream is not an error condition. |
| 15 | readFn: async<*Allocator> fn (self: *Self, buffer: []u8) Error!usize, | |
| 14 | readFn: async fn (self: *Self, buffer: []u8) Error!usize, | |
| 16 | 15 | |
| 17 | 16 | /// Return the number of bytes read. It may be less than buffer.len. |
| 18 | 17 | /// If the number of bytes read is 0, it means end of stream. |
| 19 | 18 | /// End of stream is not an error condition. |
| 20 | 19 | pub async fn read(self: *Self, buffer: []u8) !usize { |
| 21 | return await (async self.readFn(self, buffer) catch unreachable); | |
| 20 | return self.readFn(self, buffer); | |
| 22 | 21 | } |
| 23 | 22 | |
| 24 | 23 | /// Return the number of bytes read. If it is less than buffer.len |
| ... | ... | @@ -26,7 +25,7 @@ pub fn InStream(comptime ReadError: type) type { |
| 26 | 25 | pub async fn readFull(self: *Self, buffer: []u8) !usize { |
| 27 | 26 | var index: usize = 0; |
| 28 | 27 | while (index != buf.len) { |
| 29 | const amt_read = try await (async self.read(buf[index..]) catch unreachable); | |
| 28 | const amt_read = try self.read(buf[index..]); | |
| 30 | 29 | if (amt_read == 0) return index; |
| 31 | 30 | index += amt_read; |
| 32 | 31 | } |
| ... | ... | @@ -35,25 +34,25 @@ pub fn InStream(comptime ReadError: type) type { |
| 35 | 34 | |
| 36 | 35 | /// Same as `readFull` but end of stream returns `error.EndOfStream`. |
| 37 | 36 | pub async fn readNoEof(self: *Self, buf: []u8) !void { |
| 38 | const amt_read = try await (async self.readFull(buf[index..]) catch unreachable); | |
| 37 | const amt_read = try self.readFull(buf[index..]); | |
| 39 | 38 | if (amt_read < buf.len) return error.EndOfStream; |
| 40 | 39 | } |
| 41 | 40 | |
| 42 | 41 | pub async fn readIntLittle(self: *Self, comptime T: type) !T { |
| 43 | 42 | var bytes: [@sizeOf(T)]u8 = undefined; |
| 44 | try await (async self.readNoEof(bytes[0..]) catch unreachable); | |
| 43 | try self.readNoEof(bytes[0..]); | |
| 45 | 44 | return mem.readIntLittle(T, &bytes); |
| 46 | 45 | } |
| 47 | 46 | |
| 48 | 47 | pub async fn readIntBe(self: *Self, comptime T: type) !T { |
| 49 | 48 | var bytes: [@sizeOf(T)]u8 = undefined; |
| 50 | try await (async self.readNoEof(bytes[0..]) catch unreachable); | |
| 49 | try self.readNoEof(bytes[0..]); | |
| 51 | 50 | return mem.readIntBig(T, &bytes); |
| 52 | 51 | } |
| 53 | 52 | |
| 54 | 53 | pub async fn readInt(self: *Self, comptime T: type, endian: builtin.Endian) !T { |
| 55 | 54 | var bytes: [@sizeOf(T)]u8 = undefined; |
| 56 | try await (async self.readNoEof(bytes[0..]) catch unreachable); | |
| 55 | try self.readNoEof(bytes[0..]); | |
| 57 | 56 | return mem.readInt(T, &bytes, endian); |
| 58 | 57 | } |
| 59 | 58 | |
| ... | ... | @@ -61,7 +60,7 @@ pub fn InStream(comptime ReadError: type) type { |
| 61 | 60 | // Only extern and packed structs have defined in-memory layout. |
| 62 | 61 | comptime assert(@typeInfo(T).Struct.layout != builtin.TypeInfo.ContainerLayout.Auto); |
| 63 | 62 | var res: [1]T = undefined; |
| 64 | try await (async self.readNoEof(@sliceToBytes(res[0..])) catch unreachable); | |
| 63 | try self.readNoEof(@sliceToBytes(res[0..])); | |
| 65 | 64 | return res[0]; |
| 66 | 65 | } |
| 67 | 66 | }; |
| ... | ... | @@ -72,6 +71,6 @@ pub fn OutStream(comptime WriteError: type) type { |
| 72 | 71 | const Self = @This(); |
| 73 | 72 | pub const Error = WriteError; |
| 74 | 73 | |
| 75 | writeFn: async<*Allocator> fn (self: *Self, buffer: []u8) Error!void, | |
| 74 | writeFn: async fn (self: *Self, buffer: []u8) Error!void, | |
| 76 | 75 | }; |
| 77 | 76 | } |
std/event/lock.zig+21-33| ... | ... | @@ -3,8 +3,6 @@ const builtin = @import("builtin"); |
| 3 | 3 | const assert = std.debug.assert; |
| 4 | 4 | const testing = std.testing; |
| 5 | 5 | const mem = std.mem; |
| 6 | const AtomicRmwOp = builtin.AtomicRmwOp; | |
| 7 | const AtomicOrder = builtin.AtomicOrder; | |
| 8 | 6 | const Loop = std.event.Loop; |
| 9 | 7 | |
| 10 | 8 | /// Thread-safe async/await lock. |
| ... | ... | @@ -17,7 +15,7 @@ pub const Lock = struct { |
| 17 | 15 | queue: Queue, |
| 18 | 16 | queue_empty_bit: u8, // TODO make this a bool |
| 19 | 17 | |
| 20 | const Queue = std.atomic.Queue(promise); | |
| 18 | const Queue = std.atomic.Queue(anyframe); | |
| 21 | 19 | |
| 22 | 20 | pub const Held = struct { |
| 23 | 21 | lock: *Lock, |
| ... | ... | @@ -30,19 +28,19 @@ pub const Lock = struct { |
| 30 | 28 | } |
| 31 | 29 | |
| 32 | 30 | // We need to release the lock. |
| 33 | _ = @atomicRmw(u8, &self.lock.queue_empty_bit, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst); | |
| 34 | _ = @atomicRmw(u8, &self.lock.shared_bit, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst); | |
| 31 | _ = @atomicRmw(u8, &self.lock.queue_empty_bit, .Xchg, 1, .SeqCst); | |
| 32 | _ = @atomicRmw(u8, &self.lock.shared_bit, .Xchg, 0, .SeqCst); | |
| 35 | 33 | |
| 36 | 34 | // There might be a queue item. If we know the queue is empty, we can be done, |
| 37 | 35 | // because the other actor will try to obtain the lock. |
| 38 | 36 | // But if there's a queue item, we are the actor which must loop and attempt |
| 39 | 37 | // to grab the lock again. |
| 40 | if (@atomicLoad(u8, &self.lock.queue_empty_bit, AtomicOrder.SeqCst) == 1) { | |
| 38 | if (@atomicLoad(u8, &self.lock.queue_empty_bit, .SeqCst) == 1) { | |
| 41 | 39 | return; |
| 42 | 40 | } |
| 43 | 41 | |
| 44 | 42 | while (true) { |
| 45 | const old_bit = @atomicRmw(u8, &self.lock.shared_bit, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst); | |
| 43 | const old_bit = @atomicRmw(u8, &self.lock.shared_bit, .Xchg, 1, .SeqCst); | |
| 46 | 44 | if (old_bit != 0) { |
| 47 | 45 | // We did not obtain the lock. Great, the queue is someone else's problem. |
| 48 | 46 | return; |
| ... | ... | @@ -55,11 +53,11 @@ pub const Lock = struct { |
| 55 | 53 | } |
| 56 | 54 | |
| 57 | 55 | // Release the lock again. |
| 58 | _ = @atomicRmw(u8, &self.lock.queue_empty_bit, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst); | |
| 59 | _ = @atomicRmw(u8, &self.lock.shared_bit, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst); | |
| 56 | _ = @atomicRmw(u8, &self.lock.queue_empty_bit, .Xchg, 1, .SeqCst); | |
| 57 | _ = @atomicRmw(u8, &self.lock.shared_bit, .Xchg, 0, .SeqCst); | |
| 60 | 58 | |
| 61 | 59 | // Find out if we can be done. |
| 62 | if (@atomicLoad(u8, &self.lock.queue_empty_bit, AtomicOrder.SeqCst) == 1) { | |
| 60 | if (@atomicLoad(u8, &self.lock.queue_empty_bit, .SeqCst) == 1) { | |
| 63 | 61 | return; |
| 64 | 62 | } |
| 65 | 63 | } |
| ... | ... | @@ -88,15 +86,11 @@ pub const Lock = struct { |
| 88 | 86 | /// All calls to acquire() and release() must complete before calling deinit(). |
| 89 | 87 | pub fn deinit(self: *Lock) void { |
| 90 | 88 | assert(self.shared_bit == 0); |
| 91 | while (self.queue.get()) |node| cancel node.data; | |
| 89 | while (self.queue.get()) |node| resume node.data; | |
| 92 | 90 | } |
| 93 | 91 | |
| 94 | 92 | pub async fn acquire(self: *Lock) Held { |
| 95 | // TODO explicitly put this memory in the coroutine frame #1194 | |
| 96 | suspend { | |
| 97 | resume @handle(); | |
| 98 | } | |
| 99 | var my_tick_node = Loop.NextTickNode.init(@handle()); | |
| 93 | var my_tick_node = Loop.NextTickNode.init(@frame()); | |
| 100 | 94 | |
| 101 | 95 | errdefer _ = self.queue.remove(&my_tick_node); // TODO test canceling an acquire |
| 102 | 96 | suspend { |
| ... | ... | @@ -107,9 +101,9 @@ pub const Lock = struct { |
| 107 | 101 | |
| 108 | 102 | // We set this bit so that later we can rely on the fact, that if queue_empty_bit is 1, some actor |
| 109 | 103 | // will attempt to grab the lock. |
| 110 | _ = @atomicRmw(u8, &self.queue_empty_bit, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst); | |
| 104 | _ = @atomicRmw(u8, &self.queue_empty_bit, .Xchg, 0, .SeqCst); | |
| 111 | 105 | |
| 112 | const old_bit = @atomicRmw(u8, &self.shared_bit, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst); | |
| 106 | const old_bit = @atomicRmw(u8, &self.shared_bit, .Xchg, 1, .SeqCst); | |
| 113 | 107 | if (old_bit == 0) { |
| 114 | 108 | if (self.queue.get()) |node| { |
| 115 | 109 | // Whether this node is us or someone else, we tail resume it. |
| ... | ... | @@ -123,8 +117,7 @@ pub const Lock = struct { |
| 123 | 117 | }; |
| 124 | 118 | |
| 125 | 119 | test "std.event.Lock" { |
| 126 | // TODO https://github.com/ziglang/zig/issues/2377 | |
| 127 | if (true) return error.SkipZigTest; | |
| 120 | // TODO https://github.com/ziglang/zig/issues/1908 | |
| 128 | 121 | if (builtin.single_threaded) return error.SkipZigTest; |
| 129 | 122 | |
| 130 | 123 | const allocator = std.heap.direct_allocator; |
| ... | ... | @@ -136,39 +129,34 @@ test "std.event.Lock" { |
| 136 | 129 | var lock = Lock.init(&loop); |
| 137 | 130 | defer lock.deinit(); |
| 138 | 131 | |
| 139 | const handle = try async<allocator> testLock(&loop, &lock); | |
| 140 | defer cancel handle; | |
| 132 | _ = async testLock(&loop, &lock); | |
| 141 | 133 | loop.run(); |
| 142 | 134 | |
| 143 | 135 | testing.expectEqualSlices(i32, [1]i32{3 * @intCast(i32, shared_test_data.len)} ** shared_test_data.len, shared_test_data); |
| 144 | 136 | } |
| 145 | 137 | |
| 146 | 138 | async fn testLock(loop: *Loop, lock: *Lock) void { |
| 147 | // TODO explicitly put next tick node memory in the coroutine frame #1194 | |
| 148 | suspend { | |
| 149 | resume @handle(); | |
| 150 | } | |
| 151 | const handle1 = async lockRunner(lock) catch @panic("out of memory"); | |
| 139 | const handle1 = async lockRunner(lock); | |
| 152 | 140 | var tick_node1 = Loop.NextTickNode{ |
| 153 | 141 | .prev = undefined, |
| 154 | 142 | .next = undefined, |
| 155 | .data = handle1, | |
| 143 | .data = &handle1, | |
| 156 | 144 | }; |
| 157 | 145 | loop.onNextTick(&tick_node1); |
| 158 | 146 | |
| 159 | const handle2 = async lockRunner(lock) catch @panic("out of memory"); | |
| 147 | const handle2 = async lockRunner(lock); | |
| 160 | 148 | var tick_node2 = Loop.NextTickNode{ |
| 161 | 149 | .prev = undefined, |
| 162 | 150 | .next = undefined, |
| 163 | .data = handle2, | |
| 151 | .data = &handle2, | |
| 164 | 152 | }; |
| 165 | 153 | loop.onNextTick(&tick_node2); |
| 166 | 154 | |
| 167 | const handle3 = async lockRunner(lock) catch @panic("out of memory"); | |
| 155 | const handle3 = async lockRunner(lock); | |
| 168 | 156 | var tick_node3 = Loop.NextTickNode{ |
| 169 | 157 | .prev = undefined, |
| 170 | 158 | .next = undefined, |
| 171 | .data = handle3, | |
| 159 | .data = &handle3, | |
| 172 | 160 | }; |
| 173 | 161 | loop.onNextTick(&tick_node3); |
| 174 | 162 | |
| ... | ... | @@ -185,7 +173,7 @@ async fn lockRunner(lock: *Lock) void { |
| 185 | 173 | |
| 186 | 174 | var i: usize = 0; |
| 187 | 175 | while (i < shared_test_data.len) : (i += 1) { |
| 188 | const lock_promise = async lock.acquire() catch @panic("out of memory"); | |
| 176 | const lock_promise = async lock.acquire(); | |
| 189 | 177 | const handle = await lock_promise; |
| 190 | 178 | defer handle.release(); |
| 191 | 179 |
std/event/loop.zig+2-2| ... | ... | @@ -457,7 +457,7 @@ pub const Loop = struct { |
| 457 | 457 | var resume_node = ResumeNode.Basic{ |
| 458 | 458 | .base = ResumeNode{ |
| 459 | 459 | .id = ResumeNode.Id.Basic, |
| 460 | .handle = @handle(), | |
| 460 | .handle = @frame(), | |
| 461 | 461 | .overlapped = ResumeNode.overlapped_init, |
| 462 | 462 | }, |
| 463 | 463 | }; |
| ... | ... | @@ -469,7 +469,7 @@ pub const Loop = struct { |
| 469 | 469 | var resume_node = ResumeNode.Basic{ |
| 470 | 470 | .base = ResumeNode{ |
| 471 | 471 | .id = ResumeNode.Id.Basic, |
| 472 | .handle = @handle(), | |
| 472 | .handle = @frame(), | |
| 473 | 473 | .overlapped = ResumeNode.overlapped_init, |
| 474 | 474 | }, |
| 475 | 475 | .kev = undefined, |
std/event/net.zig+23-30| ... | ... | @@ -9,17 +9,17 @@ const File = std.fs.File; |
| 9 | 9 | const fd_t = os.fd_t; |
| 10 | 10 | |
| 11 | 11 | pub const Server = struct { |
| 12 | handleRequestFn: async<*mem.Allocator> fn (*Server, *const std.net.Address, File) void, | |
| 12 | handleRequestFn: async fn (*Server, *const std.net.Address, File) void, | |
| 13 | 13 | |
| 14 | 14 | loop: *Loop, |
| 15 | 15 | sockfd: ?i32, |
| 16 | accept_coro: ?promise, | |
| 16 | accept_coro: ?anyframe, | |
| 17 | 17 | listen_address: std.net.Address, |
| 18 | 18 | |
| 19 | 19 | waiting_for_emfile_node: PromiseNode, |
| 20 | 20 | listen_resume_node: event.Loop.ResumeNode, |
| 21 | 21 | |
| 22 | const PromiseNode = std.TailQueue(promise).Node; | |
| 22 | const PromiseNode = std.TailQueue(anyframe).Node; | |
| 23 | 23 | |
| 24 | 24 | pub fn init(loop: *Loop) Server { |
| 25 | 25 | // TODO can't initialize handler coroutine here because we need well defined copy elision |
| ... | ... | @@ -41,7 +41,7 @@ pub const Server = struct { |
| 41 | 41 | pub fn listen( |
| 42 | 42 | self: *Server, |
| 43 | 43 | address: *const std.net.Address, |
| 44 | handleRequestFn: async<*mem.Allocator> fn (*Server, *const std.net.Address, File) void, | |
| 44 | handleRequestFn: async fn (*Server, *const std.net.Address, File) void, | |
| 45 | 45 | ) !void { |
| 46 | 46 | self.handleRequestFn = handleRequestFn; |
| 47 | 47 | |
| ... | ... | @@ -53,7 +53,7 @@ pub const Server = struct { |
| 53 | 53 | try os.listen(sockfd, os.SOMAXCONN); |
| 54 | 54 | self.listen_address = std.net.Address.initPosix(try os.getsockname(sockfd)); |
| 55 | 55 | |
| 56 | self.accept_coro = try async<self.loop.allocator> Server.handler(self); | |
| 56 | self.accept_coro = async Server.handler(self); | |
| 57 | 57 | errdefer cancel self.accept_coro.?; |
| 58 | 58 | |
| 59 | 59 | self.listen_resume_node.handle = self.accept_coro.?; |
| ... | ... | @@ -86,12 +86,7 @@ pub const Server = struct { |
| 86 | 86 | continue; |
| 87 | 87 | } |
| 88 | 88 | var socket = File.openHandle(accepted_fd); |
| 89 | _ = async<self.loop.allocator> self.handleRequestFn(self, &accepted_addr, socket) catch |err| switch (err) { | |
| 90 | error.OutOfMemory => { | |
| 91 | socket.close(); | |
| 92 | continue; | |
| 93 | }, | |
| 94 | }; | |
| 89 | self.handleRequestFn(self, &accepted_addr, socket); | |
| 95 | 90 | } else |err| switch (err) { |
| 96 | 91 | error.ProcessFdQuotaExceeded => @panic("TODO handle this error"), |
| 97 | 92 | error.ConnectionAborted => continue, |
| ... | ... | @@ -124,7 +119,7 @@ pub async fn connectUnixSocket(loop: *Loop, path: []const u8) !i32 { |
| 124 | 119 | mem.copy(u8, sock_addr.path[0..], path); |
| 125 | 120 | const size = @intCast(u32, @sizeOf(os.sa_family_t) + path.len); |
| 126 | 121 | try os.connect_async(sockfd, &sock_addr, size); |
| 127 | try await try async loop.linuxWaitFd(sockfd, os.EPOLLIN | os.EPOLLOUT | os.EPOLLET); | |
| 122 | try loop.linuxWaitFd(sockfd, os.EPOLLIN | os.EPOLLOUT | os.EPOLLET); | |
| 128 | 123 | try os.getsockoptError(sockfd); |
| 129 | 124 | |
| 130 | 125 | return sockfd; |
| ... | ... | @@ -149,7 +144,7 @@ pub async fn read(loop: *std.event.Loop, fd: fd_t, buffer: []u8) ReadError!usize |
| 149 | 144 | .iov_len = buffer.len, |
| 150 | 145 | }; |
| 151 | 146 | const iovs: *const [1]os.iovec = &iov; |
| 152 | return await (async readvPosix(loop, fd, iovs, 1) catch unreachable); | |
| 147 | return readvPosix(loop, fd, iovs, 1); | |
| 153 | 148 | } |
| 154 | 149 | |
| 155 | 150 | pub const WriteError = error{}; |
| ... | ... | @@ -160,7 +155,7 @@ pub async fn write(loop: *std.event.Loop, fd: fd_t, buffer: []const u8) WriteErr |
| 160 | 155 | .iov_len = buffer.len, |
| 161 | 156 | }; |
| 162 | 157 | const iovs: *const [1]os.iovec_const = &iov; |
| 163 | return await (async writevPosix(loop, fd, iovs, 1) catch unreachable); | |
| 158 | return writevPosix(loop, fd, iovs, 1); | |
| 164 | 159 | } |
| 165 | 160 | |
| 166 | 161 | pub async fn writevPosix(loop: *Loop, fd: i32, iov: [*]const os.iovec_const, count: usize) !void { |
| ... | ... | @@ -174,7 +169,7 @@ pub async fn writevPosix(loop: *Loop, fd: i32, iov: [*]const os.iovec_const, cou |
| 174 | 169 | os.EINVAL => unreachable, |
| 175 | 170 | os.EFAULT => unreachable, |
| 176 | 171 | os.EAGAIN => { |
| 177 | try await (async loop.linuxWaitFd(fd, os.EPOLLET | os.EPOLLOUT) catch unreachable); | |
| 172 | try loop.linuxWaitFd(fd, os.EPOLLET | os.EPOLLOUT); | |
| 178 | 173 | continue; |
| 179 | 174 | }, |
| 180 | 175 | os.EBADF => unreachable, // always a race condition |
| ... | ... | @@ -205,7 +200,7 @@ pub async fn readvPosix(loop: *std.event.Loop, fd: i32, iov: [*]os.iovec, count: |
| 205 | 200 | os.EINVAL => unreachable, |
| 206 | 201 | os.EFAULT => unreachable, |
| 207 | 202 | os.EAGAIN => { |
| 208 | try await (async loop.linuxWaitFd(fd, os.EPOLLET | os.EPOLLIN) catch unreachable); | |
| 203 | try loop.linuxWaitFd(fd, os.EPOLLET | os.EPOLLIN); | |
| 209 | 204 | continue; |
| 210 | 205 | }, |
| 211 | 206 | os.EBADF => unreachable, // always a race condition |
| ... | ... | @@ -232,7 +227,7 @@ pub async fn writev(loop: *Loop, fd: fd_t, data: []const []const u8) !void { |
| 232 | 227 | }; |
| 233 | 228 | } |
| 234 | 229 | |
| 235 | return await (async writevPosix(loop, fd, iovecs.ptr, data.len) catch unreachable); | |
| 230 | return writevPosix(loop, fd, iovecs.ptr, data.len); | |
| 236 | 231 | } |
| 237 | 232 | |
| 238 | 233 | pub async fn readv(loop: *Loop, fd: fd_t, data: []const []u8) !usize { |
| ... | ... | @@ -246,7 +241,7 @@ pub async fn readv(loop: *Loop, fd: fd_t, data: []const []u8) !usize { |
| 246 | 241 | }; |
| 247 | 242 | } |
| 248 | 243 | |
| 249 | return await (async readvPosix(loop, fd, iovecs.ptr, data.len) catch unreachable); | |
| 244 | return readvPosix(loop, fd, iovecs.ptr, data.len); | |
| 250 | 245 | } |
| 251 | 246 | |
| 252 | 247 | pub async fn connect(loop: *Loop, _address: *const std.net.Address) !File { |
| ... | ... | @@ -256,7 +251,7 @@ pub async fn connect(loop: *Loop, _address: *const std.net.Address) !File { |
| 256 | 251 | errdefer os.close(sockfd); |
| 257 | 252 | |
| 258 | 253 | try os.connect_async(sockfd, &address.os_addr, @sizeOf(os.sockaddr_in)); |
| 259 | try await try async loop.linuxWaitFd(sockfd, os.EPOLLIN | os.EPOLLOUT | os.EPOLLET); | |
| 254 | try loop.linuxWaitFd(sockfd, os.EPOLLIN | os.EPOLLOUT | os.EPOLLET); | |
| 260 | 255 | try os.getsockoptError(sockfd); |
| 261 | 256 | |
| 262 | 257 | return File.openHandle(sockfd); |
| ... | ... | @@ -275,17 +270,16 @@ test "listen on a port, send bytes, receive bytes" { |
| 275 | 270 | tcp_server: Server, |
| 276 | 271 | |
| 277 | 272 | const Self = @This(); |
| 278 | async<*mem.Allocator> fn handler(tcp_server: *Server, _addr: *const std.net.Address, _socket: File) void { | |
| 273 | async fn handler(tcp_server: *Server, _addr: *const std.net.Address, _socket: File) void { | |
| 279 | 274 | const self = @fieldParentPtr(Self, "tcp_server", tcp_server); |
| 280 | 275 | var socket = _socket; // TODO https://github.com/ziglang/zig/issues/1592 |
| 281 | 276 | defer socket.close(); |
| 282 | 277 | // TODO guarantee elision of this allocation |
| 283 | const next_handler = async errorableHandler(self, _addr, socket) catch unreachable; | |
| 284 | (await next_handler) catch |err| { | |
| 278 | const next_handler = errorableHandler(self, _addr, socket) catch |err| { | |
| 285 | 279 | std.debug.panic("unable to handle connection: {}\n", err); |
| 286 | 280 | }; |
| 287 | 281 | suspend { |
| 288 | cancel @handle(); | |
| 282 | cancel @frame(); | |
| 289 | 283 | } |
| 290 | 284 | } |
| 291 | 285 | async fn errorableHandler(self: *Self, _addr: *const std.net.Address, _socket: File) !void { |
| ... | ... | @@ -306,15 +300,14 @@ test "listen on a port, send bytes, receive bytes" { |
| 306 | 300 | defer server.tcp_server.deinit(); |
| 307 | 301 | try server.tcp_server.listen(&addr, MyServer.handler); |
| 308 | 302 | |
| 309 | const p = try async<std.debug.global_allocator> doAsyncTest(&loop, &server.tcp_server.listen_address, &server.tcp_server); | |
| 310 | defer cancel p; | |
| 303 | _ = async doAsyncTest(&loop, &server.tcp_server.listen_address, &server.tcp_server); | |
| 311 | 304 | loop.run(); |
| 312 | 305 | } |
| 313 | 306 | |
| 314 | 307 | async fn doAsyncTest(loop: *Loop, address: *const std.net.Address, server: *Server) void { |
| 315 | 308 | errdefer @panic("test failure"); |
| 316 | 309 | |
| 317 | var socket_file = try await try async connect(loop, address); | |
| 310 | var socket_file = try connect(loop, address); | |
| 318 | 311 | defer socket_file.close(); |
| 319 | 312 | |
| 320 | 313 | var buf: [512]u8 = undefined; |
| ... | ... | @@ -340,9 +333,9 @@ pub const OutStream = struct { |
| 340 | 333 | }; |
| 341 | 334 | } |
| 342 | 335 | |
| 343 | async<*mem.Allocator> fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void { | |
| 336 | async fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void { | |
| 344 | 337 | const self = @fieldParentPtr(OutStream, "stream", out_stream); |
| 345 | return await (async write(self.loop, self.fd, bytes) catch unreachable); | |
| 338 | return write(self.loop, self.fd, bytes); | |
| 346 | 339 | } |
| 347 | 340 | }; |
| 348 | 341 | |
| ... | ... | @@ -362,8 +355,8 @@ pub const InStream = struct { |
| 362 | 355 | }; |
| 363 | 356 | } |
| 364 | 357 | |
| 365 | async<*mem.Allocator> fn readFn(in_stream: *Stream, bytes: []u8) Error!usize { | |
| 358 | async fn readFn(in_stream: *Stream, bytes: []u8) Error!usize { | |
| 366 | 359 | const self = @fieldParentPtr(InStream, "stream", in_stream); |
| 367 | return await (async read(self.loop, self.fd, bytes) catch unreachable); | |
| 360 | return read(self.loop, self.fd, bytes); | |
| 368 | 361 | } |
| 369 | 362 | }; |
std/event/rwlock.zig+43-42| ... | ... | @@ -3,8 +3,6 @@ const builtin = @import("builtin"); |
| 3 | 3 | const assert = std.debug.assert; |
| 4 | 4 | const testing = std.testing; |
| 5 | 5 | const mem = std.mem; |
| 6 | const AtomicRmwOp = builtin.AtomicRmwOp; | |
| 7 | const AtomicOrder = builtin.AtomicOrder; | |
| 8 | 6 | const Loop = std.event.Loop; |
| 9 | 7 | |
| 10 | 8 | /// Thread-safe async/await lock. |
| ... | ... | @@ -28,19 +26,19 @@ pub const RwLock = struct { |
| 28 | 26 | const ReadLock = 2; |
| 29 | 27 | }; |
| 30 | 28 | |
| 31 | const Queue = std.atomic.Queue(promise); | |
| 29 | const Queue = std.atomic.Queue(anyframe); | |
| 32 | 30 | |
| 33 | 31 | pub const HeldRead = struct { |
| 34 | 32 | lock: *RwLock, |
| 35 | 33 | |
| 36 | 34 | pub fn release(self: HeldRead) void { |
| 37 | 35 | // If other readers still hold the lock, we're done. |
| 38 | if (@atomicRmw(usize, &self.lock.reader_lock_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst) != 1) { | |
| 36 | if (@atomicRmw(usize, &self.lock.reader_lock_count, .Sub, 1, .SeqCst) != 1) { | |
| 39 | 37 | return; |
| 40 | 38 | } |
| 41 | 39 | |
| 42 | _ = @atomicRmw(u8, &self.lock.reader_queue_empty_bit, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst); | |
| 43 | if (@cmpxchgStrong(u8, &self.lock.shared_state, State.ReadLock, State.Unlocked, AtomicOrder.SeqCst, AtomicOrder.SeqCst) != null) { | |
| 40 | _ = @atomicRmw(u8, &self.lock.reader_queue_empty_bit, .Xchg, 1, .SeqCst); | |
| 41 | if (@cmpxchgStrong(u8, &self.lock.shared_state, State.ReadLock, State.Unlocked, .SeqCst, .SeqCst) != null) { | |
| 44 | 42 | // Didn't unlock. Someone else's problem. |
| 45 | 43 | return; |
| 46 | 44 | } |
| ... | ... | @@ -61,17 +59,17 @@ pub const RwLock = struct { |
| 61 | 59 | } |
| 62 | 60 | |
| 63 | 61 | // We need to release the write lock. Check if any readers are waiting to grab the lock. |
| 64 | if (@atomicLoad(u8, &self.lock.reader_queue_empty_bit, AtomicOrder.SeqCst) == 0) { | |
| 62 | if (@atomicLoad(u8, &self.lock.reader_queue_empty_bit, .SeqCst) == 0) { | |
| 65 | 63 | // Switch to a read lock. |
| 66 | _ = @atomicRmw(u8, &self.lock.shared_state, AtomicRmwOp.Xchg, State.ReadLock, AtomicOrder.SeqCst); | |
| 64 | _ = @atomicRmw(u8, &self.lock.shared_state, .Xchg, State.ReadLock, .SeqCst); | |
| 67 | 65 | while (self.lock.reader_queue.get()) |node| { |
| 68 | 66 | self.lock.loop.onNextTick(node); |
| 69 | 67 | } |
| 70 | 68 | return; |
| 71 | 69 | } |
| 72 | 70 | |
| 73 | _ = @atomicRmw(u8, &self.lock.writer_queue_empty_bit, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst); | |
| 74 | _ = @atomicRmw(u8, &self.lock.shared_state, AtomicRmwOp.Xchg, State.Unlocked, AtomicOrder.SeqCst); | |
| 71 | _ = @atomicRmw(u8, &self.lock.writer_queue_empty_bit, .Xchg, 1, .SeqCst); | |
| 72 | _ = @atomicRmw(u8, &self.lock.shared_state, .Xchg, State.Unlocked, .SeqCst); | |
| 75 | 73 | |
| 76 | 74 | self.lock.commonPostUnlock(); |
| 77 | 75 | } |
| ... | ... | @@ -93,17 +91,16 @@ pub const RwLock = struct { |
| 93 | 91 | /// All calls to acquire() and release() must complete before calling deinit(). |
| 94 | 92 | pub fn deinit(self: *RwLock) void { |
| 95 | 93 | assert(self.shared_state == State.Unlocked); |
| 96 | while (self.writer_queue.get()) |node| cancel node.data; | |
| 97 | while (self.reader_queue.get()) |node| cancel node.data; | |
| 94 | while (self.writer_queue.get()) |node| resume node.data; | |
| 95 | while (self.reader_queue.get()) |node| resume node.data; | |
| 98 | 96 | } |
| 99 | 97 | |
| 100 | 98 | pub async fn acquireRead(self: *RwLock) HeldRead { |
| 101 | _ = @atomicRmw(usize, &self.reader_lock_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); | |
| 99 | _ = @atomicRmw(usize, &self.reader_lock_count, .Add, 1, .SeqCst); | |
| 102 | 100 | |
| 103 | 101 | suspend { |
| 104 | // TODO explicitly put this memory in the coroutine frame #1194 | |
| 105 | 102 | var my_tick_node = Loop.NextTickNode{ |
| 106 | .data = @handle(), | |
| 103 | .data = @frame(), | |
| 107 | 104 | .prev = undefined, |
| 108 | 105 | .next = undefined, |
| 109 | 106 | }; |
| ... | ... | @@ -115,10 +112,10 @@ pub const RwLock = struct { |
| 115 | 112 | |
| 116 | 113 | // We set this bit so that later we can rely on the fact, that if reader_queue_empty_bit is 1, |
| 117 | 114 | // some actor will attempt to grab the lock. |
| 118 | _ = @atomicRmw(u8, &self.reader_queue_empty_bit, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst); | |
| 115 | _ = @atomicRmw(u8, &self.reader_queue_empty_bit, .Xchg, 0, .SeqCst); | |
| 119 | 116 | |
| 120 | 117 | // Here we don't care if we are the one to do the locking or if it was already locked for reading. |
| 121 | const have_read_lock = if (@cmpxchgStrong(u8, &self.shared_state, State.Unlocked, State.ReadLock, AtomicOrder.SeqCst, AtomicOrder.SeqCst)) |old_state| old_state == State.ReadLock else true; | |
| 118 | const have_read_lock = if (@cmpxchgStrong(u8, &self.shared_state, State.Unlocked, State.ReadLock, .SeqCst, .SeqCst)) |old_state| old_state == State.ReadLock else true; | |
| 122 | 119 | if (have_read_lock) { |
| 123 | 120 | // Give out all the read locks. |
| 124 | 121 | if (self.reader_queue.get()) |first_node| { |
| ... | ... | @@ -134,9 +131,8 @@ pub const RwLock = struct { |
| 134 | 131 | |
| 135 | 132 | pub async fn acquireWrite(self: *RwLock) HeldWrite { |
| 136 | 133 | suspend { |
| 137 | // TODO explicitly put this memory in the coroutine frame #1194 | |
| 138 | 134 | var my_tick_node = Loop.NextTickNode{ |
| 139 | .data = @handle(), | |
| 135 | .data = @frame(), | |
| 140 | 136 | .prev = undefined, |
| 141 | 137 | .next = undefined, |
| 142 | 138 | }; |
| ... | ... | @@ -148,10 +144,10 @@ pub const RwLock = struct { |
| 148 | 144 | |
| 149 | 145 | // We set this bit so that later we can rely on the fact, that if writer_queue_empty_bit is 1, |
| 150 | 146 | // some actor will attempt to grab the lock. |
| 151 | _ = @atomicRmw(u8, &self.writer_queue_empty_bit, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst); | |
| 147 | _ = @atomicRmw(u8, &self.writer_queue_empty_bit, .Xchg, 0, .SeqCst); | |
| 152 | 148 | |
| 153 | 149 | // Here we must be the one to acquire the write lock. It cannot already be locked. |
| 154 | if (@cmpxchgStrong(u8, &self.shared_state, State.Unlocked, State.WriteLock, AtomicOrder.SeqCst, AtomicOrder.SeqCst) == null) { | |
| 150 | if (@cmpxchgStrong(u8, &self.shared_state, State.Unlocked, State.WriteLock, .SeqCst, .SeqCst) == null) { | |
| 155 | 151 | // We now have a write lock. |
| 156 | 152 | if (self.writer_queue.get()) |node| { |
| 157 | 153 | // Whether this node is us or someone else, we tail resume it. |
| ... | ... | @@ -169,8 +165,8 @@ pub const RwLock = struct { |
| 169 | 165 | // obtain the lock. |
| 170 | 166 | // But if there's a writer_queue item or a reader_queue item, |
| 171 | 167 | // we are the actor which must loop and attempt to grab the lock again. |
| 172 | if (@atomicLoad(u8, &self.writer_queue_empty_bit, AtomicOrder.SeqCst) == 0) { | |
| 173 | if (@cmpxchgStrong(u8, &self.shared_state, State.Unlocked, State.WriteLock, AtomicOrder.SeqCst, AtomicOrder.SeqCst) != null) { | |
| 168 | if (@atomicLoad(u8, &self.writer_queue_empty_bit, .SeqCst) == 0) { | |
| 169 | if (@cmpxchgStrong(u8, &self.shared_state, State.Unlocked, State.WriteLock, .SeqCst, .SeqCst) != null) { | |
| 174 | 170 | // We did not obtain the lock. Great, the queues are someone else's problem. |
| 175 | 171 | return; |
| 176 | 172 | } |
| ... | ... | @@ -180,13 +176,13 @@ pub const RwLock = struct { |
| 180 | 176 | return; |
| 181 | 177 | } |
| 182 | 178 | // Release the lock again. |
| 183 | _ = @atomicRmw(u8, &self.writer_queue_empty_bit, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst); | |
| 184 | _ = @atomicRmw(u8, &self.shared_state, AtomicRmwOp.Xchg, State.Unlocked, AtomicOrder.SeqCst); | |
| 179 | _ = @atomicRmw(u8, &self.writer_queue_empty_bit, .Xchg, 1, .SeqCst); | |
| 180 | _ = @atomicRmw(u8, &self.shared_state, .Xchg, State.Unlocked, .SeqCst); | |
| 185 | 181 | continue; |
| 186 | 182 | } |
| 187 | 183 | |
| 188 | if (@atomicLoad(u8, &self.reader_queue_empty_bit, AtomicOrder.SeqCst) == 0) { | |
| 189 | if (@cmpxchgStrong(u8, &self.shared_state, State.Unlocked, State.ReadLock, AtomicOrder.SeqCst, AtomicOrder.SeqCst) != null) { | |
| 184 | if (@atomicLoad(u8, &self.reader_queue_empty_bit, .SeqCst) == 0) { | |
| 185 | if (@cmpxchgStrong(u8, &self.shared_state, State.Unlocked, State.ReadLock, .SeqCst, .SeqCst) != null) { | |
| 190 | 186 | // We did not obtain the lock. Great, the queues are someone else's problem. |
| 191 | 187 | return; |
| 192 | 188 | } |
| ... | ... | @@ -199,8 +195,8 @@ pub const RwLock = struct { |
| 199 | 195 | return; |
| 200 | 196 | } |
| 201 | 197 | // Release the lock again. |
| 202 | _ = @atomicRmw(u8, &self.reader_queue_empty_bit, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst); | |
| 203 | if (@cmpxchgStrong(u8, &self.shared_state, State.ReadLock, State.Unlocked, AtomicOrder.SeqCst, AtomicOrder.SeqCst) != null) { | |
| 198 | _ = @atomicRmw(u8, &self.reader_queue_empty_bit, .Xchg, 1, .SeqCst); | |
| 199 | if (@cmpxchgStrong(u8, &self.shared_state, State.ReadLock, State.Unlocked, .SeqCst, .SeqCst) != null) { | |
| 204 | 200 | // Didn't unlock. Someone else's problem. |
| 205 | 201 | return; |
| 206 | 202 | } |
| ... | ... | @@ -215,6 +211,9 @@ test "std.event.RwLock" { |
| 215 | 211 | // https://github.com/ziglang/zig/issues/2377 |
| 216 | 212 | if (true) return error.SkipZigTest; |
| 217 | 213 | |
| 214 | // https://github.com/ziglang/zig/issues/1908 | |
| 215 | if (builtin.single_threaded) return error.SkipZigTest; | |
| 216 | ||
| 218 | 217 | const allocator = std.heap.direct_allocator; |
| 219 | 218 | |
| 220 | 219 | var loop: Loop = undefined; |
| ... | ... | @@ -224,8 +223,7 @@ test "std.event.RwLock" { |
| 224 | 223 | var lock = RwLock.init(&loop); |
| 225 | 224 | defer lock.deinit(); |
| 226 | 225 | |
| 227 | const handle = try async<allocator> testLock(&loop, &lock); | |
| 228 | defer cancel handle; | |
| 226 | const handle = testLock(&loop, &lock); | |
| 229 | 227 | loop.run(); |
| 230 | 228 | |
| 231 | 229 | const expected_result = [1]i32{shared_it_count * @intCast(i32, shared_test_data.len)} ** shared_test_data.len; |
| ... | ... | @@ -233,28 +231,31 @@ test "std.event.RwLock" { |
| 233 | 231 | } |
| 234 | 232 | |
| 235 | 233 | async fn testLock(loop: *Loop, lock: *RwLock) void { |
| 236 | // TODO explicitly put next tick node memory in the coroutine frame #1194 | |
| 237 | suspend { | |
| 238 | resume @handle(); | |
| 239 | } | |
| 240 | ||
| 241 | 234 | var read_nodes: [100]Loop.NextTickNode = undefined; |
| 242 | 235 | for (read_nodes) |*read_node| { |
| 243 | read_node.data = async readRunner(lock) catch @panic("out of memory"); | |
| 236 | const frame = loop.allocator.create(@Frame(readRunner)) catch @panic("memory"); | |
| 237 | read_node.data = frame; | |
| 238 | frame.* = async readRunner(lock); | |
| 244 | 239 | loop.onNextTick(read_node); |
| 245 | 240 | } |
| 246 | 241 | |
| 247 | 242 | var write_nodes: [shared_it_count]Loop.NextTickNode = undefined; |
| 248 | 243 | for (write_nodes) |*write_node| { |
| 249 | write_node.data = async writeRunner(lock) catch @panic("out of memory"); | |
| 244 | const frame = loop.allocator.create(@Frame(writeRunner)) catch @panic("memory"); | |
| 245 | write_node.data = frame; | |
| 246 | frame.* = async writeRunner(lock); | |
| 250 | 247 | loop.onNextTick(write_node); |
| 251 | 248 | } |
| 252 | 249 | |
| 253 | 250 | for (write_nodes) |*write_node| { |
| 254 | await @ptrCast(promise->void, write_node.data); | |
| 251 | const casted = @ptrCast(*const @Frame(writeRunner), write_node.data); | |
| 252 | await casted; | |
| 253 | loop.allocator.destroy(casted); | |
| 255 | 254 | } |
| 256 | 255 | for (read_nodes) |*read_node| { |
| 257 | await @ptrCast(promise->void, read_node.data); | |
| 256 | const casted = @ptrCast(*const @Frame(readRunner), read_node.data); | |
| 257 | await casted; | |
| 258 | loop.allocator.destroy(casted); | |
| 258 | 259 | } |
| 259 | 260 | } |
| 260 | 261 | |
| ... | ... | @@ -269,7 +270,7 @@ async fn writeRunner(lock: *RwLock) void { |
| 269 | 270 | var i: usize = 0; |
| 270 | 271 | while (i < shared_test_data.len) : (i += 1) { |
| 271 | 272 | std.time.sleep(100 * std.time.microsecond); |
| 272 | const lock_promise = async lock.acquireWrite() catch @panic("out of memory"); | |
| 273 | const lock_promise = async lock.acquireWrite(); | |
| 273 | 274 | const handle = await lock_promise; |
| 274 | 275 | defer handle.release(); |
| 275 | 276 | |
| ... | ... | @@ -287,7 +288,7 @@ async fn readRunner(lock: *RwLock) void { |
| 287 | 288 | |
| 288 | 289 | var i: usize = 0; |
| 289 | 290 | while (i < shared_test_data.len) : (i += 1) { |
| 290 | const lock_promise = async lock.acquireRead() catch @panic("out of memory"); | |
| 291 | const lock_promise = async lock.acquireRead(); | |
| 291 | 292 | const handle = await lock_promise; |
| 292 | 293 | defer handle.release(); |
| 293 | 294 |
std/zig/parser_test.zig+1-1| ... | ... | @@ -1183,7 +1183,7 @@ test "zig fmt: resume from suspend block" { |
| 1183 | 1183 | try testCanonical( |
| 1184 | 1184 | \\fn foo() void { |
| 1185 | 1185 | \\ suspend { |
| 1186 | \\ resume @handle(); | |
| 1186 | \\ resume @frame(); | |
| 1187 | 1187 | \\ } |
| 1188 | 1188 | \\} |
| 1189 | 1189 | \\ |
test/compile_errors.zig+9-26| ... | ... | @@ -1403,24 +1403,14 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { |
| 1403 | 1403 | ); |
| 1404 | 1404 | |
| 1405 | 1405 | cases.add( |
| 1406 | "@handle() called outside of function definition", | |
| 1407 | \\var handle_undef: promise = undefined; | |
| 1408 | \\var handle_dummy: promise = @handle(); | |
| 1406 | "@frame() called outside of function definition", | |
| 1407 | \\var handle_undef: anyframe = undefined; | |
| 1408 | \\var handle_dummy: anyframe = @frame(); | |
| 1409 | 1409 | \\export fn entry() bool { |
| 1410 | 1410 | \\ return handle_undef == handle_dummy; |
| 1411 | 1411 | \\} |
| 1412 | 1412 | , |
| 1413 | "tmp.zig:2:29: error: @handle() called outside of function definition", | |
| 1414 | ); | |
| 1415 | ||
| 1416 | cases.add( | |
| 1417 | "@handle() in non-async function", | |
| 1418 | \\export fn entry() bool { | |
| 1419 | \\ var handle_undef: promise = undefined; | |
| 1420 | \\ return handle_undef == @handle(); | |
| 1421 | \\} | |
| 1422 | , | |
| 1423 | "tmp.zig:3:28: error: @handle() in non-async function", | |
| 1413 | "tmp.zig:2:30: error: @frame() called outside of function definition", | |
| 1424 | 1414 | ); |
| 1425 | 1415 | |
| 1426 | 1416 | cases.add( |
| ... | ... | @@ -1796,15 +1786,9 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { |
| 1796 | 1786 | |
| 1797 | 1787 | cases.add( |
| 1798 | 1788 | "suspend inside suspend block", |
| 1799 | \\const std = @import("std",); | |
| 1800 | \\ | |
| 1801 | 1789 | \\export fn entry() void { |
| 1802 | \\ var buf: [500]u8 = undefined; | |
| 1803 | \\ var a = &std.heap.FixedBufferAllocator.init(buf[0..]).allocator; | |
| 1804 | \\ const p = (async<a> foo()) catch unreachable; | |
| 1805 | \\ cancel p; | |
| 1790 | \\ _ = async foo(); | |
| 1806 | 1791 | \\} |
| 1807 | \\ | |
| 1808 | 1792 | \\async fn foo() void { |
| 1809 | 1793 | \\ suspend { |
| 1810 | 1794 | \\ suspend { |
| ... | ... | @@ -1812,8 +1796,8 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { |
| 1812 | 1796 | \\ } |
| 1813 | 1797 | \\} |
| 1814 | 1798 | , |
| 1815 | "tmp.zig:12:9: error: cannot suspend inside suspend block", | |
| 1816 | "tmp.zig:11:5: note: other suspend block here", | |
| 1799 | "tmp.zig:6:9: error: cannot suspend inside suspend block", | |
| 1800 | "tmp.zig:5:5: note: other suspend block here", | |
| 1817 | 1801 | ); |
| 1818 | 1802 | |
| 1819 | 1803 | cases.add( |
| ... | ... | @@ -1854,15 +1838,14 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { |
| 1854 | 1838 | |
| 1855 | 1839 | cases.add( |
| 1856 | 1840 | "returning error from void async function", |
| 1857 | \\const std = @import("std",); | |
| 1858 | 1841 | \\export fn entry() void { |
| 1859 | \\ const p = async<std.debug.global_allocator> amain() catch unreachable; | |
| 1842 | \\ _ = async amain(); | |
| 1860 | 1843 | \\} |
| 1861 | 1844 | \\async fn amain() void { |
| 1862 | 1845 | \\ return error.ShouldBeCompileError; |
| 1863 | 1846 | \\} |
| 1864 | 1847 | , |
| 1865 | "tmp.zig:6:17: error: expected type 'void', found 'error{ShouldBeCompileError}'", | |
| 1848 | "tmp.zig:5:17: error: expected type 'void', found 'error{ShouldBeCompileError}'", | |
| 1866 | 1849 | ); |
| 1867 | 1850 | |
| 1868 | 1851 | cases.add( |