Skip to content

Commit 58e2729

Browse files
committed
[GR-16164] Fix sharing with the benchmark harness, RubyLauncher and IO buffers.
PullRequest: truffleruby/881
2 parents 9b445b0 + 4dcffb3 commit 58e2729

22 files changed

+106
-55
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# 20.0.0 beta 2
2+
3+
Bug fixes:
4+
5+
* Sharing for thread-safety of objects is now triggered later as intended, e.g., when a second `Thread` is started.
6+
17
# 20.0.0 beta 1
28

39
Bug fixes:

bench/benchmark-interface/lib/benchmark-interface/backends/bips.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ module Bips
1313
LONG_ITERATION_THRESHOLD = 0.1 # seconds
1414

1515
def self.run(benchmark_set, names, options)
16-
BeckhmarkInterface.require_rubygems
16+
BenchmarkInterface.require_rubygems
1717
benchmark_interface_original_require 'benchmark/ips'
1818

1919
unless options['--no-scale']

bench/benchmark-interface/lib/benchmark-interface/backends/deep.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ def self.run(benchmark_set, names, options)
1414
unless names.size == 1
1515
abort 'The deep backend only works when you run just one benchmark at a time - specify the name on the command line'
1616
end
17-
18-
BeckhmarkInterface.require_rubygems
17+
18+
BenchmarkInterface.require_rubygems
1919
benchmark_interface_original_require 'deep-bench/backend'
2020

2121
benchmark = benchmark_set.benchmark(names.first)

bench/benchmark-interface/lib/benchmark-interface/benchmark-set.rb

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,22 @@ class BenchmarkSet
1414
def initialize
1515
@benchmarks = []
1616
@counter = 0
17-
@@current = self
17+
Thread.current[:benchmark_interface_benchmark_set] = self
1818
@iterations = 1
1919
end
2020

2121
def load_benchmarks(path)
2222
@path = path
23-
@@current = self
2423
load(path)
2524
end
2625

2726
def load_inlined_benchmark(code)
2827
@path = '-e'
29-
@@current = self
3028
TOPLEVEL_BINDING.eval(code)
3129
end
3230

3331
def load_mri_benchmarks(path, options)
3432
@path = path
35-
@@current = self
3633
Frontends::MRI.load_mri path, options
3734
end
3835

@@ -84,10 +81,8 @@ def benchmark(name)
8481
benchmarks([name]).first
8582
end
8683

87-
@@current = nil
88-
8984
def self.current
90-
@@current
85+
Thread.current[:benchmark_interface_benchmark_set]
9186
end
9287

9388
end

bench/benchmark-interface/lib/benchmark-interface/frontends/benchmark.rb

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,6 @@ def self.realtime(name=nil, &block)
3434
def self.benchmark(caption='', label_width=nil, format=nil, *labels)
3535
yield BenchmarkInterface::BenchmarkContext.new
3636
end
37-
38-
def self.realtime(name=nil, &block)
39-
BenchmarkInterface.benchmark name, &block
40-
end
4137

4238
def self.bm(label_width=0, *labels)
4339
yield BenchmarkInterface::BenchmarkContext.new

bench/benchmark-interface/lib/benchmark-interface/require.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# GNU General Public License version 2, or
77
# GNU Lesser General Public License version 2.1.
88

9-
module BeckhmarkInterface
9+
module BenchmarkInterface
1010

1111
def self.require_rubygems
1212
begin
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
slow:Sharing is correctly propagated for classes and constants and they are not shared until sharing is started
2+
slow:Sharing is correctly propagated for thread-local IO buffers which should not trigger sharing

spec/truffle/thread_safe_objects_spec.rb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,4 +255,34 @@ def wb; @wb; end
255255
shared?(new_hash).should == true
256256
end
257257

258+
it "Fiber local variables which do not share the value" do
259+
thread = Thread.current
260+
shared?(thread).should == true
261+
262+
obj = Object.new
263+
thread[:sharing_spec] = obj
264+
begin
265+
shared?(obj).should == false
266+
ensure
267+
thread[:sharing_spec] = nil
268+
end
269+
end
270+
271+
it "Thread local variables which share the value (probably they should not)" do
272+
thread = Thread.current
273+
shared?(thread).should == true
274+
275+
obj = Object.new
276+
thread.thread_variable_set(:sharing_spec, obj)
277+
shared?(obj).should == true # TODO (eregon, 5 Jun 2019): this is the current non-ideal behavior
278+
end
279+
280+
it "classes and constants and they are not shared until sharing is started" do
281+
ruby_exe("p Truffle::Debug.shared?(Object)").should == "false\n"
282+
end
283+
284+
it "thread-local IO buffers which should not trigger sharing" do
285+
ruby_exe("File.read(#{__FILE__.inspect}); p Truffle::Debug.shared?(Object)").should == "false\n"
286+
end
287+
258288
end

