Skip to content

Commit 1be14c0

Browse files
authored
Merge pull request #956 from fglock/feature/perl-threads-final-delivery
Complete threads API and add fresh runtime reset
2 parents 1d7ab65 + b77c16f commit 1be14c0

21 files changed

Lines changed: 628 additions & 88 deletions

dev/design/concurrency.md

Lines changed: 53 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -647,7 +647,7 @@ Phase 33's release gate completed with `./jcpan --jobs 8 -t DBIx::Class`:
647647
non-local labeled control flow tears down every abandoned Perl frame before the
648648
target resumes, preserving scope-guard diagnostics and redirected STDERR.
649649

650-
### Phase 35 — Lexical regex debugging (implemented core 2026-08-14)
650+
### Phase 35 — Lexical regex debugging (completed 2026-08-14)
651651

652652
Implement scoped `use/no re 'debug'` and `debugcolor` as compiler hints carried
653653
by regex and CODE metadata on both backends. Diagnostics use the bound runtime's
@@ -656,10 +656,10 @@ trace flag. Acceptance: `re/stclass_threads.t` reaches 6/6 and direct/child
656656
traces have identical behavior and runtime ownership.
657657

658658
The compiler hints, JVM/interpreter propagation, runtime-owned STDERR routing,
659-
snapshot behavior, and focused six-assertion oracle are implemented. The core
660-
`stclass_threads.t` gate is 3/6: all three trace-linearity assertions pass, but
661-
each child trace contains one additional record. That direct/child formatting
662-
delta remains part of Phase 35 acceptance.
659+
snapshot behavior, and focused six-assertion oracle are implemented. Debug
660+
regex lifecycle records now drain after END in the owning main or child
661+
runtime. The core `stclass_threads.t` gate reaches 6/6 with equal direct/child
662+
record counts and linear scaling.
663663

664664
### Phase 36 — Complete regex parity exercised by thread wrappers (in progress)
665665

@@ -671,10 +671,16 @@ applicable `perl5_t/t/re/*thr*.t` test and direct companion completes its plan
671671
without unexpected failure on JVM or interpreter backends.
672672

673673
This tranche restores recursive-definition compilation for `reg_email` and
674-
keeps direct/thread compilation behavior aligned. Its test body is still
675-
blocked in both paths by the direct DATA-handle gap. `pat_re_eval` and the
676-
remaining `qr//`, conditional, control-verb, lookbehind, Unicode-property, and
677-
diagnostic coverage remain shared regex-language work.
674+
keeps direct/thread compilation behavior aligned. DATA now models the source
675+
file positioned after its marker, remains seekable to the source start, and
676+
crosses thread snapshots through the named-handle inheritance policy. Direct
677+
and threaded `reg_email` therefore pass 13/13 on both backends. `pat_re_eval`
678+
now parses quoted code-block-shaped text correctly: `(?{` inside `\Q...\E`
679+
is literal rather than an embedded Perl block. This advances both direct and
680+
threaded files to runtime construction, where arbitrary match-time `(?{...})`
681+
execution remains the next blocker. The remaining `qr//`, conditional,
682+
control-verb, lookbehind, Unicode-property, and diagnostic coverage likewise
683+
remains shared regex-language work.
678684

679685
### Phase 37 — General filehandle and resource inheritance (implemented tranche 2026-08-14)
680686

@@ -737,14 +743,20 @@ runtime that releases the final cross-runtime owner. Separate destructive plain
737743
includes nested rebless/store-back, fresh `refaddr` views, cycles, weak refs,
738744
one global destructor, and share-versus-shared_clone system-Perl oracles.
739745

740-
### Phase 40 — Complete public `threads` API
746+
### Phase 40 — Complete public `threads` API (completed 2026-08-14)
741747

742748
Close every remaining lifecycle, signal, context, exit-status, stack-size,
743749
import, stringify, alias-object, and shutdown-warning gap. Upgrade the module
744750
version only when its upstream surface passes. A nonzero stack request always
745751
selects a platform child even after virtual threads become the default.
746752

