Skip to content

Fix IO::Async B::SV refcount regression masked by suite timeout #1136

Description

@fglock

Summary

IO::Async 0.805 regressed from PASS to FAIL in CPAN random-tester run 20260826-133506-83222. The report is recorded as a 300-second timeout, but the suite had already completed several failing tests before the timeout. The failures are reproducible reference-count mismatches in both PerlOnJava backends.

There are two related compatibility problems:

  1. B::SV::REFCNT reports an extra owner for otherwise singly-owned IO::Async::Loop objects (expected 1, found 2).
  2. The PerlOnJava patch for t/05notifier-loop.t still expects a historical extra notifier owner (3), while current PerlOnJava reports 2, which now matches system Perl.

The overall 300-second cap is a secondary concern: IO::Async deliberately contains multi-second timer tests, and the archived run was still making progress. This is not evidence of a hang.

Distribution and configuration

  • Distribution: PEVANS/IO-Async-0.805.tar.gz
  • Previous result: PASS on 2026-05-12 at PerlOnJava commit b5c01ce1c
  • Current result: REGRESS on 2026-08-26
  • Distropref patches applied:
    • IO-Async/NoFork.patch
    • IO-Async/PerlOnJava.patch
    • IO-Async/SkipUnsupportedSocketTests.patch
  • Distropref environment: IO_ASYNC_NO_FORK=1

Configuration and build both succeed. The failures occur during Build test.

Focused results

System Perl

Using the same IO::Async 0.805 source and required test dependencies:

  • t/05notifier-loop.t: PASS, 35/35
  • t/11loop-poll-timer.t: PASS, 19/19

The final native-Perl expectation in t/05notifier-loop.t is 2 references.

PerlOnJava JVM backend

t/05notifier-loop.t:

not ok 35 - $loop has PerlOnJava notifier owner finally
# expected 3 references, found 2

t/11loop-poll-timer.t:

not ok 3 - $loop has refcount 1
# expected 1 references, found 2
not ok 5 - $loop has refcount 1 after watch_time
# expected 1 references, found 2
not ok 19 - $loop has refcount 1 finally
# expected 1 references, found 2

PerlOnJava interpreter backend

Both focused tests produce the same failures and counts as the JVM backend. This points to shared runtime/refcount behavior rather than backend-specific compilation.

Other failures visible before timeout

The archived CPAN run also reports expected 1, found 2 in both Poll and Select variants of:

  • timer tests
  • signal tests
  • idle tests
  • control tests
  • metrics tests

The run reached tests after t/22timer-absolute.t before the tester's 300-second total-runtime limit fired. Therefore the timeout classification masks real completed failures.

Likely regression area

History and blame point to commit fe9d6bc5f (feat: complete Perl ithread lifecycle compatibility, 2026-08-15). That commit changed B::SV::REFCNT to call:

Internals::SvREFCNT($_[0]->{ref}, 1)

and added a special case in Internals.svRefcount() that increments the reported count when:

rc == 2
    && the B-specific flag is true
    && !ReachabilityWalker.hasLiveStrongScalarReferentOtherThan(base, arg)

The special case was introduced to keep a real aggregate owner visible in DBIx::Class/thread lifecycle paths. It appears to misclassify the temporary/private owner created by B::SV for these IO::Async loop objects, producing 2 where native Perl and IO::Async expect 1.

This regression point is inferred from the focused behavior and source history; it has not yet been confirmed by a commit-by-commit bisect.

The opposite-looking t/05notifier-loop.t result is a stale compatibility expectation: PerlOnJava now reports the native-Perl value of 2, but SkipUnsupportedSocketTests.patch changes that assertion to expect 3.

Proposed work

  1. Add a small project-owned regression test that reproduces the false extra B::SV::REFCNT owner without requiring the full IO::Async suite. Validate it on system Perl first and on both PerlOnJava backends.
  2. Refine the B-specific Internals::SvREFCNT(..., 1) heuristic so singly-owned IO::Async-style objects report 1 without breaking the DBIx::Class and thread lifecycle cases it was added to protect.
  3. Update IO-Async-0.805/SkipUnsupportedSocketTests.patch so the final notifier-loop assertion expects the upstream/native count of 2.
  4. Rerun the focused Poll and Select timer, signal, idle, control, and metrics tests on both backends.
  5. Rerun the full IO::Async suite. Consider adding TEST_QUICK_TIMERS=1 to the IO::Async distropref, or a justified runtime exception, so the random tester can finish within its global runtime policy. Keep this timeout adjustment separate from the correctness fix.

Acceptance criteria

  • The new focused refcount regression passes under system Perl, JVM PerlOnJava, and interpreter PerlOnJava.
  • t/05notifier-loop.t and the affected Poll/Select loop tests pass on both PerlOnJava backends.
  • Existing DBIx::Class and thread lifecycle/refcount regression coverage remains green.
  • A bounded full jcpan -t IO::Async run completes and passes with the maintained distropref configuration.

Activity

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 lifecyclebugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions