Class TaskManager
Scans beans for Scheduled annotated methods and registers them
as Bukkit tasks. Automatically cancels all tasks when a plugin is unloaded.
Thread-confinement assumption (IN-02, gate-1 review, 16-REVIEW-residue.md):
pluginTasks/externalTasks/coreTasks are plain, non-concurrent
collections (HashMap/ArrayList). scanAndSchedule(UltiToolsPlugin, Object, Consumer)
mutates them synchronously, once per successfully-scheduled task, inside its own scan loop
(#410) - every current call site (PluginManager.onPluginRegistered,
registerScheduledMethodsCore(Object), registerScheduledMethodsExternal(String, Object)) runs during plugin loading on the main thread, so this is safe today, but nothing
enforces it. A future caller invoking any registerScheduledMethods* entry point from
an async context would race these collections with no compile-time or runtime signal - the
same main-thread-only contract GuiScheduler's
own class javadoc documents explicitly for its collections.
Config-bound tasks (#531): a Scheduled method whose period or delay is bound to
a module config key is scheduled from the key's value (seconds, times 20) and kept as a
TaskManager.BoundTask handle in boundTasks, so rescheduleBound(UltiToolsPlugin)
can find that one method's task again after /ul reload. A method with no binding takes
exactly the pre-#531 path -- same scheduler call, same bucket, same log line.
- Since:
- 6.2.0
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescription(package private) static final classPer-method record of one config-boundScheduledtask: what to invoke, where its bound values come from, its current timing in ticks, when it was armed and last ran, and its current Bukkit task. -
Field Summary
FieldsModifier and TypeFieldDescriptionprivate final Map<UltiToolsPlugin, List<TaskManager.BoundTask>> Config-bound tasks, per module, as handles that remember their method, their binding, their current timing and their last run -- the per-method record the three buckets above do not keep.private final List<org.bukkit.scheduler.BukkitTask> Tasks owned by the framework itself rather than by a plugin module or an external plugin.private final org.bukkit.plugin.java.JavaPluginprivate final Map<UltiToolsPlugin, List<org.bukkit.scheduler.BukkitTask>> -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionprivate org.bukkit.scheduler.BukkitTaskarm(TaskManager.BoundTask handle, long firstDelay) Scheduleshandle's method with its current timing, first running afterfirstDelayticks.private static PluginModuleExceptionbindingOutsideModule(Class<?> targetClass, Method method) voidcancelAll(UltiToolsPlugin plugin) Cancel all scheduled tasks for a plugin.voidCancel every framework-owned scheduled task.voidcancelAllExternal(String pluginName) Cancel all scheduled tasks for an external plugin.private static booleancheckSignatureOrWarn(Class<?> targetClass, Method method) The two signature rulesscanAndSchedule(com.ultikits.ultitools.abstracts.UltiToolsPlugin, java.lang.Object, java.util.function.Consumer<org.bukkit.scheduler.BukkitTask>)skips a method for, each logging the same WARNING it always has.(package private) static StringfirstBoundMethod(Object bean) (package private) intGet the number of registered framework-owned tasks (for testing).(package private) intgetExternalTaskCount(String pluginName) Get the number of registered tasks for an external plugin (for testing).private Class<?> getTargetClass(Class<?> clazz) Get the original class, unwrapping proxies generated byProxyFactory.(package private) intgetTaskCount(UltiToolsPlugin plugin) Get the number of registered tasks for a plugin (for testing).private static booleanisSchedulableSignature(Method method) The two signature rulesscanAndSchedule(com.ultikits.ultitools.abstracts.UltiToolsPlugin, java.lang.Object, java.util.function.Consumer<org.bukkit.scheduler.BukkitTask>)skips a method for, without its logging.(package private) static voidrefuseConfigBindings(Object bean) Refuses any config-boundScheduledmethod onbean-- for a container with no module config registry (an external plugin's).voidregisterScheduledMethods(UltiToolsPlugin plugin, Object bean) Scan a bean forScheduledmethods and register them as Bukkit tasks.voidScan a framework-owned object forScheduledmethods and register them.voidregisterScheduledMethodsExternal(String pluginName, Object bean) Scan a bean forScheduledmethods and register them for an external plugin.private static longreloadedTicks(UltiToolsPlugin plugin, TaskManager.BoundTask handle, ConfigBindings.Source source, long currentTicks, Set<String> warnedKeys, ReloadReport report) voidrescheduleBound(UltiToolsPlugin plugin) Appliesplugin's reloaded configuration to its config-boundScheduledtasks.voidrescheduleBound(UltiToolsPlugin plugin, ReloadReport report) rescheduleBound(UltiToolsPlugin), also recording inreportone partial reason per bound value kept in place of a refused one (#595), so the module's reload is not reported as complete while a task kept its old timing.private voidrescheduleOne(UltiToolsPlugin plugin, TaskManager.BoundTask handle, ReloadReport report) private voidscanAndSchedule(UltiToolsPlugin module, Object bean, Consumer<org.bukkit.scheduler.BukkitTask> recorder) Scan one bean forScheduledmethods and schedule each valid one, recording each task in its owning bucket as soon as it is created rather than batching them into a list returned at the end.private voidscheduleBound(UltiToolsPlugin module, Object bean, Class<?> targetClass, Method method, Scheduled scheduled) Schedules one config-boundScheduledmethod from its resolved binding and records its handle inboundTasksimmediately (#410).(package private) static voidvalidateConfigBindings(UltiToolsPlugin module, Object bean) Load-time check of every config-boundScheduledmethod onbean, over exactly the methodsscanAndSchedule(com.ultikits.ultitools.abstracts.UltiToolsPlugin, java.lang.Object, java.util.function.Consumer<org.bukkit.scheduler.BukkitTask>)would schedule.
-
Field Details
-
pluginTasks
-
externalTasks
-
coreTasks
Tasks owned by the framework itself rather than by a plugin module or an external plugin.A framework-owned object belongs to no
SimpleContainer, so it is reached by neitherregisterScheduledMethods(UltiToolsPlugin, Object)norregisterScheduledMethodsExternal(String, Object)-- both of which iterate a container's beans. Before 6.3.0 there was no third bucket, which is whyPlayerCacheManager.sweepExpiredEntries()carried a@Scheduledannotation that was never registered and never ran (#384).- Since:
- 6.3.0
-
boundTasks
Config-bound tasks, per module, as handles that remember their method, their binding, their current timing and their last run -- the per-method record the three buckets above do not keep. Only modules can bind, so there is no external or core counterpart.- Since:
- 6.3.0
-
hostPlugin
private final org.bukkit.plugin.java.JavaPlugin hostPlugin
-
-
Constructor Details
-
TaskManager
public TaskManager(org.bukkit.plugin.java.JavaPlugin hostPlugin)
-
-
Method Details
-
registerScheduledMethods
Scan a bean forScheduledmethods and register them as Bukkit tasks.- Parameters:
plugin- the owning plugin modulebean- the bean instance to scan
-
registerScheduledMethodsCore
Scan a framework-owned object forScheduledmethods and register them.The other two entry points key their tasks on a plugin module or an external plugin, and both are reached by iterating a
SimpleContainer's beans. An object the framework constructs directly --PlayerCacheManager, for instance -- belongs to no container and is therefore reached by neither, which is why its@Scheduledmethod silently never ran before 6.3.0 (#384). This is the third bucket, for objects the framework owns.Callers must not invoke this ad hoc. The set of framework-owned types whose scheduled methods are registered is declared by
PluginManager.FRAMEWORK_SCHEDULED_OWNER_TYPESand enforced byFrameworkScheduledWiringTest; adding an object here without adding its type there would reintroduce exactly the drift that guard exists to prevent.- Parameters:
bean- the framework-owned instance to scan- Since:
- 6.3.0
-
cancelAllCore
public void cancelAllCore()Cancel every framework-owned scheduled task.Called from
PluginManager.close(), whichUltiTools.onDisable()invokes. A repeating task that is not cancelled here survives a/reloadand a second copy is scheduled on the next enable.- Since:
- 6.3.0
-
scanAndSchedule
private void scanAndSchedule(UltiToolsPlugin module, Object bean, Consumer<org.bukkit.scheduler.BukkitTask> recorder) Scan one bean forScheduledmethods and schedule each valid one, recording each task in its owning bucket as soon as it is created rather than batching them into a list returned at the end.The single implementation behind all three registration entry points, which differ only in which bucket (
recorder) they file the resulting tasks under. Three copies of this body would be three places to keep a signature check, a scheduling rule or a log line in step.#410: before this, tasks were accumulated in a local list and handed to the owning bucket only via
created.addAll(...)in the CALLER, after this method returned. If scheduling a laterScheduledmethod threw (e.g.runTaskTimerthrowing because the host plugin became disabled mid-registration), every task already scheduled for earlier methods in this same scan was already a live Bukkit task, but the exception meant this method never returned, socreatednever reached any bucket --cancelAll/cancelAllExternal/cancelAllCorehad no record of them and could never cancel them at teardown. Callingrecorderinside the loop, immediately after each task is scheduled, means an exception from a later method can no longer un-record an earlier one: whatever the loop reached before throwing is already exactly where it needs to be for teardown to find it. This also restores the original, pre-three-bucket-split behaviour of recording each task as it was scheduled, one at a time. #531: a config-bound method is handed toscheduleBound(com.ultikits.ultitools.abstracts.UltiToolsPlugin, java.lang.Object, java.lang.Class<?>, java.lang.reflect.Method, com.ultikits.ultitools.annotations.Scheduled)instead and recorded inboundTasksas soon as it is scheduled, by the same #410 rule. Only a module has a config registry to bind against, so a bound method reached with nomodule(an external plugin's or a framework-owned bean) is refused.- Parameters:
module- the owning module, ornullfor an external plugin or the frameworkbean- the instance to scanrecorder- invoked once per successfully scheduled task, in declaration order, with that task -- files it into this bean's owning bucket immediately- Throws:
PluginModuleException- for a config-bound method with nomodule, or for a binding that cannot be resolved- Since:
- 6.3.0
-
cancelAll
Cancel all scheduled tasks for a plugin.- Parameters:
plugin- the plugin to cancel tasks for
-
scheduleBound
private void scheduleBound(UltiToolsPlugin module, Object bean, Class<?> targetClass, Method method, Scheduled scheduled) Schedules one config-boundScheduledmethod from its resolved binding and records its handle inboundTasksimmediately (#410).- Throws:
PluginModuleException- if the binding cannot be resolvedConfigurationException- if a bound value is out of range
-
arm
Scheduleshandle's method with its current timing, first running afterfirstDelayticks. Each call builds a new runnable -- aBukkitRunnablecan be scheduled only once. Always on the main thread: a bound task cannot beasync(ConfigBindings.checkShape(String, Scheduled)), so its run is observed exactly where the reload step also runs. -
rescheduleBound
Appliesplugin's reloaded configuration to its config-boundScheduledtasks. Called byUltiToolsPlugin.reloadSelf(), throughPluginManager.applyReloadedConfigBindings, only after the configuration reload succeeded.For each bound task whose bound value changed:
- it has run before: the next run is last run + new period, or the next tick if that moment has already passed;
- it has not run yet: the first run is arm tick + new delay, or the next tick if that moment has already passed.
< 1second,null, or too large) is not applied: the running value is kept and one WARNING names the module, the key, the rejected value and the value kept.Where "the last run" comes from. A bound task is always sync, so it runs on the main thread, and its last run is observed at the moment it starts -- the same thread this step runs on, so the two cannot race. An
asyncbinding is refused at load instead of predicting when the server dispatches async work: that prediction was wrong for tasks armed at boot (#531 gate-1 round 2, WR-01).Must run on the main thread, where
TaskManager's collections live; from any other thread it throws before reading them (PluginManagerchecks first and logs instead).- Parameters:
plugin- the module whose configuration was just reloaded- Since:
- 6.3.0
-
rescheduleBound
rescheduleBound(UltiToolsPlugin), also recording inreportone partial reason per bound value kept in place of a refused one (#595), so the module's reload is not reported as complete while a task kept its old timing.- Parameters:
plugin- the module whose configuration was just reloadedreport- the module's reload report- Since:
- 6.3.0
-
rescheduleOne
private void rescheduleOne(UltiToolsPlugin plugin, TaskManager.BoundTask handle, ReloadReport report) -
reloadedTicks
private static long reloadedTicks(UltiToolsPlugin plugin, TaskManager.BoundTask handle, ConfigBindings.Source source, long currentTicks, Set<String> warnedKeys, ReloadReport report) - Returns:
- the reloaded value of
sourcein ticks, orcurrentTicksif it is unbound or its new value is out of range (then warned and reported once per key)
-
validateConfigBindings
Load-time check of every config-boundScheduledmethod onbean, over exactly the methodsscanAndSchedule(com.ultikits.ultitools.abstracts.UltiToolsPlugin, java.lang.Object, java.util.function.Consumer<org.bukkit.scheduler.BukkitTask>)would schedule. Used byPluginManagerbefore any Bukkit side effect, so an invalid binding refuses the one module (#531).- Parameters:
module- the module being assembledbean- one of its container singletons- Throws:
PluginModuleException- if a binding cannot be resolvedConfigurationException- if a bound value is out of range- Since:
- 6.3.0
-
firstBoundMethod
- Parameters:
bean- a container singleton- Returns:
Class.methodof the first config-boundScheduledmethod thatscanAndSchedule(com.ultikits.ultitools.abstracts.UltiToolsPlugin, java.lang.Object, java.util.function.Consumer<org.bukkit.scheduler.BukkitTask>)would schedule, ornull- Since:
- 6.3.0
-
refuseConfigBindings
Refuses any config-boundScheduledmethod onbean-- for a container with no module config registry (an external plugin's).- Parameters:
bean- one of the container's singletons- Throws:
PluginModuleException- naming the first bound method found- Since:
- 6.3.0
-
checkSignatureOrWarn
The two signature rulesscanAndSchedule(com.ultikits.ultitools.abstracts.UltiToolsPlugin, java.lang.Object, java.util.function.Consumer<org.bukkit.scheduler.BukkitTask>)skips a method for, each logging the same WARNING it always has.- Returns:
trueifmethodtakes no parameters and returns void
-
isSchedulableSignature
The two signature rulesscanAndSchedule(com.ultikits.ultitools.abstracts.UltiToolsPlugin, java.lang.Object, java.util.function.Consumer<org.bukkit.scheduler.BukkitTask>)skips a method for, without its logging. -
bindingOutsideModule
-
getTargetClass
Get the original class, unwrapping proxies generated byProxyFactory.Delegates entirely to
ProxyFactory.unwrap(Class)- proxy identity is owned by that class, not derived here. A proxy of a proxy already carries aProxyOfmarker naming the original target, so this needs no hierarchy walk and noObject.classfallback:unwrapreturns a non-proxy argument unchanged, which is the same answer the removed fallback was approximating for the common case. -
registerScheduledMethodsExternal
Scan a bean forScheduledmethods and register them for an external plugin.- Parameters:
pluginName- the external plugin name (used as key)bean- the bean instance to scan- Since:
- 6.2.2
-
cancelAllExternal
Cancel all scheduled tasks for an external plugin.- Parameters:
pluginName- the external plugin name- Since:
- 6.2.2
-
getTaskCount
Get the number of registered tasks for a plugin (for testing). -
getCoreTaskCount
int getCoreTaskCount()Get the number of registered framework-owned tasks (for testing).- Since:
- 6.3.0
-
getExternalTaskCount
Get the number of registered tasks for an external plugin (for testing).
-