747-
### Phase 41 — Fresh-runtime reset
753+
The public 2.43 method surface is implemented and advertised. Creation context,
754+
alias objects, current/class detach, signals, exit policy, stack metadata,
755+
stringification, terminal errors, daemon-carrier shutdown, and attached-child
756+
exit warnings are covered by focused JVM/interpreter tests. Core
757+
`op/threads.t` completes 30/30.
758+
759+
### Phase 41 — Fresh-runtime reset (completed 2026-08-14)
748760

749761
Add reset as a lifecycle distinct from terminal `close()`. Reset is allowed only
750762
after execution, compilation, callbacks, children, locks, waiters, handles, and
@@ -753,6 +765,18 @@ destruction work quiesce. Rebuild every domain in
753765
poison a runtime after any partial reset failure. Acceptance is exhaustive
754766
`A; reset; B == fresh; B` parity plus classloader/package-graph collection.
755767

768+
`PerlRuntime.reset()` is now a distinct exclusive lifecycle transition. It
769+
rejects active bindings, compilation, children, shared locks, and waiters;
770+
drains END/destruction and owned resources; replaces every runtime state holder;
771+
rebuilds standard handles and core globals; clears terminal thread-family state;
772+
and poisons the runtime after any partial failure. JVM/interpreter differentials
773+
prove representative package, CODE, `%INC`, regex, execution, and I/O freshness.
774+
Pooling remains off pending Phase 42's checkout stress, collection, and measured
775+
benefit gates.
776+
777+
The post-reset regression gate retains all 325 DBIx::Class files and 42,671
778+
assertions under `./jcpan --jobs 8 -t DBIx::Class`.
779+
756780
### Phase 42 — Opt-in pooling and concurrent PSGI
757781

758782
Add a bounded runtime-family pool configured by
@@ -793,7 +817,7 @@ CI.
793817

794818
## 7. Progress Tracking
795819

796-
### Current Status: Phases 35–39 implemented for the supported tranche
820+
### Current Status: Phase 41 complete; Phase 36 and Phase 39b remain open
797821

798822
Hints, warnings, filters, and source maps are runtime-owned while compiler-only
799823
scratch remains protected by the global compile lock. The Phase 11 inventory is
@@ -949,10 +973,16 @@ request history.
949973

950974
The core differential runner now reserves an exclusive serial lane for the
951975
resource-sensitive `gv.t`, advanced-regex, regex-speed, GH7094 benchmark, and
952-
Abigail JAPH tests. The thread wrappers for `pat.t`, `pat_psycho.t`, and
953-
`speed.t` use that same lane and a 600-second minimum outer deadline because
954-
runtime snapshot startup plus the upstream watchdogs exceed the normal
955-
300-second budget under parallel load. These tests have internal watchdogs or
976+
Abigail JAPH tests. The thread wrappers `pat_thr.t`, `pat_psycho_thr.t`,
977+
`regexp_qr_embed_thr.t`, and `speed_thr.t` use that same lane and a 600-second
978+
minimum outer deadline because runtime snapshot startup plus the upstream
979+
watchdogs exceed the normal 300-second budget under parallel load. The
980+
`regexp_qr_embed_thr.t` classification also prevents a full-corpus memory spike
981+
from exhausting its child runtime near the end of the 2,210-case matrix. Thread
982+
snapshots inherit named IO slots only when they contain a real handle; inert
983+
parser placeholders are child-vivified on demand instead of being copied
984+
quadratically across thousands of eval-created runtimes. These tests have
985+
internal watchdogs or
956986
timing assertions whose TAP totals changed when they competed with the normal
957987
parallel corpus; they retain stable original indices, and `gv.t` receives the
958988
upstream timeout factor. This is test scheduling policy, not a relaxation of
@@ -965,15 +995,16 @@ three assertions from the adjacent-import parser fix.
965995

966996
### Next Steps
967997

