You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
PerlOnJava's Java-backed IO::Socket::SSL path can mark an HTTPS socket closed while Net::HTTP::NB is reading a chunked response body. The same request completes under system Perl with native IO::Socket::SSL/Net::SSLeay.
Reproduction and evidence
On PerlOnJava source/JAR commit 4b3ef9770 (Perl 5.44.1, Java 24.0.2, macOS arm64), a WWW::Mechanize GET to http://www.pearsoned.co.uk/Bookshop/ followed redirects to https://www.pearson.com/en-gb/higher-education/educators.html. Both runtimes sent the same User-Agent and Accept-Encoding: gzip request headers. The final response was 200 OK, Transfer-Encoding: chunked, and Content-Encoding: gzip.
With system Perl 5.42.2, LWP read the response to completion (about 379 KB). With PerlOnJava, the response body read consumed 13–15 bytes already buffered with the response headers, then the next sysread failed with sysread() on closed filehandle; the returned body was empty. PerlOnJava loads the bundled Java-backed IO::Socket::SSL shim and wraps the socket with Java SSLSocket; system Perl loaded its native SSL modules.
The CPAN run 20261103-123832-91742 showed a related visible failure in WWW-Scraper-ISBN-Pearson_Driver 0.24, t/10object.t: the expected Pearson search form was unavailable. The endpoint currently redirects to a modern Pearson page with no forms, so that live-site test is not a stable regression test. A deterministic local TLS/chunked-body regression should cover the socket read failure.
Expected and actual
Expected: PerlOnJava reads the full chunked TLS response body, matching system Perl.
Actual: a later sysread sees a closed filehandle after a small buffered prefix, and LWP returns an empty/partial body.
Related work
Issue #1435 / PR #1805 covered HTTPS response metadata and local TLS bridge coverage, not body reads. PR #1856 covered Unix datagram socket owner retention; no duplicate for this TLS/chunked read failure was found.
State: released
Agent/session: codex-2026-11-03-ssl-chunked-resume
Scope: #1957, investigate the Java TLS premature-close regression and provide a focused ownership reproducer.
Work: fix/1957-java-ssl-chunked-body, source b43a652507d836bfbd78e3e6c250021c0b3355aa; test-only local change, no commit or PR.
Checkpoint: 2026-11-03 17:03 UTC; the HTTP/TLS fixture was narrowed to a Perl-only closure-owner regression and posted as a separate issue comment.
Evidence: src/test/resources/unit/deferred_capture_live_argument.t passes all three assertions with system Perl. timeout 60 ./jperl ... and timeout 60 ./jperl --interpreter ... both fail the first assertion (destroy_count is 1 while the temporary referent remains in active @_). The earlier Pearson CPAN probe also showed system Perl reading the response to completion while POJ closes after a 32-byte prefix.
Blocked by: none.
Next: user plans to add the reproducer to #1649's ownership-migration work; #1957 remains open and has no fix PR.
A Perl-only reproducer isolates the closure-capture ownership case behind this failure, without HTTP or TLS. It passes with system Perl and fails on PerlOnJava's JVM and interpreter backends: the temporary referent is destroyed at the inner scope boundary while it is still the active @_ argument.
use strict;
use warnings;
use Test::More;
{
packageLocal::ClosureOwnerProbe;
our$destroy_count = 0;
our$destroyed_while_argument_live = -1;
subnew {
returnbless { token=>'alive' }, shift;
}
subDESTROY {
++$destroy_count;
}
}
subread_after_capture_scope_exits {
{
my$captured = $_[0];
my$callback = sub { $captured->{token} };
}
$Local::ClosureOwnerProbe::destroyed_while_argument_live =
$Local::ClosureOwnerProbe::destroy_count;
return$_[0]->{token};
}
my$value = read_after_capture_scope_exits(Local::ClosureOwnerProbe->new);
is($Local::ClosureOwnerProbe::destroyed_while_argument_live, 0,
'the active call argument keeps its referent alive after the captured lexical leaves scope');
is($value, 'alive', 'the active call argument remains usable');
is($Local::ClosureOwnerProbe::destroy_count, 1,
'the temporary referent is destroyed after the call returns');
done_testing;
Results on source b43a652507d836bfbd78e3e6c250021c0b3355aa:
perl src/test/resources/unit/deferred_capture_live_argument.t: all 3 assertions pass.
State: released
Agent/session: codex-2026-11-04-io-regressions
Scope: #1957; fix temporary blessed filehandle destruction during chunked TLS body reads and add deterministic local coverage.
Work: draft stacked PR #1976, branch fix/1957-temporary-filehandle-owner, commit 51e7b31873f315a860af40273f26e575ea008cce, based on draft PR #1938 head 95b7c0cd8abbec3d55e9ee377f67a9c1a3c8ca05.
Checkpoint: 2026-11-04; RuntimeCode.retainArgumentAliasOwners now retains temporary strong GLOBREFERENCE values for the active @_ frame. The TLS fixture keeps its callback alive through the body read and verifies full chunk preservation.
Evidence: the exact fixture fails on parent eee26cd6e on JVM and interpreter with sysread() on closed filehandle; system Perl passes. It passes on the candidate on system Perl, JVM, and interpreter. Related ArgumentAliasOwnerSlotTest and weak blessed-tree/closure tests pass after narrowing the change to GLOBREFERENCE arguments. nice -n 19 timeout 3600 make passes on 51e7b3187 in 10m32; nice -n 19 make check-links passes with 791 links and zero errors. Logs: /private/tmp/perlonjava-pr1957-parent-exact-repro.log, /private/tmp/perlonjava-pr1957-alias-owner-fix-final.log, /private/tmp/perlonjava-pr1957-globref-focused.log, /private/tmp/perlonjava-pr1957-full-make-51e7b3187.log, /private/tmp/perlonjava-pr1957-links-51e7b3187.log.
Blocked by: #1649/#1938 base integration; PR #1976 is intentionally draft until the ownership foundation lands.
Next: after #1938 integrates, rebase #1976 onto master, rerun the full gate, and promote it for review.
Follow-up on the deterministic TLS acceptance case: the local TLS fixture passes with system Perl and the PerlOnJava interpreter, but still fails on the JVM with sysread() on closed filehandle after response-header prefetch. This remains open pending the ownership work in #1649; the focused Perl closure-owner regression passing on the current #1649 candidate is not yet enough to close the end-to-end TLS case. Please rerun this fixture after #1649 integrates.
I tightened the local reproducer after finding that the initial version let the callback become dead before body reads. With the callback held through the read, the TLS fixture still fails on the JVM only when a freshly constructed IO::Socket::SSL is passed directly as a temporary argument to the reader; system Perl passes. Keeping that socket in a caller lexical makes the fixture pass on both current master and the #1649 candidate. The JVM failure reaches IO::Handle::DESTROY and closes the socket during TAP output, rather than going through RuntimeScalar.releaseIoOwner.
This narrows #1957 to lifetime handling of temporary blessed filehandle arguments; it is related to the ownership semantics under #1649, while the candidate's existing hash-object closure regression alone does not prove the TLS case fixed. Keeping #1957 open pending a runtime fix and a passing direct-temporary TLS regression.
The narrowed temporary-filehandle fix and local TLS regression are in draft PR #1976, stacked on #1938. The parent fails the deterministic fixture on JVM and interpreter; the candidate passes on system Perl, JVM, and interpreter, and the full make gate passes. Keeping #1957 open until the #1649 base integrates and the stacked PR is retested on master.
Activity
fglock commentedon Nov 3, 2026
State: released
Agent/session: codex-2026-11-03-ssl-chunked-resume
Scope: #1957, investigate the Java TLS premature-close regression and provide a focused ownership reproducer.
Work:
fix/1957-java-ssl-chunked-body, sourceb43a652507d836bfbd78e3e6c250021c0b3355aa; test-only local change, no commit or PR.Checkpoint: 2026-11-03 17:03 UTC; the HTTP/TLS fixture was narrowed to a Perl-only closure-owner regression and posted as a separate issue comment.
Evidence:
src/test/resources/unit/deferred_capture_live_argument.tpasses all three assertions with system Perl.timeout 60 ./jperl ...andtimeout 60 ./jperl --interpreter ...both fail the first assertion (destroy_countis 1 while the temporary referent remains in active@_). The earlier Pearson CPAN probe also showed system Perl reading the response to completion while POJ closes after a 32-byte prefix.Blocked by: none.
Next: user plans to add the reproducer to #1649's ownership-migration work; #1957 remains open and has no fix PR.
fglock commentedon Nov 3, 2026
A Perl-only reproducer isolates the closure-capture ownership case behind this failure, without HTTP or TLS. It passes with system Perl and fails on PerlOnJava's JVM and interpreter backends: the temporary referent is destroyed at the inner scope boundary while it is still the active
@_argument.Results on source
b43a652507d836bfbd78e3e6c250021c0b3355aa:perl src/test/resources/unit/deferred_capture_live_argument.t: all 3 assertions pass.timeout 60 ./jperl src/test/resources/unit/deferred_capture_live_argument.t: assertion 1 fails (got 1, expected0).timeout 60 ./jperl --interpreter src/test/resources/unit/deferred_capture_live_argument.t: assertion 1 fails (got 1, expected0).The test is prepared locally at
src/test/resources/unit/deferred_capture_live_argument.tand is not committed.fglock commentedon Nov 4, 2026
State: released
Agent/session: codex-2026-11-04-io-regressions
Scope: #1957; fix temporary blessed filehandle destruction during chunked TLS body reads and add deterministic local coverage.
Work: draft stacked PR #1976, branch
fix/1957-temporary-filehandle-owner, commit51e7b31873f315a860af40273f26e575ea008cce, based on draft PR #1938 head95b7c0cd8abbec3d55e9ee377f67a9c1a3c8ca05.Checkpoint: 2026-11-04;
RuntimeCode.retainArgumentAliasOwnersnow retains temporary strong GLOBREFERENCE values for the active@_frame. The TLS fixture keeps its callback alive through the body read and verifies full chunk preservation.Evidence: the exact fixture fails on parent
eee26cd6eon JVM and interpreter withsysread() on closed filehandle; system Perl passes. It passes on the candidate on system Perl, JVM, and interpreter. RelatedArgumentAliasOwnerSlotTestand weak blessed-tree/closure tests pass after narrowing the change to GLOBREFERENCE arguments.nice -n 19 timeout 3600 makepasses on51e7b3187in 10m32;nice -n 19 make check-linkspasses with 791 links and zero errors. Logs:/private/tmp/perlonjava-pr1957-parent-exact-repro.log,/private/tmp/perlonjava-pr1957-alias-owner-fix-final.log,/private/tmp/perlonjava-pr1957-globref-focused.log,/private/tmp/perlonjava-pr1957-full-make-51e7b3187.log,/private/tmp/perlonjava-pr1957-links-51e7b3187.log.Blocked by: #1649/#1938 base integration; PR #1976 is intentionally draft until the ownership foundation lands.
Next: after #1938 integrates, rebase #1976 onto master, rerun the full gate, and promote it for review.
fglock commentedon Nov 4, 2026
Follow-up on the deterministic TLS acceptance case: the local TLS fixture passes with system Perl and the PerlOnJava interpreter, but still fails on the JVM with
sysread() on closed filehandleafter response-header prefetch. This remains open pending the ownership work in #1649; the focused Perl closure-owner regression passing on the current #1649 candidate is not yet enough to close the end-to-end TLS case. Please rerun this fixture after #1649 integrates.fglock commentedon Nov 4, 2026
I tightened the local reproducer after finding that the initial version let the callback become dead before body reads. With the callback held through the read, the TLS fixture still fails on the JVM only when a freshly constructed
IO::Socket::SSLis passed directly as a temporary argument to the reader; system Perl passes. Keeping that socket in a caller lexical makes the fixture pass on both current master and the #1649 candidate. The JVM failure reachesIO::Handle::DESTROYand closes the socket during TAP output, rather than going throughRuntimeScalar.releaseIoOwner.This narrows #1957 to lifetime handling of temporary blessed filehandle arguments; it is related to the ownership semantics under #1649, while the candidate's existing hash-object closure regression alone does not prove the TLS case fixed. Keeping #1957 open pending a runtime fix and a passing direct-temporary TLS regression.
fglock commentedon Nov 4, 2026
The narrowed temporary-filehandle fix and local TLS regression are in draft PR #1976, stacked on #1938. The parent fails the deterministic fixture on JVM and interpreter; the candidate passes on system Perl, JVM, and interpreter, and the full
makegate passes. Keeping #1957 open until the #1649 base integrates and the stacked PR is retested on master.