Class PluginInitiationUtils
As of 6.3.1 the connection speaks UltiCloud protocol v2 only: initWebsocket() starts the current
session's agent connection, and every inbound request is routed by the v2 agent router that
CloudFeatures registers per connection. The protocol v1 inbound dispatch this class used to hold (its
message-type table, the capability gate in front of it, the per-type v1 handlers and the v1 handshake callback)
was removed in 6.3.1 (D-01: no v1 adapter); every public member keeps its 6.3.0 signature (D-29).
- Since:
- 6.0.0
- Author:
- wisdomme
-
Field Summary
FieldsModifier and TypeFieldDescriptionprivate static booleanWhether the framework accepts inbound panel work (UltiTools-Reborn#621).(package private) static final String(package private) static final longHow longstopAcceptingPanelMessages()waits for the state changes already running (a file write, aserver.propertiesupdate, a module responder or player-link handler a request was handed to); each takes milliseconds, so the bound is reached only by a stalled disk or a module handler that does not end.(package private) static final ShutdownGate.Signalprivate static final ObjectGuardspanelChangesRunning;stopAcceptingPanelMessages()waits on it for the count to reach zero (22-23 shutdown-window round).private static intHow many permitsenterPanelChange()gave that were not returned yet.private static final longThe elapsed-time threshold above which aPanelMessageEventpublish is considered slow enough to warn about, in milliseconds. -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionprivate static voidstatic voidCloses the cloud connection and invalidates the current session, putting the reconnection state machine into an explicit disabled state.(package private) static CloudSessiondisableCloud(CloudSession session) Thesession-targeted,CloudSession-returning form ofdisableCloud()(CR-01/WR-02, 16-REVIEW-cloud.md) -- package-private, since it is new-in-6.3.0 and reached only by other members of this package (CloudAuthManager#logout()).private static CloudSessiondoDisableCloud(CloudSession session) The actual teardown performed bydisableCloud()/disableCloud(CloudSession).static voidEnsures the current session is usable, replacing it with a fresh one if it has been invalidated.(package private) static booleanThe shutdown signal of every remote state change (ShutdownGate): takes a permit and returnstrueonly whileisAcceptingPanelMessages()holds; the caller then callsexitPanelChange()exactly once, from any thread, when the change and its action-log entry are done, including work it handed to another thread.(package private) static voidReturns a permitenterPanelChange()gave; may run on a thread other than the one that entered.static voidInitialize websocket on the current session.(package private) static voidinitWebsocket(CloudSession session) Starts the supplied session's v2 connection; a stale or unlinked session does nothing.static booleanWhether inbound panel work may run now: the accepting flag is set, and UltiTools is not being disabled.(package private) static booleanLets a test assert whether the state machine is currently enabled -- i.e.static booleanisFrameworkOwnedType(String messageType) WhethermessageTypeis a message type of the framework's own protocol: the single sourcePanelResponderRegistry.registerResponderconsults before letting a module claim a message type (D-26, WIRE-16, Plan 06-08 Task 1).private static void(package private) static voidpublishPanelMessageEvent(String type, com.google.gson.JsonObject message, com.google.gson.JsonObject data) Bridges a panel message the framework has already handled onto the module-facingEventBus(WIRE-16).static voidRestarts the current session's v2 connection without loading or refreshing a user token.(package private) static voidreinitWebSocket(CloudSession session) static CapabilityresolveFileOperationCapability(String operation) The single fileoperationname toCapabilitymapping, read byFileOperationManager's action-log recording (D-22, Plan 06-04 Task 1).static booleanLoads the saved server credential into the current session without any network I/O.static <T> TscheduleUnlessDisabling(String what, Supplier<T> scheduling) The one check every scheduler hand-off reachable from the WebSocket thread goes through (UltiTools-Reborn#621, swept by class: the inbound event publish, the queued config callbacks, the remote command dispatch, and the monitoring tasks a completing handshake starts).static voidStarts accepting inbound panel work again; called byUltiTools#onEnable.static voidDeprecated, for removal: This API element is subject to removal in a future version.No replacement is needed for non-expiring server credentials; removed in 6.4.0.static voidStops accepting inbound panel work (UltiTools-Reborn#621); called as the very first step ofUltiTools#onDisable.static voidStops device-login polling; the server credential never expires, so no refresh scheduler exists.private static voidStops the protocol v1 server monitor, if one exists; it runs only when a caller started it.static voidStops the v2 connection and invalidates pending login results during plugin shutdown.private static voidteardownStep(String what, Runnable action) Runs one teardown step; a failure is only logged at FINE, never thrown out.
-
Field Details
-
LEGACY_TOKEN_REMOVED_KEY
- See Also:
-
SLOW_PANEL_EVENT_HANDLER_THRESHOLD_MILLIS
private static final long SLOW_PANEL_EVENT_HANDLER_THRESHOLD_MILLISThe elapsed-time threshold above which aPanelMessageEventpublish is considered slow enough to warn about, in milliseconds. Set below one server tick (50ms at the nominal 20 TPS) so a subscriber costing a visible fraction of the tick budget is named before players feel it — this constant is the runtime half of D-24's mitigation;PanelMessageEvent's javadoc is the other half, stating the contract a reader sees before ever hitting this warning at runtime.- See Also:
-
acceptingPanelMessages
private static volatile boolean acceptingPanelMessagesWhether the framework accepts inbound panel work (UltiTools-Reborn#621). Set byUltiTools#onEnableand cleared as the very first step ofUltiTools#onDisable, before any manager shuts down and before the WebSocket is closed. Startstrueso a framework that was never disabled (and every unit test that does not touch it) behaves as before. SeeisAcceptingPanelMessages()for the full condition. -
PANEL_CHANGE_DRAIN_MS
static final long PANEL_CHANGE_DRAIN_MSHow longstopAcceptingPanelMessages()waits for the state changes already running (a file write, aserver.propertiesupdate, a module responder or player-link handler a request was handed to); each takes milliseconds, so the bound is reached only by a stalled disk or a module handler that does not end.- See Also:
-
PANEL_CHANGES
GuardspanelChangesRunning;stopAcceptingPanelMessages()waits on it for the count to reach zero (22-23 shutdown-window round). A count, not a read-write lock, because a permit is returned by whichever thread ends the change: a request handed to a module responder's worker or to the player-link handler thread keeps its permit until that work ends (22-23 second verification). -
panelChangesRunning
private static int panelChangesRunningHow many permitsenterPanelChange()gave that were not returned yet. Guarded byPANEL_CHANGES. -
PANEL_CHANGE_SIGNAL
-
-
Constructor Details
-
PluginInitiationUtils
public PluginInitiationUtils()
-
-
Method Details
-
startAcceptingPanelMessages
@Internal public static void startAcceptingPanelMessages()Starts accepting inbound panel work again; called byUltiTools#onEnable.- Since:
- 6.3.0
-
stopAcceptingPanelMessages
@Internal public static void stopAcceptingPanelMessages()Stops accepting inbound panel work (UltiTools-Reborn#621); called as the very first step ofUltiTools#onDisable. From this moment every scheduler hand-off reachable from the UltiCloud connection's threads is dropped with one FINE line instead of scheduling a task for a plugin that is being disabled.As of 6.3.1 (protocol v2, maintainer decision of 2026-10-26) every request that changes server state -- a command, a power action, a player action, a file write or delete, a
server.propertiesor configuration update, a module request, a credential rotation -- is refused from this moment with a failure reply saying the server is shutting down (a command is acknowledged and answered with a failedcommand.result, so it is never run again after a restart); read-only requests are still answered untilonDisablecloses the connection. Before it returns, this method waits (at most 5000L ms) for every state change that started earlier to finish -- including the work such a request handed to another thread: a module responder running for amodule.request, the player-link handler running for aplayer.link.completed-- so each one, and its action-log entry, is complete beforeonDisablecloses the action log. A request handed to the server thread is not waited for: it runs in a later tick, which never comes once the server stops, and Bukkit cancels the task when UltiTools disables. Such a request accepted before UltiTools' own disable starts -- while the server is already stopping, for example while plugins loaded after UltiTools disable -- is not carried out and gets no result. Must not be called by a thread inside such a change.- Since:
- 6.3.0
-
enterPanelChange
static boolean enterPanelChange()The shutdown signal of every remote state change (ShutdownGate): takes a permit and returnstrueonly whileisAcceptingPanelMessages()holds; the caller then callsexitPanelChange()exactly once, from any thread, when the change and its action-log entry are done, including work it handed to another thread. Returnsfalse, holding nothing, from the first step ofonDisableon. Never blocks for longer than another caller's check.- Returns:
- whether the state change may run
-
exitPanelChange
static void exitPanelChange()Returns a permitenterPanelChange()gave; may run on a thread other than the one that entered. -
awaitPanelChanges
private static void awaitPanelChanges() -
isAcceptingPanelMessages
@Internal public static boolean isAcceptingPanelMessages()Whether inbound panel work may run now: the accepting flag is set, and UltiTools is not being disabled. Bukkit turnsisEnabled()tofalsebefore it callsonDisable(), so the second half covers the moment between the two; Paper refuses every task of a disabled plugin withIllegalPluginAccessException. No instance (a unit test without one) is not "disabling": the hand-off sites below need an instance anyway.- Returns:
truewhen inbound panel work may be dispatched and scheduled- Since:
- 6.3.0
-
scheduleUnlessDisabling
The one check every scheduler hand-off reachable from the WebSocket thread goes through (UltiTools-Reborn#621, swept by class: the inbound event publish, the queued config callbacks, the remote command dispatch, and the monitoring tasks a completing handshake starts). Runsscheduling-- exactly one call into the Bukkit scheduler -- only whileisAcceptingPanelMessages()holds; otherwise, and when the plugin is disabled between that check and the scheduler's own check (the scheduler then throwsIllegalPluginAccessException, the only exception caught here), the work is dropped with one FINE line naming it. Nothing is lost that a stopping server could still use: the scheduler would refuse the task anyway.- Type Parameters:
T- what the scheduler call returns- Parameters:
what- what is being scheduled, for the FINE linescheduling- the scheduler call- Returns:
- what
schedulingreturned, ornullwhen the work was dropped - Since:
- 6.3.0
-
logDroppedWhileDisabling
-
resumeSavedCredentialOnStartup
public static boolean resumeSavedCredentialOnStartup()Loads the saved server credential into the current session without any network I/O. No user token is loaded, refreshed or sent to a legacy registration endpoint.- Returns:
- whether a saved server credential was adopted
-
startTokenRefreshScheduler
Deprecated, for removal: This API element is subject to removal in a future version.No replacement is needed for non-expiring server credentials; removed in 6.4.0.No-op: the protocol v2 server credential never expires and has no refresh token.- Scheduled removal version:
- 6.4.0
-
stopCredentialSchedulers
public static void stopCredentialSchedulers()Stops device-login polling; the server credential never expires, so no refresh scheduler exists. -
initWebsocket
Initialize websocket on the current session.- Throws:
IOException
-
initWebsocket
Starts the supplied session's v2 connection; a stale or unlinked session does nothing.- Throws:
IOException
-
resolveFileOperationCapability
The single fileoperationname toCapabilitymapping, read byFileOperationManager's action-log recording (D-22, Plan 06-04 Task 1). The mapping is unchanged from 6.3.0 and covers the operation names of the protocol v2 file requests (files.read,files.list,files.write,files.delete):writeneedsCapability.FILE_WRITE,deleteneedsCapability.FILE_DELETE, andread,list(listing is reading) and an unrecognised or absent operation resolve toCapability.FILE_READ, the most permitted of the three, so an unknown verb is still gated.- Parameters:
operation- theoperationfield's value, possiblynull- Returns:
- the required capability
-
isFrameworkOwnedType
WhethermessageTypeis a message type of the framework's own protocol: the single sourcePanelResponderRegistry.registerResponderconsults before letting a module claim a message type (D-26, WIRE-16, Plan 06-08 Task 1).As of 6.3.1 the answer comes from the UltiCloud protocol v2 message registry (the frozen 2.0.0 contract's message table, for example
command.execute,files.readormodule.request); a module's responder key (<module>.<action>, for exampleultimail.inbox.list) is not one of them, and neither is a removed protocol v1 type name. ExactStringmembership: no case folding, no Unicode normalization.- Parameters:
messageType- the message type to check, possiblynull- Returns:
trueif the framework's own protocol defines this exact type
-
publishPanelMessageEvent
static void publishPanelMessageEvent(String type, com.google.gson.JsonObject message, com.google.gson.JsonObject data) Bridges a panel message the framework has already handled onto the module-facingEventBus(WIRE-16). This is the single publish site, invoked from the UltiCloud protocol v2 module bridge (ModuleRequestHandler), which publishes one event per module-owned request. It is kept becausePanelMessageEventis public since 6.3.0 and a module subscribing to observe its own panel traffic keeps receiving it now that the protocol v1 inbound path is removed (6.3.1).EventBus.publishAsync(com.ultikits.ultitools.events.ModuleEvent)was considered and rejected: it submits to an async worker pool and never reaches the main thread, so it does not address Paper's AsyncCatcher at all — it only keeps the WebSocket I/O thread unblocked. A Minecraft module's handler touches Bukkit API by definition, so the real choice here was main-thread versus not-main-thread, not sync-dispatch versus async-dispatch; onlyBukkit.getScheduler().runTask(...)puts a handler on the main thread. The whole helper body is wrapped in a catch so a missing scheduler (no Bukkit server booted, as in a plain unit test) or a missingEventBuscan never break the inbound message path — both are logged no-ops.- Parameters:
type- the resolved message typemessage- the full inbound envelopedata- the message'sdataobject, possiblynull
-
reinitWebSocket
public static void reinitWebSocket()Restarts the current session's v2 connection without loading or refreshing a user token. -
reinitWebSocket
-
disableCloud
public static void disableCloud()Closes the cloud connection and invalidates the current session, putting the reconnection state machine into an explicit disabled state.Called by
/ulticloud logout. The difference fromstopWebsocket()is that the latter only disconnects the current client, and the reconnection chain will bring it back up; this method invalidatesCloudSession.current()first, soreinitWebSocket(CloudSession)returns immediately afterward and the state machine does not resurrect itself.Also strips the log handler and the transport thread off the root logger (as of plan 16-10, WR-01, this now happens INSIDE
CloudSession.invalidate()rather than as a separate, unlocked step before it -- see that method's own javadoc), and stops the server monitor and the player-event listener — all part of what makes the statement "cloud features are disabled" true. The teardown synchronizes on the session instance itself (CloudSession.invalidate()); see the two review rounds on PR #264 for the original race the former global lifecycle lock guarded.CR-01 (16-REVIEW-cloud.md):
disableCloud(CloudSession)-- the overload internal callers use -- returns the exact session that overload tore down, captured by the caller before any teardown step runs.CloudAuthManager.logout()reads its disk-clear decision off that return value rather than a second, independent call toCloudSession.current()taken after teardown returns -- eliminating the specific race where a concurrentlogin()installs a new session in the gap between those two reads (seeCloudAuthManager#logout()'s own javadoc for the full account). This particular overload keeps its originalvoidreturn --disableCloud()waspublicin the 6.2.5 release, and changing a released public method's return type is a genuine binary incompatibility japicmp correctly rejects; there is no same-release exception available for a signature that already shipped. -
disableCloud
Thesession-targeted,CloudSession-returning form ofdisableCloud()(CR-01/WR-02, 16-REVIEW-cloud.md) -- package-private, since it is new-in-6.3.0 and reached only by other members of this package (CloudAuthManager#logout()).It acts on the session its caller names, rather than on whatever
CloudSession.current()happens to be when it runs, as the no-argdisableCloud()does: a fresh login racing in would otherwise let a teardown meant for an old session tear down an unrelated, brand-new one. (The protocol v1 reconnect loop's budget-exhaustion branch, its other 6.3.0 caller, was removed in 6.3.1.)CloudAuthManager#logout()calls this withCloudSession.current()captured at its own call site -- immediately before teardown begins -- and acts on the returned reference afterward rather than re-readingCloudSession.current()a second, independent time.- Parameters:
session- the session to invalidate and tear down- Returns:
session, unchanged -- returned so the caller can act on exactly what was torn down without a second read ofCloudSession.current()
-
doDisableCloud
The actual teardown performed bydisableCloud()/disableCloud(CloudSession).sessionis the caller's own choice of target, captured before this method does anything else --CloudSession.invalidate()below acts on exactly this reference, never a fresh read ofCloudSession.current()taken partway through teardown (CR-01).Round-1 external review finding, second pass (16-10, PR #464): the two steps below act on GLOBAL, session-independent singletons (exactly one server monitor and one player-event manager for the whole plugin, not one per session) -- but "session-independent" does not mean "safe to run unconditionally". If
sessionhas already been superseded by a newer one by the time this runs (the same narrow window WR-02 already accounts for on the invalidation half), and that newer session's own handshake has already wired these same managers up, an unconditional stop here would tear down wiring that belongs to the session that is actually current now -- with nothing left to ever restart it, since no further handshake is coming. Gating both steps onsessionstill being current closes that: a stale teardown call for an already-superseded session leaves whatever the current session has wired alone.- Parameters:
session- the session to invalidate- Returns:
session
-
stopV1Monitor
private static void stopV1Monitor()Stops the protocol v1 server monitor, if one exists; it runs only when a caller started it. -
teardownStep
Runs one teardown step; a failure is only logged at FINE, never thrown out.Every teardown step must run to the best of its ability: any step throwing would skip every step after it, and those steps are exactly what makes the statement "cloud features are disabled" true. This used to be six identical try/catch blocks; extracting it only states that invariant once — the behaviour is unchanged.
- Parameters:
what- the action description written to the log on failureaction- the teardown action
-
enableCloud
public static void enableCloud()Ensures the current session is usable, replacing it with a fresh one if it has been invalidated.Only an explicit action should call this: cloud login at server startup, and after a successful
/ulticloud login.initWebsocket()deliberately does not call it — it is also reused byreinitWebSocket(), and calling it there would let an in-flight reconnection resurrect a state machine that had just been turned off by logout.As of 16-08 Task 2 there is no separate enabled flag to flip: "enabled" simply means
CloudSession.current()is not invalidated. If it already is not (the common case -- most callers reach this with a perfectly good session already installed), the session itself is not replaced; doing so for no reason would gratuitously drop its credential and its connection. Only a session thatCloudSession.invalidate()already ran on (typicallydisableCloud(), without a following/ulticloud login) gets replaced. (The protocol v1 reconnect budget this method also reset in 6.3.0 was removed in 6.3.1: the protocol v2 agent connection keeps its own backoff.) -
isCloudEnabled
static boolean isCloudEnabled()Lets a test assert whether the state machine is currently enabled -- i.e. whether the current session is still current. -
stopWebsocket
public static void stopWebsocket()Stops the v2 connection and invalidates pending login results during plugin shutdown.The pending reliable-delivery state is written before this returns:
invalidate()flushes the outbox and the command-dedupe set (D-15), so a stop persists what the 30 s tick has not written yet.
-