Skip to content

Commit 52ed45b

Browse files
fglockcodex
andcommitted
fix: keep live weak closure arguments callable
DBIx::Class::Storage::BlockRunner weakens a fresh closure argument copy before invoking it. Perl keeps the caller's strong lexical alive, but PerlOnJava's CODE weak-ref sweep cleared the weak copy immediately when the closure had no counted owners. Teach the CODE weak-ref cleanup path to preserve weak CODE refs while a current live scalar still points at the same RuntimeCode, without preserving stale deleted stash slots used by Sub::Quote cleanup. Generated with Codex (https://openai.com/codex) Co-Authored-By: Codex <codex@openai.com>
1 parent 7716b53 commit 52ed45b

3 files changed

Lines changed: 51 additions & 1 deletion

File tree

src/main/java/org/perlonjava/runtime/runtimetypes/ReachabilityWalker.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,26 @@ && followGlobalCodeCaptures(code, target, seen, todo)) {
522522
return false;
523523
}
524524

525+
public static boolean hasLiveStrongScalarReferent(RuntimeBase target) {
526+
if (target == null) return false;
527+
for (Object liveVar : MyVarCleanupStack.snapshotLiveVars()) {
528+
if (liveVar instanceof RuntimeScalar sc
529+
&& !WeakRefRegistry.isweak(sc)
530+
&& !sc.scopeExited
531+
&& sc.value == target) {
532+
return true;
533+
}
534+
}
535+
for (RuntimeScalar sc : ScalarRefRegistry.snapshot()) {
536+
if (sc == null) continue;
537+
if (WeakRefRegistry.isweak(sc)) continue;
538+
if (sc.scopeExited) continue;
539+
if (!MyVarCleanupStack.isLive(sc)) continue;
540+
if (sc.value == target) return true;
541+
}
542+
return false;
543+
}
544+
525545
public static boolean isReachableFromGlobalCodeCaptures(RuntimeBase target) {
526546
if (target == null) return false;
527547
Set<RuntimeBase> seen = Collections.newSetFromMap(new IdentityHashMap<>());

src/main/java/org/perlonjava/runtime/runtimetypes/WeakRefRegistry.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,10 +188,13 @@ public static void weaken(RuntimeScalar ref) {
188188
ref.refCountOwned = false;
189189
base.refCount = WEAKLY_TRACKED;
190190
}
191+
boolean shouldSweepLiveCodeRef = weakenedLiveCodeRef
192+
&& codeRefHasCountedOwners(base)
193+
&& !ModuleInitGuard.inModuleInit();
191194
if (base instanceof RuntimeCode code
192195
&& code.refCount >= 0
193196
&& weakRefsExist
194-
&& ((weakenedLiveCodeRef && !ModuleInitGuard.inModuleInit())
197+
&& (shouldSweepLiveCodeRef
195198
|| (code.hadStashRef
196199
&& code.stashRefCount <= 0
197200
&& !isInstalledGlobalCodeRef(code)))) {
@@ -205,6 +208,10 @@ public static void weaken(RuntimeScalar ref) {
205208
}
206209
}
207210

211+
private static boolean codeRefHasCountedOwners(RuntimeBase base) {
212+
return base.refCount > 0 || base.activeOwnerCount() > 0;
213+
}
214+
208215
/**
209216
* Check if a RuntimeScalar is a weak reference.
210217
*/
@@ -295,6 +302,7 @@ private static boolean isInstalledGlobalCodeRef(RuntimeCode code) {
295302

296303
private static boolean shouldKeepCodeWeakRefs(RuntimeCode code) {
297304
if (code.stashRefCount > 0 || isInstalledGlobalCodeRef(code)) return true;
305+
if (ReachabilityWalker.hasLiveStrongScalarReferent(code)) return true;
298306
return RuntimeCode.isActiveCode(code);
299307
}
300308

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/usr/bin/env perl
2+
use strict;
3+
use warnings;
4+
5+
use Scalar::Util qw(isweak weaken);
6+
use Test::More tests => 3;
7+
8+
sub run_callback {
9+
my $callback = shift;
10+
return weaken_and_call($callback);
11+
}
12+
13+
sub weaken_and_call {
14+
weaken(my $weak_callback = shift);
15+
16+
ok(isweak($weak_callback), 'closure argument copy is weak');
17+
ok(defined $weak_callback, 'weak closure argument copy survives while caller holds it');
18+
is($weak_callback->(), 'captured value', 'weak closure argument remains callable');
19+
}
20+
21+
my $captured = 'captured value';
22+
run_callback(sub { $captured });

0 commit comments

Comments
 (0)