Skip to content

Commit 2acb403

Browse files
committed
CABI: rename SuspendResult to Cancelled (no behavior change)
1 parent 7b5faca commit 2acb403

4 files changed

Lines changed: 157 additions & 191 deletions

File tree

design/mvp/CanonicalABI.md

Lines changed: 58 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ class Store:
140140
random.shuffle(self.pending)
141141
for thread in self.pending:
142142
if thread.ready():
143-
thread.resume()
143+
thread.resume(Cancelled.FALSE)
144144
return
145145
```
146146
The `Store.tick` method does not have an analogue in Core WebAssembly and
@@ -617,14 +617,14 @@ ability to switch stacks. In any case, the use of `threading.Thread` is
617617
encapsulated by the `Thread` class so that the rest of the Canonical ABI can
618618
simply use `suspend`, `resume`, etc.
619619

620-
When a `Thread` is suspended and then resumed, it receives a `SuspendResult`
620+
When a `Thread` is suspended and then resumed, it receives a `Cancelled`
621621
value indicating whether the caller has cooperatively requested that the thread
622622
cancel itself which is communicated to Core WebAssembly with the following
623623
integer values:
624624
```python
625-
class SuspendResult(IntEnum):
626-
NOT_CANCELLED = 0
627-
CANCELLED = 1
625+
class Cancelled(IntEnum):
626+
FALSE = 0
627+
TRUE = 1
628628
```
629629

630630
Introducing the `Thread` class in chunks, a `Thread` has the following fields
@@ -642,7 +642,7 @@ class Thread:
642642
parent_lock: Optional[threading.Lock]
643643
ready_func: Optional[Callable[[], bool]]
644644
cancellable: bool
645-
suspend_result: Optional[SuspendResult]
645+
cancelled: Cancelled
646646
index: Optional[int]
647647
context: list[int]
648648

@@ -678,15 +678,13 @@ immediately blocked `acquire()`ing `fiber_lock` (which will be `release()`ed by
678678
self.parent_lock = None
679679
self.ready_func = None
680680
self.cancellable = False
681-
self.suspend_result = None
681+
self.cancelled = Cancelled.FALSE
682682
self.index = None
683683
self.context = [0] * Thread.CONTEXT_LENGTH
684684
def fiber_func():
685685
self.fiber_lock.acquire()
686-
assert(self.running() and self.suspend_result == SuspendResult.NOT_CANCELLED)
687-
self.suspend_result = None
688-
thread_func(self)
689686
assert(self.running())
687+
thread_func(self)
690688
self.task.thread_stop(self)
691689
if self.index is not None:
692690
self.task.inst.threads.remove(self.index)
@@ -721,7 +719,7 @@ resumed thread will `release()` when it suspends or exits.
721719
When a thread calls `Thread.suspend`, it indicates whether it is able to handle
722720
cancellation. This information is stored in the `cancellable` field which is
723721
used by `Task.request_cancellation` (defined below) to only `resume` with
724-
`SuspendResult.CANCELLED` when the thread expects it.
722+
`Cancelled.TRUE` when the thread expects it.
725723

726724
Lastly, several `Thread` methods below will set the `ready_func` and add the
727725
`Thread` to the `Store.pending` list so that `Store.tick` will call `resume`
@@ -732,34 +730,28 @@ when the `ready_func` returns `True`. Once `Thread.resume` is called, the
732730
Given the above, `Thread.resume` and `Thread.suspend` can be defined
733731
complementarily using `parent_lock` and `fiber_lock` as follows:
734732
```python
735-
def resume(self, suspend_result = SuspendResult.NOT_CANCELLED):
736-
assert(not self.running() and self.suspend_result is None)
733+
def resume(self, cancelled):
734+
assert(not self.running() and (self.cancellable or not cancelled))
737735
if self.ready_func:
738-
assert(suspend_result == SuspendResult.CANCELLED or self.ready_func())
736+
assert(cancelled or self.ready_func())
739737
self.ready_func = None
740738
self.task.inst.store.pending.remove(self)
741-
assert(self.cancellable or suspend_result == SuspendResult.NOT_CANCELLED)
742-
self.suspend_result = suspend_result
739+
assert(self.cancellable or not cancelled)
740+
self.cancelled = cancelled
743741
self.parent_lock = threading.Lock()
744742
self.parent_lock.acquire()
745743
self.fiber_lock.release()
746744
self.parent_lock.acquire()
747745
self.parent_lock = None
748746
assert(not self.running())
749747

