| ... | ... | @@ -182,7 +182,7 @@ const Group = struct { |
| 182 | 182 | const Task = struct { |
| 183 | 183 | runnable: Runnable, |
| 184 | 184 | group: *Io.Group, |
| 185 | | func: *const fn (context: *const anyopaque) void, |
| 185 | func: *const fn (context: *const anyopaque) Io.Cancelable!void, |
| 186 | 186 | context_alignment: Alignment, |
| 187 | 187 | alloc_len: usize, |
| 188 | 188 | |
| ... | ... | @@ -192,7 +192,7 @@ const Group = struct { |
| 192 | 192 | group: Group, |
| 193 | 193 | context: []const u8, |
| 194 | 194 | context_alignment: Alignment, |
| 195 | | func: *const fn (context: *const anyopaque) void, |
| 195 | func: *const fn (context: *const anyopaque) Io.Cancelable!void, |
| 196 | 196 | ) Allocator.Error!*Task { |
| 197 | 197 | const max_context_misalignment = context_alignment.toByteUnits() -| @alignOf(Task); |
| 198 | 198 | const worst_case_context_offset = context_alignment.forward(@sizeOf(Task) + max_context_misalignment); |
| ... | ... | @@ -247,7 +247,20 @@ const Group = struct { |
| 247 | 247 | }, .monotonic); |
| 248 | 248 | } |
| 249 | 249 | |
| 250 | | assertGroupResult(task.func(task.contextPointer())); |
| 250 | const result = task.func(task.contextPointer()); |
| 251 | const cancel_acknowledged = switch (thread.status.load(.monotonic).cancelation) { |
| 252 | .none, .canceling => false, |
| 253 | .canceled => true, |
| 254 | .parked => unreachable, |
| 255 | .blocked => unreachable, |
| 256 | .blocked_windows_dns => unreachable, |
| 257 | .blocked_canceling => unreachable, |
| 258 | }; |
| 259 | if (result) { |
| 260 | assert(!cancel_acknowledged); // group task acknowledged cancelation but did not return `error.Canceled` |
| 261 | } else |err| switch (err) { |
| 262 | error.Canceled => assert(cancel_acknowledged), // group task returned `error.Canceled` but was never canceled |
| 263 | } |
| 251 | 264 | |
| 252 | 265 | thread.status.store(.{ .cancelation = .none, .awaitable = .null }, .monotonic); |
| 253 | 266 | const old_status = group.status().fetchSub(.{ |
| ... | ... | @@ -348,7 +361,8 @@ const Future = struct { |
| 348 | 361 | pending_awaited = 0b01, |
| 349 | 362 | /// Like `pending`, but the future is being canceled. `Future.awaiter` is populated. |
| 350 | 363 | pending_canceled = 0b11, |
| 351 | | /// The future has already completed. `thread` is `null`. |
| 364 | /// The future has already completed. `thread` is `.null`, unless the future terminated |
| 365 | /// with an acknowledged cancel request, in which case `thread` is `.all_ones`. |
| 352 | 366 | done = 0b10, |
| 353 | 367 | }, |
| 354 | 368 | /// When the future begins execution, this is atomically updated from `null` to the thread running the |
| ... | ... | @@ -437,10 +451,18 @@ const Future = struct { |
| 437 | 451 | |
| 438 | 452 | future.func(future.contextPointer(), future.resultPointer()); |
| 439 | 453 | |
| 454 | const had_acknowledged_cancel = switch (thread.status.load(.monotonic).cancelation) { |
| 455 | .none, .canceling => false, |
| 456 | .canceled => true, |
| 457 | .parked => unreachable, |
| 458 | .blocked => unreachable, |
| 459 | .blocked_windows_dns => unreachable, |
| 460 | .blocked_canceling => unreachable, |
| 461 | }; |
| 440 | 462 | thread.status.store(.{ .cancelation = .none, .awaitable = .null }, .monotonic); |
| 441 | 463 | const old_status = future.status.swap(.{ |
| 442 | 464 | .tag = .done, |
| 443 | | .thread = .null, |
| 465 | .thread = if (had_acknowledged_cancel) .all_ones else .null, |
| 444 | 466 | }, .acq_rel); // acquire `future.awaiter`, release results |
| 445 | 467 | switch (old_status.tag) { |
| 446 | 468 | .pending => {}, |
| ... | ... | @@ -1712,11 +1734,11 @@ fn groupAsync( |
| 1712 | 1734 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 1713 | 1735 | const g: Group = .{ .ptr = type_erased }; |
| 1714 | 1736 | |
| 1715 | | if (builtin.single_threaded) return start(context.ptr) catch unreachable; |
| 1737 | if (builtin.single_threaded) return groupAsyncEager(start, context.ptr); |
| 1716 | 1738 | |
| 1717 | 1739 | const gpa = t.allocator; |
| 1718 | 1740 | const task = Group.Task.create(gpa, g, context, context_alignment, start) catch |err| switch (err) { |
| 1719 | | error.OutOfMemory => return t.assertGroupResult(start(context.ptr)), |
| 1741 | error.OutOfMemory => return groupAsyncEager(start, context.ptr), |
| 1720 | 1742 | }; |
| 1721 | 1743 | |
| 1722 | 1744 | t.mutex.lock(); |
| ... | ... | @@ -1726,7 +1748,7 @@ fn groupAsync( |
| 1726 | 1748 | if (busy_count >= @intFromEnum(t.async_limit)) { |
| 1727 | 1749 | t.mutex.unlock(); |
| 1728 | 1750 | task.destroy(gpa); |
| 1729 | | return t.assertGroupResult(start(context.ptr)); |
| 1751 | return groupAsyncEager(start, context.ptr); |
| 1730 | 1752 | } |
| 1731 | 1753 | |
| 1732 | 1754 | t.busy_count = busy_count + 1; |
| ... | ... | @@ -1739,7 +1761,7 @@ fn groupAsync( |
| 1739 | 1761 | t.busy_count = busy_count; |
| 1740 | 1762 | t.mutex.unlock(); |
| 1741 | 1763 | task.destroy(gpa); |
| 1742 | | return t.assertGroupResult(start(context.ptr)); |
| 1764 | return groupAsyncEager(start, context.ptr); |
| 1743 | 1765 | }; |
| 1744 | 1766 | thread.detach(); |
| 1745 | 1767 | } |
| ... | ... | @@ -1757,23 +1779,45 @@ fn groupAsync( |
| 1757 | 1779 | t.mutex.unlock(); |
| 1758 | 1780 | t.cond.signal(); |
| 1759 | 1781 | } |
| 1760 | | |
| 1761 | | fn assertGroupResult(result: Io.Cancelable!void) void { |
| 1762 | | const cancel_acknowledged = if (Thread.current) |thread| |
| 1763 | | switch (thread.status.load(.monotonic).cancelation) { |
| 1782 | fn groupAsyncEager( |
| 1783 | start: *const fn (context: *const anyopaque) Io.Cancelable!void, |
| 1784 | context: *const anyopaque, |
| 1785 | ) void { |
| 1786 | const pre_acknowledged = if (Thread.current) |thread| ack: { |
| 1787 | break :ack switch (thread.status.load(.monotonic).cancelation) { |
| 1764 | 1788 | .none, .canceling => false, |
| 1765 | 1789 | .canceled => true, |
| 1766 | 1790 | .parked => unreachable, |
| 1767 | 1791 | .blocked => unreachable, |
| 1768 | 1792 | .blocked_windows_dns => unreachable, |
| 1769 | 1793 | .blocked_canceling => unreachable, |
| 1770 | | } |
| 1771 | | else |
| 1772 | | false; |
| 1794 | }; |
| 1795 | } else false; |
| 1796 | const result = start(context); |
| 1797 | const post_acknowledged = if (Thread.current) |thread| ack: { |
| 1798 | break :ack switch (thread.status.load(.monotonic).cancelation) { |
| 1799 | .none, .canceling => false, |
| 1800 | .canceled => true, |
| 1801 | .parked => unreachable, |
| 1802 | .blocked => unreachable, |
| 1803 | .blocked_windows_dns => unreachable, |
| 1804 | .blocked_canceling => unreachable, |
| 1805 | }; |
| 1806 | } else false; |
| 1807 | |
| 1773 | 1808 | if (result) { |
| 1774 | | assert(!cancel_acknowledged); // group task acknowledged cancelation but did not return `error.Canceled` |
| 1809 | if (pre_acknowledged) { |
| 1810 | assert(post_acknowledged); // group task called `recancel` but was not canceled |
| 1811 | } else { |
| 1812 | assert(!post_acknowledged); // group task acknowledged cancelation but did not return `error.Canceled` |
| 1813 | } |
| 1775 | 1814 | } else |err| switch (err) { |
| 1776 | | error.Canceled => assert(cancel_acknowledged), // group task returned `error.Canceled` but was never canceled |
| 1815 | // Don't swallow the cancelation: make it visible to the `Group.async` caller. |
| 1816 | error.Canceled => { |
| 1817 | assert(!pre_acknowledged); // group task called `recancel` but was not canceled |
| 1818 | assert(post_acknowledged); // group task returned `error.Canceled` but was never canceled |
| 1819 | recancelInner(); |
| 1820 | }, |
| 1777 | 1821 | } |
| 1778 | 1822 | } |
| 1779 | 1823 | |
| ... | ... | @@ -1920,6 +1964,9 @@ fn groupCancel(userdata: ?*anyopaque, type_erased: *Io.Group, initial_token: *an |
| 1920 | 1964 | fn recancel(userdata: ?*anyopaque) void { |
| 1921 | 1965 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 1922 | 1966 | _ = t; |
| 1967 | recancelInner(); |
| 1968 | } |
| 1969 | fn recancelInner() void { |
| 1923 | 1970 | const thread = Thread.current.?; // called `recancel` but was not canceled |
| 1924 | 1971 | switch (thread.status.fetchXor(.{ |
| 1925 | 1972 | .cancelation = @enumFromInt(0b001), |
| ... | ... | @@ -1993,7 +2040,16 @@ fn await( |
| 1993 | 2040 | future.waitForCancelWithSignaling(t, &num_completed, null); |
| 1994 | 2041 | }, |
| 1995 | 2042 | } |
| 1996 | | recancel(t); |
| 2043 | // If the future did not acknowledge the cancelation, we need to mark it outstanding |
| 2044 | // for us. Because `future.status.tag == .done`, the information about whether there |
| 2045 | // was an acknowledged cancelation is encoded in `future.status.thread`. |
| 2046 | const final_status = future.status.load(.monotonic); |
| 2047 | assert(final_status.tag == .done); |
| 2048 | switch (final_status.thread) { |
| 2049 | .null => recancelInner(), // cancelation was not acknowledged, so it's ours |
| 2050 | .all_ones => {}, // cancelation was acknowledged, so it was this task's job to propagate it |
| 2051 | _ => unreachable, |
| 2052 | } |
| 1997 | 2053 | }, |
| 1998 | 2054 | }, |
| 1999 | 2055 | .pending_awaited => unreachable, // `await` raced with `await` |
| ... | ... | @@ -11669,7 +11725,7 @@ fn unlockStderr(userdata: ?*anyopaque) void { |
| 11669 | 11725 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 11670 | 11726 | t.stderr_writer.interface.flush() catch |err| switch (err) { |
| 11671 | 11727 | error.WriteFailed => switch (t.stderr_writer.err.?) { |
| 11672 | | error.Canceled => recancel(t), |
| 11728 | error.Canceled => recancelInner(), |
| 11673 | 11729 | else => {}, |
| 11674 | 11730 | }, |
| 11675 | 11731 | }; |