Skip to content

Commit d202706

Browse files
committed
Eliminate locks from the marking service.
This change eliminates locks from the marking service by making all information on objects which must be kept thread local, and running the mark functions asynchronsously on the reference processing thread. This has been done to avoid deadlocks which could be created in OpenSSL and other libraries.
1 parent 49ae2a4 commit d202706

File tree

5 files changed

+216
-48
lines changed

5 files changed

+216
-48
lines changed

src/main/java/org/truffleruby/cext/CExtNodes.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1541,8 +1541,8 @@ public abstract static class PushPreservingFrame extends CoreMethodArrayArgument
15411541

15421542
@Specialization
15431543
public DynamicObject pushFrame(VirtualFrame frame,
1544-
@Cached("create()") MarkingServiceNodes.GetPreservationStackNode getStackNode) {
1545-
getStackNode.execute(frame).push();
1544+
@Cached("create()") MarkingServiceNodes.GetMarkerThreadLocalDataNode getDataNode) {
1545+
getDataNode.execute(frame).getPreservationStack().push();
15461546
return nil();
15471547
}
15481548
}
@@ -1552,8 +1552,8 @@ public abstract static class PopPreservingFrame extends CoreMethodArrayArguments
15521552

15531553
@Specialization
15541554
public DynamicObject popFrame(VirtualFrame frame,
1555-
@Cached("create()") MarkingServiceNodes.GetPreservationStackNode getStackNode) {
1556-
getStackNode.execute(frame).pop();
1555+
@Cached("create()") MarkingServiceNodes.GetMarkerThreadLocalDataNode getDataNode) {
1556+
getDataNode.execute(frame).getPreservationStack().pop();
15571557
return nil();
15581558
}
15591559
}

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

Lines changed: 195 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import java.util.ArrayList;
1616

1717
import org.truffleruby.RubyContext;
18+
import org.truffleruby.core.queue.UnsizedQueue;
1819

1920
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
2021
import com.oracle.truffle.api.object.DynamicObject;
@@ -79,16 +80,176 @@ public ReferenceProcessingService<MarkerReference> service() {
7980
}
8081
}
8182

83+
public static class MarkRunnerReference extends WeakReference<Object> implements ReferenceProcessingService.ProcessingReference<MarkRunnerReference> {
84+
85+
private final MarkRunnerService service;
86+
private MarkRunnerReference next = null;
87+
private MarkRunnerReference prev = null;
88+
89+
public MarkRunnerReference(Object object, ReferenceQueue<? super Object> queue, MarkRunnerService service) {
90+
super(object, queue);
91+
this.service = service;
92+
}
93+
94+
public MarkRunnerReference getPrevious() {
95+
return prev;
96+
}
97+
98+
public void setPrevious(MarkRunnerReference previous) {
99+
prev = previous;
100+
}
101+
102+
public MarkRunnerReference getNext() {
103+
return next;
104+
}
105+
106+
public void setNext(MarkRunnerReference next) {
107+
this.next = next;
108+
}
109+
110+
public ReferenceProcessingService<MarkRunnerReference> service() {
111+
return service;
112+
}
113+
}
114+
115+
public static class MarkRunnerService extends ReferenceProcessingService<MarkingService.MarkRunnerReference> {
116+
117+
private final MarkingService markingService;
118+
119+
public MarkRunnerService(RubyContext context, ReferenceProcessor referenceProcessor, MarkingService markingService) {
120+
super(context, referenceProcessor);
121+
this.markingService = markingService;
122+
}
123+
124+
@Override
125+
protected void processReference(ProcessingReference<?> reference) {
126+
ArrayList<Object[]> keptObjectLists = new ArrayList<>();
127+
Object[] list;
128+
while (true) {
129+
list = (Object[]) markingService.keptObjectQueue.poll();
130+
if (list == null) {
131+
break;
132+
} else {
133+
keptObjectLists.add(list);
134+
}
135+
}
136+
if (!keptObjectLists.isEmpty()) {
137+
runAllMarkers();
138+
}
139+
super.processReference(reference);
140+
keptObjectLists.clear();
141+
}
142+
143+
@TruffleBoundary
144+
public void runAllMarkers() {
145+
MarkerReference currentMarker = markingService.getFirst();
146+
MarkerReference nextMarker;
147+
while (currentMarker != null) {
148+
nextMarker = currentMarker.next;
149+
markingService.runMarker(currentMarker);
150+
if (nextMarker == currentMarker) {
151+
throw new Error("The MarkerReference linked list structure has become broken.");
152+
}
153+
currentMarker = nextMarker;
154+
}
155+
}
156+
}
157+
82158
private final int cacheSize;
83159

