Repository navigation
Rewrite reference counting with complete Perl-compatible ownership semantics #1649
Description
Proposal and size estimate
Replace PerlOnJava's selective reference-counting overlay and corrective reachability decisions with an explicit, comprehensive Perl ownership model. Keep JVM allocation and physical memory reclamation, but make Perl-visible reference counts, weak-reference clearing, destruction, resurrection, and resource release follow standard Perl.
This is a major runtime/compiler project: provisionally 20–36 engineer-weeks for design, migration, both execution backends, and production acceptance (roughly 5–9 months for one experienced full-time maintainer). Two experienced contributors may shorten elapsed time, but the ownership model, migration barriers, and integration gates remain sequential dependencies. This is an engineering estimate from a source/design review, not a measured implementation schedule. Re-estimate after the ownership audit; native extension compatibility or changes to runtime value identity could expand it substantially.
Explicit goals: resolve #1618 and #1642, with permanent regression coverage and complete acceptance evidence. Keep those issues open until their individual acceptance criteria pass. This ticket proposes implementation; no rewrite or new validation has been performed as part of filing it.
Why this is larger than changing a counter
Reviewed baseline: master c38ef8adff2b8067c4ff51fa929ec491f89baa8e (2026-10-05).
Current code distributes lifetime decisions across RuntimeBase, scalar stores and proxies, container operations, call-frame aliases, captured pads, generated scope cleanup, interpreter registers, deferred releases, weak-owner registries, global destruction, thread graph cloning, and native/resource wrappers. RuntimeBase.refCount also encodes lifecycle sentinels, while other fields record ownership absent from that counter. MortalList can preserve a zero-count referent after root/capture/cycle checks. Internals::SvREFCNT contains calling-convention and named-binding adjustments. The existing design deliberately retains selective accounting and reachability fallback; this proposal requires a stronger invariant.
Java references are implementation pointers, not automatically Perl owners. Counting every Java field/temporary would also be incorrect. The rewrite must represent Perl scalar identity, RV-to-referent ownership, aggregate element ownership, aliases, pad ownership, and temporary ownership distinctly. Standard Perl's conventional borrowed argument stack and its optional reference-counted stack also differ; aliasing does not imply that every internal stack policy has identical numeric counts.
Evidence motivating the project:
- HTML::Tree 5.07 refloop test retains objects after releasing references #1618: HTML::Tree 5.07
t/refloop.tfails 4/8 assertions; expected releases leave observed counts of 2, 3, and 5, and destruction fails. Excess ownership or delayed release is a hypothesis, not yet an established root cause. - Track independent reachability/lifecycle fixes and repeated Catalyst root walks #1642: its independent lifecycle candidate reports 4,830 of 4,836 JFR samples containing
ReachabilityWalker.isReachableFromRootsduring Catalyst prerequisite/setup. Repeated per-aggregate root walks are an identified cost. Those samples do not establish an infinite loop or quantify end-to-end speedup. Historical combined-parent passes do not validate the independent candidate.
Meaning of “exactly like standard Perl”
Use the repository's Perl 5.44 compatibility target as the intended oracle. Record the precise Perl version, build configuration (including argument-stack policy), relevant platform, and probe calling convention. Older installed Perl can provide preliminary evidence; record differences and obtain the target oracle before claiming exact parity.
Required observable behavior:
- Strong ownership creation, transfer, replacement and release match Perl across scalar cells, RV referents, arrays, hashes, globs, code/pads and applicable runtime objects. A borrowed alias creates no independent referent owner merely because a Java wrapper exists.
- Counts reflect the selected Perl oracle at controlled
B::svref_2object(...)->REFCNTandInternals::SvREFCNT(...)observation points, including probe-induced temporary owners and API-specific semantics. Cover both reads and supported writes; assess artificial refcount changes explicitly rather than silently normalizing them. Do not compute the answer by forcing JVM GC or reconstructing reachability. - Releasing the last actual owner triggers the appropriate weak-reference and destruction behavior at Perl's cleanup boundary. Mortal temporary releases remain deferred according to Perl semantics; “exact” does not mean every decrement must occur at Java assignment time.
- Correct
DESTROYreentrancy, resurrection, reblessing, exception/warning behavior and preservation of relevant special variables; global destruction follows Perl's documented behavior and oracle-observable constraints. Do not impose a total destructor order where Perl leaves it unspecified. - Strong cycles behave as Perl cycles. Standard Perl does not generally collect an unreachable strong cycle; weak observers must not disappear solely because JVM GC runs. Design semantic retention for positive-count cyclic objects, scoped to the owning runtime, with defined global teardown. An opt-in diagnostic sweep must not silently change default lifetime semantics.
- Filehandles, accepted sockets, pipes, closures, ithreads and shared aggregates retain their existing documented behavior under the new ownership model.
JVM heap addresses, allocation timing and allocator internals need not match Perl's C implementation. Full native XS/C-API support is a separate project (#1586); ownership exposed by existing Java/bundled providers is in scope.
References: Perl ownership and mortality, Perl circular references. Correct the existing architecture table's “cycle collector” claim when updating documentation. Also replace stale statements about selective tracking, argument copies and reachability with verified current behavior.
Proposed implementation contract
- Define an ownership table for every value-producing/consuming operation: owned result, borrowed cell/alias, ownership transfer, or mortal temporary. Distinguish scalar-cell and referent counts.
- Centralize retain/release/transfer in a small documented API. Prefer explicit ownership tokens or equivalently auditable slot/frame contracts; prototype before choosing the representation. Do not assume every Java object maps one-to-one to a Perl SV.
- Keep lifecycle state separate from a nonnegative semantic count. Define ordinary zero transitions, destructor entry/exit, weak-reference handling, resurrection, immortal constants, and externally adjusted counts.
- Stores acquire or transfer the incoming owner before releasing the outgoing owner when needed to preserve self-assignment and reentrant destructor safety. Container mutations retain Perl-specific sequencing and alias behavior.
- Generate cleanup for all exits: fall-through, return, exceptions/eval/try, loop exits, goto/tail calls and backend fallback. Release escaping results only after ownership is transferred to the caller; captured pads survive their declaring frame.
- Use explicit runtime roots for ownership and cycle retention. Runtime reset/thread teardown must release its own registry and avoid holding other runtimes alive. Ordinary local accounting should not require global atomic counters; cross-thread shared storage needs a defined synchronized lifetime protocol.
- Graph walkers may remain diagnostic or transitional tools. In fully migrated default paths, ordinary release, weak clearing, count queries and destructor dispatch must not require corrective whole-root scans, JVM GC, or fabricated owner counts.
- A migration flag may enable comparison of legacy and new accounting, but only one implementation may dispatch destructors or close resources. Define explicit bridges at migrated/unmigrated boundaries; never count the same owner twice.
Phases: complete, enabled ownership paths
Organize implementation around complete ownership paths so every phase can be fully merged, tested, and measured. A path includes its producers, storage, consumers, cleanup on both backends, and any weak-reference, destruction, introspection, or resource behavior needed to make its lifetime correct. Core API work belongs inside the first complete path; a dormant API alone does not complete an implementation phase.
| Phase | Scope | Deliverable and exit criterion |
|---|---|---|
| 0. Oracle, baselines and migration boundaries | Pin the Perl 5.44 oracle; reproduce #1618/#1642; inventory every ownership source/sink; record count/event/cost traces, performance variance and memory baselines; identify safe migration boundaries | Merge reproducible baselines and diagnostic coverage, finalize the design and child tickets, and select the first complete path with an explicit measurable improvement |
| 1. First complete ownership path | Introduce the minimum identity/count/lifecycle contracts, retain/release/transfer primitives, cycle retention and migration bridge together with one selected path's producers, storage, cleanup and required lifetime behavior | Enable the path by default on both backends; a permanent externally observable regression changes from failing to passing, preferably a reduced #1618 case if the audit supports it; primitive and boundary tests pass |
| 2. Aggregate ownership and HTML::Tree | Extend complete paths through scalar/proxy and array/hash operations, including replacement, deletion, aliases and all call/cleanup/destruction behavior needed by HTML::Tree | Migrated paths are enabled by default; unmodified HTML::Tree 5.07 t/refloop.t passes 8/8 with correct counts and destruction; satisfy all #1618 acceptance criteria |
| 3. Calls, temporaries and captured values | Migrate remaining argument/list ownership, return values, mortal temporaries, closures and pads together with JVM/interpreter cleanup on every exit, nested eval and fallback | Additional lifecycle regressions pass with migrated paths enabled by default; no leaked or prematurely released semantic owners; migrated ordinary cleanup and count queries require zero corrective whole-root scans |
| 4. Remaining ownership families and integrations | Complete globs, code/constants, remaining tied/magic and operator/provider boundaries, refcount APIs, thread cloning/shared lifetimes, resources, runtime reset and global destruction | Every remaining supported family is enabled by default and passes differential and integration coverage; the migration inventory has no uncovered ownership edges |
| 5. Production qualification and legacy removal | Complete ecosystem/performance acceptance, satisfy #1642, stabilize the fully migrated runtime, then remove obsolete accounting and recovery | Final acceptance passes on the default model and again after legacy removal; record CPU/allocation/heap effects and deterministic cleanup costs, update architecture/docs/changelog, and demonstrate no supported path depends on legacy ownership |
Effort remains provisionally 20–36 engineer-weeks overall, including approximately 2–3 weeks for phase 0. The previous estimates by technical layer do not directly map to these complete paths. Re-estimate individual phases after the audit establishes their dependencies; moving work into earlier phases does not by itself reduce total effort.
The audit must establish whether the current representation permits these boundaries. Pull prerequisites such as scalar identity, destruction or call cleanup into the earliest phase that needs them. If a family cannot be isolated safely, expand that phase or combine adjacent phases before implementation. Split phase 4 into separately numbered delivery phases if its families can be isolated. Do not declare a phase complete with a dormant implementation or an incomplete default lifetime path.
Required completion evidence for every phase
- The phase's PRs are merged and its documented checks pass on the final integrated source commit. Phase 0 establishes measurements; every implementation phase delivers an enabled result with a named observable improvement. Improvements may be correctness, supported behavior or reduced cleanup cost; do not imply every phase must make every benchmark faster.
- Every migrated object has exactly one authoritative lifetime implementation. All producers, storage slots, consumers and exits for its path are covered; transfers across the remaining legacy boundary are explicit and tested, including reentrancy, escaping values and prevention of double-release. New accounting stays non-dispatching until the complete path is ready to enable.
- New Perl-level regressions are validated against the recorded oracle, reproduce the failure on the unfixed parent, and pass on JVM and interpreter after the fix. Primitive tests cover count overflow/underflow, lifecycle transitions, cycle retention and reentrancy as applicable. All previously delivered phase regressions remain green.
nice -n 19 make, relevant bundled gates and relevant unchanged upstream suites pass from immutable source barriers. Baseline known failures must have an explicit diagnostic/expected-failure mechanism so phase 0 can merge without breaking required gates; promote them to required passing coverage when fixed. Do not alter or delete existing tests to obtain a pass.- Record before/after passing cases, retain/releases, root queries, roots seeded, edges visited, snapshot builds, runtime, allocation and retained heap where relevant. Compare against both the original baseline and the previous phase using the same workloads/configurations and the phase 0 tolerances. Fully migrated ordinary cleanup/count-query paths must require zero corrective whole-root scans; report remaining legacy costs separately.
- Update the design document with the merged commit, enabled value/owner families, remaining legacy boundaries, commands and full evidence, observed improvement, next phase and blockers. Keep a documented rollback mechanism during stabilization; a rollback must select a coherent lifetime implementation and must never switch the ownership authority of live objects unsafely.
Acceptance and evidence
- Project-owned minimal regressions for HTML::Tree 5.07 refloop test retains objects after releasing references #1618: standard Perl passes; unfixed PerlOnJava fails; new JVM and interpreter pass. Unmodified HTML::Tree 5.07
t/refloop.tpasses 8/8, including final destruction and expected counts. - Track independent reachability/lifecycle fixes and repeated Catalyst root walks #1642 retains all DOM/weak-parent, resurrection, accepted-socket and process-pipe ownership regressions. Review its independent candidate and sibling PR fix(parser): resolve five high impact parser issues #1623 before porting; neither previous green runs nor a snapshot-cache change proves this rewrite correct.
- Deterministic cost tests exercise repeated cleanup through
MortalListor its replacement. Count retain/releases, root queries, roots seeded, edges visited and snapshot builds. Fully migrated ordinary cleanup and count queries perform zero corrective whole-root scans; unrelated roots do not multiply cleanup work. Recursive release work may scale with the owned subgraph actually released. - Catalyst prerequisite/setup and its actual unchanged suite complete normally in an isolated CPAN home. Separate setup failures from suite outcomes. Run fresh end-to-end
make test-cpan-release-acceptanceand preserve the existing exclusions from Track independent reachability/lifecycle fixes and repeated Catalyst root walks #1642; add no exclusions to conceal lifetime defects. - Unchanged Mojolicious
dom.tand lifecycle-sensitive ecosystem suites (Moo/Moose, DBIx::Class, Sub::Quote/Sub::Defer, Resource::Silo, Dist::Zilla, Algorithm::SlidingWindow, relevant AnyEvent cases and Math::Decimal) pass against recorded versions; baseline independent failures are reported separately. - Differential tests cover alias versus copy, replace/delete/splice/clear, temporaries, nested calls, closures, tied containers, scalar refs, globs/code/pad constants, cycles with weak observers, all scope exits, resurrection/reblessing and global destruction. Supported refcount introspection APIs match the selected oracle at explicit checkpoints.
- Thread clone/shared ownership, runtime isolation/reset and resource-release suites pass; repeated runtime teardown leaves no retained registries or orphan resources.
-
nice -n 19 makeand relevant bundled gates pass from immutable source barriers. Validate new Perl-level tests on system Perl first; capture full output and use timeouts for all jperl/jcpan/prove executions. Never alter/delete existing tests or mutate a checkout while a gate runs. - Record CPU/allocation/heap effects against baseline on no-DESTROY workloads, closure calls, DOM construction and repeated cleanup. Set regression tolerances from baseline variance in phase 0; deterministic complexity checks accompany timings. A larger scalar/container metadata footprint is a material risk.
- Update a dedicated
dev/design/document after each phase, reconcile existing architecture/alignment docs, add a terse changelog entry under Work in progress, and document per-phase enablement, rollback and legacy-removal criteria.
Risks and design decisions to resolve in phase 0
Exact count compatibility may require separating wrapper identity from Perl SV identity and broadening tracking to all relevant cells. Thread-local versus shared lifetimes, reentrant ties/destructors, constant caches, probe-induced owners and explicitly writable counts are difficult cases. Preserving Perl strong-cycle semantics may intentionally retain memory that JVM tracing would otherwise reclaim. The migration must not trade delayed destruction for premature destruction. If phase 0 finds that the current value representation cannot support this contract, publish the architectural change and revised estimate before continuing.
Related existing plans are historical context, not implementation-complete evidence: dev/architecture/weaken-destroy.md, dev/design/refcount_alignment_plan.md, dev/design/refcount_alignment_progress.md, dev/design/refcount_alignment_52leaks_plan.md. This project supersedes the selective-accounting-plus-reachability strategy only as each path is migrated and validated.
Merge strategy and reviewable PR boundaries
A phase is a delivery milestone with a complete, enabled ownership path and recorded acceptance evidence. It may contain several independently reviewable PRs. Supporting PRs may introduce dormant primitives or diagnostics, but the phase is complete only after its behavior is enabled by default and its final integrated commit passes all phase gates. Every PR that changes runtime behavior must pass nice -n 19 make, focused standard-Perl/JVM/interpreter regressions and relevant unchanged upstream tests before merge. Keep source immutable while a gate runs.
- Phase 0 PRs — Baselines and migration design. Add project-owned, system-Perl-validated reproducers for HTML::Tree 5.07 refloop test retains objects after releasing references #1618 and the Track independent reachability/lifecycle fixes and repeated Catalyst root walks #1642 repeated-cleanup cost case, deterministic diagnostic counters, and the ownership inventory/design. Record failing parent evidence on both backends where applicable. The cost test must exercise real deferred cleanup and report root queries, roots seeded, edges visited and snapshots built. Preserve passing required gates while recording known baseline failures. Merge the baseline and audit deliverables; keep HTML::Tree 5.07 refloop test retains objects after releasing references #1618 and Track independent reachability/lifecycle fixes and repeated Catalyst root walks #1642 open.
- Phase 1 PRs — Core contracts plus the first working path. Develop documented owner kinds/transitions and focused primitive tests as part of the selected path. Include every required producer, storage slot, consumer, backend cleanup edge and lifecycle action. Test overflow/underflow, reentrancy and migration bridges. Enable the complete path by default in the phase's delivery PR and record its observable improvement. A dormant core API PR is supporting work, not the phase exit criterion.
- Phases 2–4 PRs — Expand complete default-enabled paths. Each delivery PR migrates a coherent set of ownership edges and enables that path after its boundary and acceptance tests pass. Exactly one implementation dispatches destruction or releases resources for each object; aliases and cross-boundary references must not create a second authority. Keep incomplete new accounting non-dispatching. Pull required lifecycle/compiler integration forward rather than deferring it to a nominal later layer. Never mix in unrelated ownership changes without renewing acceptance evidence. Finish each phase with gates on the merged integrated commit and before/after results.
- Phase 5 PRs — Final qualification and stabilization. After the inventory is complete, qualify the already default-enabled model with release acceptance, Catalyst, Mojolicious, lifecycle-sensitive CPAN suites, runtime-isolation/thread/resource tests, and performance/complexity checks on the exact candidate commit. Require HTML::Tree 5.07 refloop test retains objects after releasing references #1618 acceptance and removal of Track independent reachability/lifecycle fixes and repeated Catalyst root walks #1642's deterministic repeated-walk cause or a demonstrated bounded-cost replacement consistent with the zero corrective whole-root scan contract for fully migrated ordinary paths. Preserve a documented rollback mechanism for a stabilization window whose exit criteria are defined in the design.
- Phase 5 completion PR — Remove legacy accounting and recovery. After stabilization evidence shows no supported path depends on legacy ownership, delete old selective-counter corrections, duplicate ownership fields, walker gates and migration bridges. Preserve useful semantically inert diagnostic graph tools. Re-run the complete final acceptance gate after removal. Phase 5 and this parent ticket remain incomplete until that evidence is recorded.
Each PR must update the design document's progress table with its source commit and, after merge, the merged commit; migrated value/owner families; remaining legacy boundary; tests/evidence; next PR; and blockers. Close #1618 or #1642 only when that issue's individual acceptance criteria are satisfied, which may precede completion of this parent project. Close #1649 only after all phases, including legacy removal and its final acceptance, are complete.
Sequencing constraint: define and revise phase boundaries around complete ownership paths. If the audit finds that identity or cleanup cannot be isolated safely, expand or combine the affected phases before proceeding. An always-on incomplete counter, a feature flag left off, or primitive tests alone do not constitute a completed implementation phase.
Activity
fglock commentedon Oct 5, 2026
Implementation handoff (2026-10-05)
The user requested a comprehensive rewrite proposal and explicitly named fixing #1618 and #1642 as goals. This comment hands off the investigation and implementation. No runtime code was changed or tested while preparing this ticket. The local checkout was clean at review time and HEAD was
c38ef8adff2b8067c4ff51fa929ec491f89baa8e; re-check current master and issue/PR state before starting.First work package: establish the oracle and expose the actual owner imbalance
AGENTS.mdand the applicabledebug-perlonjavaskill. Use an isolated feature worktree for the rewrite. If unrelated user changes exist, follow the mandatory patch plus WIP-commit backup before touching that checkout; never stash. Keep each test/build gate's source immutable.dev/design/describing authoritative ownership, value identity, migration boundaries and phase progress. Existing docs contain stale assumptions; verify them against current code and standard Perl.src/test/resources/unit/refcount/coverage before adding tests. Temporary local Perl versions can establish preliminary semantics, but do not label their numeric-count expectations as exact 5.44 evidence.t/refloop.t. Recover/tmp/issue1528-html-tree.logif it still exists, but do not depend on that investigator-local file. Reduce the failing owner transition into permanent coverage. Run on system Perl first, retain failing-parent evidence, then run JVM and interpreter. Trace owner IDs and acquisition/release sites to distinguish extra scalar copies, missed decrements, stale aliases and cleanup suppression; the issue body does not prove which one is responsible.MortalList.processDeferredBase(or its replacement): root-query count, roots seeded, edges examined, visited nodes and snapshot builds. This directly targets the fallback described in Track independent reachability/lifecycle fixes and repeated Catalyst root walks #1642. Start with a bounded focused case; reserve expensive fresh-home acceptance for immutable phase barriers. Raw JFR includes environment/process metadata and must not be published.Code map to start the audit
All paths below are relative to the repository; directory prefixes avoid prescribing speculative new classes.
src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeBase.java:refCountsentinels, ownership metadata, shared lifecycle and owner inspection. Separate lifetime state from semantic count; audit existing semantic capture owners before replacing them.RuntimeScalar.java(setLargeRefCounted, cleanup and alias paths),RuntimeBaseProxy.java,RuntimeArrayProxyEntry.java,RuntimeHashProxyEntry.java,RuntimeArray.java,RuntimeHash.java,RuntimeList.java,RuntimeGlob.java: scalar-cell identity, slot ownership, mutations, borrowing/copying and transfer.RuntimeCode.javaandMyVarCleanupStack.java: argument aliases, active frames, captured pads, escaping return values and scope roots.MortalList.java: deferred owner release, mortal marks, temporary roots, zero-count rescue and repeated cleanup walks. This is the principal Track independent reachability/lifecycle fixes and repeated Catalyst root walks #1642 entry point.DestroyDispatch.java,GlobalDestruction.java,WeakRefRegistry.java,ScalarRefRegistry.java,ReachabilityWalker.java,LifecycleRuntimeState.java: weak-observer ordering, destructor reentrancy/resurrection, root snapshots and global teardown.src/main/java/org/perlonjava/backend/jvm/EmitStatement.java,EmitBlock.java,EmitSubroutine.java;backend/bytecode/BytecodeInterpreter.java,InterpretedCode.java, opcode handlers: every ordinary/exceptional scope exit, cleanup marks, register ownership, nested eval and backend fallback.src/main/java/org/perlonjava/runtime/perlmodule/Internals.java(svRefcount) and the B implementation: scalar-versus-referent probes, observable counts, calling-convention adjustments and supported setters. Locate B withrg; do not assume it is a singleB.javafile.RuntimeGraphCloner.java, shared-storage/thread modules andruntime/operators/IOOperator.java: cloned versus shared owners, runtime teardown, socket and process-pipe resource ownership.Required first deliverable and continuation
Deliver an ownership-source/sink table and a small core prototype, with an oracle-validated test for #1618 and a deterministic #1642 cleanup-cost reproducer. Document whether current scalar identities can represent Perl SV/RV ownership without a wider value-layout change. Re-estimate the 20–36 engineer-week budget at that point. Choose explicit owned/borrowed/transfer contracts before broadly editing callers; direct scattered counter changes will perpetuate the existing ambiguity.
Maintain one authoritative destructor/resource-release path during migration. Counting Java references indiscriminately, retaining every temporary forever, forcing JVM GC before count probes, or keeping normal graph walks as the final lifetime oracle cannot satisfy the ticket. Strong cycles require explicit Perl-semantic retention; a graph unreachable from roots can still have positive Perl ownership counts.
Use
nice -n 19 makefor build/unit gates, save complete logs, and wrap every jperl/jcpan/prove invocation intimeout. Builds write the shared development JAR: start reader gates only once make and its workers finish. Run new Perl-level tests on system Perl first and preserve unfixed-parent failures. At integration barriers run both backends, targeted unchanged upstream suites and the release acceptance target as specified in #1642. Do not claim completion from focused tests alone.For each completed phase, update the design document's completion date, decisions, changed files, next steps and remaining blockers. Finish through feature branches and reviewed PRs with required attribution and Work in progress changelog impact. Close #1618/#1642 only when their individual outcomes and this ticket's associated lifetime/performance gates are demonstrated.
feat: retain positive owner slots per Perl runtime
feat: retain positive owner slots per Perl runtime
feat: retain positive owner slots per Perl runtime
feat: retain positive owner slots per Perl runtime
feat: retain positive owner slots per Perl runtime
feat: retain positive owner slots per Perl runtime
fglock commentedon Oct 7, 2026
Phase 1 status (2026-10-07)
The Phase 1 candidate is rebased onto current master
7bdd1cfbe16cd123dd5ee8a6b103e0f8375ab0a8in draft PR #1653 (headcb0a1b1c236df93b5857b741735d38dc01579877). Local acceptance is complete:nice -n 19 makepassed; the latest Perl 5.45.4 test suite synced with no content changes; full UAT passed 575/575 files with 681,471 passing assertions; comparison with the 2026-10-05 baseline found 575 unchanged files and zero regressions. CI for the rebased PR is running.The requested CPAN reruns still show separate pre-existing failures: Moo has seven
t/croak-locations.tfailures (#1656); DBIx::Class has a broad load cascade from the heredoc conflict-marker diagnostic (#1434), with two assertions still needing separate classification (#1646). These do not reproduce as Phase 1 regressions.Next step: merge #1653 after its CI is green. Then begin Phase 2 by extending ownership through scalar proxies and ordinary array/hash slots. Use HTML::Tree teardown (#1618) as the integration exit criterion and verify
t/refloop.tremains unchanged. Keep the Moo and DBIx::Class fixes in their separate issues and rerun those distributions after those fixes land.fglock commentedon Oct 7, 2026
Phase 1 merged (2026-10-07)
PR #1653 has merged as
84f32be179bdc2b351c3eaae1cb26df39964ab6b. Its final headc52918c92269d7295bb3095b4f156c389af58f23passed Ubuntu and Windows CI.Acceptance evidence on the merged candidate:
nice -n 19 makepassed; Perl 5.45.4 tests synced with no content changes; UAT passed 575/575 files with 681,471 passing assertions; comparison against the 2026-10-05 baseline found 575 unchanged files and zero regressions. The additional Moo and DBIx::Class CPAN reruns still fail for the separate pre-existing issues #1656 and #1434; the two DBIx::Class assertion failures remain for classification in #1646.Next phase: Phase 2. Extend default-enabled ownership through scalar proxies and ordinary array/hash slots. Use HTML::Tree teardown issue #1618 as the integration exit criterion and verify
t/refloop.tremains unchanged. Continue the #1642 Catalyst cleanup profile as an integration follow-up. Rerun Moo and DBIx::Class after their separate fixes land.fglock commentedon Oct 8, 2026
DBIx::Class acceptance has reproduced an ownership defect directly relevant to this issue; the running follow-up is #1696.
In upstream DBIx::Class 0.082844,
t/100extra_source.tpasses on system Perl (11 assertions) but PerlOnJava fails both assertions that aDBIx::Class::Storage::DBIobject is released after its last Perl owner goes away. Runtime tracing shows the Storage hash reaches refcount 131 and retains 130 recorded owner slots; its weak observers are not cleared. Forcing a full reachability sweep on every statement does not fix the failure, so the next investigation is the ownership ledger/release paths rather than test changes or DBIx patches.This is additional evidence for the ownership model and release semantics in #1649. The current returned-array and
$#arrayfixes are tracked with the broader unchanged-upstream acceptance plan in #1696.fix: stop rooting released closure capture metadata
fix: stop rooting released closure capture metadata
fix: stop rooting released closure capture metadata
fix: stop rooting released closure capture metadata
fix: stop rooting released closure capture metadata
fix: stop rooting released closure capture metadata
fix: stop rooting released closure capture metadata
fix: stop rooting released closure capture metadata
fix: stop rooting released closure capture metadata
fix: stop rooting released closure capture metadata
docs: record ownership regressions passing without recovery scan
Continue explicit Perl owner-slot migration
fglock commentedon Oct 31, 2026
State: active
Agent/session: Codex / ownership-goal-resume-20261101
Scope: #1649 explicit owner-slot migration and removal of ordinary lifecycle reachability fallbacks after acquisition, transfer, and cleanup paths are covered.
Work: local branch
wip/socket-gensym-owner-20261101-020558; latest source candidate27e877e19; current design checkpoint14dd567cf. Draft PR #1938 remains open at remote headb2f44fc2d36b8f0a1bc7067790e1731a604cba45, based on4a5c33753f1da3f9838c44567bbba878e8f567e0. Local work remains unpublished.Checkpoint: 2026-11-01 UTC.
PerlOwnerSlot.acquireCountedIdentitynow registers the stable slot on its replacement referent before removing it from the previous referent, and retains the replacement runtime root before releasing the prior root.PerlOwnerSlotReplacementOrderTestfails on the parent and passes 1/1 after the fix. The full no-sweepstimeout 1800 nice -n 19 makepasses on immutable source27e877e19in 6m19 at/private/tmp/ownership-counted-slot-replacement-immutable-make-27e877e19.log;make check-linkschecks 821 links with zero errors at/private/tmp/ownership-counted-slot-replacement-final-docs-check-links.log.Audit:
MortalList.processDeferredBasestill restores counts from unverified counted slots for blessed referents, callsreachableOwnerCount, and consults root snapshots for package and unblessed aggregate cases. Tail-call release retains the raw counted-slot bridge. First-weaken's package helper validates exact untracked scalar slots, but its compatibility pin remains. Tied-handler, resource-owner, shared-storage, closure/CODE, and async-cancellation acquisition and release paths still need coverage.Evidence: Parent ordering failure is
/private/tmp/owner-slot-replacement-order-parent-focused.log; fixed focused gate is/private/tmp/owner-slot-replacement-order-fixed-focused.log. The 15-minute serial unit attempt was incomplete and timed out; the full parallel no-sweeps make gate above is the acceptance result. Prior async sourcefa07d852aand tied-assignment source30ae72581also have passing full no-sweeps logs recorded in the design doc.Blocked by: none.
Next: trace each remaining deferred count/root fallback to its actual producer, transfer and cleanup sites, then migrate one complete owner family and validate it before reconciling draft PR #1938.