968-
1. Close the remaining Phase 35 trace-record delta and Phase 36 direct regex
969-
language/DATA-handle gaps; wrapper behavior must follow the corrected direct
970-
implementation without special cases.
998+
1. Complete Phase 36's direct regex-language gaps in `pat_re_eval`, `qr//`,
999+
conditionals, control verbs, lookbehind, Unicode properties, and diagnostics;
1000+
wrapper behavior must follow the corrected direct implementation without
1001+
special cases.
9711002
2. Implement Phase 39b's fetch-time nested shared proxies, global destruction,
9721003
weak/cyclic ownership, and the destructive `share` versus preserving
9731004
`shared_clone` distinction.
974-
3. Land Phases 40–44 as the final delivery sequence: public API closure,
975-
fresh-runtime reset, opt-in pooling and concurrent PSGI, virtual threads by
976-
default, and the complete release gate.
1005+
3. Land Phases 42–44 as the final delivery sequence: opt-in pooling and
1006+
concurrent PSGI, virtual threads by default, and the complete release gate.
1007+
Phases 40 and 41's public API and fresh-reset foundations are complete.
9771008
4. Preserve the green core, Storable, Test2, Net::SSLeay, index/substr, DBI, and
9781009
DBIx::Class anchors after every phase. The 2026-08-14 DBIx::Class gate passed
9791010
all 325 files and 42,671 assertions under

dev/design/runtime-pooling-reset-contract.md

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
## Status
44

5-
Runtime pooling was re-evaluated on 2026-08-13 and remains deliberately
6-
disabled. `PerlRuntime.close()` is a terminal resource-release
7-
operation, not a reset operation, and a closed runtime deliberately rejects
8-
`bind`, `initialize`, and `execute`. Reusing it would currently expose state that
9-
a newly constructed runtime does not contain.
5+
Fresh-runtime reset was implemented on 2026-08-14. Runtime pooling remains
6+
deliberately disabled until Phase 42's checkout stress, retention measurements,
7+
and performance gate pass. `PerlRuntime.close()` remains a terminal
8+
resource-release operation; reusable runtimes use the separate exclusive
9+
`reset()` transition.
1010

1111
This document defines the proof required before a pool may be implemented. It
1212
does not authorize clearing state opportunistically or enabling pooling behind
@@ -64,24 +64,24 @@ correct terminal lifecycle, but it is intentionally insufficient for pooling.
6464

6565
Pooling remains disabled until all items below are complete:
6666

67-
- [ ] Introduce one exclusive lifecycle transition that prevents reset while
67+
- [x] Introduce one exclusive lifecycle transition that prevents reset while
6868
execution, compilation, callbacks, ithreads, detached children, shared-lock
6969
ownership, or condition waiters remain active.
70-
- [ ] Define whether core bootstrap state is reconstructed or restored from an
70+
- [x] Define whether core bootstrap state is reconstructed or restored from an
7171
immutable template; user package/CODE/class state and `%INC` must never leak.
72-
- [ ] Recreate standard I/O wrappers and glob topology without closing borrowed
72+
- [x] Recreate standard I/O wrappers and glob topology without closing borrowed
7373
JVM streams, and restore selected/last-handle and visibility defaults.
74-
- [ ] Drain END/destruction work according to normal Perl semantics before
74+
- [x] Drain END/destruction work according to normal Perl semantics before
7575
clearing lifecycle roots; prove weak references and rescued objects do not
7676
cross tenants.
77-
- [ ] Clear every state domain in the inventory, including counters and caches,
77+
- [x] Clear every state domain in the inventory, including counters and caches,
7878
without retaining generated classes or prior workload object graphs.
79-
- [ ] Restore process-derived defaults (`cwd`, environment view, random policy,
79+
- [x] Restore process-derived defaults (`cwd`, environment view, random policy,
8080
warning/feature defaults) according to an explicitly documented checkout
8181
contract.
82-
- [ ] Reject or quarantine a runtime after reset failure; a partially reset
82+
- [x] Reject or quarantine a runtime after reset failure; a partially reset
8383
runtime must never return to the pool.
84-
- [ ] Prove `A; reset; B == fresh; B` on both compiler backends across globals,
84+
- [x] Prove `A; reset; B == fresh; B` on both compiler backends across globals,
8585
closures, eval/require, regex, warnings/hints, MRO, I/O, lifecycle, signals,
8686
native modules, DATA, debugger state, and exceptions.
8787
- [ ] Add concurrency/stress coverage for checkout ownership, cancellation,
@@ -93,12 +93,12 @@ Pooling remains disabled until all items below are complete:
9393

9494
## Current automated guard
9595

96-
`PerlRuntimePoolingResetContractTest` records the present negative contract:
97-
close is terminal, closed runtimes cannot be rebound or executed, and package,
98-
regex-cache, and execution settings retained by the terminal object differ from
99-
a fresh runtime. The test prevents a future pool from treating `close()` as a
100-
reset without first replacing this negative proof with the full equivalence
101-
suite above.
96+
`PerlRuntimePoolingResetContractTest` preserves the negative `close()` contract.
97+
`PerlRuntimeResetTest` proves the positive transition: representative package,
98+
CODE, `%INC`, regex, execution, thread-option, and standard-I/O state matches a
99+
fresh runtime on both backends; reset rejects bindings, child threads, and
100+
shared locks; pending END work drains; failed reset poisons the runtime; and the
101+
same Java runtime identity executes again after successful reset.
102102

103103
## Related documents
104104

dev/tools/perl_test_runner.pl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,7 @@ sub timeout_for_test {
465465
| (?:^|/)perl5_t/t/re/pat_psycho(?:_thr)?\.t$
466466
| (?:^|/)perl5_t/t/op/gv\.t$
467467
| (?:^|/)perl5_t/t/re/pat_advanced(?:_thr)?\.t$
468+
| (?:^|/)perl5_t/t/re/regexp_qr_embed_thr\.t$
468469
| (?:^|/)perl5_t/t/re/speed(?:_thr)?\.t$
469470
| (?:^|/)perl5_t/t/benchmark/gh7094-speed-up-keys-on-empty-hash\.t$
470471
| (?:^|/)perl5_t/t/japh/abigail\.t$
@@ -479,6 +480,7 @@ sub requires_exclusive_slot {
479480
| (?:^|/)perl5_t/t/re/pat(?:_thr)?\.t$
480481
| (?:^|/)perl5_t/t/re/pat_psycho(?:_thr)?\.t$
481482
| (?:^|/)perl5_t/t/re/pat_advanced(?:_thr)?\.t$
483+
| (?:^|/)perl5_t/t/re/regexp_qr_embed_thr\.t$
482484
| (?:^|/)perl5_t/t/re/speed(?:_thr)?\.t$
483485
| (?:^|/)perl5_t/t/benchmark/gh7094-speed-up-keys-on-empty-hash\.t$
484486
| (?:^|/)perl5_t/t/japh/abigail\.t$

src/main/java/org/perlonjava/app/scriptengine/PerlLanguageProvider.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.perlonjava.runtime.io.StandardIO;
2323
import org.perlonjava.runtime.perlmodule.BHooksEndOfScope;
2424
import org.perlonjava.runtime.perlmodule.Strict;
25+
import org.perlonjava.runtime.regex.RuntimeRegex;
2526
import org.perlonjava.runtime.runtimetypes.*;
2627
import org.perlonjava.runtime.WarningBitsRegistry;
2728

@@ -618,6 +619,7 @@ private static RuntimeList executeCodeImpl(RuntimeCode runtimeCode, Node ast, Em
618619
// END may itself fail; captured cleanup still belongs after
619620
// the attempted END dispatch and before runtime teardown.
620621
MortalList.flushDeferredCaptures();
622+
RuntimeRegex.emitCurrentRuntimeDebugFreeTraces();
621623
}
622624
// Global destruction: walk stashes for tracked blessed objects
623625
GlobalDestruction.runGlobalDestruction();
@@ -647,6 +649,7 @@ private static RuntimeList executeCodeImpl(RuntimeCode runtimeCode, Node ast, Em
647649
} finally {
648650
CallerStack.pop();
649651
MortalList.flushDeferredCaptures(); // Live captures outlast END
652+
RuntimeRegex.emitCurrentRuntimeDebugFreeTraces();
650653
}
651654
RuntimeIO.closeAllHandles();
652655
}

0 commit comments

Comments
 (0)