84-
private final ThreadLocal<MarkerStack> stackPreservation = ThreadLocal.withInitial(() -> new MarkerStack());
160+
private final ThreadLocal<MarkerThreadLocalData> threadLocalData;
85161

86-
private Object[] keptObjects;
87162
private final ArrayDeque<Object[]> oldKeptObjects = new ArrayDeque<>();
163+
private MarkRunnerService runnerService;
164+
165+
private UnsizedQueue keptObjectQueue = new UnsizedQueue();
166+
167+
public static class MarkerThreadLocalData {
168+
private final MarkerKeptObjects keptObjects;
169+
private final MarkerStack preservationStack;
170+
171+
public MarkerThreadLocalData(MarkingService service) {
172+
this.preservationStack = new MarkerStack();
173+
this.keptObjects = new MarkerKeptObjects(service);
174+
}
175+
176+
public MarkerKeptObjects getKeptObjects() {
177+
return keptObjects;
178+
}
179+
180+
public MarkerStack getPreservationStack() {
181+
return preservationStack;
182+
}
183+
}
184+
185+
public static class MarkerKeptObjects {
186+
private final MarkingService service;
187+
protected Object[] objects;
188+
protected int counter;
189+
190+
protected MarkerKeptObjects(MarkingService service) {
191+
objects = new Object[service.cacheSize];
192+
counter = 0;
193+
this.service = service;
194+
}
88195

89-
private int counter = 0;
196+
public void keepObject(Object object) {
197+
/*
198+
* It is important to get the ordering of events correct to avoid references being
199+
* garbage collected too soon. If we are attempting to add a handle to a native
200+
* structure then that consists of two events. First we create the handle, and then the
201+
* handle is stored in the struct. If we run mark functions immediate after adding the
202+
* handle to the list of kept objects then the mark function will be run before that
203+
* handle is stored in the structure, and since it will be removed from the list of kept
204+
* objects it could be collected before the mark functions are run again.
205+
*
206+
* Instead we check for the kept list being full before adding an object to it, as those
207+
* handles are already stored in structs by this point.
208+
*/
209+
if (isFull()) {
210+
queueAndReset();
211+
}
212+
objects[counter++] = object;
213+
}
214+
215+
private void queueAndReset() {
216+
service.queueForMarking(objects);
217+
objects = new Object[service.cacheSize];
218+
counter = 0;
219+
}
90220

91-
protected class MarkerStackEntry {
221+
private boolean isFull() {
222+
return counter == service.cacheSize;
223+
}
224+
225+
public void keepObjects(MarkerKeptObjects otherObjects) {
226+
if (isFull()) {
227+
queueAndReset();
228+
}
229+
if (otherObjects.isFull()) {
230+
service.queueForMarking(otherObjects.objects);
231+
return;
232+
} else if (otherObjects.counter == 0) {
233+
return;
234+
} else if (otherObjects.counter + counter <= service.cacheSize) {
235+
System.arraycopy(otherObjects.objects, 0, objects, counter, otherObjects.counter);
236+
counter += otherObjects.counter;
237+
return;
238+
} else {
239+
int overflowLength = counter + otherObjects.counter - service.cacheSize;
240+
int initialLength = otherObjects.counter - overflowLength;
241+
System.arraycopy(otherObjects.objects, 0, objects, counter, initialLength);
242+
counter = service.cacheSize;
243+
queueAndReset();
244+
System.arraycopy(otherObjects.objects, initialLength, objects, 0, overflowLength);
245+
counter = overflowLength;
246+
return;
247+
}
248+
}
249+
250+
}
251+
252+
protected static class MarkerStackEntry {
92253
protected final MarkerStackEntry previous;
93254
protected final ArrayList<Object> entries = new ArrayList<>();
94255

@@ -97,7 +258,7 @@ protected MarkerStackEntry(MarkerStackEntry previous) {
97258
}
98259
}
99260

100-
public class MarkerStack {
261+
public static class MarkerStack {
101262
protected MarkerStackEntry current = new MarkerStackEntry(null);
102263

103264
public ArrayList<Object> get() {
@@ -113,42 +274,48 @@ public void push() {
113274
}
114275
}
115276

277+
@TruffleBoundary
278+
public MarkerThreadLocalData getThreadLocalData() {
279+
return threadLocalData.get();
280+
}
281+
116282
@TruffleBoundary
117283
public MarkerStack getStackFromThreadLocal() {
118-
return stackPreservation.get();
284+
return threadLocalData.get().getPreservationStack();
285+
}
286+
287+
@TruffleBoundary
288+
public MarkerKeptObjects getKeptObjectsFromThreadLocal() {
289+
return threadLocalData.get().getKeptObjects();
119290
}
120291

121292
public MarkingService(RubyContext context, ReferenceProcessor referenceProcessor) {
122293
super(context, referenceProcessor);
123294
cacheSize = context.getOptions().CEXTS_MARKING_CACHE;
124-
keptObjects = new Object[cacheSize];
295+
threadLocalData = ThreadLocal.withInitial(() -> new MarkerThreadLocalData(this));
296+
runnerService = new MarkRunnerService(context, referenceProcessor, this);
125297
}
126298

127-
synchronized void addToKeptObjects(Object object) {
128-
/*
129-
* It is important to get the ordering of events correct to avoid references being garbage
130-
* collected too soon. If we are attempting to add a handle to a native structure then that
131-
* consists of two events. First we create the handle, and then the handle is stored in the
132-
* struct. If we run mark functions immediate after adding the handle to the list of kept
133-
* objects then the mark function will be run before that handle is stored in the structure,
134-
* and since it will be removed from the list of kept objects it could be collected before
135-
* the mark functions are run again.
136-
*
137-
* Instead we check for the kept list being full before adding an object to it, as those
138-
* handles are already stored in structs by this point.
139-
*/
140-
if (counter == cacheSize) {
141-
runAllMarkers();
142-
}
143-
keptObjects[counter++] = object;
299+
@TruffleBoundary
300+
public void makeThreadLocalData() {
301+
MarkerThreadLocalData data = new MarkerThreadLocalData(this);
302+
MarkerKeptObjects keptObjects = data.getKeptObjects();
303+
context.getFinalizationService().addFinalizer(data, null, MarkingService.class, () -> getThreadLocalData().keptObjects.keepObjects(keptObjects), null);
304+
}
305+
306+
@TruffleBoundary
307+
public void queueForMarking(Object[] objects) {
308+
keptObjectQueue.add(objects);
309+
runnerService.add(new MarkRunnerReference(new Object(), referenceProcessor.processingQueue, runnerService));
144310
}
145311

146312
@TruffleBoundary
147-
public synchronized void runAllMarkers() {
148-
counter = 0;
149-
oldKeptObjects.push(keptObjects);
313+
public void runAllMarkers() {
314+
MarkerKeptObjects objects = getKeptObjectsFromThreadLocal();
315+
objects.counter = 0;
316+
oldKeptObjects.push(objects.objects);
150317
try {
151-
keptObjects = new Object[cacheSize];
318+
objects.objects = new Object[cacheSize];
152319
MarkerReference currentMarker = getFirst();
153320
MarkerReference nextMarker;
154321
while (currentMarker != null) {

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

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
import java.util.ArrayList;
1313

14-
import org.truffleruby.core.MarkingService.MarkerStack;
14+
import org.truffleruby.core.MarkingService.MarkerThreadLocalData;
1515
import org.truffleruby.language.RubyBaseNode;
1616

1717
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
@@ -27,9 +27,10 @@ public static abstract class KeepAliveNode extends RubyBaseNode {
2727

2828
@Specialization
2929
public void keepObjectAlive(VirtualFrame frame, Object object,
30-
@Cached("create()") GetPreservationStackNode getStackNode) {
31-
addToList(getStackNode.execute(frame).get(), object);
32-
getContext().getMarkingService().addToKeptObjects(object);
30+
@Cached("create()") GetMarkerThreadLocalDataNode getThreadLocalDataNode) {
31+
MarkerThreadLocalData data = getThreadLocalDataNode.execute(frame);
32+
addToList(data.getPreservationStack().get(), object);
33+
data.getKeptObjects().keepObject(object);
3334
}
3435

3536
@TruffleBoundary
@@ -42,20 +43,20 @@ public static KeepAliveNode create() {
4243
}
4344
}
4445

45-
public static abstract class GetPreservationStackNode extends RubyBaseNode {
46+
public static abstract class GetMarkerThreadLocalDataNode extends RubyBaseNode {
4647

47-
public abstract MarkerStack execute(VirtualFrame frame);
48+
public abstract MarkerThreadLocalData execute(VirtualFrame frame);
4849

4950
@Specialization(guards = "thread == currentJavaThread(frame)", limit = "getCacheLimit()")
50-
public MarkerStack getStackOnKnownThread(VirtualFrame frame,
51+
public MarkerThreadLocalData getDataOnKnownThread(VirtualFrame frame,
5152
@Cached("currentJavaThread(frame)") Thread thread,
52-
@Cached("getMarkerStack()") MarkerStack stack) {
53-
return stack;
53+
@Cached("getData()") MarkerThreadLocalData data) {
54+
return data;
5455
}
5556

56-
@Specialization(replaces = "getStackOnKnownThread")
57-
protected MarkerStack getMarkerStack() {
58-
return getContext().getMarkingService().getStackFromThreadLocal();
57+
@Specialization(replaces = "getDataOnKnownThread")
58+
protected MarkerThreadLocalData getData() {
59+
return getContext().getMarkingService().getThreadLocalData();
5960
}
6061

6162
static protected Thread currentJavaThread(VirtualFrame frame) {
@@ -66,8 +67,8 @@ public int getCacheLimit() {
6667
return getContext().getOptions().THREAD_CACHE;
6768
}
6869

69-
public static GetPreservationStackNode create() {
70-
return MarkingServiceNodesFactory.GetPreservationStackNodeGen.create();
70+
public static GetMarkerThreadLocalDataNode create() {
71+
return MarkingServiceNodesFactory.GetMarkerThreadLocalDataNodeGen.create();
7172
}
7273
}
7374
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public static abstract class VMGCStartPrimitiveNode extends PrimitiveArrayArgume
109109
@TruffleBoundary
110110
@Specialization
111111
public DynamicObject vmGCStart() {
112-
getContext().getMarkingService().runAllMarkers();
112+
getContext().getMarkingService().queueForMarking(new Object[0]);
113113
System.gc();
114114
return nil();
115115
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public static Set<DynamicObject> newRubyObjectSet() {
4040

4141
@TruffleBoundary
4242
public static Set<DynamicObject> stopAndGetAllObjects(Node currentNode, final RubyContext context) {
43-
context.getMarkingService().runAllMarkers();
43+
context.getMarkingService().queueForMarking(new Object[0]);
4444
final Set<DynamicObject> visited = newRubyObjectSet();
4545

4646
final Thread initiatingJavaThread = Thread.currentThread();

0 commit comments

Comments
 (0)