Skip to content

Commit 1d0ed29

Browse files
committed
Fix ObjectSpace.trace_object_allocations
* It would cause a stack overflow, because we would create a String before setting `tracingPaused`. * Make ObjectSpaceManager thread-safe.
1 parent 5defdc8 commit 1d0ed29

File tree

5 files changed

+43
-36
lines changed

5 files changed

+43
-36
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Bug fixes:
77
* Removed extra `public` methods on `IO` (#1702).
88
* Fixed `Process.kill(signal, Process.pid)` when the signal is trapped as `:IGNORE` (#1702).
99
* Fixed `Addrinfo.new(String)` to reliably find the address family (#1702).
10+
* Fixed `ObjectSpace.trace_object_allocations` (#1456).
1011

1112
Compatibility:
1213

lib/truffle/objspace.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,6 @@ def reachable_objects_from_root
184184

185185
def trace_object_allocations
186186
trace_object_allocations_start
187-
188187
begin
189188
yield
190189
ensure

src/main/java/org/truffleruby/core/objectspace/ObjectSpaceManager.java

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040

4141
import com.oracle.truffle.api.Assumption;
4242
import com.oracle.truffle.api.CompilerDirectives;
43+
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
4344
import com.oracle.truffle.api.object.DynamicObject;
4445
import com.oracle.truffle.api.utilities.CyclicAssumption;
4546

@@ -48,6 +49,7 @@
4849

4950
import java.lang.management.GarbageCollectorMXBean;
5051
import java.lang.management.ManagementFactory;
52+
import java.util.concurrent.atomic.AtomicInteger;
5153
import java.util.concurrent.atomic.AtomicLong;
5254

5355
/**
@@ -59,9 +61,9 @@ public class ObjectSpaceManager {
5961
private final RubyContext context;
6062

6163
private final CyclicAssumption tracingAssumption = new CyclicAssumption("objspace-tracing");
62-
@CompilerDirectives.CompilationFinal private boolean isTracing = false;
63-
private int tracingAssumptionActivations = 0;
64-
private boolean tracingPaused = false;
64+
@CompilationFinal private boolean isTracing = false;
65+
private final AtomicInteger tracingAssumptionActivations = new AtomicInteger(0);
66+
private final ThreadLocal<Boolean> tracingPaused = ThreadLocal.withInitial(() -> false);
6567

6668
private final AtomicLong nextObjectID = new AtomicLong(ValueWrapperManager.TAG_MASK + 1);
6769

@@ -70,37 +72,19 @@ public ObjectSpaceManager(RubyContext context) {
7072
}
7173

7274
public void traceAllocationsStart() {
73-
tracingAssumptionActivations++;
74-
75-
if (tracingAssumptionActivations == 1) {
75+
if (tracingAssumptionActivations.incrementAndGet() == 1) {
7676
isTracing = true;
7777
tracingAssumption.invalidate();
7878
}
7979
}
8080

8181
public void traceAllocationsStop() {
82-
tracingAssumptionActivations--;
83-
84-
if (tracingAssumptionActivations == 0) {
82+
if (tracingAssumptionActivations.decrementAndGet() == 0) {
8583
isTracing = false;
8684
tracingAssumption.invalidate();
8785
}
8886
}
8987

90-
public void traceAllocation(DynamicObject object, DynamicObject classPath, DynamicObject methodId, DynamicObject sourcefile, int sourceline) {
91-
if (tracingPaused) {
92-
return;
93-
}
94-
95-
tracingPaused = true;
96-
97-
try {
98-
context.send(context.getCoreLibrary().getObjectSpaceModule(), "trace_allocation", object, classPath, methodId, sourcefile, sourceline, getCollectionCount());
99-
} finally {
100-
tracingPaused = false;
101-
}
102-
}
103-
10488
public Assumption getTracingAssumption() {
10589
return tracingAssumption.getAssumption();
10690
}
@@ -109,6 +93,14 @@ public boolean isTracing() {
10993
return isTracing;
11094
}
11195

96+
public boolean isTracingPaused() {
97+
return tracingPaused.get();
98+
}
99+
100+
public void setTracingPaused(boolean tracingPaused) {
101+
this.tracingPaused.set(tracingPaused);
102+
}
103+
112104
public long getNextObjectID() {
113105
final long id = nextObjectID.getAndAdd(ValueWrapperManager.TAG_MASK + 1);
114106

src/main/java/org/truffleruby/language/objects/AllocateObjectNode.java

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@
2323
import com.oracle.truffle.api.source.SourceSection;
2424
import org.jcodings.specific.UTF8Encoding;
2525
import org.truffleruby.Layouts;
26-
import org.truffleruby.core.rope.CodeRange;
26+
import org.truffleruby.core.objectspace.ObjectSpaceManager;
2727
import org.truffleruby.core.string.StringNodes;
28+
import org.truffleruby.core.string.StringOperations;
2829
import org.truffleruby.language.RubyBaseNode;
2930
import org.truffleruby.language.arguments.RubyArguments;
3031
import org.truffleruby.language.control.RaiseException;
@@ -81,10 +82,24 @@ public DynamicObject allocateUncached(DynamicObject classToAllocate, Object[] va
8182
@CompilerDirectives.TruffleBoundary
8283
@Specialization(guards = {"!isSingleton(classToAllocate)", "isTracing()"},
8384
assumptions = "getTracingAssumption()")
84-
public DynamicObject allocateTracing(DynamicObject classToAllocate, Object[] values,
85-
@Cached("create()") StringNodes.MakeStringNode makeStringNode) {
85+
public DynamicObject allocateTracing(DynamicObject classToAllocate, Object[] values) {
8686
final DynamicObject object = allocate(getInstanceFactory(classToAllocate), values);
8787

88+
final ObjectSpaceManager objectSpaceManager = getContext().getObjectSpaceManager();
89+
if (!objectSpaceManager.isTracingPaused()) {
90+
objectSpaceManager.setTracingPaused(true);
91+
try {
92+
callTraceAllocation(object);
93+
} finally {
94+
objectSpaceManager.setTracingPaused(false);
95+
}
96+
}
97+
98+
return object;
99+
}
100+
101+
@CompilerDirectives.TruffleBoundary
102+
private void callTraceAllocation(DynamicObject object) {
88103
final FrameInstance allocatingFrameInstance;
89104
final SourceSection allocatingSourceSection;
90105

@@ -101,22 +116,23 @@ public DynamicObject allocateTracing(DynamicObject classToAllocate, Object[] val
101116
final Object allocatingSelf = RubyArguments.getSelf(allocatingFrame);
102117
final String allocatingMethod = RubyArguments.getMethod(allocatingFrame).getName();
103118

104-
getContext().getObjectSpaceManager().traceAllocation(
119+
getContext().send(coreLibrary().getObjectSpaceModule(), "trace_allocation",
105120
object,
106-
string(makeStringNode, Layouts.CLASS.getFields(coreLibrary().getLogicalClass(allocatingSelf)).getName()),
121+
string(Layouts.CLASS.getFields(coreLibrary().getLogicalClass(allocatingSelf)).getName()),
107122
getSymbol(allocatingMethod),
108-
string(makeStringNode, getContext().getPath(allocatingSourceSection.getSource())),
109-
allocatingSourceSection.getStartLine());
110-
111-
return object;
123+
string(getContext().getPath(allocatingSourceSection.getSource())),
124+
allocatingSourceSection.getStartLine(),
125+
ObjectSpaceManager.getCollectionCount());
112126
}
113127

114128
protected DynamicObjectFactory getInstanceFactory(DynamicObject classToAllocate) {
115129
return Layouts.CLASS.getInstanceFactory(classToAllocate);
116130
}
117131

118-
private DynamicObject string(StringNodes.MakeStringNode makeStringNode, String value) {
119-
return makeStringNode.executeMake(value, UTF8Encoding.INSTANCE, CodeRange.CR_UNKNOWN);
132+
private DynamicObject string(String value) {
133+
// No point to use MakeStringNode (which uses AllocateObjectNode) here, as we should not trace the allocation
134+
// of Strings used for tracing allocations.
135+
return StringOperations.createString(getContext(), StringOperations.encodeRope(value, UTF8Encoding.INSTANCE));
120136
}
121137

122138
@Specialization(guards = "isSingleton(classToAllocate)")

test/mri/excludes/TestObjSpace.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
exclude :test_memsize_of_root_shared_string, "needs investigation"
2020
exclude :test_reachable_objects_size, "needs investigation"
2121
exclude :test_trace_object_allocations, "needs investigation"
22-
exclude :test_trace_object_allocations_start_stop_clear, "needs investigation"
2322
exclude :test_count_objects_size_with_wrong_type, "needs investigation"
2423
exclude :test_dump_addresses_match_dump_all_addresses, "needs investigation"
2524
exclude :test_dump_all_full, "needs investigation"

0 commit comments

Comments
 (0)