fix(RendererWorkingSetImpl): break lock-order deadlock between commit and get

This commit is contained in:
extio-de
2026-10-04 14:33:38 +02:00
parent cd2a80b75a
commit 4528649b49

View File

@@ -18,6 +18,8 @@ public class RendererWorkingSetImpl implements RendererWorkingSet {
private final ConcurrentMap<String, RendererWork> workingSet = new ConcurrentHashMap<>();
private final ConcurrentMap<String, Object> commitLocks = new ConcurrentHashMap<>();
private final BlockingQueue<Map<String, RenderingBo>> mapsPool = new ArrayBlockingQueue<>(100);
private final RenderingBoPool rendererBoPool;
@@ -82,7 +84,7 @@ public class RendererWorkingSetImpl implements RendererWorkingSet {
public RenderingBo get(final String producerId, final String id) {
final var next = this.getWorkingSetByProducer(producerId).next();
synchronized (next) {
final var bo = this.getWorkingSetByProducer(producerId).next().get(id);
final var bo = next.get(id);
if (bo == null) {
return null;
}
@@ -99,7 +101,7 @@ public class RendererWorkingSetImpl implements RendererWorkingSet {
public <T extends RenderingBo> T get(final String producerId, final String id, final Class<T> type) {
final var next = this.getWorkingSetByProducer(producerId).next();
synchronized (next) {
final RenderingBo bo = this.getWorkingSetByProducer(producerId).next().get(id);
final RenderingBo bo = next.get(id);
if (bo == null) {
return null;
}
@@ -123,29 +125,28 @@ public class RendererWorkingSetImpl implements RendererWorkingSet {
@Override
public Map<String, RenderingBo> commit(final String producerId, final boolean clone) {
final AtomicReference<Map<String, RenderingBo>> previousLiveRef = new AtomicReference<>();
synchronized (this.commitLockFor(producerId)) {
final AtomicReference<RendererWork> previousWorkRef = new AtomicReference<>();
final RendererWork rendererWork = this.workingSet.compute(producerId, (k, v) -> {
if (v == null) {
return new RendererWork(this.obtainMapFromPool(), this.obtainMapFromPool());
}
previousLiveRef.set(v.live());
if (clone) {
final Map<String, RenderingBo> newNext = this.obtainMapFromPool();
synchronized (v.next()) {
v.next().entrySet().forEach(e -> {
final RenderingBo copy = this.rendererBoPool.copy(e.getValue());
newNext.put(e.getKey(), copy);
});
}
return new RendererWork(v.next(), newNext);
}
else {
previousWorkRef.set(v);
return new RendererWork(v.next(), this.obtainMapFromPool());
}
});
final var previousLiveSet = previousLiveRef.get();
if (previousLiveSet != null) {
final var previousWork = previousWorkRef.get();
if (previousWork != null) {
if (clone) {
final var oldNext = previousWork.next();
synchronized (oldNext) {
oldNext.entrySet().forEach(e -> {
final RenderingBo copy = this.rendererBoPool.copy(e.getValue());
rendererWork.next().put(e.getKey(), copy);
});
}
}
final var previousLiveSet = previousWork.live();
if (clone) {
synchronized (previousLiveSet) {
previousLiveSet.values().forEach(this.rendererBoPool::returnToPool);
@@ -156,6 +157,7 @@ public class RendererWorkingSetImpl implements RendererWorkingSet {
return rendererWork.next();
}
}
@Override
public void clearNext(final String producerId) {
@@ -168,6 +170,7 @@ public class RendererWorkingSetImpl implements RendererWorkingSet {
@Override
public void clear(final String producerId) {
synchronized (this.commitLockFor(producerId)) {
final RendererWork rendererWork = this.workingSet.remove(producerId);
if (rendererWork != null) {
synchronized (rendererWork.live()) {
@@ -178,6 +181,8 @@ public class RendererWorkingSetImpl implements RendererWorkingSet {
}
}
}
this.commitLocks.remove(producerId);
}
@Override
public void getLiveSet(final List<RenderingBo> combinedLiveSet, final Predicate<String> filter) {
@@ -194,6 +199,10 @@ public class RendererWorkingSetImpl implements RendererWorkingSet {
return this.workingSet.computeIfAbsent(producerId, k -> new RendererWork(this.obtainMapFromPool(), this.obtainMapFromPool()));
}
private Object commitLockFor(final String producerId) {
return this.commitLocks.computeIfAbsent(producerId, k -> new Object());
}
private Map<String, RenderingBo> obtainMapFromPool() {
var map = this.mapsPool.poll();
if (map == null) {