Skip to content

Commit 86b2b41

Browse files
committed
[GR-26058] Fix locking in single-threaded multi-context case.
PullRequest: graalpython/1275
2 parents 549760e + 9c15b36 commit 86b2b41

File tree

7 files changed

+75
-16
lines changed

7 files changed

+75
-16
lines changed

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/PythonLanguage.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,12 @@ public final class PythonLanguage extends TruffleLanguage<PythonContext> {
133133

134134
public final Assumption singleContextAssumption = Truffle.getRuntime().createAssumption("Only a single context is active");
135135

136+
/**
137+
* This assumption will be valid if all contexts are single-threaded. Hence, it will be
138+
* invalidated as soon as at least one context has been initialized for multi-threading.
139+
*/
140+
public final Assumption singleThreadedAssumption = Truffle.getRuntime().createAssumption("Only a single thread is active");
141+
136142
private final NodeFactory nodeFactory;
137143
private final ConcurrentHashMap<String, RootCallTarget> builtinCallTargetCache = new ConcurrentHashMap<>();
138144

@@ -621,6 +627,7 @@ protected boolean isThreadAccessAllowed(Thread thread, boolean singleThreaded) {
621627

622628
@Override
623629
protected void initializeMultiThreading(PythonContext context) {
630+
singleThreadedAssumption.invalidate();
624631
context.initializeMultiThreading();
625632
}
626633

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/BuiltinConstructors.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@
221221
import com.oracle.graal.python.nodes.util.CastToJavaStringNodeGen;
222222
import com.oracle.graal.python.nodes.util.SplitArgsNode;
223223
import com.oracle.graal.python.runtime.ExecutionContext.ForeignCallContext;
224+
import com.oracle.graal.python.runtime.ExecutionContextFactory.ForeignCallContextNodeGen;
224225
import com.oracle.graal.python.runtime.PythonContext;
225226
import com.oracle.graal.python.runtime.PythonCore;
226227
import com.oracle.graal.python.runtime.exception.PException;
@@ -2126,6 +2127,7 @@ public abstract static class TypeNode extends PythonBuiltinNode {
21262127
@Child private IsSubtypeNode isSubtypeNode;
21272128
@Child private GetObjectArrayNode getObjectArrayNode;
21282129
@Child private IsAcceptableBaseNode isAcceptableBaseNode;
2130+
@Child private ForeignCallContext foreignCallContext;
21292131

21302132
protected abstract Object execute(VirtualFrame frame, Object cls, Object name, Object bases, Object dict, PKeyword[] kwds);
21312133

@@ -2391,7 +2393,7 @@ private PythonClass typeMetaclass(VirtualFrame frame, String name, PTuple bases,
23912393
// Make slots into a tuple
23922394
}
23932395
PythonContext context = getContextRef().get();
2394-
Object state = ForeignCallContext.enter(frame, context, this);
2396+
Object state = ensureForeignCallContext().enter(frame, context, this);
23952397
try {
23962398
pythonClass.setAttribute(__SLOTS__, slotsObject);
23972399
if (basesArray.length > 1) {
@@ -2407,7 +2409,7 @@ private PythonClass typeMetaclass(VirtualFrame frame, String name, PTuple bases,
24072409
addNativeSlots(pythonClass, newSlots);
24082410
}
24092411
} finally {
2410-
ForeignCallContext.exit(frame, context, state);
2412+
ensureForeignCallContext().exit(frame, context, state);
24112413
}
24122414
}
24132415

@@ -2788,6 +2790,14 @@ private IsAcceptableBaseNode ensureIsAcceptableBaseNode() {
27882790
}
27892791
return isAcceptableBaseNode;
27902792
}
2793+
2794+
private ForeignCallContext ensureForeignCallContext() {
2795+
if (foreignCallContext == null) {
2796+
CompilerDirectives.transferToInterpreterAndInvalidate();
2797+
foreignCallContext = insert(ForeignCallContextNodeGen.create());
2798+
}
2799+
return foreignCallContext;
2800+
}
27912801
}
27922802

27932803
@Builtin(name = MODULE, minNumOfPositionalArgs = 1, takesVarArgs = true, takesVarKeywordArgs = true, constructsClass = PythonBuiltinClassType.PythonModule, isPublic = false)

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/ExternalFunctionNodes.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
import com.oracle.graal.python.nodes.truffle.PythonTypes;
8686
import com.oracle.graal.python.runtime.ExecutionContext.CalleeContext;
8787
import com.oracle.graal.python.runtime.ExecutionContext.ForeignCallContext;
88+
import com.oracle.graal.python.runtime.ExecutionContextFactory.ForeignCallContextNodeGen;
8889
import com.oracle.graal.python.runtime.PythonContext;
8990
import com.oracle.graal.python.runtime.object.PythonObjectFactory;
9091
import com.oracle.graal.python.util.PythonUtils;
@@ -199,6 +200,7 @@ static final class ExternalFunctionInvokeNode extends PNodeWithContext implement
199200
@Child private ToJavaStealingNode asPythonObjectNode = ToJavaStealingNodeGen.create();
200201
@Child private InteropLibrary lib;
201202
@Child private PRaiseNode raiseNode;
203+
@Child private ForeignCallContext foreignCallContext;
202204

203205
@CompilationFinal private Assumption nativeCodeDoesntNeedExceptionState = Truffle.getRuntime().createAssumption();
204206
@CompilationFinal private Assumption nativeCodeDoesntNeedMyFrame = Truffle.getRuntime().createAssumption();
@@ -249,7 +251,7 @@ public Object execute(VirtualFrame frame, String name, Object callable, Object[]
249251

250252
// If any code requested the caught exception (i.e. used 'sys.exc_info()'), we store
251253
// it to the context since we cannot propagate it through the native frames.
252-
Object state = ForeignCallContext.enter(frame, ctx, this);
254+
Object state = ensureForeignCallContext().enter(frame, ctx, this);
253255

254256
try {
255257
return fromNative(asPythonObjectNode.execute(checkResultNode.execute(name, lib.execute(callable, cArguments))));
@@ -263,7 +265,7 @@ public Object execute(VirtualFrame frame, String name, Object callable, Object[]
263265
// special case after calling a C function: transfer caught exception back to frame
264266
// to simulate the global state semantics
265267
PArguments.setException(frame, ctx.getCaughtException());
266-
ForeignCallContext.exit(frame, ctx, state);
268+
ensureForeignCallContext().exit(frame, ctx, state);
267269
}
268270
}
269271

@@ -287,6 +289,14 @@ private PythonContext getContext() {
287289
return contextRef.get();
288290
}
289291

292+
private ForeignCallContext ensureForeignCallContext() {
293+
if (foreignCallContext == null) {
294+
CompilerDirectives.transferToInterpreterAndInvalidate();
295+
foreignCallContext = insert(ForeignCallContextNodeGen.create());
296+
}
297+
return foreignCallContext;
298+
}
299+
290300
public static ExternalFunctionInvokeNode create() {
291301
return new ExternalFunctionInvokeNode();
292302
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/ImpModuleBuiltins.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,15 +194,15 @@ public abstract static class CreateDynamic extends PythonBuiltinNode {
194194
@Child private LookupAndCallUnaryNode callReprNode = LookupAndCallUnaryNode.create(SpecialMethodNames.__REPR__);
195195

196196
@Specialization
197-
@SuppressWarnings("try")
198197
public Object run(VirtualFrame frame, PythonObject moduleSpec, @SuppressWarnings("unused") Object filename,
198+
@Cached ForeignCallContext foreignCallContext,
199199
@CachedLibrary(limit = "1") InteropLibrary interop) {
200200
PythonContext context = getContextRef().get();
201-
Object state = ForeignCallContext.enter(frame, context, this);
201+
Object state = foreignCallContext.enter(frame, context, this);
202202
try {
203203
return run(moduleSpec, interop);
204204
} finally {
205-
ForeignCallContext.exit(frame, context, state);
205+
foreignCallContext.exit(frame, context, state);
206206
}
207207
}
208208

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/hpy/HPyExternalFunctionNodes.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ abstract static class HPyExternalFunctionInvokeNode extends Node implements Indi
121121
Object doIt(VirtualFrame frame, String name, Object callable, Object[] frameArgs,
122122
@CachedLibrary("callable") InteropLibrary lib,
123123
@CachedContext(PythonLanguage.class) PythonContext ctx,
124+
@Cached ForeignCallContext foreignCallContext,
124125
@Cached HPyEnsureHandleNode ensureHandleNode,
125126
@Cached HPyCheckFunctionResultNode checkFunctionResultNode,
126127
@Cached HPyAsPythonObjectNode asPythonObjectNode,
@@ -134,7 +135,7 @@ Object doIt(VirtualFrame frame, String name, Object callable, Object[] frameArgs
134135

135136
// If any code requested the caught exception (i.e. used 'sys.exc_info()'), we store
136137
// it to the context since we cannot propagate it through the native frames.
137-
Object state = ForeignCallContext.enter(frame, ctx, this);
138+
Object state = foreignCallContext.enter(frame, ctx, this);
138139

139140
try {
140141
GraalHPyHandle resultHandle = ensureHandleNode.execute(hPyContext, lib.execute(callable, arguments));
@@ -147,7 +148,7 @@ Object doIt(VirtualFrame frame, String name, Object callable, Object[] frameArgs
147148
// special case after calling a C function: transfer caught exception back to frame
148149
// to simulate the global state semantics
149150
PArguments.setException(frame, ctx.getCaughtException());
150-
ForeignCallContext.exit(frame, ctx, state);
151+
foreignCallContext.exit(frame, ctx, state);
151152
}
152153
}
153154

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/foreign/ForeignObjectBuiltins.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,7 @@ protected Object doInteropCall(VirtualFrame frame, Object callee, Object[] argum
590590
@SuppressWarnings("unused") @CachedLibrary("callee") PythonObjectLibrary plib,
591591
@CachedLibrary("callee") InteropLibrary lib,
592592
@CachedContext(PythonLanguage.class) PythonContext context,
593+
@Cached ForeignCallContext foreignCallContext,
593594
@Cached PTypeToForeignNode toForeignNode,
594595
@Cached PForeignToPTypeNode toPTypeNode) {
595596
try {
@@ -598,7 +599,7 @@ protected Object doInteropCall(VirtualFrame frame, Object callee, Object[] argum
598599
convertedArgs[i] = toForeignNode.executeConvert(arguments[i]);
599600
}
600601
Object res = null;
601-
Object state = ForeignCallContext.enter(frame, context, this);
602+
Object state = foreignCallContext.enter(frame, context, this);
602603
try {
603604
if (lib.isExecutable(callee)) {
604605
res = lib.execute(callee, convertedArgs);
@@ -608,7 +609,7 @@ protected Object doInteropCall(VirtualFrame frame, Object callee, Object[] argum
608609
return toPTypeNode.executeConvert(res);
609610
}
610611
} finally {
611-
ForeignCallContext.exit(frame, context, state);
612+
foreignCallContext.exit(frame, context, state);
612613
}
613614
} catch (ArityException | UnsupportedTypeException | UnsupportedMessageException e) {
614615
throw raise(PythonErrorType.TypeError, ErrorMessages.INVALID_INSTANTIATION_OF_FOREIGN_OBJ);

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/ExecutionContext.java

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,22 +41,27 @@
4141

4242
package com.oracle.graal.python.runtime;
4343

44+
import com.oracle.graal.python.PythonLanguage;
4445
import com.oracle.graal.python.builtins.objects.frame.PFrame;
4546
import com.oracle.graal.python.builtins.objects.frame.PFrame.Reference;
4647
import com.oracle.graal.python.builtins.objects.function.PArguments;
4748
import com.oracle.graal.python.nodes.IndirectCallNode;
49+
import com.oracle.graal.python.nodes.PNodeWithContext;
4850
import com.oracle.graal.python.nodes.PRootNode;
4951
import com.oracle.graal.python.nodes.control.TopLevelExceptionHandler;
5052
import com.oracle.graal.python.nodes.frame.MaterializeFrameNode;
5153
import com.oracle.graal.python.nodes.frame.MaterializeFrameNodeGen;
5254
import com.oracle.graal.python.nodes.frame.ReadCallerFrameNode;
5355
import com.oracle.graal.python.nodes.util.ExceptionStateNodes.GetCaughtExceptionNode;
5456
import com.oracle.graal.python.runtime.exception.PException;
57+
import com.oracle.truffle.api.Assumption;
5558
import com.oracle.truffle.api.CompilerAsserts;
5659
import com.oracle.truffle.api.CompilerDirectives;
5760
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
5861
import com.oracle.truffle.api.CompilerDirectives.ValueType;
5962
import com.oracle.truffle.api.RootCallTarget;
63+
import com.oracle.truffle.api.dsl.Cached;
64+
import com.oracle.truffle.api.dsl.Specialization;
6065
import com.oracle.truffle.api.frame.Frame;
6166
import com.oracle.truffle.api.frame.FrameInstance;
6267
import com.oracle.truffle.api.frame.VirtualFrame;
@@ -377,7 +382,28 @@ public static void exit(VirtualFrame frame, PythonContext context, Object savedS
377382
}
378383
}
379384

380-
public abstract static class ForeignCallContext {
385+
public abstract static class ForeignCallContext extends PNodeWithContext {
386+
387+
/**
388+
* Return {@code true} if we need to acquire/release the interop lock.
389+
*/
390+
protected abstract boolean execute(PythonContext context);
391+
392+
@Specialization(assumptions = "singleThreadedAssumption()")
393+
static boolean doEnterSingleThreaded(@SuppressWarnings("unused") PythonContext context) {
394+
return false;
395+
}
396+
397+
@Specialization(guards = "singleThreadedAssumption == context.getSingleThreadedAssumption()", limit = "3", replaces = "doEnterSingleThreaded")
398+
static boolean doEnterMultiContextCached(@SuppressWarnings("unused") PythonContext context,
399+
@Cached("context.getSingleThreadedAssumption()") Assumption singleThreadedAssumption) {
400+
return !singleThreadedAssumption.isValid();
401+
}
402+
403+
@Specialization(replaces = "doEnterMultiContextCached")
404+
static boolean doEnterMultiContextMultiThreaded(@SuppressWarnings("unused") PythonContext context) {
405+
return true;
406+
}
381407

382408
/**
383409
* Prepare a call from a Python frame to foreign callable. This will also call
@@ -409,11 +435,11 @@ public abstract static class ForeignCallContext {
409435
* </pre>
410436
* </p>
411437
*/
412-
public static Object enter(VirtualFrame frame, PythonContext context, IndirectCallNode callNode) {
438+
public final Object enter(VirtualFrame frame, PythonContext context, IndirectCallNode callNode) {
413439
if (context == null) {
414440
return null;
415441
}
416-
if (!context.getSingleThreadedAssumption().isValid()) {
442+
if (execute(context)) {
417443
context.acquireInteropLock();
418444
}
419445
return IndirectCallContext.enter(frame, context, callNode);
@@ -422,14 +448,18 @@ public static Object enter(VirtualFrame frame, PythonContext context, IndirectCa
422448
/**
423449
* Cleanup after an interop call. For more details, see {@link #enter}.
424450
*/
425-
public static void exit(VirtualFrame frame, PythonContext context, Object savedState) {
451+
public final void exit(VirtualFrame frame, PythonContext context, Object savedState) {
426452
if (context != null) {
427453
IndirectCallContext.exit(frame, context, savedState);
428-
if (!context.getSingleThreadedAssumption().isValid()) {
454+
if (execute(context)) {
429455
context.releaseInteropLock();
430456
}
431457
}
432458
}
459+
460+
static Assumption singleThreadedAssumption() {
461+
return PythonLanguage.getCurrent().singleThreadedAssumption;
462+
}
433463
}
434464

435465
public abstract static class IndirectCalleeContext {

0 commit comments

Comments
 (0)