src/main/java/org/truffleruby/RubyContext.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ public void initialize() {
262262

263263
// Share once everything is loaded
264264
if (options.SHARED_OBJECTS_ENABLED && options.SHARED_OBJECTS_FORCE) {
265-
sharedObjects.startSharing();
265+
sharedObjects.startSharing(OptionsCatalog.SHARED_OBJECTS_FORCE.getName() + " being true");
266266
}
267267

268268
if (isPreInitializing()) {

src/main/java/org/truffleruby/core/FinalizationService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ public synchronized FinalizerReference addFinalizer(Object object, FinalizerRefe
111111

112112
finalizerReference.addFinalizer(owner, action, root);
113113

114-
referenceProcessor.processReferenceQueue();
114+
referenceProcessor.processReferenceQueue(owner);
115115
return finalizerReference;
116116
}
117117

src/main/java/org/truffleruby/core/ReferenceProcessingService.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ public ReferenceProcessor(RubyContext context) {
113113
this.context = context;
114114
}
115115

116-
protected void processReferenceQueue() {
116+
protected void processReferenceQueue(Class<?> owner) {
117117
if (context.getOptions().SINGLE_THREADED) {
118118

119119
drainReferenceQueue();
@@ -132,18 +132,19 @@ protected void processReferenceQueue() {
132132
*/
133133

134134
if (processingThread == null && !context.isPreInitializing() && context.isInitialized() && !context.isFinalizing()) {
135-
createProcessingThread();
135+
createProcessingThread(owner);
136136
}
137137

138138
}
139139
}
140140

141-
protected void createProcessingThread() {
141+
protected void createProcessingThread(Class<?> owner) {
142142
final ThreadManager threadManager = context.getThreadManager();
143143
processingThread = threadManager.createBootThread(threadName());
144144
context.send(processingThread, "internal_thread_initialize");
145+
final String sharingReason = "creating " + threadName() + " thread for " + owner.getSimpleName();
145146

146-
threadManager.initialize(processingThread, null, threadName(), () -> {
147+
threadManager.initialize(processingThread, null, threadName(), sharingReason, () -> {
147148
while (true) {
148149
final ProcessingReference<?> reference = (ProcessingReference<?>) threadManager.runUntilResult(null, processingQueue::remove);
149150

src/main/java/org/truffleruby/core/array/ArrayAppendManyNode.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import com.oracle.truffle.api.dsl.Cached;
1313
import com.oracle.truffle.api.dsl.ImportStatic;
14+
import com.oracle.truffle.api.dsl.ReportPolymorphism;
1415
import com.oracle.truffle.api.dsl.Specialization;
1516
import com.oracle.truffle.api.object.DynamicObject;
1617
import com.oracle.truffle.api.profiles.ConditionProfile;
@@ -23,6 +24,7 @@
2324
import org.truffleruby.language.objects.shared.PropagateSharingNode;
2425

2526
@ImportStatic(ArrayGuards.class)
27+
@ReportPolymorphism
2628
public abstract class ArrayAppendManyNode extends RubyBaseNode {
2729

2830
@Child private PropagateSharingNode propagateSharingNode = PropagateSharingNode.create();

src/main/java/org/truffleruby/core/array/ArrayAppendOneNode.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import com.oracle.truffle.api.dsl.Cached;
1313
import com.oracle.truffle.api.dsl.ImportStatic;
1414
import com.oracle.truffle.api.dsl.NodeChild;
15+
import com.oracle.truffle.api.dsl.ReportPolymorphism;
1516
import com.oracle.truffle.api.dsl.Specialization;
1617
import com.oracle.truffle.api.object.DynamicObject;
1718
import com.oracle.truffle.api.profiles.ConditionProfile;
@@ -24,6 +25,7 @@
2425
@NodeChild("array")
2526
@NodeChild("value")
2627
@ImportStatic(ArrayGuards.class)
28+
@ReportPolymorphism
2729
public abstract class ArrayAppendOneNode extends RubyNode {
2830

2931
@Child private PropagateSharingNode propagateSharingNode = PropagateSharingNode.create();

src/main/java/org/truffleruby/core/support/IONodes.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -477,8 +477,6 @@ public int write(int fd, DynamicObject string) {
477477
@Primitive(name = "io_get_thread_buffer", needsSelf = false)
478478
public static abstract class GetThreadBufferNode extends PrimitiveArrayArgumentsNode {
479479

480-
private static final ThreadLocal<Pointer> THREAD_BUFFER_THREAD_LOCAL = ThreadLocal.withInitial(() -> Pointer.NULL);
481-
482480
@Specialization
483481
public DynamicObject getThreadBuffer(long size,
484482
@Cached("create()") AllocateObjectNode allocateObjectNode) {
@@ -487,13 +485,15 @@ public DynamicObject getThreadBuffer(long size,
487485

488486
@TruffleBoundary
489487
public static Pointer getBuffer(RubyContext context, long size) {
490-
final Pointer buffer = THREAD_BUFFER_THREAD_LOCAL.get();
488+
final DynamicObject rubyThread = context.getThreadManager().getCurrentThread();
489+
final Pointer buffer = Layouts.THREAD.getIoBuffer(rubyThread);
490+
491491
if (buffer.getSize() >= size) {
492492
return buffer;
493493
} else {
494+
buffer.freeNoAutorelease();
494495
final Pointer newBuffer = Pointer.malloc(Math.max(size * 2, 1024));
495-
newBuffer.enableAutorelease(context.getFinalizationService());
496-
THREAD_BUFFER_THREAD_LOCAL.set(newBuffer);
496+
Layouts.THREAD.setIoBuffer(rubyThread, newBuffer);
497497
return newBuffer;
498498
}
499499
}

src/main/java/org/truffleruby/core/thread/ThreadLayout.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.truffleruby.core.InterruptMode;
1919
import org.truffleruby.core.basicobject.BasicObjectLayout;
2020
import org.truffleruby.core.fiber.FiberManager;
21+
import org.truffleruby.extra.ffi.Pointer;
2122
import org.truffleruby.language.threadlocal.ThreadLocalGlobals;
2223

2324
import java.util.List;
@@ -45,6 +46,7 @@ Object[] build(
4546
@Nullable @Volatile Object value,
4647
AtomicBoolean wakeUp,
4748
@Volatile int priority,
49+
Pointer ioBuffer,
4850
DynamicObject threadGroup,
4951
String sourceLocation,
5052
DynamicObject name);
@@ -85,6 +87,9 @@ Object[] build(
8587
int getPriority(DynamicObject object);
8688
void setPriority(DynamicObject object, int value);
8789

90+
Pointer getIoBuffer(DynamicObject object);
91+
void setIoBuffer(DynamicObject object, Pointer value);
92+
8893
DynamicObject getThreadGroup(DynamicObject object);
8994
void setThreadGroup(DynamicObject object, DynamicObject value);
9095

src/main/java/org/truffleruby/core/thread/ThreadManager.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ private Object[] packThreadFields(DynamicObject currentGroup, String info) {
183183
null,
184184
new AtomicBoolean(false),
185185
Thread.NORM_PRIORITY,
186+
Pointer.NULL,
186187
currentGroup,
187188
info,
188189
nil());
@@ -228,8 +229,8 @@ private void setupNativeThreadSupport(TruffleNFIPlatform nfi, NativeConfiguratio
228229
pthread_kill = nfi.getFunction("pthread_kill", "(" + pthread_t + ",sint32):sint32");
229230
}
230231

231-
public void initialize(DynamicObject rubyThread, Node currentNode, String info, Supplier<Object> task) {
232-
startSharing(rubyThread);
232+
public void initialize(DynamicObject rubyThread, Node currentNode, String info, String sharingReason, Supplier<Object> task) {
233+
startSharing(rubyThread, sharingReason);
233234

234235
Layouts.THREAD.setSourceLocation(rubyThread, info);
235236

@@ -308,16 +309,16 @@ private static void setException(RubyContext context, DynamicObject thread, Dyna
308309
}
309310

310311
// Share the Ruby Thread before it can be accessed concurrently, and before it is added to Thread.list
311-
public void startSharing(DynamicObject rubyThread) {
312+
public void startSharing(DynamicObject rubyThread, String reason) {
312313
if (context.getOptions().SHARED_OBJECTS_ENABLED) {
313314
// TODO (eregon, 22 Sept 2017): no need if singleThreaded in isThreadAccessAllowed()
314-
context.getSharedObjects().startSharing();
315+
context.getSharedObjects().startSharing(reason);
315316
SharedObjects.writeBarrier(context, rubyThread);
316317
}
317318
}
318319

319320
public void startForeignThread(DynamicObject rubyThread, Thread javaThread) {
320-
startSharing(rubyThread);
321+
startSharing(rubyThread, "creating a foreign thread");
321322
start(rubyThread, javaThread);
322323
}
323324

@@ -336,6 +337,8 @@ public void cleanup(DynamicObject thread, Thread javaThread) {
336337
final FiberManager fiberManager = Layouts.THREAD.getFiberManager(thread);
337338
fiberManager.shutdown(javaThread);
338339

340+
Layouts.THREAD.getIoBuffer(thread).freeNoAutorelease();
341+
339342
unregisterThread(thread);
340343
Layouts.THREAD.setThread(thread, null);
341344

src/main/java/org/truffleruby/core/thread/ThreadNodes.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -276,15 +276,17 @@ public abstract static class ThreadInitializeNode extends PrimitiveArrayArgument
276276
public DynamicObject initialize(DynamicObject thread, DynamicObject arguments, DynamicObject block,
277277
@Cached("of(arguments)") ArrayStrategy strategy,
278278
@Cached("strategy.boxedCopyNode()") ArrayOperationNodes.ArrayBoxedCopyNode boxedCopyNode) {
279+
final SourceSection sourceSection = Layouts.PROC.getSharedMethodInfo(block).getSourceSection();
280+
final String info = getContext().fileLine(sourceSection);
281+
final Object[] args = boxedCopyNode.execute(Layouts.ARRAY.getStore(arguments), Layouts.ARRAY.getSize(arguments));
282+
final String sharingReason = "creating Ruby Thread " + info;
283+
279284
if (getContext().getOptions().SHARED_OBJECTS_ENABLED) {
280-
getContext().getThreadManager().startSharing(thread);
285+
getContext().getThreadManager().startSharing(thread, sharingReason);
281286
SharedObjects.shareDeclarationFrame(getContext(), block);
282287
}
283288

284-
final Object[] args = boxedCopyNode.execute(Layouts.ARRAY.getStore(arguments), Layouts.ARRAY.getSize(arguments));
285-
final SourceSection sourceSection = Layouts.PROC.getSharedMethodInfo(block).getSourceSection();
286-
final String info = getContext().fileLine(sourceSection);
287-
getContext().getThreadManager().initialize(thread, this, info,
289+
getContext().getThreadManager().initialize(thread, this, info, sharingReason,
288290
() -> ProcOperations.rootCall(block, args));
289291
return nil();
290292
}

src/main/java/org/truffleruby/language/RubyInlineParsingRequestNode.java

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import org.truffleruby.language.methods.DeclarationContext;
2727
import org.truffleruby.language.methods.InternalMethod;
2828
import org.truffleruby.language.methods.SharedMethodInfo;
29-
import org.truffleruby.language.objects.shared.SharedObjects;
3029
import org.truffleruby.parser.ParserContext;
3130
import org.truffleruby.parser.RubySource;
3231
import org.truffleruby.parser.TranslatorDriver;
@@ -88,14 +87,9 @@ public Object execute(VirtualFrame frame) {
8887
RubyArguments.getBlock(frame),
8988
new Object[]{});
9089

91-
final Object value = callNode.call(arguments);
92-
93-
// The return value will be leaked to Java, share it.
94-
if (context.getOptions().SHARED_OBJECTS_ENABLED) {
95-
SharedObjects.writeBarrier(context, value);
96-
}
97-
98-
return value;
90+
// No need to share the returned value here, InlineParsingRequest is not exposed to the Context API
91+
// and is only used by instruments (e.g., the debugger) or the RubyLanguage itself.
92+
return callNode.call(arguments);
9993
}
10094

10195
}

src/main/java/org/truffleruby/language/RubyParsingRequestNode.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,9 @@ public Object execute(VirtualFrame frame) {
9090
frame.getArguments());
9191
final Object value = callNode.call(arguments);
9292

93-
// The return value will be leaked to Java, share it.
94-
if (context.getOptions().SHARED_OBJECTS_ENABLED) {
93+
// The return value will be leaked to Java, so share it if the Context API is used.
94+
// We share conditionally on EMBEDDED to avoid sharing return values used in RubyLauncher.
95+
if (context.getOptions().SHARED_OBJECTS_ENABLED && context.getOptions().EMBEDDED) {
9596
SharedObjects.writeBarrier(context, value);
9697
}
9798

0 commit comments

Comments
 (0)