| ... | @@ -182,7 +182,7 @@ const Group = struct { | ... | @@ -182,7 +182,7 @@ const Group = struct { |
| 182 | const Task = struct { | 182 | const Task = struct { |
| 183 | runnable: Runnable, | 183 | runnable: Runnable, |
| 184 | group: *Io.Group, | 184 | group: *Io.Group, |
| 185 | func: *const fn (context: *const anyopaque) void, | 185 | func: *const fn (context: *const anyopaque) Io.Cancelable!void, |
| 186 | context_alignment: Alignment, | 186 | context_alignment: Alignment, |
| 187 | alloc_len: usize, | 187 | alloc_len: usize, |
| 188 | | 188 | |
| ... | @@ -192,7 +192,7 @@ const Group = struct { | ... | @@ -192,7 +192,7 @@ const Group = struct { |
| 192 | group: Group, | 192 | group: Group, |
| 193 | context: []const u8, | 193 | context: []const u8, |
| 194 | context_alignment: Alignment, | 194 | context_alignment: Alignment, |
| 195 | func: *const fn (context: *const anyopaque) void, | 195 | func: *const fn (context: *const anyopaque) Io.Cancelable!void, |
| 196 | ) Allocator.Error!*Task { | 196 | ) Allocator.Error!*Task { |
| 197 | const max_context_misalignment = context_alignment.toByteUnits() -| @alignOf(Task); | 197 | const max_context_misalignment = context_alignment.toByteUnits() -| @alignOf(Task); |
| 198 | const worst_case_context_offset = context_alignment.forward(@sizeOf(Task) + max_context_misalignment); | 198 | const worst_case_context_offset = context_alignment.forward(@sizeOf(Task) + max_context_misalignment); |
| ... | @@ -247,7 +247,20 @@ const Group = struct { | ... | @@ -247,7 +247,20 @@ const Group = struct { |
| 247 | }, .monotonic); | 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 | thread.status.store(.{ .cancelation = .none, .awaitable = .null }, .monotonic); | 265 | thread.status.store(.{ .cancelation = .none, .awaitable = .null }, .monotonic); |
| 253 | const old_status = group.status().fetchSub(.{ | 266 | const old_status = group.status().fetchSub(.{ |
| ... | @@ -348,7 +361,8 @@ const Future = struct { | ... | @@ -348,7 +361,8 @@ const Future = struct { |
| 348 | pending_awaited = 0b01, | 361 | pending_awaited = 0b01, |
| 349 | /// Like `pending`, but the future is being canceled. `Future.awaiter` is populated. | 362 | /// Like `pending`, but the future is being canceled. `Future.awaiter` is populated. |
| 350 | pending_canceled = 0b11, | 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 | done = 0b10, | 366 | done = 0b10, |
| 353 | }, | 367 | }, |
| 354 | /// When the future begins execution, this is atomically updated from `null` to the thread running the | 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,10 +451,18 @@ const Future = struct { |
| 437 | | 451 | |
| 438 | future.func(future.contextPointer(), future.resultPointer()); | 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 | thread.status.store(.{ .cancelation = .none, .awaitable = .null }, .monotonic); | 462 | thread.status.store(.{ .cancelation = .none, .awaitable = .null }, .monotonic); |
| 441 | const old_status = future.status.swap(.{ | 463 | const old_status = future.status.swap(.{ |
| 442 | .tag = .done, | 464 | .tag = .done, |
| 443 | .thread = .null, | 465 | .thread = if (had_acknowledged_cancel) .all_ones else .null, |
| 444 | }, .acq_rel); // acquire `future.awaiter`, release results | 466 | }, .acq_rel); // acquire `future.awaiter`, release results |
| 445 | switch (old_status.tag) { | 467 | switch (old_status.tag) { |
| 446 | .pending => {}, | 468 | .pending => {}, |
| ... | @@ -1712,11 +1734,11 @@ fn groupAsync( | ... | @@ -1712,11 +1734,11 @@ fn groupAsync( |
| 1712 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 1734 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 1713 | const g: Group = .{ .ptr = type_erased }; | 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 | const gpa = t.allocator; | 1739 | const gpa = t.allocator; |
| 1718 | const task = Group.Task.create(gpa, g, context, context_alignment, start) catch |err| switch (err) { | 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 | t.mutex.lock(); | 1744 | t.mutex.lock(); |
| ... | @@ -1726,7 +1748,7 @@ fn groupAsync( | ... | @@ -1726,7 +1748,7 @@ fn groupAsync( |
| 1726 | if (busy_count >= @intFromEnum(t.async_limit)) { | 1748 | if (busy_count >= @intFromEnum(t.async_limit)) { |
| 1727 | t.mutex.unlock(); | 1749 | t.mutex.unlock(); |
| 1728 | task.destroy(gpa); | 1750 | task.destroy(gpa); |
| 1729 | return t.assertGroupResult(start(context.ptr)); | 1751 | return groupAsyncEager(start, context.ptr); |
| 1730 | } | 1752 | } |
| 1731 | | 1753 | |
| 1732 | t.busy_count = busy_count + 1; | 1754 | t.busy_count = busy_count + 1; |
| ... | @@ -1739,7 +1761,7 @@ fn groupAsync( | ... | @@ -1739,7 +1761,7 @@ fn groupAsync( |
| 1739 | t.busy_count = busy_count; | 1761 | t.busy_count = busy_count; |
| 1740 | t.mutex.unlock(); | 1762 | t.mutex.unlock(); |
| 1741 | task.destroy(gpa); | 1763 | task.destroy(gpa); |
| 1742 | return t.assertGroupResult(start(context.ptr)); | 1764 | return groupAsyncEager(start, context.ptr); |
| 1743 | }; | 1765 | }; |
| 1744 | thread.detach(); | 1766 | thread.detach(); |
| 1745 | } | 1767 | } |
| ... | @@ -1757,23 +1779,45 @@ fn groupAsync( | ... | @@ -1757,23 +1779,45 @@ fn groupAsync( |
| 1757 | t.mutex.unlock(); | 1779 | t.mutex.unlock(); |
| 1758 | t.cond.signal(); | 1780 | t.cond.signal(); |
| 1759 | } | 1781 | } |
| 1760 | | 1782 | fn groupAsyncEager( |
| 1761 | fn assertGroupResult(result: Io.Cancelable!void) void { | 1783 | start: *const fn (context: *const anyopaque) Io.Cancelable!void, |
| 1762 | const cancel_acknowledged = if (Thread.current) |thread| | 1784 | context: *const anyopaque, |
| 1763 | switch (thread.status.load(.monotonic).cancelation) { | 1785 | ) void { |
| | 1786 | const pre_acknowledged = if (Thread.current) |thread| ack: { |
| | 1787 | break :ack switch (thread.status.load(.monotonic).cancelation) { |
| 1764 | .none, .canceling => false, | 1788 | .none, .canceling => false, |
| 1765 | .canceled => true, | 1789 | .canceled => true, |
| 1766 | .parked => unreachable, | 1790 | .parked => unreachable, |
| 1767 | .blocked => unreachable, | 1791 | .blocked => unreachable, |
| 1768 | .blocked_windows_dns => unreachable, | 1792 | .blocked_windows_dns => unreachable, |
| 1769 | .blocked_canceling => unreachable, | 1793 | .blocked_canceling => unreachable, |
| 1770 | } | 1794 | }; |
| 1771 | else | 1795 | } else false; |
| 1772 | 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 | if (result) { | 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 | } else |err| switch (err) { | 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,6 +1964,9 @@ fn groupCancel(userdata: ?*anyopaque, type_erased: *Io.Group, initial_token: *an |
| 1920 | fn recancel(userdata: ?*anyopaque) void { | 1964 | fn recancel(userdata: ?*anyopaque) void { |
| 1921 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 1965 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 1922 | _ = t; | 1966 | _ = t; |
| | 1967 | recancelInner(); |
| | 1968 | } |
| | 1969 | fn recancelInner() void { |
| 1923 | const thread = Thread.current.?; // called `recancel` but was not canceled | 1970 | const thread = Thread.current.?; // called `recancel` but was not canceled |
| 1924 | switch (thread.status.fetchXor(.{ | 1971 | switch (thread.status.fetchXor(.{ |
| 1925 | .cancelation = @enumFromInt(0b001), | 1972 | .cancelation = @enumFromInt(0b001), |
| ... | @@ -1993,7 +2040,16 @@ fn await( | ... | @@ -1993,7 +2040,16 @@ fn await( |
| 1993 | future.waitForCancelWithSignaling(t, &num_completed, null); | 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 | .pending_awaited => unreachable, // `await` raced with `await` | 2055 | .pending_awaited => unreachable, // `await` raced with `await` |
| ... | @@ -11669,7 +11725,7 @@ fn unlockStderr(userdata: ?*anyopaque) void { | ... | @@ -11669,7 +11725,7 @@ fn unlockStderr(userdata: ?*anyopaque) void { |
| 11669 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 11725 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 11670 | t.stderr_writer.interface.flush() catch |err| switch (err) { | 11726 | t.stderr_writer.interface.flush() catch |err| switch (err) { |
| 11671 | error.WriteFailed => switch (t.stderr_writer.err.?) { | 11727 | error.WriteFailed => switch (t.stderr_writer.err.?) { |
| 11672 | error.Canceled => recancel(t), | 11728 | error.Canceled => recancelInner(), |
| 11673 | else => {}, | 11729 | else => {}, |
| 11674 | }, | 11730 | }, |
| 11675 | }; | 11731 | }; |