1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-01-24 16:07:01 +00:00

Fix deadlock when adding/removing observers

When adding/removing observers, we locked on the observer, then
acquired the global lock. When a metric is observed, then we acquire the
global lock and then the observer lock.

If these happen at the same time, we can easily end up with a deadlock.
We simply avoid holding the observer lock for the entire add/remove
process (instead only locking when actually needed).

Closes #1639
This commit is contained in:
Jonathan Coates 2023-12-01 12:33:03 +00:00
parent 2043939531
commit eb3e8ba677
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
2 changed files with 24 additions and 16 deletions

View File

@ -46,12 +46,14 @@ public final class GlobalMetrics {
* Add a new global metrics observer. This will receive metrics data for all computers. * Add a new global metrics observer. This will receive metrics data for all computers.
* *
* @param tracker The observer to add. * @param tracker The observer to add.
* @return Whether the observer was added. {@code false} if the observer was already registered.
*/ */
public void addObserver(ComputerMetricsObserver tracker) { public boolean addObserver(ComputerMetricsObserver tracker) {
synchronized (lock) { synchronized (lock) {
if (trackers.contains(tracker)) return; if (trackers.contains(tracker)) return false;
trackers.add(tracker); trackers.add(tracker);
enabled = true; enabled = true;
return true;
} }
} }
@ -59,11 +61,13 @@ public final class GlobalMetrics {
* Remove a previously-registered global metrics observer. * Remove a previously-registered global metrics observer.
* *
* @param tracker The observer to add. * @param tracker The observer to add.
* @return Whether the observer was removed. {@code false} if the observer was not registered.
*/ */
public void removeObserver(ComputerMetricsObserver tracker) { public boolean removeObserver(ComputerMetricsObserver tracker) {
synchronized (lock) { synchronized (lock) {
trackers.remove(tracker); var changed = trackers.remove(tracker);
enabled = !trackers.isEmpty(); enabled = !trackers.isEmpty();
return changed;
} }
} }

View File

@ -10,6 +10,7 @@ import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.metrics.ComputerMetricsObserver; import dan200.computercraft.shared.computer.metrics.ComputerMetricsObserver;
import dan200.computercraft.shared.computer.metrics.GlobalMetrics; import dan200.computercraft.shared.computer.metrics.GlobalMetrics;
import javax.annotation.concurrent.GuardedBy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -21,29 +22,31 @@ import java.util.Map;
*/ */
public class BasicComputerMetricsObserver implements ComputerMetricsObserver { public class BasicComputerMetricsObserver implements ComputerMetricsObserver {
private final GlobalMetrics owner; private final GlobalMetrics owner;
private boolean tracking = false;
@GuardedBy("this")
private final List<ComputerMetrics> timings = new ArrayList<>(); private final List<ComputerMetrics> timings = new ArrayList<>();
@GuardedBy("this")
private final Map<ServerComputer, ComputerMetrics> timingLookup = new MapMaker().weakKeys().makeMap(); private final Map<ServerComputer, ComputerMetrics> timingLookup = new MapMaker().weakKeys().makeMap();
public BasicComputerMetricsObserver(GlobalMetrics owner) { public BasicComputerMetricsObserver(GlobalMetrics owner) {
this.owner = owner; this.owner = owner;
} }
public synchronized void start() { public void start() {
if (!tracking) owner.addObserver(this); if (!owner.addObserver(this)) return;
tracking = true;
synchronized (this) {
timings.clear(); timings.clear();
timingLookup.clear(); timingLookup.clear();
} }
}
public synchronized boolean stop() { public boolean stop() {
if (!tracking) return false; if (!owner.removeObserver(this)) return false;
synchronized (this) {
owner.removeObserver(this);
tracking = false;
timingLookup.clear(); timingLookup.clear();
}
return true; return true;
} }
@ -57,6 +60,7 @@ public class BasicComputerMetricsObserver implements ComputerMetricsObserver {
return new ArrayList<>(timings); return new ArrayList<>(timings);
} }
@GuardedBy("this")
private ComputerMetrics getMetrics(ServerComputer computer) { private ComputerMetrics getMetrics(ServerComputer computer) {
var existing = timingLookup.get(computer); var existing = timingLookup.get(computer);
if (existing != null) return existing; if (existing != null) return existing;