Skip to content

支持 Folia#1059

Open
StarWishsama wants to merge 54 commits into
devfrom
feature/folia
Open

支持 Folia#1059
StarWishsama wants to merge 54 commits into
devfrom
feature/folia

Conversation

@StarWishsama
Copy link
Copy Markdown
Member

@StarWishsama StarWishsama commented May 5, 2025

  • 迁移至 Folia Scheduler
  • 为部分模块添加多线程支持
    • 为 TickerTask 引入异步调度器并行运行任务
    • sub region slimefun ticker (由于 Folia 没有显式获取所有 Region 的方法,故取消)
  • Fix feature/folia分支部分多方快结构无法使用 #1082
  • 正确处理传送
  • 新增 Ticker 线程池配置,便于用户根据服务器情况调优
  • Folia 不支持的事件替代方案(重生、登录、...)
  • [API] concurrent safe 标记*
  • 线程池管理
  • 合并到 Insider 通道

*: BlockTicker#isConcurrentSafe() 用于标记当前机器 Ticker 是并发安全的,Slimefun 默认认为附属的机器是并发不安全的,并移至异步单线程处理这些机器。

@StarWishsama StarWishsama removed the N label May 22, 2025
@StarWishsama StarWishsama linked an issue Jul 15, 2025 that may be closed by this pull request
3 tasks
@StarWishsama StarWishsama added 🔧 插件兼容性 🤔需要测试 对应 PR 需要测试 and removed 🔨 进行中 在做了 咕咕咕 labels Aug 2, 2025
# Conflicts:
#	src/main/java/com/xzavier0722/mc/plugin/slimefun4/storage/controller/BlockDataController.java
Folia remove support for player respawn related event
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 61 out of 61 changed files in this pull request and generated 12 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +86 to +94
Slimefun.getPlatformScheduler().runLater(runnable, node.getDelay());
} else {
Slimefun.getPlatformScheduler().runNextTick((task) -> runnable.run());
}
} else {
if (node.getDelay() > 0) {
Slimefun.getPlatformScheduler().runLaterAsync(runnable, node.getDelay());
} else {
Slimefun.getPlatformScheduler().runAsync((task) -> runnable.run());
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic in scheduleGeneral appears to be inverted. When node.isAsynchronous() is true, the code schedules synchronously (runLater/runNextTick), and when it's false, it schedules asynchronously (runLaterAsync/runAsync). This should be corrected so that asynchronous nodes run asynchronously and synchronous nodes run synchronously.

Suggested change
Slimefun.getPlatformScheduler().runLater(runnable, node.getDelay());
} else {
Slimefun.getPlatformScheduler().runNextTick((task) -> runnable.run());
}
} else {
if (node.getDelay() > 0) {
Slimefun.getPlatformScheduler().runLaterAsync(runnable, node.getDelay());
} else {
Slimefun.getPlatformScheduler().runAsync((task) -> runnable.run());
Slimefun.getPlatformScheduler().runLaterAsync(runnable, node.getDelay());
} else {
Slimefun.getPlatformScheduler().runAsync((task) -> runnable.run());
}
} else {
if (node.getDelay() > 0) {
Slimefun.getPlatformScheduler().runLater(runnable, node.getDelay());
} else {
Slimefun.getPlatformScheduler().runNextTick((task) -> runnable.run());

Copilot uses AI. Check for mistakes.
Comment on lines +67 to +74
# 自定义 Ticker 的异步线程池配置
custom-async-ticker:
# 线程池初始线程数, 推荐数为主机的 CPU 核心数
init-size: 2
# 线程数最大线程数,推荐数为主机的 CPU 核心数 * 2
max-size: 8
# 每个线程池的任务队列大小,数值越大每个线程能处理的任务越多,但会增加总体延迟
queue-size: 1024
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Chinese comments mention "线程池初始线程数" (initial thread count) and "线程数最大线程数" (maximum thread count), but the actual parameter names in the code use "init-size" and "max-size" which are ambiguous. In thread pool terminology, these typically refer to core pool size and maximum pool size. Consider renaming these configuration keys to be more explicit, such as "core-pool-size" and "max-pool-size" to match standard Java ExecutorService terminology and avoid confusion.

Copilot uses AI. Check for mistakes.
Comment on lines 398 to 409
public boolean locked() {
return lock.get();
return lock.isLocked();
}

public void lock() {
lock.getAndSet(true);
lock.lock();
InventoryUtil.closeInventory(this.inventory);
}

public void unlock() {
lock.getAndSet(false);
lock.unlock();
}
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ReentrantLock is being used in ChestMenu to replace AtomicBoolean for locking, but the locked() method only checks isLocked() without checking if the current thread owns the lock. This could lead to incorrect behavior if multiple threads are involved, as isLocked() returns true if ANY thread holds the lock, not just the current thread. If the intention is to prevent re-entrant operations by the same thread, consider using tryLock() instead or checking lock ownership. Additionally, ensure that unlock() is always called in a finally block to prevent deadlocks if an exception occurs between lock() and unlock().

Copilot uses AI. Check for mistakes.
Comment on lines +63 to 71
try {
menu.lock();

for (int slot : getInputSlots()) {
menu.replaceExistingItem(slot, null);
}
} finally {
menu.unlock();
}
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TrashCan implementation correctly locks and unlocks the menu in a try-finally block, which is good practice. However, the lock is acquired on every tick, which means if the ticker is executing concurrently, multiple threads could contend for this lock frequently. While this is thread-safe, it might impact performance. Consider whether the lock granularity is appropriate, or if there's a more efficient way to handle concurrent access to the trash can's inventory slots.

Copilot uses AI. Check for mistakes.
l.getBlockX(), l.getBlockY(), l.getBlockZ(), item.getId()
});
Slimefun.logger().log(Level.SEVERE, "在过去的 4 个 Tick 中发生多次错误,该方块对应的机器已被停用。");
Slimefun.logger().log(Level.SEVERE, "该位置上的机器在过去一段时间内多次报错,对应机器已被停用。");
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message uses Chinese characters "该位置上的机器在过去一段时间内多次报错,对应机器已被停用。" The comment change appears to make the message less specific (changing from "在过去的 4 个 Tick 中发生多次错误" which mentions "4 Ticks" to "在过去一段时间内" which just says "a period of time"). However, this is marked as a change rather than a fix in context. Verify that this error message change is intentional and accurate, as the bug tracking logic may have changed.

Copilot uses AI. Check for mistakes.
}

return instance.getServer().getScheduler().runTaskLater(instance, runnable, delay);
return getPlatformScheduler().runLater(runnable, Math.max(1, delay));
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The delay parameter is being passed with Math.max(1, delay) in runSync, but the validation already ensures delay >= 0. If delay is 0, this forces it to 1 tick. This changes the behavior from immediate execution (delay 0) to delayed execution (delay 1), which might not be the intended behavior. If delay of 0 should execute immediately, consider using runNextTick() instead when delay is 0, otherwise document why minimum delay of 1 is enforced.

Copilot uses AI. Check for mistakes.
Comment on lines 120 to +122
"Attempted to refresh skin cache, got this response: {0}: {1}",
new Object[] {x.getClass().getSimpleName(), x.getMessage()});
Slimefun.logger().log(Level.WARNING, "Error during refreshing skin cache", x);
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A new log statement has been added to log the exception when skin cache refresh fails. However, the exception is logged twice: once with Level.WARNING and a custom message, and then again with the exception details. This creates duplicate log entries. Consider consolidating into a single log statement that includes both the message and the exception, for example: Slimefun.logger().log(Level.WARNING, "Attempted to refresh skin cache, got response: " + x.getClass().getSimpleName() + ": " + x.getMessage(), x);

Suggested change
"Attempted to refresh skin cache, got this response: {0}: {1}",
new Object[] {x.getClass().getSimpleName(), x.getMessage()});
Slimefun.logger().log(Level.WARNING, "Error during refreshing skin cache", x);
"Attempted to refresh skin cache, got response: "
+ x.getClass().getSimpleName()
+ ": "
+ x.getMessage(),
x);

Copilot uses AI. Check for mistakes.
Comment on lines +27 to 77
public <T> T runSyncMethod(@Nonnull Callable<T> callable) {
return runSyncMethod(callable, null, null);
}

@SneakyThrows
public <T> T runSyncMethod(@Nonnull Callable<T> callable, @Nonnull Location l) {
return runSyncMethod(callable, l, null);
}

@SneakyThrows
public <T> T runSyncMethod(@Nonnull Callable<T> callable, @Nonnull Entity entity) {
return runSyncMethod(callable, null, entity);
}

@SneakyThrows
private <T> T runSyncMethod(@Nonnull Callable<T> callable, @Nullable Location l, @Nullable Entity entity) {
if (!Slimefun.isFolia()) {
return Bukkit.isPrimaryThread()
? callable.call()
: Bukkit.getScheduler()
.callSyncMethod(Slimefun.instance(), callable)
.get(2, TimeUnit.SECONDS);
}

final CompletableFuture<T> result = new CompletableFuture<>();

if (l != null) {
Slimefun.getPlatformScheduler().runAtLocation(l, task -> {
try {
result.complete(callable.call());
} catch (Exception e) {
result.completeExceptionally(e);
}
});
} else {
try {
return Bukkit.getScheduler()
.callSyncMethod(Slimefun.instance(), callable)
.get(1, TimeUnit.SECONDS);
} catch (TimeoutException e) {
Slimefun.logger().log(Level.WARNING, "Timeout when executing sync method", e);
return null;
if (entity != null) {
Slimefun.getPlatformScheduler().runAtEntity(entity, task -> {
try {
result.complete(callable.call());
} catch (Exception e) {
result.completeExceptionally(e);
}
});
} else {
throw new IllegalArgumentException(
"Location or entity must be provided when executing sync task on Folia!");
}
}

return result.get(2, TimeUnit.SECONDS);
}
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The runSyncMethod implementation for Folia requires either a Location or Entity to be provided, but when both are null, it throws an IllegalArgumentException. However, the no-argument version runSyncMethod(Callable) calls the private method with both parameters as null, which will always throw an exception on Folia. This breaks the API contract. Consider either using a global region scheduler for Folia when no location/entity is provided, or documenting that the no-argument version is not supported on Folia.

Copilot uses AI. Check for mistakes.
Comment on lines +59 to +68
/**
* 声明当前 {@link BlockTicker} 是否线程安全
* </br>
* 默认不启用,此时会将这些机器放置到单线程调度器 (旧方法) 上运行
*
* @return 是否并发安全
*/
public boolean isConcurrent() {
return false;
}
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The javadoc comment incorrectly states "声明当前 {@link BlockTicker} 是否线程安全" and mentions "默认不启用,此时会将这些机器放置到单线程调度器 (旧方法) 上运行". However, thread safety and concurrent execution are related but distinct concepts. A method being concurrent-safe means it can be called by multiple threads simultaneously without issues, while the default behavior of using a single-threaded scheduler means sequential execution. The documentation should clarify that this method indicates whether the ticker can safely execute in parallel with other tickers, not just general thread safety.

Copilot uses AI. Check for mistakes.
Comment on lines +285 to 286
@SneakyThrows
public boolean removeHologram(@Nonnull Location loc) {
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The @SneakyThrows annotation from Lombok is being used to suppress checked exception handling. While this can make code cleaner, it hides the fact that the method can throw exceptions, making it harder for callers to understand the error conditions. Consider explicitly declaring the throws clause or handling the exception more explicitly, especially for a public API method like removeHologram.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings February 25, 2026 05:52
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 61 out of 61 changed files in this pull request and generated 7 comments.

Comments suppressed due to low confidence (2)

src/main/java/city/norain/slimefun4/utils/InventoryUtil.java:49

  • closeInventory(inventory, callback) uses Slimefun.runSync(callback) when off-thread. Under Folia this still schedules without entity/location context, and callers may expect the callback to run on the same safe thread as the inventory closing. Consider scheduling the callback via an explicit entity/location context (or running it after all viewer-close tasks complete) so Folia thread rules are not violated.
    public void closeInventory(Inventory inventory, Runnable callback) {
        closeInventory(inventory);

        if (!Slimefun.isFolia() && Bukkit.isPrimaryThread()) {
            callback.run();
        } else {
            Slimefun.runSync(callback);
        }

src/main/java/city/norain/slimefun4/utils/InventoryUtil.java:39

  • On Folia, closeInventory schedules HumanEntity::closeInventory via Slimefun.runSync(...) without an entity/location context. Folia requires entity operations (like closing a player's inventory) to run on that entity's scheduler; running this on a global/main scheduler can still violate Folia thread rules. Consider iterating viewers and scheduling closeInventory via PlatformScheduler#runAtEntity(...) (or equivalent) per viewer.
        if (!Slimefun.isFolia() && Bukkit.isPrimaryThread()) {
            new LinkedList<>(inventory.getViewers()).forEach(HumanEntity::closeInventory);
        } else {
            Slimefun.runSync(() -> new LinkedList<>(inventory.getViewers()).forEach(HumanEntity::closeInventory));
        }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +98 to +106
var initSize = Slimefun.getConfigManager().getAsyncTickerInitSize();
var maxSize = Slimefun.getConfigManager().getAsyncTickerMaxSize();
var poolSize = Slimefun.getConfigManager().getAsyncTickerQueueSize();

this.asyncTickerService = new SlimefunPoolExecutor(
"Slimefun-Ticker-Pool",
initSize - 1,
maxSize - 1,
1,
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TickerTask.start() subtracts 1 from both configured init/max sizes (initSize - 1, maxSize - 1). With low values (e.g., 1 core / default half-procs), this can produce maximumPoolSize <= 0 and throw IllegalArgumentException during thread pool creation. Consider enforcing minimums (>=1) after adjustment or avoid subtracting here (use the configured values directly, and account for fallback pool separately).

Copilot uses AI. Check for mistakes.
Comment on lines +29 to +30
private final Map<UUID, Map<Integer, ItemStack>> soulbound = new HashMap<>();
private final Map<UUID, WrappedTask> returnTasks = new HashMap<>();
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SoulboundListener stores state in plain HashMaps (soulbound, returnTasks). On Folia, player events can execute concurrently on different region threads, so these maps can be mutated concurrently, leading to races/ConcurrentModificationException/lost updates. Use ConcurrentHashMap (and thread-safe nested maps if needed) for these fields.

Copilot uses AI. Check for mistakes.
Comment on lines +77 to +99
if (event.getEntity() instanceof Player p) {
if (returnTasks.containsKey(p.getUniqueId())) {
return;
}

WrappedTask task = Slimefun.getPlatformScheduler()
.runAtEntityLater(
p,
() -> {
if (p.getHealth() > 0) {
returnSoulboundItems(p);

WrappedTask returnTask = returnTasks.remove(p.getUniqueId());

if (returnTask != null) {
returnTask.cancel();
}
}
},
10L);

returnTasks.put(event.getEntity().getUniqueId(), task);
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The containsKey -> runAtEntityLater -> put sequence is not atomic; two concurrent deaths could schedule duplicate return tasks for the same UUID. Use returnTasks.putIfAbsent(...) / computeIfAbsent(...) to ensure only one task is scheduled per player, and cancel/replace deterministically if rescheduling is intended.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback

Comment on lines 26 to +39
@SneakyThrows
public <T> T runSyncMethod(Callable<T> callable) {
if (Bukkit.isPrimaryThread()) {
return callable.call();
public <T> T runSyncMethod(@Nonnull Callable<T> callable) {
return runSyncMethod(callable, null, null);
}

@SneakyThrows
public <T> T runSyncMethod(@Nonnull Callable<T> callable, @Nonnull Location l) {
return runSyncMethod(callable, l, null);
}

@SneakyThrows
public <T> T runSyncMethod(@Nonnull Callable<T> callable, @Nonnull Entity entity) {
return runSyncMethod(callable, null, entity);
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Folia, the no-context overload runSyncMethod(Callable<T>) calls the private implementation with both Location and Entity null, which will throw IllegalArgumentException. Any existing call sites using this overload will start failing under Folia; consider either requiring callers to pass a Location/Entity (e.g., by removing/deprecating the no-context overload when Folia is active) or providing a safe Folia-compatible default scheduling strategy.

Copilot uses AI. Check for mistakes.
Comment on lines +147 to +154
pluginConfig.setDefaultValue("URID.custom-async-ticker.init-size", serverHalfProcs);
pluginConfig.setDefaultValue(
"URID.custom-async-ticker.max-size", Runtime.getRuntime().availableProcessors());
pluginConfig.setDefaultValue("URID.custom-async-ticker.queue-size", 1024);

asyncTickerInitSize = pluginConfig.getInt("URID.custom-async-ticker.init-size");
asyncTickerMaxSize = pluginConfig.getInt("URID.custom-async-ticker.max-size");
asyncTickerQueueSize = pluginConfig.getInt("URID.custom-async-ticker.queue-size");
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The async ticker pool settings are only sanitized for values < 0. Values of 0 (or 1 given TickerTask subtracts 1) will still result in invalid pool sizes at runtime. Consider validating these settings against the actual ThreadPoolExecutor requirements (e.g., init-size >= 1, max-size >= 1, queue-size >= 1) and logging/normalizing accordingly.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback

Comment on lines 21 to 27
public void schedule(long delay) {
setID(Bukkit.getScheduler().scheduleSyncDelayedTask(Slimefun.instance(), this, delay));
setTask(Slimefun.getPlatformScheduler().runLaterAsync(this, delay));
}

public void scheduleRepeating(long delay, long interval) {
setID(Bukkit.getScheduler().scheduleSyncRepeatingTask(Slimefun.instance(), this, delay, interval));
setTask(Slimefun.getPlatformScheduler().runTimer(this, delay, interval));
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AbstractPlayerTask.schedule(long delay) switched from scheduleSyncDelayedTask to runLaterAsync, which will execute executeTask() off-thread. Since executeTask() commonly interacts with Player, this can violate Bukkit/Folia thread rules. Schedule on the player's entity scheduler (e.g., runAtEntityLater(p, ...)) or use the synchronous scheduler method instead of runLaterAsync.

Copilot uses AI. Check for mistakes.
Comment on lines +43 to +49
if (!Slimefun.isFolia()) {
return Bukkit.isPrimaryThread()
? callable.call()
: Bukkit.getScheduler()
.callSyncMethod(Slimefun.instance(), callable)
.get(2, TimeUnit.SECONDS);
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TaskUtil.runSyncMethod(Callable) uses Future#get(2, TimeUnit.SECONDS) under @SneakyThrows without handling TimeoutException/ExecutionException. This means timeouts or sync-call failures will bubble up as unchecked exceptions and can crash the calling thread. Consider catching these exceptions, logging context, and returning a safe fallback (or rethrowing a checked exception explicitly).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

Copilot AI commented Feb 27, 2026

@StarWishsama I've opened a new pull request, #1192, to work on those changes. Once the pull request is ready, I'll request review from you.

Copilot AI and others added 4 commits February 27, 2026 03:58
…nd 1

Co-authored-by: StarWishsama <25561848+StarWishsama@users.noreply.github.com>
Co-authored-by: StarWishsama <25561848+StarWishsama@users.noreply.github.com>
Copy link
Copy Markdown

Copilot AI commented Feb 27, 2026

@StarWishsama I've opened a new pull request, #1193, to work on those changes. Once the pull request is ready, I'll request review from you.

Copilot AI and others added 2 commits February 27, 2026 07:49
…o prevent race condition

Co-authored-by: StarWishsama <25561848+StarWishsama@users.noreply.github.com>
…-again

Fix race condition in SoulboundListener Folia task scheduling
Copilot AI review requested due to automatic review settings March 3, 2026 03:22
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 61 out of 61 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 21 to 23
public void schedule(long delay) {
setID(Bukkit.getScheduler().scheduleSyncDelayedTask(Slimefun.instance(), this, delay));
setTask(Slimefun.getPlatformScheduler().runLaterAsync(this, delay));
}
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

schedule(...) uses runLaterAsync(this, delay), but implementations mutate Player state (inventory/velocity/effects). On Folia this can run off the entity thread and cause region/entity-thread violations. Use runAtEntityLater(p, ...) (and similarly an entity-bound repeating scheduler) for these tasks.

Copilot uses AI. Check for mistakes.
Comment on lines +82 to +86
() -> {
if (p.getHealth() > 0) {
returnSoulboundItems(p);

WrappedTask returnTask = returnTasks.remove(uuid);
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This Folia fallback schedules only a single delayed check and only completes when p.getHealth() > 0. If the player respawns later than this delay, items may never be returned and the returnTasks entry is never removed (blocking future scheduling). Use a repeating entity-bound task that retries until the player is alive/valid, and always remove the map entry when the task finishes.

Copilot uses AI. Check for mistakes.
Comment on lines +71 to 73
throw new IllegalArgumentException(
"Location or entity must be provided when executing sync task on Folia!");
}
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Folia, the no-context overload now throws if neither a Location nor Entity is provided. There are still call sites using runSyncMethod(Callable) without context (e.g. DebugFishListener), which will now crash at runtime. Consider keeping the no-context overload safe (run inline when already on a region thread, otherwise require callers to pass Location/Entity), and/or deprecate it and update remaining call sites.

Copilot uses AI. Check for mistakes.
Comment on lines +64 to +68
return;
}

try {
menu.lock(false);
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pattern is still vulnerable to a race: another thread can acquire the menu lock between menu.locked() and menu.lock(false), causing the ticker thread to block unexpectedly now that lock() is a blocking ReentrantLock. Prefer a tryLock-style API (return immediately when locked) or make lock(false) itself non-blocking.

Copilot uses AI. Check for mistakes.
Comment on lines +102 to +106
this.asyncTickerService = new SlimefunPoolExecutor(
"Slimefun-Ticker-Pool",
initSize - 1,
maxSize - 1,
1,
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The executor is created with initSize - 1 / maxSize - 1, but the config keys are named as thread counts (init-size, max-size). This off-by-one mapping is surprising for operators and easy to misconfigure. Either pass the config values directly, or rename/update the config docs/defaults to reflect the - 1 semantics.

Copilot uses AI. Check for mistakes.
# Conflicts:
#	.github/workflows/pr-checker.yml
#	pom.xml
#	src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/elevator/ElevatorPlate.java
#	src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/AutomatedPanningMachine.java
#	src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/miner/MiningTask.java
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ticker 多线程问题

5 participants