750-
def suspend(self, cancellable) -> SuspendResult:
751-
assert(self.task.may_block())
752-
assert(self.running() and not self.cancellable and self.suspend_result is None)
748+
def suspend(self, cancellable) -> Cancelled:
749+
assert(self.running() and self.task.may_block())
753750
self.cancellable = cancellable
754751
self.parent_lock.release()
755752
self.fiber_lock.acquire()
756-
assert(self.running())
757-
self.cancellable = False
758-
suspend_result = self.suspend_result
759-
self.suspend_result = None
760-
assert(suspend_result is not None)
761-
assert(cancellable or suspend_result == SuspendResult.NOT_CANCELLED)
762-
return suspend_result
753+
assert(self.running() and (cancellable or not self.cancelled))
754+
return self.cancelled
763755
```
764756

765757
The `Thread.resume_later` method is called by `canon_thread_resume_later` below
@@ -776,11 +768,10 @@ in the near future:
776768
The `Thread.suspend_until` method is used by a multiple internal callers below
777769
to specify a custom `ready_func` that is polled by `Store.tick`:
778770
```python
779-
def suspend_until(self, ready_func, cancellable = False) -> SuspendResult:
780-
assert(self.task.may_block())
781-
assert(self.running())
771+
def suspend_until(self, ready_func, cancellable = False) -> Cancelled:
772+
assert(self.running() and self.task.may_block())
782773
if ready_func() and not DETERMINISTIC_PROFILE and random.randint(0,1):
783-
return SuspendResult.NOT_CANCELLED
774+
return Cancelled.FALSE
784775
self.ready_func = ready_func
785776
self.task.inst.store.pending.append(self)
786777
return self.suspend(cancellable)
@@ -801,32 +792,26 @@ of internal `thread.switch-to`s before suspending, the `async`-lowered caller
801792
resumes execution immediately (as if there were no `thread.switch-to` and
802793
[Asyncify] was used to emulate stack switching instead).
803794
```python
804-
def switch_to(self, cancellable, other: Thread) -> SuspendResult:
805-
assert(self.running() and not self.cancellable and self.suspend_result is None)
806-
assert(other.suspended() and other.suspend_result is None)
795+
def switch_to(self, cancellable, other: Thread) -> Cancelled:
796+
assert(self.running() and other.suspended())
807797
self.cancellable = cancellable
808-
other.suspend_result = SuspendResult.NOT_CANCELLED
798+
other.cancelled = Cancelled.FALSE
809799
assert(self.parent_lock and not other.parent_lock)
810800
other.parent_lock = self.parent_lock
811801
self.parent_lock = None
812802
assert(not self.running() and other.running())
813803
other.fiber_lock.release()
814804
self.fiber_lock.acquire()
815-
assert(self.running())
816-
self.cancellable = False
817-
suspend_result = self.suspend_result
818-
self.suspend_result = None
819-
assert(suspend_result is not None)
820-
assert(cancellable or suspend_result == SuspendResult.NOT_CANCELLED)
821-
return suspend_result
805+
assert(self.running() and (cancellable or not self.cancelled))
806+
return self.cancelled
822807
```
823808

