Skip to content

Rewrite reference counting with complete Perl-compatible ownership semantics #1649

Open

Description

@fglock

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:

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:

  1. 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.
  2. Counts reflect the selected Perl oracle at controlled B::svref_2object(...)->REFCNT and Internals::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.
  3. 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.
  4. Correct DESTROY reentrancy, 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.
  5. 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.
  6. 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.t passes 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 MortalList or 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-acceptance and 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.t and 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 make and 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.

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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

  1. added
    enhancementNew feature or request
    area:memoryReference tracking memory leaks and lifecycle
    area:runtimeCore Perl runtime semantics
    area:backendJVM interpreter or execution-backend behavior
    on Oct 5, 2026
  2. fglock commented on Oct 5, 2026

    @fglock
    OwnerAuthor

    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

    1. Read repository AGENTS.md and the applicable debug-perlonjava skill. 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.
    2. Read this ticket's contract and the existing architecture/alignment documents. Create a new design document under 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.
    3. Pin the intended Perl 5.44 oracle and record its version/build/stack configuration. Inventory existing 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.
    4. Fetch HTML::Tree 5.07 in an isolated CPAN home and reproduce unchanged t/refloop.t. Recover /tmp/issue1528-html-tree.log if 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.
    5. Inspect Track independent reachability/lifecycle fixes and repeated Catalyst root walks #1642's current independent candidate, PR fix(parser): resolve five high impact parser issues #1623 and UAT PR WIP: Prepare CPAN compatibility fixes for UAT with independent lifecycle handoff #1628. Its reported combined-parent passes are historical evidence. Recover its permanent regression manifest rather than assuming all files already exist on master. Preserve complete tests when moving changes. Separate the Catalyst setup bottleneck from Catalyst suite failures and other UAT fixes.
    6. Add deterministic counters around repeated cleanup of unblessed aggregates through 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: refCount sentinels, ownership metadata, shared lifecycle and owner inspection. Separate lifetime state from semantic count; audit existing semantic capture owners before replacing them.
    • Same directory: 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.java and MyVarCleanupStack.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 with rg; do not assume it is a single B.java file.
    • RuntimeGraphCloner.java, shared-storage/thread modules and runtime/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 make for build/unit gates, save complete logs, and wrap every jperl/jcpan/prove invocation in timeout. 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.

  3. fglock commented on Oct 7, 2026

    @fglock
    OwnerAuthor

    Phase 1 status (2026-10-07)

    The Phase 1 candidate is rebased onto current master 7bdd1cfbe16cd123dd5ee8a6b103e0f8375ab0a8 in draft PR #1653 (head cb0a1b1c236df93b5857b741735d38dc01579877). Local acceptance is complete: nice -n 19 make passed; 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.t failures (#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.t remains unchanged. Keep the Moo and DBIx::Class fixes in their separate issues and rerun those distributions after those fixes land.

  4. fglock commented on Oct 7, 2026

    @fglock
    OwnerAuthor

    Phase 1 merged (2026-10-07)

    PR #1653 has merged as 84f32be179bdc2b351c3eaae1cb26df39964ab6b. Its final head c52918c92269d7295bb3095b4f156c389af58f23 passed Ubuntu and Windows CI.

    Acceptance evidence on the merged candidate: nice -n 19 make passed; 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.t remains unchanged. Continue the #1642 Catalyst cleanup profile as an integration follow-up. Rerun Moo and DBIx::Class after their separate fixes land.

  5. fglock commented on Oct 8, 2026

    @fglock
    OwnerAuthor

    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.t passes on system Perl (11 assertions) but PerlOnJava fails both assertions that a DBIx::Class::Storage::DBI object 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 $#array fixes are tracked with the broader unchanged-upstream acceptance plan in #1696.

  6. fglock commented on Oct 31, 2026

    @fglock
    OwnerAuthor

    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 candidate 27e877e19; current design checkpoint 14dd567cf. Draft PR #1938 remains open at remote head b2f44fc2d36b8f0a1bc7067790e1731a604cba45, based on 4a5c33753f1da3f9838c44567bbba878e8f567e0. Local work remains unpublished.
    Checkpoint: 2026-11-01 UTC. PerlOwnerSlot.acquireCountedIdentity now 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. PerlOwnerSlotReplacementOrderTest fails on the parent and passes 1/1 after the fix. The full no-sweeps timeout 1800 nice -n 19 make passes on immutable source 27e877e19 in 6m19 at /private/tmp/ownership-counted-slot-replacement-immutable-make-27e877e19.log; make check-links checks 821 links with zero errors at /private/tmp/ownership-counted-slot-replacement-final-docs-check-links.log.
    Audit: MortalList.processDeferredBase still restores counts from unverified counted slots for blessed referents, calls reachableOwnerCount, 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 source fa07d852a and tied-assignment source 30ae72581 also 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:backendJVM interpreter or execution-backend behaviorarea:memoryReference tracking memory leaks and lifecyclearea:runtimeCore Perl runtime semanticsenhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions