Clients reported a bug when transferring a closure bound to $this. We have an extensive $this test matrix (19 tests, tests/thread/053–071) — but it only covers spawn_thread. ThreadPool::submit and ThreadChannel::send are separate transfer paths and currently have zero $this coverage.
Coverage gap
tests/thread_pool/ — closure tests cover only use() vars (024), class type-hints (029/031-union), complex op_array features (031). None bind $this.
tests/thread_channel/037 — closure transfer, but not $this-bound.
Current behavior (empirical)
The ThreadPool path works today for a basic $this closure — deep-copy of $this, parent unchanged, requires a bootloader to declare the class on the worker (same as spawn_thread):
worker: n=42 tag=hi class=Worker
result=999
parent n=42
But because nothing is tested, we cannot rule out divergence on specific sub-cases.
Tasks
- Port the full
$this matrix to ThreadPool::submit, mirroring tests/thread/053–071:
- base property access + mutation isolation
- private / protected properties (the original client repro,
055)
$this self-cycle ($this->self === $this)
- readonly property
- enum instance as
$this + enum-typed property
WeakReference property (target reachable / unreachable)
- typed/strict properties
- missing class without bootloader → clear error, no SEGV
- Method-as-closure cases (class refers to its own method as a closure):
- first-class callable from instance method:
$this->method(...) / $obj->method(...)
Closure::fromCallable([$this, 'method'])
Closure::bind to an object before submit
- arrow fn
fn() => $this->x
- nested closure inheriting
$this
- first-class callable from a static method (no
$this)
- Add
$this-bound + method-as-closure coverage to ThreadChannel::send.
- Reproduce the client's exact case to confirm whether any sub-case (non-transferable property, cycle, private props, method-as-closure) diverges from
spawn_thread.
Clients reported a bug when transferring a closure bound to
$this. We have an extensive$thistest matrix (19 tests,tests/thread/053–071) — but it only coversspawn_thread.ThreadPool::submitandThreadChannel::sendare separate transfer paths and currently have zero$thiscoverage.Coverage gap
tests/thread_pool/— closure tests cover onlyuse()vars (024), class type-hints (029/031-union), complex op_array features (031). None bind$this.tests/thread_channel/037— closure transfer, but not$this-bound.Current behavior (empirical)
The
ThreadPoolpath works today for a basic$thisclosure — deep-copy of$this, parent unchanged, requires a bootloader to declare the class on the worker (same asspawn_thread):But because nothing is tested, we cannot rule out divergence on specific sub-cases.
Tasks
$thismatrix toThreadPool::submit, mirroringtests/thread/053–071:055)$thisself-cycle ($this->self === $this)$this+ enum-typed propertyWeakReferenceproperty (target reachable / unreachable)$this->method(...)/$obj->method(...)Closure::fromCallable([$this, 'method'])Closure::bindto an object before submitfn() => $this->x$this$this)$this-bound + method-as-closure coverage toThreadChannel::send.spawn_thread.