824809
Lastly, the `Thread.yield_to` method is used by `canon_thread_yield_to` below
825810
to switch execution to some other thread (like `Thread.switch_to`), but leave
826811
the current thread `ready` instead of `suspended`.
827812
```python
828-
def yield_to(self, cancellable, other: Thread) -> SuspendResult:
829-
assert(not self.ready_func)
813+
def yield_to(self, cancellable, other: Thread) -> Cancelled:
814+
assert(self.running() and other.suspended())
830815
self.ready_func = lambda: True
831816
self.task.inst.store.pending.append(self)
832817
return self.switch_to(cancellable, other)
@@ -1069,9 +1054,9 @@ exports.
10691054
if has_backpressure() or self.inst.num_waiting_to_enter > 0:
10701055
self.state = Task.State.BACKPRESSURE
10711056
self.inst.num_waiting_to_enter += 1
1072-
result = thread.suspend_until(lambda: not has_backpressure(), cancellable = True)
1057+
cancelled = thread.suspend_until(lambda: not has_backpressure(), cancellable = True)
10731058
self.inst.num_waiting_to_enter -= 1
1074-
if result == SuspendResult.CANCELLED:
1059+
if cancelled:
10751060
self.cancel()
10761061
return False
10771062
self.state = Task.State.UNRESOLVED
@@ -1121,15 +1106,15 @@ multiple), giving the thread the chance to handle cancellation promptly
11211106
if self.state == Task.State.BACKPRESSURE:
11221107
assert(len(self.threads) == 1)
11231108
self.state = Task.State.CANCEL_DELIVERED
1124-
self.threads[0].resume(SuspendResult.CANCELLED)
1109+
self.threads[0].resume(Cancelled.TRUE)
11251110
return
11261111
assert(self.state == Task.State.UNRESOLVED)
11271112
if not self.needs_exclusive() or not self.inst.exclusive or self.inst.exclusive is self:
11281113
random.shuffle(self.threads)
11291114
for thread in self.threads:
11301115
if thread.cancellable:
11311116
self.state = Task.State.CANCEL_DELIVERED
1132-
thread.resume(SuspendResult.CANCELLED)
1117+
thread.resume(Cancelled.TRUE)
11331118
return
11341119
self.state = Task.State.PENDING_CANCEL
11351120
```
@@ -1154,28 +1139,28 @@ by `Task.deliver_pending_cancel`, which is checked at all cancellation points:
11541139
The following `Task` methods wrap corresponding `Thread` methods after first
11551140
delivering any pending cancellations set by `Task.request_cancellation`:
11561141
```python
1157-
def suspend(self, thread, cancellable) -> SuspendResult:
1142+
def suspend(self, thread, cancellable) -> Cancelled:
11581143
assert(thread in self.threads and thread.task is self)
11591144
if self.deliver_pending_cancel(cancellable):
1160-
return SuspendResult.CANCELLED
1145+
return Cancelled.TRUE
11611146
return thread.suspend(cancellable)
11621147

1163-
def suspend_until(self, ready_func, thread, cancellable) -> SuspendResult:
1148+
def suspend_until(self, ready_func, thread, cancellable) -> Cancelled:
11641149
assert(thread in self.threads and thread.task is self)
11651150
if self.deliver_pending_cancel(cancellable):
1166-
return SuspendResult.CANCELLED
1151+
return Cancelled.TRUE
11671152
return thread.suspend_until(ready_func, cancellable)
11681153

1169-
def switch_to(self, thread, cancellable, other_thread) -> SuspendResult:
1154+
def switch_to(self, thread, cancellable, other_thread) -> Cancelled:
11701155
assert(thread in self.threads and thread.task is self)
11711156
if self.deliver_pending_cancel(cancellable):
1172-
return SuspendResult.CANCELLED
1157+
return Cancelled.TRUE
11731158
return thread.switch_to(cancellable, other_thread)
11741159

1175-
def yield_to(self, thread, cancellable, other_thread) -> SuspendResult:
1160+
def yield_to(self, thread, cancellable, other_thread) -> Cancelled:
11761161
assert(thread in self.threads and thread.task is self)
11771162
if self.deliver_pending_cancel(cancellable):
1178-
return SuspendResult.CANCELLED
1163+
return Cancelled.TRUE
11791164
return thread.yield_to(cancellable, other_thread)
11801165
```
11811166

@@ -1192,9 +1177,9 @@ trap if another task tries to drop the waitable set being used.
11921177
def ready_and_has_event():
11931178
return ready_func() and wset.has_pending_event()
11941179
match self.suspend_until(ready_and_has_event, thread, cancellable):
1195-
case SuspendResult.CANCELLED:
1180+
case Cancelled.TRUE:
11961181
event = (EventCode.TASK_CANCELLED, 0, 0)
1197-
case SuspendResult.NOT_CANCELLED:
1182+
case Cancelled.FALSE:
11981183
event = wset.get_pending_event()
11991184
wset.num_waiting -= 1
12001185
return event
@@ -1208,9 +1193,9 @@ another task (or not).
12081193
def yield_until(self, ready_func, thread, cancellable) -> EventTuple:
12091194
assert(thread in self.threads and thread.task is self)
12101195
match self.suspend_until(ready_func, thread, cancellable):
1211-
case SuspendResult.CANCELLED:
1196+
case Cancelled.TRUE:
12121197
return (EventCode.TASK_CANCELLED, 0, 0)
1213-
case SuspendResult.NOT_CANCELLED:
1198+
case Cancelled.FALSE:
12141199
return (EventCode.NONE, 0, 0)
12151200
```
12161201

@@ -3466,7 +3451,7 @@ the callee is still running concurrently in the `Thread` created here (see
34663451
the [concurrency explainer] for more on this).
34673452
```python
34683453
thread = Thread(task, thread_func)
3469-
thread.resume()
3454+
thread.resume(Cancelled.FALSE)
34703455
return task
34713456
```
34723457

@@ -4644,10 +4629,10 @@ def canon_thread_switch_to(cancellable, thread, i):
46444629
trap_if(not thread.task.inst.may_leave)
46454630
other_thread = thread.task.inst.threads.get(i)
46464631
trap_if(not other_thread.suspended())
4647-
suspend_result = thread.task.switch_to(thread, cancellable, other_thread)
4648-
return [suspend_result]
4632+
cancelled = thread.task.switch_to(thread, cancellable, other_thread)
4633+
return [cancelled]
46494634
```
4650-
If `cancellable` is set, then `thread.switch-to` will return a `SuspendResult`
4635+
If `cancellable` is set, then `thread.switch-to` will return a `Cancelled`
46514636
value to indicate whether the supertask has already or concurrently requested
46524637
cancellation. `thread.switch-to` (and other cancellable operations) will only
46534638
indicate cancellation once and thus, if a caller is not prepared to propagate
@@ -4671,13 +4656,13 @@ calling component.
46714656
def canon_thread_suspend(cancellable, thread):
46724657
trap_if(not thread.task.inst.may_leave)
46734658
trap_if(not thread.task.may_block())
4674-
suspend_result = thread.task.suspend(thread, cancellable)
4675-
return [suspend_result]
4659+
cancelled = thread.task.suspend(thread, cancellable)
4660+
return [cancelled]
46764661
```
46774662
A non-`async`-typed function export that has not yet returned a value traps if
46784663
it transitively attempts to call `thread.suspend`.
46794664

4680-
If `cancellable` is set, then `thread.suspend` will return a `SuspendResult`
4665+
If `cancellable` is set, then `thread.suspend` will return a `Cancelled`
46814666
value to indicate whether the supertask has already or concurrently requested
46824667
cancellation. `thread.suspend` (and other cancellable operations) will only
46834668
indicate cancellation once and thus, if a caller is not prepared to propagate
@@ -4729,10 +4714,10 @@ def canon_thread_yield_to(cancellable, thread, i):
47294714
trap_if(not thread.task.inst.may_leave)
47304715
other_thread = thread.task.inst.threads.get(i)
47314716
trap_if(not other_thread.suspended())
4732-
suspend_result = thread.task.yield_to(thread, cancellable, other_thread)
4733-
return [suspend_result]
4717+
cancelled = thread.task.yield_to(thread, cancellable, other_thread)
4718+
return [cancelled]
47344719
```
4735-
If `cancellable` is set, then `thread.yield-to` will return a `SuspendResult`
4720+
If `cancellable` is set, then `thread.yield-to` will return a `Cancelled`
47364721
value indicating whether the supertask has already or concurrently requested
47374722
cancellation. `thread.yield-to` (and other cancellable operations) will only
47384723
indicate cancellation once and thus, if a caller is not prepared to propagate
@@ -4758,13 +4743,13 @@ other threads in a cooperative setting.
47584743
def canon_thread_yield(cancellable, thread):
47594744
trap_if(not thread.task.inst.may_leave)
47604745
if not thread.task.may_block():
4761-
return [SuspendResult.NOT_CANCELLED]
4746+
return [Cancelled.FALSE]
47624747
event_code,_,_ = thread.task.yield_until(lambda: True, thread, cancellable)
47634748
match event_code:
47644749
case EventCode.NONE:
4765-
return [SuspendResult.NOT_CANCELLED]
4750+
return [Cancelled.FALSE]
47664751
case EventCode.TASK_CANCELLED:
4767-
return [SuspendResult.CANCELLED]
4752+
return [Cancelled.TRUE]
47684753
```
47694754
If a non-`async`-typed function export that has not yet returned a value
47704755
transitively calls `thread.yield`, it returns immediately without blocking
@@ -4777,7 +4762,7 @@ Even though `yield_until` passes `lambda: True` as the condition it is waiting
47774762
for, `yield_until` does transitively peform a `Thread.suspend` which allows
47784763
the embedder to nondeterministically switch to executing another thread.
47794764

4780-
If `cancellable` is set, then `thread.yield` will return a `SuspendResult`
4765+
If `cancellable` is set, then `thread.yield` will return a `Cancelled`
47814766
value indicating whether the supertask has already or concurrently requested
47824767
cancellation. `thread.yield` (and other cancellable operations) will only
47834768
indicate cancellation once and thus, if a caller is not prepared to propagate

0 commit comments

Comments
 (0)