Class CloudSession
A session owns the protocol v2 server credential, the device-login poll it started and its agent
connection, which keeps its own reconnect backoff (D-16). login constructs a new
session via startNew(); logout replaces the current one the same way. Everything
the replaced session started -- an in-flight poll, a reconnect attempt --
lapses on its own once invalidate() has run, because commitCredential(ServerCredential)
and every gate this class exposes check this specific instance's invalidated flag,
never a shared counter another session's activity could also advance. (The 6.3.0 user token, its
store and its refresh scheduler were removed in 6.3.1, plan 22-24: protocol v2 never sends it.)
This replaces the credential-generation convention CloudAuthManager used before 6.3.0.
That convention's own javadoc stated the invariant this class now enforces, and is worth quoting
verbatim because the reason has not changed, only the mechanism that satisfies it:
cancellation is not invalidation.A generation counter can only tell a late result that it is late; it cannot stop the scheduler, the WebSocket client, or the panel-manager wiring an old login started from outliving the credential that authorised them. Session identity can, and does: invalidating a session tears down everything it owns in one call, so no separate teardown-ordering discipline is needed for any of the five timing windows issue #298 named.stopTokenRefreshScheduler()andstopPolling()both usecancel(false)plusshutdown(), and both only promise not to schedule a new execution -- neither constrains a task that has already entered an HTTP request. [...] every in-flight asynchronous credential operation records the generation it saw when it started, and compares against it [...] before committing its result; the teardown path [...] advances the generation, so any late-arriving result is discarded unconditionally.
Global lock order (16-10 gap-closure addendum, issues #465/#466). The locks involved
across this class, CloudAuthManager and PluginInitiationUtils, and every method
that takes more than one of them must acquire them in this fixed order -- outer to inner:
CloudAuthManager.class-- held for the duration ofCloudAuthManager.logout(),CloudAuthManager.logoutReporting(java.util.function.Consumer)andCloudAuthManager.reconnect()(all three aresynchronizedmethods). They DO nest it with the locks below, in this list's order: logout's and logoutReporting's ownsynchronized (CloudSession.class)block callsinvalidate()on the session monitor inside it, and reconnect takes the session monitor insideCloudSession#reconnectNow(). No path in this package acquires them the other way round.CloudSession.class-- held bystartNew()and byCloudAuthManager.login(com.ultikits.ultitools.utils.CloudAuthManager.DeviceLoginListener)(16-10 gap-closure addendum, issue #466) for its own re-check-through-replacement region.- a specific session's own monitor (
synchronized (this)/synchronized(session)) -- held byinvalidate(),stopPolling(), (protocol v2, plan 22-01)commitCredential(ServerCredential),beginDeviceLogin(com.ultikits.ultitools.utils.CloudAuthManager.DeviceLoginListener),startDevicePolling(com.ultikits.ultitools.utils.DeviceAuthorizationClient.Grant, com.ultikits.ultitools.utils.CloudAuthManager.DeviceLoginListener)and the device-step scheduling they share, and by every caller inPluginInitiationUtilsthat needs to read-then-act on a session's currency (reinitWebSocket's second confirmation,doDisableCloud's two global-manager steps). CloudStateStore's JVM-wide write lock (plan 22-07) -- taken by every load, flush and detach ofcloud-outbox.json, always below the session monitor:invalidate()'s and the shutdown flush's write cannot interleave with the 30 s task's, which holds only this lock and never the session monitor. Its inner locks are theCloudOutboxandCommandDedupeSetmonitors, which are leaves.- The
AgentConnectionmonitor andCredentialStore.LOCK, taken byServerCredentialStore, are siblings: never nested in one another, always below the session monitor. AgentConnection never calls out while holding its monitor. Its scheduler callbacks take the session monitor first and reject invalidated or replaced sessions and replaced connections before acting. - (plan 22-20) The private lock of the connection's
AgentProtocol-- below the session monitor (taken inside it byofferReliableEvent(java.lang.String, com.google.gson.JsonObject)andstopConnection()) and above theAgentConnectionmonitor and theCloudOutboxandCommandDedupeSetleaf monitors, which it takes while handing a frame to the socket or applying the welcome's limits. It is never held while calling a listener or this session, so the inbound path -- which reads the current protocol under the session monitor, then releases it beforeAgentProtocol.onText(java.lang.String)-- cannot invert it.
AgentConnection.stop() and
executor shutdown(). It never waits for the agent thread (no awaitTermination, Future.get,
join, connectBlocking or closeBlocking): that thread may itself be waiting for the session monitor.
startNew()'s own order -- CloudSession.class outer, the replaced session's
monitor inner -- is the anchor every other method's order is chosen to match; inverting it
anywhere (acquiring a session monitor first, then trying to enter a synchronized
(CloudSession.class) block while still holding it) is exactly the AB-BA deadlock issue #466
fixed. No method in this package acquires a session monitor and then blocks trying to acquire
CloudSession.class it does not already hold. commitCredential(ServerCredential)
writes through ServerCredentialStore, so CredentialStore's own lock is taken inside
the session monitor -- never the other way round.
Protocol v2 device login (plan 22-01). A session also owns one RFC 8628 device
authorization: beginDeviceLogin(com.ultikits.ultitools.utils.CloudAuthManager.DeviceLoginListener) requests the device code and startDevicePolling(com.ultikits.ultitools.utils.DeviceAuthorizationClient.Grant, com.ultikits.ultitools.utils.CloudAuthManager.DeviceLoginListener)
polls for the owner's confirmation, both as steps on this session's own poll executor, so a
replaced or logged-out session's late steps lapse by the same invalidated check
commitCredential(ServerCredential) uses. The granted ucs_ credential is persisted only through
commitCredential(ServerCredential).
- Since:
- 6.3.0
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionprivate static final classThe mutable state of one device-code poll; touched only by its poll steps, one at a time.(package private) static interfaceSchedules one device-login step; the test seam ofsetDeviceSeamsForTesting(com.ultikits.ultitools.utils.DeviceAuthorizationClient, com.ultikits.ultitools.utils.CloudSession.DeviceStepScheduler, java.util.function.LongSupplier).private final classprivate final classOne 30 s tick (D-15). -
Field Summary
FieldsModifier and TypeFieldDescriptionprivate LongSupplierprivate static LongSupplierprivate AgentConnectionprivate static URIprivate ScheduledExecutorServiceprivate static Supplier<ScheduledExecutorService> The listener ofagentConnection, so a rotation can re-point the credential its 4401 deletes.private static ReconnectPolicyprivate AgentProtocolThe protocol layer of the current connection's open attempt (plan 22-20), ornullbetween attempts.private static AgentConnection.SocketFactoryprivate static AgentConnection.Timingprivate CloudOutboxThe bounded buffer of unacknowledged reliable events of this session's credential, ornullwhile no credential is adopted (or after the identity was discarded).private CloudStateStoreThe state file of this session's credential, ornull.private StringThe server id whose buffered state the three fields above hold, ornull.private CommandDedupeSetThe recently seen command ids of this session's credential, ornull.private ServerCredentialThe protocol v2 server credential this session holds, ornull-- the one owner of the current server credential (plan 22-21 fix).private static CloudSessionThe current session.private static final longAdded to the device poll interval for eachslow_downanswer (RFC 8628 section 3.5).private final DeviceAuthorizationClientThe REST client of the device authorization grant.private static DeviceAuthorizationClientTest seam: replaces the device-login REST client of every session;null= default.private static LongSupplierTest seam: replaces the device-login clock of every session;null= wall clock.private static CloudSession.DeviceStepSchedulerTest seam: replaces the device-step scheduler of every session;null= poll executor.private booleanprivate SessionDedupeLedger.TargetThe id set, state file and server id above as one reference (plan 22-21), so the sharedSessionDedupeLedgernever sees one session's set with another identity's file.private booleantrueifinvalidate()found -- and cancelled -- a pending authentication still in flight on this session (as of 6.3.1 the device-code request or poll; a magic-link poll in 6.3.0), and that fact has not yet been reported and reset byconsumePendingCancellation().private ScheduledExecutorServiceprivate ScheduledFuture<?> (package private) static final StringThe language-catalogue key of the one SEVERE line printed when the gateway's welcome names a different server id than the linked one (contract answer 21, SEMANTICS 16).private static final longHow often the session persists its reliable-delivery state (D-15): the outbox and the command-dedupe set are written to.ultikits/cloud-outbox.jsononly when one of them changed since the last write, but the change itself is observed on this cadence.private booleanWhether the current state-write failure streak already logged its one WARNING (T-22-31).private ScheduledFuture<?> The pending 30 s state-flush task of this session's connection, ornull. -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionprivate static voidaddIf(List<String> names, Capability capability, String... contractNames) private voidadoptCloudState(ServerCredential adopted) Creates and loads this session's reliable-delivery state foradopted(D-15).(package private) AgentProtocol(package private) voidRequests a device code on this session's poll executor and, once UltiCloud answers, reports it throughlistenerand starts polling.private void(package private) CloudOutbox(package private) CloudStateStore(package private) CommandDedupeSet(package private) booleancommitCredential(ServerCredential newCredential) PersistsnewCredentialthroughServerCredentialStore, but only if this session has not been invalidated and is still current, so a grant that arrives after/ulticloud logoutor after a newer login replaced this session is discarded and never written.(package private) voidcompleteCredentialLogout(ServerCredential retiring) private voidcompleteDeviceLogin(CloudSession.DevicePoll poll, ServerCredential granted) (package private) String(package private) AgentConnection.State(package private) boolean(package private) static CloudSessioncurrent()The session everyCloudAuthManager/PluginInitiationUtilsstatic facade method currently delegates to.private DeviceAuthorizationClient(package private) voidForgets this session's reliable-delivery state for good (S-12, S-32): the 30 s task is cancelled, the store is detached and its file deleted under the store's write lock, and the in-memory buffer and command-id set are cleared.private voidfinishDeviceLogin(CloudSession.DevicePoll poll, DeviceAuthorizationClient.PollResult.Kind kind, String code, String message) Stops polling and reports a terminal outcome other than a grant, if this session is current.(package private) voidWrites the pending outbox and command-dedupe changes, if there are any.private voidflushStateQuietly(CloudStateStore store, String serverId) One state write; never throws.The hello's capabilities from the operator'sCapabilityswitches (S-65, without the error-report and module entries 22-21 adds):powerandlinkare always declared,backupsnever.(package private) static com.google.gson.JsonObject(package private) ServerCredential(package private) boolean(package private) boolean(package private) voidMarks this session invalid and tears down everything it owns: the log stream manager (as of plan 16-10, WR-01), the device-login poll, the pending reliable-delivery state (D-15) and the agent connection.(package private) boolean(package private) SessionDedupeLedger.Target(package private) booleanLoads framework-owned state without rewriting it or performing any network request.(package private) voidTest-only: marks this session invalidated without touching the poller or the connection -- the flag-setting half ofinvalidate(), isolated so a test can establish "logout has been decided" as a precondition and then permute the mechanical teardown actions (stopPolling(),stopConnection()) in any order afterward, proving their relative order does not change the outcome (D-16/D-18, issue #298's fourth acceptance criterion).private AgentProtocolnewProtocol(AgentConnection connection) Builds the protocol layer of one upgraded attempt ofconnection(plan 22-20) and, while the plugin'sCloudFeaturesare installed, registers every inbound handler on it (plan 22-21): the hello declares what the operator's switches allow (AgentCapabilities, S-65), reports the operator settings, and the router re-checks the switch of every request (S-66).private static longnow()(package private) voidofferReliableEvent(String type, com.google.gson.JsonObject payload) Adds one reliable event to this session's outbox, if it can be captured at all (D-14, S-16): a session that holds no credential returns silently, so an unlinked server writes no state file, and so does one whose operator turnedultipanel.capabilities.player-eventsoff.private voidOne poll step.(package private) CloudAuthManager.ReconnectResult.Kindprivate voidprivate static StringrequestSuffix(String requestId) (package private) static voidTest-only: replacescurrent()with a brand-new, non-invalidated session, without invalidating whatever was current before.restartForManualReconnect(AgentConnection captured) private longretryDelayUnits(long unitMillis) (package private) booleanrotateCredential(String secret, String credentialId) Rotates this session's credential (plan 22-21, D-10): thecredential.rotatepersister of every connection this session owns.private booleanscheduleDeviceStep(Runnable step, long delayMillis) Schedules one device-login step on this session's poll executor, creating it on first use.private voidArms the state cadence from whatever this session currently holds; called once per connection, since the flush task lives on the connection's own executor.private voidscheduleStateFlush(CloudStateStore store, String serverId, ScheduledExecutorService executor) (package private) static voidsetAgentEndpointOverrideForTesting(URI endpoint) (package private) static voidsetAgentRuntimeForTesting(Supplier<ScheduledExecutorService> executors, AgentConnection.SocketFactory sockets, LongSupplier clock) (package private) static voidsetAgentTimingOverrideForTesting(ReconnectPolicy policy, AgentConnection.Timing timing) (package private) static voidsetDeviceSeamsForTesting(DeviceAuthorizationClient client, CloudSession.DeviceStepScheduler scheduler, LongSupplier clock) Test-only: replaces the device-login REST client, step scheduler and clock for every session, so polling arithmetic is tested without sleeping.private voidShuts down the framework's log stream manager, if one is configured.(package private) voidStarts this session's single connection owner; repeated calls never restart a terminal state.(package private) voidstartDevicePolling(DeviceAuthorizationClient.Grant grant, CloudAuthManager.DeviceLoginListener listener) PollsPOST /v1/device/tokenforgranton this session's poll executor until it reaches a terminal outcome.(package private) static CloudSessionstartNew()Constructs a new session, installs it ascurrent(), and invalidates whatever session was current before -- the single operation bothloginandlogoutperform (D-16).(package private) voidStops and detaches the connection without ever waiting for its scheduler thread.(package private) voidStops this session's device-login steps (the request and the polling) and their executor.
-
Field Details
-
STATE_FLUSH_INTERVAL_MS
private static final long STATE_FLUSH_INTERVAL_MSHow often the session persists its reliable-delivery state (D-15): the outbox and the command-dedupe set are written to.ultikits/cloud-outbox.jsononly when one of them changed since the last write, but the change itself is observed on this cadence.- See Also:
-
SERVER_ID_MISMATCH_KEY
The language-catalogue key of the one SEVERE line printed when the gateway's welcome names a different server id than the linked one (contract answer 21, SEMANTICS 16).- See Also:
-
DEVICE_SLOW_DOWN_INCREMENT_MS
private static final long DEVICE_SLOW_DOWN_INCREMENT_MSAdded to the device poll interval for eachslow_downanswer (RFC 8628 section 3.5).- See Also:
-
deviceClientOverride
Test seam: replaces the device-login REST client of every session;null= default. -
deviceSchedulerOverride
Test seam: replaces the device-step scheduler of every session;null= poll executor. -
deviceClockOverride
Test seam: replaces the device-login clock of every session;null= wall clock. -
current
The current session. Nevernull-- initialised eagerly so every static facade method onCloudAuthManageralways has something to delegate to, even before any login has ever happened. -
invalidated
private volatile boolean invalidated -
pendingAuthenticationCancelled
private volatile boolean pendingAuthenticationCancelledtrueifinvalidate()found -- and cancelled -- a pending authentication still in flight on this session (as of 6.3.1 the device-code request or poll; a magic-link poll in 6.3.0), and that fact has not yet been reported and reset byconsumePendingCancellation().Real-machine UAT fix, plan 16-08 (
ultitools.ulticloud.logout.neg-mid-poll). Cancelling a pending authentication attempt IS a real logout, even though it never committed a credential. Without this field,/ulticloud logoutrun while a login's poll is still running reported "Not currently logged in to UltiCloud." even though it genuinely tore the poll down -- observed on a real Paper 1.21.11 server. The safety half of this row (a stale approval landing after this logout must not reactivate the server) was never in question:commitCredential(ServerCredential)'s owninvalidatedcheck rejects it regardless of this field. Only the reported OUTCOME of the logout that cancelled the poll was wrong.Captured in
invalidate(), immediately beforestopPolling()clearspollTask-- readingpollTaskany later would always seenulland this field could never becometrue. Reset byconsumePendingCancellation(), so a second, independentlogout()call on this same (already-invalidated) session instance -- e.g. a second/ulticloud logoutbefore any new login replaces it -- does not keep reporting success for a poll this session already reported cancelling once. -
credential
The protocol v2 server credential this session holds, ornull-- the one owner of the current server credential (plan 22-21 fix). Every change of the storedcloud_credentialentry is made under this session's monitor together with this field:loadServerCredential()(adopt from disk),commitCredential(ServerCredential)(device login),rotateCredential(String, String)(credential.rotate, disk first),completeCredentialLogout(ServerCredential)and the listener's rejection path (delete-if-matches, then cleared). Every reader in the session -- the bearer of each attempt, the protocol's server id, the linked checks -- andCloudAuthManager's status, login and logout read this field; they read the file only while the session holds no credential. -
cloudOutbox
The bounded buffer of unacknowledged reliable events of this session's credential, ornullwhile no credential is adopted (or after the identity was discarded). Written only while holding this session's monitor; volatile because the 30 s flush task on the agent thread reads it without taking that monitor. -
commandDedupe
The recently seen command ids of this session's credential, ornull. -
cloudState
The state file of this session's credential, ornull. -
cloudStateServerId
The server id whose buffered state the three fields above hold, ornull. -
ledgerTarget
The id set, state file and server id above as one reference (plan 22-21), so the sharedSessionDedupeLedgernever sees one session's set with another identity's file. Set and cleared with them, under this session's monitor; read without it. -
stateFlushTask
The pending 30 s state-flush task of this session's connection, ornull. -
stateFlushFailureLogged
private volatile boolean stateFlushFailureLoggedWhether the current state-write failure streak already logged its one WARNING (T-22-31). -
agentConnection
-
agentListener
The listener ofagentConnection, so a rotation can re-point the credential its 4401 deletes. -
agentProtocol
The protocol layer of the current connection's open attempt (plan 22-20), ornullbetween attempts. One per upgraded socket: created when the upgrade is accepted, closed when the connection leaves OPEN. Written under this session's monitor. -
agentExecutor
-
agentClock
-
agentEndpointOverride
-
agentPolicyOverride
-
agentTimingOverride
-
agentExecutorOverride
-
agentSocketsOverride
-
agentClockOverride
-
deviceClient
The REST client of the device authorization grant. -
pollExecutor
-
pollTask
-
-
Constructor Details
-
CloudSession
CloudSession()
-
-
Method Details
-
current
The session everyCloudAuthManager/PluginInitiationUtilsstatic facade method currently delegates to.- Returns:
- the current session, never
null
-
startNew
Constructs a new session, installs it ascurrent(), and invalidates whatever session was current before -- the single operation bothloginandlogoutperform (D-16). Invalidating the previous session happens after it has been replaced ascurrent(), but that ordering is not itself load-bearing:invalidate()'s own gate is this specific instance'sinvalidatedflag, not a comparison againstcurrent(), so a caller already holding a reference to the old session sees it become invalid regardless of when the swap becomes visible to a third reader.- Returns:
- the new, now-current session
-
resetForTesting
static void resetForTesting()Test-only: replacescurrent()with a brand-new, non-invalidated session, without invalidating whatever was current before. Production code has no equivalent -- every real replacement goes throughstartNew(), which does invalidate the session it replaces. Exists only because this class's staticcurrentfield is JVM-wide and this module's Surefire configuration runs with noforkCount(issue #250), so a test suite that leaves a stale, already-invalidated session installed as current would leak that state into every test class that runs afterward in the same JVM. -
invalidate
void invalidate()Marks this session invalid and tears down everything it owns: the log stream manager (as of plan 16-10, WR-01), the device-login poll, the pending reliable-delivery state (D-15) and the agent connection. Safe to call on a session that never started any of those and safe to call more than once -- both are exercised directly by this class's own tests, independently of the staticcurrent()holder.Real-machine UAT fix, plan 16-08:
pendingAuthenticationCancelledis captured here -- readingpollTaskBEFOREstopPolling()below clears it -- sohasAnythingToClear()can report that this call genuinely cancelled a real, in-flight authentication attempt even though no credential was ever committed. See that field's own javadoc for the full account. -
shutdownLogStreamManager
private void shutdownLogStreamManager()Shuts down the framework's log stream manager, if one is configured. Extracted soinvalidate()reads as a flat list of teardown steps, and tolerant of anullmanager (unmocked in the majority of this package's tests): no manager is simply nothing to tear down. As of 6.3.1 no framework path wires the log stream manager (its protocol v1 wiring was removed, plan 22-24); the shutdown still runs so a module that wired it through the kept public API is detached on logout as in 6.3.0. -
markInvalidatedForTesting
void markInvalidatedForTesting()Test-only: marks this session invalidated without touching the poller or the connection -- the flag-setting half ofinvalidate(), isolated so a test can establish "logout has been decided" as a precondition and then permute the mechanical teardown actions (stopPolling(),stopConnection()) in any order afterward, proving their relative order does not change the outcome (D-16/D-18, issue #298's fourth acceptance criterion). Production code never calls this in isolation;invalidate()always sets the same flag together with the cleanup, in one atomic call. -
isCurrent
boolean isCurrent()- Returns:
trueif this session has not been invalidated
-
hasAnythingToClear
boolean hasAnythingToClear()- Returns:
trueif this session's teardown is responsible for something: this session's servercredential, or (real-machine UAT fix, plan 16-08) a device-login poll that was still in flight and just got cancelled byinvalidate()(seependingAuthenticationCancelled's own javadoc).falseonly if both are absent.CloudAuthManager#logout()combines this with the credential stored incredentials.json, which it reads when the session holds none.
-
consumePendingCancellation
boolean consumePendingCancellation() -
completeCredentialLogout
-
stopPolling
void stopPolling()Stops this session's device-login steps (the request and the polling) and their executor. -
loadServerCredential
boolean loadServerCredential()Loads framework-owned state without rewriting it or performing any network request.Adopting the credential also adopts its reliable-delivery state (D-15): the outbox and the command-dedupe set are created and loaded from
.ultikits/cloud-outbox.jsonhere, so an event captured before a restart is still in the buffer afterwards. -
startConnection
void startConnection()Starts this session's single connection owner; repeated calls never restart a terminal state. -
stopConnection
void stopConnection()Stops and detaches the connection without ever waiting for its scheduler thread. -
offerReliableEvent
Adds one reliable event to this session's outbox, if it can be captured at all (D-14, S-16): a session that holds no credential returns silently, so an unlinked server writes no state file, and so does one whose operator turnedultipanel.capabilities.player-eventsoff. The connection's own state is deliberately not consulted: buffering while disconnected is the point of the outbox.- Parameters:
type- the envelope type, for exampleplayer.joinpayload- the event payload
-
agentProtocol
AgentProtocol agentProtocol()- Returns:
- the protocol layer of the current connection's open attempt, or
nullbetween attempts (plan 22-20; 22-21 registers the track handlers on its router)
-
newProtocol
Builds the protocol layer of one upgraded attempt ofconnection(plan 22-20) and, while the plugin'sCloudFeaturesare installed, registers every inbound handler on it (plan 22-21): the hello declares what the operator's switches allow (AgentCapabilities, S-65), reports the operator settings, and the router re-checks the switch of every request (S-66). Without installed features (a session driven by a test alone) the hello declares the framework capabilities offrameworkCapabilities(), reports power actions as off, and the operator gate admits every capability the welcome accepted.- Parameters:
connection- the attempt's connection- Returns:
- the protocol, not yet opened
-
frameworkCapabilities
The hello's capabilities from the operator'sCapabilityswitches (S-65, without the error-report and module entries 22-21 adds):powerandlinkare always declared,backupsnever.- Returns:
- the capability names, in a stable order
-
addIf
-
frameworkSettings
static com.google.gson.JsonObject frameworkSettings()- Returns:
- the hello's operator settings until 22-21: power actions reported as off
-
cloudOutbox
CloudOutbox cloudOutbox()- Returns:
- this session's outbox, or
nullwhile no credential is adopted (plan 22-08 sends and acknowledges its entries)
-
commandDedupe
CommandDedupeSet commandDedupe()- Returns:
- this session's command-dedupe set, or
nullwhile no credential is adopted (plan 22-09 applies it to inbound command messages)
-
cloudStateStore
CloudStateStore cloudStateStore()- Returns:
- this session's state file writer, or
nullwhile no credential is adopted
-
ledgerTarget
SessionDedupeLedger.Target ledgerTarget()- Returns:
- this session's id set, state file and server id as one value for the shared ledger, or
nullwhile no credential is adopted (plan 22-21)
-
flushCloudState
void flushCloudState()Writes the pending outbox and command-dedupe changes, if there are any. Called frominvalidate()-- whichPluginInitiationUtils#stopWebsocket()runs at plugin shutdown -- so a stop persists what a 30 s tick has not yet written (D-15). A failure is logged and swallowed: teardown must not be blocked by an unwritable state file. -
discardCloudState
void discardCloudState()Forgets this session's reliable-delivery state for good (S-12, S-32): the 30 s task is cancelled, the store is detached and its file deleted under the store's write lock, and the in-memory buffer and command-id set are cleared. Every later flush of that store is a no-op, so neither the tick, norinvalidate(), nor the shutdown flush can bringcloud-outbox.jsonback for an identity that no longer exists. A new credential adopted afterwards starts from a fresh store and an empty file. -
adoptCloudState
Creates and loads this session's reliable-delivery state foradopted(D-15). The caller holds this session's monitor. A state file written by hand for another identity is discarded byCloudStateStore.load(String), and an unresolvable state location leaves the session without persistence rather than failing the login.- Parameters:
adopted- the credential this session just adopted
-
scheduleStateFlush
private void scheduleStateFlush()Arms the state cadence from whatever this session currently holds; called once per connection, since the flush task lives on the connection's own executor. -
scheduleStateFlush
private void scheduleStateFlush(CloudStateStore store, String serverId, ScheduledExecutorService executor) -
cancelStateFlushTask
private void cancelStateFlushTask() -
flushStateQuietly
One state write; never throws. A failed write keeps the store's dirty flags set, so the next tick retries, and only the first failure of a streak is reported (T-22-31).- Parameters:
store- the store to writeserverId- the linked server's id
-
reconnectNow
CloudAuthManager.ReconnectResult.Kind reconnectNow() -
restartForManualReconnect
-
connectionState
AgentConnection.State connectionState() -
connectionDescription
String connectionDescription() -
retryDelayUnits
private long retryDelayUnits(long unitMillis) -
setAgentEndpointOverrideForTesting
-
setAgentTimingOverrideForTesting
-
setAgentRuntimeForTesting
static void setAgentRuntimeForTesting(Supplier<ScheduledExecutorService> executors, AgentConnection.SocketFactory sockets, LongSupplier clock) -
requestSuffix
-
hasCredential
boolean hasCredential()- Returns:
trueif this session committed a server credential
-
getCredential
ServerCredential getCredential()- Returns:
- the server credential this session committed, or
null
-
commitCredential
PersistsnewCredentialthroughServerCredentialStore, but only if this session has not been invalidated and is still current, so a grant that arrives after/ulticloud logoutor after a newer login replaced this session is discarded and never written.- Parameters:
newCredential- the credential the device authorization granted- Returns:
trueif committed;falseif this session was already invalidated, in which case nothing was written- Throws:
DataAccessException- if the write fails
-
rotateCredential
Rotates this session's credential (plan 22-21, D-10): thecredential.rotatepersister of every connection this session owns. Under this session's monitor,ServerCredentialStore.replace(java.lang.String, java.lang.String, java.lang.String)first makes the new secret durable (one atomic, owner-only write) and only then iscredentialreplaced, so the in-memory credential and the file change in one step and the caller acknowledges only a durable rotation. The next connection attempt (readBearer) and/ulticloud logoutboth readcredential, so neither can use the retired secret. Nothing is written, and the gateway gets no acknowledgement, when this session was invalidated or replaced, holds no credential, or the file holds another server identity.- Parameters:
secret- the newucs_secretcredentialId- the new credential's public id- Returns:
truewhen the new credential is durable and held- Throws:
DataAccessException- if the write fails; the old credential then stays in place in memory and on disk
-
beginDeviceLogin
Requests a device code on this session's poll executor and, once UltiCloud answers, reports it throughlistenerand starts polling. Schedules nothing on an invalidated session.- Parameters:
listener- receives the code or the failure; called on the poll executor's thread
-
requestDeviceCode
-
startDevicePolling
void startDevicePolling(DeviceAuthorizationClient.Grant grant, CloudAuthManager.DeviceLoginListener listener) PollsPOST /v1/device/tokenforgranton this session's poll executor until it reaches a terminal outcome. Schedules nothing on an invalidated session: a late call after/ulticloud logout-- the device-code request returned after the logout -- creates no executor (the round-1 review property the 6.3.0 magic-linkstartPollingcarried).- Parameters:
grant- the device code to polllistener- receives the outcome; called on the poll executor's thread
-
scheduleDeviceStep
Schedules one device-login step on this session's poll executor, creating it on first use. The step becomespollTask, soinvalidate()both cancels it and records that a pending authentication was cancelled.- Returns:
falseif this session is invalidated and nothing was scheduled
-
pollDeviceOnce
One poll step. RFC 8628 section 3.5 semantics:authorization_pendingpolls again after the interval;slow_downfirst adds 5000L ms to it, cumulatively; a transient failure (network, 5xx, 429, unreadable answer) polls again after the interval, or aftermax(interval, Retry-After)when UltiCloud answered 429; denied, expired and rejected stop. Reaching the deadline while still pending or failing transiently is the same as expired -- checked before each poll, so no request is sent after it. -
finishDeviceLogin
private void finishDeviceLogin(CloudSession.DevicePoll poll, DeviceAuthorizationClient.PollResult.Kind kind, String code, String message) Stops polling and reports a terminal outcome other than a grant, if this session is current. -
deviceClient
- Returns:
- the device-login REST client: the test override, else this session's own
-
now
private static long now()- Returns:
- the device-login clock in epoch milliseconds: the test override, else the wall clock
-
completeDeviceLogin
-
setDeviceSeamsForTesting
static void setDeviceSeamsForTesting(DeviceAuthorizationClient client, CloudSession.DeviceStepScheduler scheduler, LongSupplier clock) Test-only: replaces the device-login REST client, step scheduler and clock for every session, so polling arithmetic is tested without sleeping. Passnulls to restore the defaults (this session's own client, its poll executor,System.currentTimeMillis());resetForTesting()does so too.- Parameters:
client- the client, ornullscheduler- the scheduler, ornullclock- the clock in epoch milliseconds, ornull
-