Skip to content
Draft
7 changes: 7 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -384,9 +384,16 @@ test {
// Provider we already record as an input so configuration-cache invalidation
// stays correct when the env var changes between invocations.
def jperlTestFilterProp = providers.environmentVariable('JPERL_TEST_FILTER').orElse('')
def jperlEnableCorrectiveSweeps = providers.environmentVariable('JPERL_ENABLE_CORRECTIVE_SWEEPS').orElse('')
def jperlDisableCorrectiveSweeps = providers.environmentVariable('JPERL_DISABLE_CORRECTIVE_SWEEPS').orElse('')
tasks.withType(Test).configureEach { t ->
t.inputs.property('jperlTestFilter', jperlTestFilterProp)
t.environment('JPERL_TEST_FILTER', jperlTestFilterProp.get())
// Policy changes must not inherit a previous run's cached Test environment.
t.inputs.property('jperlEnableCorrectiveSweeps', jperlEnableCorrectiveSweeps)
t.inputs.property('jperlDisableCorrectiveSweeps', jperlDisableCorrectiveSweeps)
t.environment('JPERL_ENABLE_CORRECTIVE_SWEEPS', jperlEnableCorrectiveSweeps.get())
t.environment('JPERL_DISABLE_CORRECTIVE_SWEEPS', jperlDisableCorrectiveSweeps.get())
}

// Fast unit tests only (tests in unit/ directory)
Expand Down
1,337 changes: 1,335 additions & 2 deletions dev/design/perl-ownership-rewrite.md

Large diffs are not rendered by default.

87 changes: 83 additions & 4 deletions dev/design/refcount-owner-ledger.md
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ Run on JVM and interpreter backends with `timeout` and complete output logs:

## Progress Tracking

### Current Status: Net::Async::HTTP captured-owner, hash-slice, and eval-capture finalization fixes complete
### Current Status: sweep scheduling removed; owner-source migration continues

The designated issue branch, `fix/issue-1132-closure-lifetime`, includes the
handoff commits below. On 2026-09-01, the required pre-change `make` rebuilt
Expand Down Expand Up @@ -529,6 +529,78 @@ external workload has drained before changing runtime source.
in `perl5_t/t/run/fresh_perl.t` test 75 now passes, restoring the baseline
73/91 result; the other 18 historical failures are unchanged. Full `make`
passed in the isolated PR worktree on 2026-09-02.
- [x] On 2026-10-28, removed automatic, targeted, and statement-boundary weak
cleanup scheduling from `MortalList`; legacy request methods remain no-op
compatibility hooks. Added direct cleanup for an untracked value when its
final scalar-cell owner exits, and kept reachability fallbacks where slot
coverage is incomplete. `nice -n 19 make` passed with
`JPERL_DISABLE_CORRECTIVE_SWEEPS=1` and Gradle limited to two workers.
- [x] On 2026-10-28, CODE weak-reference registration stopped walking scalar
roots to decide whether an anonymous CODE value still has an owner. The
existing stash, active-frame, and owner-slot checks in `clearWeakRefsTo()`
now make that decision. Added
`WeakCodeOwnerSlotNoReachabilityTest`; the full `make` gate passed with
corrective sweeps disabled.
- [x] On 2026-10-29, package-rooted untracked aggregates now retire the
temporary count rescued at first weaken when their final untracked owner
slot leaves. Added Java owner-release coverage and six Perl assertions for
package-array clear and package-hash delete; system Perl, both backends,
the full no-sweeps `make`, and HTML-Tree 5.07 `t/refloop.t` (8/8 on both
backends) passed. The first-weaken root-query entry now returns from the
owner-slot ledger before inspecting package edges; its regression sees zero
edges and nodes visited.
- [x] On 2026-10-29, tracked zero-count weakening now trusts surviving
uncounted owner slots directly and enters `WEAKLY_TRACKED`; final slot release
clears weak observers without a root walk or compatibility pin.
`WeakUncountedOwnerSlotFastPathTest`, the no-sweeps full `make`, and the weak,
DESTROY, resurrection, closure, cycle, and HTML-Tree 8/8 matrix pass.
- [x] On 2026-10-29, first weaken of an untracked aggregate now keeps a
transitional `WEAKLY_TRACKED` sentinel without the generic root-graph
fallback; its durable slot release clears the weak observer directly.
`UntrackedAggregateFirstWeakenFastPathTest`, the no-sweeps full `make`, and
HTML-Tree 5.07 `t/refloop.t` (8/8 on both backends) pass. The package-root
rescue proof remains for the legacy nested-aggregate case.
- [x] On 2026-10-29, a surviving uncounted owner slot now preserves weak
observers when `RuntimeScalar` releases the last counted slot. The final
owner release performs direct cleanup without a root query; permanent
regression coverage is in `RuntimeScalarUncountedOwnerRepairTest`.
- [x] On 2026-10-29, localized CODE restoration now releases displaced CODE
captures from active-frame and owner-slot identities, without scalar-registry
or root-graph scans. `LocalizedGlobCodeOwnerReleaseTest` covers final capture
cleanup; `LocalizedGlobCodeOwnerSlotFastPathTest` covers a live active call.
The no-sweeps full `make`, closure/weak/DESTROY/resurrection/cycle matrix, and
HTML-Tree 5.07 `t/refloop.t` (8/8) pass on both backends.
- [x] On 2026-10-29, scalar overwrite, deferred decrement, and tail-call release
now defer zero-count cleanup for aggregates with a surviving uncounted owner
slot, even with no weak observer. `UncountedOwnerSlotZeroCountReleaseTest`
verifies each path and final destruction after the sole owner slot leaves.
Scalar and IO referents still need owner-slot release coverage before this
rule can safely apply to them.
- [x] On 2026-10-29, tail-call zero-count release no longer treats a
Java-live scalar-registry wrapper as a Perl owner or forces GC to inspect it.
`TailCallScalarRegistryOwnershipTest` verifies that a released tail-call
referent is destroyed while its scalar wrapper remains Java-live.
- [x] On 2026-10-29, tail-call release now trusts any direct non-counted Perl
owner slot, excluding temporary runtime roots. Added
`TailCallUncountedOwnerSlotFastPathTest` for package scalar ownership; it
passes in the full no-sweeps `make` gate (10m58s).
- [x] On 2026-10-29, preserve live uncounted scalar owner slots through
referent promotion, while pruning slots in temporary containers without a
Perl owner at final release. `PromotedUntrackedOwnerSlotTest` covers both
cases. The no-sweeps full `make` gate passed in 14m59s; tempfile, closure,
weak/DESTROY, resurrection, weak-cycle, and HTML-Tree 8/8 checks passed on
both backends.
- [x] On 2026-10-29, async Future resume now checks counted and direct owner
slots instead of rebuilding root graphs or forcing GC for a scalar-registry
lookup. Temporary evaluation roots do not count as Perl owners.
`PerlStrongOwnerProofTest`, the full no-sweeps `make`, the closure/weak/
DESTROY/resurrection/cycle matrix, and HTML-Tree `t/refloop.t` (8/8 on both
backends) pass.
- [x] On 2026-10-29, void-discard cleanup now uses registered container
membership directly as proof that a CODE scalar slot is live, avoiding a
duplicate scalar reachability query. `MortalizeRegisteredCodeOwnerFastPathTest`
verifies zero scalar queries and root walks; the no-sweeps full `make` passed
in 21m18s.

### Handoff: issue #1132 / PR #1204

Expand All @@ -546,9 +618,16 @@ pass. Preserve both focused ownership regressions when evolving the ledger.

### Next Steps

1. Continue the owner-source migration inventory and remove only diagnosed
ownership heuristics with permanent system-Perl-validated regressions.
2. Keep Cookie2 formatting and content-coding exception handling separate from
1. Audit remaining `RuntimeScalar` and `MortalList` reachability fallbacks; add
explicit slot identities where ownership is missing, then remove each
fallback only after system Perl and both backends pass.
2. Remove the legacy package-root rescue pin after every final-slot release
path has direct coverage.
3. Migrate real weak CODE captures to capture-owner identities while retaining
compatibility behavior for unrecorded legacy captures.
4. Requalify HTML-Tree's 8/8 and the full suite after remaining owner changes;
remove compatibility APIs after all gates pass.
5. Keep Cookie2 formatting and content-coding exception handling separate from
this ownership work.

### Open Questions
Expand Down
27 changes: 27 additions & 0 deletions docs/about/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,34 @@ priorities and future plans.

## Work in progress

- Disable corrective sweeps by default while migrating to explicit reference ownership; retain temporary scalar call and mortal return values through their caller boundaries, and balance argument, scalar-cell, closure, aggregate, package scalar/array/hash roots, tied-handler, shared-view, named CODE, persistent state, and destructor lifetimes (#1649).
- Use live scalar-owner slots to preserve nested untracked aggregates on first weaken, avoid package-root walks, and clear weak observers at final release (#1649).
- Release tail-call arguments from explicit owner slots without a root-graph fallback (#1649).
- Record uncounted value ownership for tracked referents held in package scalar cells (#1649).
- Give borrowed scalar cells in live lexical aggregates owner identities for their strong values (#1649).
- Preserve non-CODE weak referents directly from surviving uncounted owner slots when a scalar releases its last counted slot (#1649).
- Release displaced localized CODE captures from active-frame and owner-slot identities, without scalar-registry or root-graph scans (#1649).
- Defer zero-count cleanup for aggregates with surviving owner slots across scalar, deferred, and tail-call releases (#1649).
- Preserve live uncounted scalar owner slots through referent promotion, and prune temporary-container slots at final release (#1649).
- Avoid duplicate scalar reachability queries when a registered container proves a live CODE owner (#1649).
- Use direct non-counted Perl owner slots to defer tail-call cleanup without a root walk; ignore temporary runtime roots (#1649).
- Repair transient unblessed referent undercounts from verified array/hash element slots during overwrite and deferred cleanup, avoiding root walks (#1649).
- Repair unblessed referent undercounts from captured scalar-cell owners only while a live closure owns the cell, in overwrite and deferred cleanup (#1649).
- Release captures from a final scalar CODE owner using explicit owner identities instead of a root walk (#1649).
- Release aggregate closure captures at scope exit from CODE owner identities, ignoring stale compatibility counts (#1649).
- Track compiled regex callback CODE references with owner slots and release captures after the final callback owner leaves (#1649).
- Repair unblessed referent undercounts from verified scalar and aggregate owner slots in immediate and deferred cleanup, without root snapshots (#1649).
- Remove live-scalar-registry reachability from first-weaken, tail-call, and async-Future owner decisions.
- Release weakly tracked referents from their final uncounted owner slot without a root walk.
- Keep nested Rose::HTML::Objects form results alive with counted argument owners so weak parent links and qualified field names survive argument evaluation (#1851).
- Preserve caller argument identity in @DB::args so borrowed debugger slots share source-cell cleanup instead of creating detached untracked references (#1649).
- Keep weakly tracked referents alive while an explicit Perl owner slot remains; undef now retires only the released cell's identity (#1649).
- Track package-root weak compatibility pins by owner-slot identity and retire each pin with the final scalar owner (#1649).
- Finalize cleared aggregate weak references from live owner identities even when a legacy count remains; retain reachability checks only for unmigrated owner cases (#1649).
- Keep queued owner releases visible to cleared-aggregate cleanup until their deferred count transition completes (#1649).
- Keep backslash-addressed optree constants alive through saved CODE references, transfer interpreter optree owner trees to published CVs, and release each constant edge with its CV owner (#1649).
- Acquire interpreter return owners before lexical cleanup so returned closures retain captured aggregates (#1649).
- Keep recursive output cleanup on explicit owner slots as lexical result graphs grow, avoiding repeated live-root snapshots (#1860).
- Warn for undefined right operands of `.=` while keeping an undefined left operand quiet (#1382).
- Fix bundled IO, nested launcher, locale, and core user/group database module compatibility.
- Snapshot mutable array entries while marking package roots and during reachability walks so concurrent thread teardown cannot fail with `ConcurrentModificationException`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -981,6 +981,16 @@ private static RuntimeCode compileToExecutable(Node ast, EmitterContext ctx) thr
generatedClass,
ctx
);
RuntimeBase[] padConstants = ctx.javaClassInfo.padConstants == null
? new RuntimeBase[0]
: ctx.javaClassInfo.padConstants.toArray(new RuntimeBase[0]);
if (padConstants.length != 0 || !ctx.javaClassInfo.embeddedPadConstants.isEmpty()) {
compiled.installPadConstantTree(
ctx.javaClassInfo.javaClassName,
padConstants,
ctx.javaClassInfo.embeddedPadConstants,
ctx.javaClassInfo.embeddedPadConstantDescendants);
}
// File-scope `our` declarations, for PadWalker::peek_our(0) at file scope
Map<String, String> ourDeclarations = VariableCollectorVisitor.declaredOurVariables(ast);
compiled.declaredOurVariables = ourDeclarations.isEmpty() ? null : ourDeclarations;
Expand Down
Loading