Skip to content

Commit 4797b6f

Browse files
Fix deadlock and race condition in AbstractCollectionPolicy.updateSizeParameters().
1 parent 719c89f commit 4797b6f

File tree

3 files changed

+49
-36
lines changed

3 files changed

+49
-36
lines changed

substratevm/mx.substratevm/suite.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,7 @@
400400
],
401401
"requiresConcealed" : {
402402
"java.base": [
403+
"jdk.internal.misc",
403404
"sun.nio.ch",
404405
],
405406
"java.management": [

substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AbstractCollectionPolicy.java

Lines changed: 46 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,6 @@
2424
*/
2525
package com.oracle.svm.core.genscavenge;
2626

27-
import java.util.concurrent.atomic.AtomicBoolean;
28-
29-
import jdk.graal.compiler.word.Word;
3027
import org.graalvm.nativeimage.Platform;
3128
import org.graalvm.nativeimage.Platforms;
3229
import org.graalvm.word.UnsignedWord;
@@ -36,12 +33,14 @@
3633
import com.oracle.svm.core.heap.PhysicalMemory;
3734
import com.oracle.svm.core.heap.ReferenceAccess;
3835
import com.oracle.svm.core.jdk.UninterruptibleUtils;
36+
import com.oracle.svm.core.thread.JavaSpinLockUtils;
3937
import com.oracle.svm.core.thread.VMOperation;
4038
import com.oracle.svm.core.util.UnsignedUtils;
4139
import com.oracle.svm.core.util.VMError;
4240

4341
import jdk.graal.compiler.api.replacements.Fold;
44-
import jdk.graal.compiler.nodes.PauseNode;
42+
import jdk.graal.compiler.word.Word;
43+
import jdk.internal.misc.Unsafe;
4544

4645
abstract class AbstractCollectionPolicy implements CollectionPolicy {
4746

@@ -54,6 +53,9 @@ static int getMaxSurvivorSpaces(Integer userValue) {
5453
return (userValue != null) ? userValue : AbstractCollectionPolicy.MAX_TENURING_THRESHOLD;
5554
}
5655

56+
private static final Unsafe U = Unsafe.getUnsafe();
57+
private static final long SIZES_UPDATE_LOCK_OFFSET = U.objectFieldOffset(AbstractCollectionPolicy.class, "sizesUpdateLock");
58+
5759
/*
5860
* Constants that can be made options if desirable. These are -XX options in HotSpot, refer to
5961
* their descriptions for details. The values are HotSpot defaults unless labeled otherwise.
@@ -78,8 +80,8 @@ static int getMaxSurvivorSpaces(Integer userValue) {
7880
protected UnsignedWord oldSize;
7981
protected int tenuringThreshold;
8082

81-
protected volatile SizeParameters sizes;
82-
private final AtomicBoolean sizesUpdateSpinLock = new AtomicBoolean();
83+
protected volatile SizeParameters sizes = null;
84+
@SuppressWarnings("unused") private volatile int sizesUpdateLock;
8385

8486
protected AbstractCollectionPolicy(int initialNewRatio, int initialTenuringThreshold) {
8587
this.initialNewRatio = initialNewRatio;
@@ -145,40 +147,49 @@ protected void guaranteeSizeParametersInitialized() {
145147

146148
@Override
147149
public void updateSizeParameters() {
148-
SizeParameters params = computeSizeParameters(sizes);
149-
SizeParameters previous = sizes;
150-
if (previous != null && params.equal(previous)) {
150+
/*
151+
* Read the old object before computing the new values. Otherwise, we risk reusing an
152+
* outdated SizeParameters object.
153+
*/
154+
SizeParameters prevParams = sizes;
155+
SizeParameters newParams = computeSizeParameters(prevParams);
156+
if (prevParams != null && newParams.equal(prevParams)) {
151157
return; // nothing to do
152158
}
153-
while (!sizesUpdateSpinLock.compareAndSet(false, true)) {
154-
/*
155-
* We use a primitive spin lock because at this point, the current thread might be
156-
* unable to use a Java lock (e.g. no Thread object yet), and the critical section is
157-
* short, so we do not want to suspend and wake up threads for it.
158-
*/
159-
PauseNode.pause();
160-
}
159+
updateSizeParameters0(newParams, prevParams);
160+
guaranteeSizeParametersInitialized(); // sanity
161+
}
162+
163+
@Uninterruptible(reason = "Holding the spin lock at a safepoint can result in deadlocks.")
164+
private void updateSizeParameters0(SizeParameters newParams, SizeParameters prevParams) {
165+
/*
166+
* We use a primitive spin lock because at this point, the current thread might be unable to
167+
* use a Java lock (e.g. no Thread object yet), and the critical section is short, so we do
168+
* not want to suspend and wake up threads for it.
169+
*/
170+
JavaSpinLockUtils.lockNoTransition(this, SIZES_UPDATE_LOCK_OFFSET);
161171
try {
162-
updateSizeParametersLocked(params, previous);
172+
if (sizes != prevParams) {
173+
/*
174+
* Some other thread beat us and we cannot tell if our values or their values are
175+
* newer, so back off - any newer values will be applied eventually.
176+
*/
177+
return;
178+
}
179+
updateSizeParametersLocked(newParams, prevParams);
163180
} finally {
164-
sizesUpdateSpinLock.set(false);
181+
JavaSpinLockUtils.unlock(this, SIZES_UPDATE_LOCK_OFFSET);
165182
}
166-
guaranteeSizeParametersInitialized(); // sanity
167183
}
168184

169-
@Uninterruptible(reason = "Must be atomic with regard to garbage collection.")
170-
private void updateSizeParametersLocked(SizeParameters params, SizeParameters previous) {
171-
if (sizes != previous) {
172-
// Some other thread beat us and we cannot tell if our values or their values are newer,
173-
// so back off -- any newer values will be applied eventually.
174-
return;
175-
}
176-
sizes = params;
185+
@Uninterruptible(reason = "Holding the spin lock at a safepoint can result in deadlocks. Updating the size parameters must be atomic with regard to garbage collection.")
186+
private void updateSizeParametersLocked(SizeParameters newParams, SizeParameters prevParams) {
187+
sizes = newParams;
177188

178-
if (previous == null || gcCount() == 0) {
179-
survivorSize = params.initialSurvivorSize;
180-
edenSize = params.initialEdenSize;
181-
oldSize = params.initialOldSize();
189+
if (prevParams == null || gcCount() == 0) {
190+
survivorSize = newParams.initialSurvivorSize;
191+
edenSize = newParams.initialEdenSize;
192+
oldSize = newParams.initialOldSize();
182193
promoSize = UnsignedUtils.min(edenSize, oldSize);
183194
}
184195

@@ -191,10 +202,10 @@ private void updateSizeParametersLocked(SizeParameters params, SizeParameters pr
191202
* We assume that such changes happen very early on and values then adapt reasonably quick,
192203
* but we must still ensure that computations can handle it (for example, no overflows).
193204
*/
194-
survivorSize = UnsignedUtils.min(survivorSize, params.maxSurvivorSize());
205+
survivorSize = UnsignedUtils.min(survivorSize, newParams.maxSurvivorSize());
195206
edenSize = UnsignedUtils.min(edenSize, getMaximumEdenSize());
196-
oldSize = UnsignedUtils.min(oldSize, params.maxOldSize());
197-
promoSize = UnsignedUtils.min(promoSize, params.maxOldSize());
207+
oldSize = UnsignedUtils.min(oldSize, newParams.maxOldSize());
208+
promoSize = UnsignedUtils.min(promoSize, newParams.maxOldSize());
198209
}
199210

200211
@Override

substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/BasicCollectionPolicies.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626

2727
import static com.oracle.svm.core.genscavenge.CollectionPolicy.shouldCollectYoungGenSeparately;
2828

29-
import jdk.graal.compiler.word.Word;
3029
import org.graalvm.nativeimage.Platform;
3130
import org.graalvm.nativeimage.Platforms;
3231
import org.graalvm.word.UnsignedWord;
@@ -40,6 +39,8 @@
4039
import com.oracle.svm.core.util.UnsignedUtils;
4140
import com.oracle.svm.core.util.VMError;
4241

42+
import jdk.graal.compiler.word.Word;
43+
4344
/** Basic/legacy garbage collection policies. */
4445
final class BasicCollectionPolicies {
4546
@Platforms(Platform.HOSTED_ONLY.class)

0 commit comments

Comments
 (0)