Skip to content

Commit 8e11af1

Browse files
committed
fix race condition on AsyncTargetFinding
1 parent faf2abd commit 8e11af1

File tree

1 file changed

+139
-68
lines changed

1 file changed

+139
-68
lines changed

leaf-server/minecraft-patches/features/0157-AsyncTargetFinding.patch

Lines changed: 139 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,24 @@ Subject: [PATCH] AsyncTargetFinding
55

66

77
diff --git a/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java
8-
index 41ee3cdc45ecc8376a2203ed588bb544ed377294..f3677a1576d4ea7e8bb2c74fb95f51620deb39b5 100644
8+
index 41ee3cdc45ecc8376a2203ed588bb544ed377294..7fe2b75e8c2718851d68429380ac71203d31a58f 100644
99
--- a/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java
1010
+++ b/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java
11-
@@ -1,6 +1,12 @@
11+
@@ -1,6 +1,13 @@
1212
package net.minecraft.world.entity.ai.goal.target;
1313

1414
import java.util.EnumSet;
1515
+import java.util.List;
1616
+import java.util.concurrent.CompletableFuture;
1717
+import java.util.concurrent.ExecutorService;
1818
+import java.util.concurrent.Executors;
19+
+import java.util.concurrent.TimeUnit;
1920
+import java.util.concurrent.atomic.AtomicBoolean;
2021
+import java.util.concurrent.atomic.AtomicReference;
2122
import javax.annotation.Nullable;
2223
import net.minecraft.server.level.ServerLevel;
2324
import net.minecraft.server.level.ServerPlayer;
24-
@@ -10,15 +16,37 @@ import net.minecraft.world.entity.ai.goal.Goal;
25+
@@ -10,15 +17,42 @@ import net.minecraft.world.entity.ai.goal.Goal;
2526
import net.minecraft.world.entity.ai.targeting.TargetingConditions;
2627
import net.minecraft.world.entity.player.Player;
2728
import net.minecraft.world.phys.AABB;
@@ -36,47 +37,51 @@ index 41ee3cdc45ecc8376a2203ed588bb544ed377294..f3677a1576d4ea7e8bb2c74fb95f5162
3637
+ protected volatile LivingEntity target;
3738
protected TargetingConditions targetConditions;
3839

39-
+ // Thread pool for async target finding with explicit naming for easier debugging
40-
+ private static final ExecutorService TARGET_FINDER_EXECUTOR = Executors.newFixedThreadPool(2, r -> {
41-
+ Thread thread = new Thread(r, "Target-Finder-Thread");
40+
+ // Single thread executor to prevent overwhelming the server
41+
+ private static final ExecutorService TARGET_FINDER_EXECUTOR = Executors.newSingleThreadExecutor(r -> {
42+
+ Thread thread = new Thread(r, "Leaf - Target-Finder-Thread");
4243
+ thread.setDaemon(true);
4344
+ thread.setPriority(Thread.MIN_PRIORITY); // Lower priority to avoid competing with main thread
4445
+ return thread;
4546
+ });
4647
+
4748
+ // Flag to track if a search is in progress
4849
+ private final AtomicBoolean isSearching = new AtomicBoolean(false);
49-
+
50-
+ // Reference holder for safe entity passing between threads
5150
+ private final AtomicReference<LivingEntity> pendingTarget = new AtomicReference<>(null);
52-
+
53-
+ // Static initializer for cleanup on shutdown
5451
+ static {
5552
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> {
56-
+ TARGET_FINDER_EXECUTOR.shutdown();
53+
+ try {
54+
+ TARGET_FINDER_EXECUTOR.shutdown();
55+
+ TARGET_FINDER_EXECUTOR.awaitTermination(2, TimeUnit.SECONDS);
56+
+ } catch (InterruptedException e) {
57+
+ Thread.currentThread().interrupt();
58+
+ } finally {
59+
+ if (!TARGET_FINDER_EXECUTOR.isTerminated()) {
60+
+ TARGET_FINDER_EXECUTOR.shutdownNow();
61+
+ }
62+
+ }
5763
+ }));
5864
+ }
5965
+
6066
public NearestAttackableTargetGoal(Mob mob, Class<T> targetType, boolean mustSee) {
6167
this(mob, targetType, 10, mustSee, false, null);
6268
}
63-
@@ -46,7 +74,14 @@ public class NearestAttackableTargetGoal<T extends LivingEntity> extends TargetG
69+
@@ -46,8 +80,12 @@ public class NearestAttackableTargetGoal<T extends LivingEntity> extends TargetG
6470
if (this.randomInterval > 0 && this.mob.getRandom().nextInt(this.randomInterval) != 0) {
6571
return false;
6672
} else {
6773
- this.findTarget();
74+
- return this.target != null;
6875
+ findTarget();
69-
+
70-
+ // Check for pending target from async operation
7176
+ LivingEntity pending = pendingTarget.getAndSet(null);
72-
+ if (pending != null) {
77+
+ if (pending != null && !pending.isRemoved() && pending.isAlive()) {
7378
+ this.target = pending;
7479
+ }
75-
+
76-
return this.target != null;
80+
+ return this.target != null && this.target.isAlive() && !this.target.isRemoved();
7781
}
7882
}
79-
@@ -55,25 +90,169 @@ public class NearestAttackableTargetGoal<T extends LivingEntity> extends TargetG
83+
84+
@@ -55,25 +93,235 @@ public class NearestAttackableTargetGoal<T extends LivingEntity> extends TargetG
8085
return this.mob.getBoundingBox().inflate(targetDistance, targetDistance, targetDistance);
8186
}
8287

@@ -102,62 +107,70 @@ index 41ee3cdc45ecc8376a2203ed588bb544ed377294..f3677a1576d4ea7e8bb2c74fb95f5162
102107
+
103108
+ // Capture mutable state to avoid race conditions
104109
+ final Mob mob = this.mob;
110+
+
111+
+ // Safety check
112+
+ if (mob == null || mob.isRemoved() || !mob.isAlive()) {
113+
+ isSearching.set(false);
114+
+ return;
115+
+ }
116+
+
105117
+ final double x = mob.getX();
106118
+ final double y = mob.getEyeY();
107119
+ final double z = mob.getZ();
108120
+ final double followDistance = this.getFollowDistance();
109121
+ final TargetingConditions targetConditions = this.getTargetConditions();
110122
+ final Class<T> targetType = this.targetType;
111123
+
112-
+ // Start async search with immutable captured state
113-
+ CompletableFuture.runAsync(() -> {
124+
+ // Start async search with immutable captured state - using submit instead of runAsync
125+
+ CompletableFuture.supplyAsync(() -> {
114126
+ try {
115-
+ // Avoid deadlocks by not performing operations that might block
116-
+ // on the main thread or require entity list synchronization
117127
+ ServerLevel serverLevel = getServerLevel(mob);
118128
+ if (serverLevel == null) {
119-
+ return;
129+
+ return null;
130+
+ }
131+
+ if (mob.isRemoved() || !mob.isAlive()) {
132+
+ return null;
120133
+ }
121-
+
122-
+ LivingEntity result = null;
123134
+
124135
+ try {
125-
+ // Use snapshot of entity list to avoid concurrent modification
126136
+ if (targetType != Player.class && targetType != ServerPlayer.class) {
127-
+ // Capture entity list in a safe way
128-
+ List<T> entities = mob.level().getEntitiesOfClass(
129-
+ targetType,
130-
+ getTargetSearchArea(followDistance),
131-
+ entity -> true
137+
+ AABB searchArea = new AABB(
138+
+ x - followDistance, y - followDistance, z - followDistance,
139+
+ x + followDistance, y + followDistance, z + followDistance
132140
+ );
133141
+
134-
+ // Don't directly modify any game state here
142+
+ List<T> entities = null;
143+
+ try {
144+
+ entities = mob.level().getEntitiesOfClass(targetType, searchArea, entity -> true);
145+
+ } catch (Exception e) {
146+
+ System.err.println("Error getting entities: " + e.getMessage());
147+
+ return null;
148+
+ }
149+
+
135150
+ if (entities != null && !entities.isEmpty()) {
136-
+ result = findNearestEntitySafely(entities, targetConditions, mob, x, y, z, serverLevel);
151+
+ return findNearestEntitySafely(entities, targetConditions, mob, x, y, z, serverLevel);
137152
+ }
138153
+ } else {
139-
+ // For players, use the safer helper method
140-
+ result = findNearestPlayerSafely(targetConditions, mob, x, y, z, serverLevel);
154+
+ return findNearestPlayerSafely(targetConditions, mob, x, y, z, serverLevel);
141155
+ }
142156
+ } catch (Exception e) {
143-
+ // Log exception but don't crash the thread
144157
+ System.err.println("Error finding entities in async target finder: " + e.getMessage());
145158
+ }
146159
+
147-
+ // Store result safely for main thread to pick up
148-
+ if (result != null) {
149-
+ pendingTarget.set(result);
150-
+ }
151-
+
160+
+ return null;
152161
+ } catch (Exception e) {
153162
+ System.err.println("Error during async target finding: " + e.getMessage());
163+
+ return null;
154164
+ } finally {
155165
+ isSearching.set(false);
156166
+ }
157-
+ }, TARGET_FINDER_EXECUTOR);
167+
+ }, TARGET_FINDER_EXECUTOR).thenAccept(result -> {
168+
+ if (result != null && result.isAlive() && !result.isRemoved()) {
169+
+ pendingTarget.set(result);
170+
+ }
171+
+ });
158172
+ }
159173
+
160-
+ // Safe helper method to find nearest entity without modifying collections
161174
+ @Nullable
162175
+ private LivingEntity findNearestEntitySafely(
163176
+ List<? extends LivingEntity> entities,
@@ -173,21 +186,32 @@ index 41ee3cdc45ecc8376a2203ed588bb544ed377294..f3677a1576d4ea7e8bb2c74fb95f5162
173186
+ }
174187
+
175188
+ try {
176-
+ // Manual distance calculation to avoid using potentially unsafe methods
177189
+ double closestDistSq = -1.0;
178190
+ LivingEntity closest = null;
179191
+
180-
+ for (LivingEntity entity : entities) {
181-
+ if (entity != null && !entity.isRemoved() && conditions.test(level, source, entity)) {
182-
+ double dx = entity.getX() - x;
183-
+ double dy = entity.getY() - y;
184-
+ double dz = entity.getZ() - z;
185-
+ double distSq = dx * dx + dy * dy + dz * dz;
192+
+ for (int i = 0; i < entities.size(); i++) {
193+
+ try {
194+
+ LivingEntity entity = entities.get(i);
195+
+ if (entity == null || entity.isRemoved() || !entity.isAlive()) {
196+
+ continue;
197+
+ }
198+
+
199+
+ if (conditions.test(level, source, entity)) {
200+
+ double dx = entity.getX() - x;
201+
+ double dy = entity.getY() - y;
202+
+ double dz = entity.getZ() - z;
203+
+ double distSq = dx * dx + dy * dy + dz * dz;
186204
+
187-
+ if (closestDistSq == -1.0 || distSq < closestDistSq) {
188-
+ closestDistSq = distSq;
189-
+ closest = entity;
205+
+ if (closestDistSq == -1.0 || distSq < closestDistSq) {
206+
+ closestDistSq = distSq;
207+
+ closest = entity;
208+
+ }
190209
+ }
210+
+ } catch (IndexOutOfBoundsException e) {
211+
+ break;
212+
+ } catch (Exception e) {
213+
+ System.err.println("Error processing entity in findNearestEntitySafely: " + e.getMessage());
214+
+ continue;
191215
+ }
192216
+ }
193217
+
@@ -198,7 +222,6 @@ index 41ee3cdc45ecc8376a2203ed588bb544ed377294..f3677a1576d4ea7e8bb2c74fb95f5162
198222
+ }
199223
+ }
200224
+
201-
+ // Safe helper method to find nearest player without modifying collections
202225
+ @Nullable
203226
+ private Player findNearestPlayerSafely(
204227
+ TargetingConditions conditions,
@@ -214,7 +237,40 @@ index 41ee3cdc45ecc8376a2203ed588bb544ed377294..f3677a1576d4ea7e8bb2c74fb95f5162
214237
+
215238
+ try {
216239
+ List<? extends Player> players = level.players();
217-
+ return (Player) findNearestEntitySafely(players, conditions, source, x, y, z, level);
240+
+ if (players == null || players.isEmpty()) {
241+
+ return null;
242+
+ }
243+
+
244+
+ double closestDistSq = -1.0;
245+
+ Player closest = null;
246+
+
247+
+ for (int i = 0; i < players.size(); i++) {
248+
+ try {
249+
+ Player player = players.get(i);
250+
+ if (player == null || player.isRemoved() || !player.isAlive()) {
251+
+ continue;
252+
+ }
253+
+
254+
+ if (conditions.test(level, source, player)) {
255+
+ double dx = player.getX() - x;
256+
+ double dy = player.getY() - y;
257+
+ double dz = player.getZ() - z;
258+
+ double distSq = dx * dx + dy * dy + dz * dz;
259+
+
260+
+ if (closestDistSq == -1.0 || distSq < closestDistSq) {
261+
+ closestDistSq = distSq;
262+
+ closest = player;
263+
+ }
264+
+ }
265+
+ } catch (IndexOutOfBoundsException e) {
266+
+ break;
267+
+ } catch (Exception e) {
268+
+ System.err.println("Error processing player in findNearestPlayerSafely: " + e.getMessage());
269+
+ continue;
270+
+ }
271+
+ }
272+
+
273+
+ return closest;
218274
+ } catch (Exception e) {
219275
+ System.err.println("Error in findNearestPlayerSafely: " + e.getMessage());
220276
+ return null;
@@ -230,16 +286,26 @@ index 41ee3cdc45ecc8376a2203ed588bb544ed377294..f3677a1576d4ea7e8bb2c74fb95f5162
230286
+ }
231287
+
232288
+ if (this.targetType != Player.class && this.targetType != ServerPlayer.class) {
233-
+ this.target = serverLevel.getNearestEntity(
234-
+ this.mob.level().getEntitiesOfClass(this.targetType, this.getTargetSearchArea(this.getFollowDistance()), entity -> true),
235-
+ this.getTargetConditions(),
236-
+ this.mob,
237-
+ this.mob.getX(),
238-
+ this.mob.getEyeY(),
239-
+ this.mob.getZ()
240-
+ );
289+
+ try {
290+
+ this.target = serverLevel.getNearestEntity(
291+
+ this.mob.level().getEntitiesOfClass(this.targetType, this.getTargetSearchArea(this.getFollowDistance()), entity -> true),
292+
+ this.getTargetConditions(),
293+
+ this.mob,
294+
+ this.mob.getX(),
295+
+ this.mob.getEyeY(),
296+
+ this.mob.getZ()
297+
+ );
298+
+ } catch (Exception e) {
299+
+ System.err.println("Error in sync entity finding: " + e.getMessage());
300+
+ this.target = null;
301+
+ }
241302
+ } else {
242-
+ this.target = serverLevel.getNearestPlayer(this.getTargetConditions(), this.mob, this.mob.getX(), this.mob.getEyeY(), this.mob.getZ());
303+
+ try {
304+
+ this.target = serverLevel.getNearestPlayer(this.getTargetConditions(), this.mob, this.mob.getX(), this.mob.getEyeY(), this.mob.getZ());
305+
+ } catch (Exception e) {
306+
+ System.err.println("Error in sync player finding: " + e.getMessage());
307+
+ this.target = null;
308+
+ }
243309
+ }
244310
+ } catch (Exception e) {
245311
+ System.err.println("Error in findTargetSync: " + e.getMessage());
@@ -251,10 +317,15 @@ index 41ee3cdc45ecc8376a2203ed588bb544ed377294..f3677a1576d4ea7e8bb2c74fb95f5162
251317
public void start() {
252318
- this.mob.setTarget(this.target, this.target instanceof ServerPlayer ? org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER : org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_ENTITY, true); // CraftBukkit - reason
253319
+ LivingEntity targetEntity = this.target;
254-
+ if (targetEntity != null && !targetEntity.isRemoved()) {
255-
+ this.mob.setTarget(targetEntity, targetEntity instanceof ServerPlayer ?
256-
+ org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER :
257-
+ org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_ENTITY, true);
320+
+ if (targetEntity != null && !targetEntity.isRemoved() && targetEntity.isAlive()) {
321+
+ try {
322+
+ this.mob.setTarget(targetEntity, targetEntity instanceof ServerPlayer ?
323+
+ org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER :
324+
+ org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_ENTITY, true);
325+
+ } catch (Exception e) {
326+
+ System.err.println("Error in setTarget: " + e.getMessage());
327+
+ this.target = null;
328+
+ }
258329
+ }
259330
super.start();
260331
}

0 commit comments

Comments
 (0)