Class AgentConnection
It is the single owner of every connect, retry and terminal decision for the
/v1/agent socket: nothing else opens a socket, schedules a retry or decides that the
connection is over. (v1 had four owners deciding whether to keep trying.)
| From | Event | To |
|---|---|---|
| IDLE, STOPPED, terminal | start() with a credential | CONNECTING (fresh cycle) |
| CONNECTING | upgrade accepted | OPEN (not yet established) |
| OPEN | markEstablished() (a valid welcome) | OPEN, established: counters reset, heartbeat started |
| CONNECTING | refused 401 | CREDENTIAL_REJECTED |
| CONNECTING | refused 426 | UPGRADE_REQUIRED |
| CONNECTING | refused 402 | PLAN_LIMITED (fixed plan-limit interval) |
| CONNECTING | refused 429 | BACKOFF (long, at least Retry-After) |
| CONNECTING | refused with another 4xx | BACKOFF (long) |
| CONNECTING | refused with 5xx, network error, close, handshake deadline | BACKOFF (normal) |
| OPEN, not established | handshake deadline (no welcome), abandonAttempt(String) |
BACKOFF (normal) |
| OPEN | stopForServerIdMismatch() | SERVER_ID_MISMATCH |
| OPEN, CONNECTING | close 4401 / 4426 | CREDENTIAL_REJECTED / UPGRADE_REQUIRED |
| OPEN, CONNECTING | close 4402 | PLAN_LIMITED (one probe after the attempt-0 delay) |
| OPEN, CONNECTING | close 4400, 4429 | BACKOFF (long) |
| OPEN, CONNECTING | close 4000 | BACKOFF (long), or SUPERSEDED on the third in a row |
| OPEN | any other close, heartbeat timeout | BACKOFF (normal) |
| BACKOFF, PLAN_LIMITED | retry due, or reconnectNow() | CONNECTING |
| SUPERSEDED | reconnectNow() | CONNECTING (counters reset) |
| any | stop() | STOPPED |
AgentConnection.Listener.
Why an attempt id. v1 reconnected by reusing one client object, so a reconnect carried
the stale headers (and bearer) of the first connect, and a late callback of an old socket could
change the state of a new one. Here every attempt builds a new socket with the bearer read at
that moment, and every socket's events are bound to the id (generation) the attempt had
when it started; the id only ever increases (an attempt's end, a retry, stop() and a
terminal state each move it on), so an event whose id is not the current one is ignored.
Threads and locks. Socket callbacks arrive on Java-WebSocket's threads (or synchronously
inside a forced close); each one only posts a task onto the single-thread agent scheduler. All
state changes run under this instance's monitor. The monitor is never held while calling out:
listener callbacks, socket opens, closes and sends, the credential supplier and the log sink
all run after it is released, so a listener may call stop(). In the session's lock
order this monitor sits beside CredentialStore.LOCK and the state-file write lock,
below the session monitor; nothing here blocks or waits (no connectBlocking, no
Future#get).
Every contract number lives in the one contract section below.
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionprivate final classOne attempt's socket events: each posts onto the agent scheduler carrying its attempt id.(package private) static enumWhat a refusal or a close means for the connection.private final classWork collected under the monitor and run after it is released: listener callbacks, log lines, socket opens and closes.(package private) static interfaceOne attempt's socket, as the state machine sees it.(package private) static interfaceCallbacks to the owner (the cloud session).(package private) static enumWhatreconnectNow()did.(package private) static interfaceEvents of one attempt's socket; each instance is bound to one attempt id.(package private) static interfaceOpens one attempt's socket; must not block.(package private) static enumThe states of the connection.(package private) static final classThe connection's timers. -
Field Summary
FieldsModifier and TypeFieldDescriptionprivate int(package private) static final StringUpgrade header carrying the credential.private final LongSupplier(package private) static final intClosed: abuse rate (including too many invalid frames).(package private) static final intClosed: an invalid hello, or a frame before welcome.(package private) static final intJava-WebSocket's code for a socket that never connected.(package private) static final intClosed: no hello within the gateway's deadline.(package private) static final intThe normal close code this side uses for its own closes.(package private) static final intClosed: the server's plan limit applies; same handling as HTTP 402.(package private) static final intClosed: the hello's protocol does not match the upgrade header.(package private) static final intClosed: the credential was revoked.(package private) static final intClosed: another connection with the same credential replaced this one.(package private) static final intTCP connect timeout handed to the socket's constructor.private intprivate intprivate ScheduledFuture<?> private final URIprivate booleanWhether the open attempt was answered with a validwelcome(markEstablished()).(package private) static final StringUpgrade header carrying the running framework version.private final Stringprivate longIdentifies the live attempt or pending retry; only ever increases.private AgentConnection.Handle(package private) static final longDeadline for an attempt to be established: upgraded and answered with a validwelcome.(package private) static final longHeartbeat: the text frameHEARTBEAT_PINGis sent this often once established.(package private) static final StringThe heartbeat request text frame.(package private) static final StringThe heartbeat reply text frame.(package private) static final longHeartbeat: without aHEARTBEAT_PONGfor this long the connection is closed.private ScheduledFuture<?> (package private) static final intThe last 4xx status.(package private) static final intThe first 4xx status; every 4xx not named above backs off long.(package private) static final intUpgrade refused: the credential is invalid.(package private) static final intUpgrade refused: the server is suspended for exceeding its plan.(package private) static final intUpgrade refused: rate limited.(package private) static final intUpgrade refused: protocol or framework too old.private longprivate final AgentConnection.Listenerprivate final BiConsumer<Level, String> private int(package private) static final String426 response header carrying the minimum framework version.private intprivate long(package private) static final longFixed plan-limit retry interval: 15 minutes, no jitter, no growth.private booleanprivate booleanprivate final ReconnectPolicy(package private) static final StringUpgrade header carrying the protocol major version.(package private) static final StringThe protocol version this client speaks.(package private) static final longLargestRetry-Afterhonoured, in seconds.private longprivate ScheduledFuture<?> private final ScheduledExecutorServiceprivate final AgentConnection.SocketFactoryprivate AgentConnection.State(package private) static final longA connection open at least this long resets the consecutive-4000 count.(package private) static final intConsecutive 4000 closes, with no long-lived connection between them, that end retrying.private final AgentConnection.Timing -
Constructor Summary
ConstructorsConstructorDescriptionAgentConnection(URI endpoint, Supplier<String> bearerSupplier, String frameworkVersion, ScheduledExecutorService scheduler, AgentConnection.Listener listener) The production connection.AgentConnection(URI endpoint, Supplier<String> bearerSupplier, String frameworkVersion, ScheduledExecutorService scheduler, AgentConnection.Listener listener, AgentConnection.SocketFactory sockets, ReconnectPolicy policy, LongSupplier clock, AgentConnection.Timing timing, BiConsumer<Level, String> log) The full constructor, for tests. -
Method Summary
Modifier and TypeMethodDescription(package private) voidabandonAttempt(String reason) Ends the open attempt because the protocol layer refused what the gateway sent (an invalidwelcome, S-51): the socket is closed with 1000 and the normal backoff follows.(package private) intattempt()private voidbeginAttempt(String bearer, AgentConnection.Effects out) Under the monitor: enters CONNECTING and queues the socket open.buildHeaders(String bearer) The upgrade headers: exactly the credential, the protocol version and the framework version.private static intbump(int counter) private voidprivate voidprivate voidprivate voidUnder the monitor: a failure other than the plan limit ends a plan-limit episode.(package private) static AgentConnection.DispositionforClose(int code) Classifies a close of an upgraded (or never-upgraded) socket.(package private) static AgentConnection.DispositionforRefusal(int httpStatus) Classifies a refused upgrade.(package private) Stringprivate voidhandleClosed(long id, int code) private voidhandleOpen(long id) private voidhandleRefused(long id, int status, UpgradeRejection rejection) private voidhandleText(long id, String text) private voidhandshakeDeadline(long id) On the scheduler: the attempt's handshake deadline, covering the upgrade and the welcome.private voidheartbeatTick(long id) On the scheduler: sends the text frameping, or closes and retries when nopongarrived within the heartbeat timeout (contract answer 2; the library's ping control frames are disabled).(package private) booleanprivate static boolean(package private) voidMarks the open connection as established, called by the protocol layer when a validwelcomeanswers thehello(D-12, contract SEMANTICS 10): resets the normal and long backoff counters and the attempt number, cancels the handshake deadline and starts the text heartbeat.private voidmoveTo(AgentConnection.State next, AgentConnection.Effects out) (package private) longprivate voidonAttemptFailed(long id, String reason) private voidopenSocket(long id, Map<String, String> headers) Outside the monitor: opens the attempt's socket and keeps it if the attempt is still live.private voidplanLimitClose(String reason, AgentConnection.Effects out) Under the monitor: close 4402.private voidplanLimitRefusal(UpgradeRejection rejection, String reason, AgentConnection.Effects out) Under the monitor: a 402 refusal.private voidprivate String(package private) static AgentConnection.SocketFactoryrealSockets(int connectTimeoutMs, BiConsumer<Level, String> log) The production socket factory: a freshAgentSocketper attempt, connected with the non-blockingconnect()(neverconnectBlocking).(package private) voidreconnectForNewHello(String reason) Closes the open attempt with 1000 so the next attempt sends a newhello(22-22 fix: a reload changed the capability set, which is negotiated only at the handshake); the normal backoff follows, which is short after an established connection.(package private) AgentConnection.ReconnectOutcomeRetries at once from BACKOFF, PLAN_LIMITED or SUPERSEDED, cancelling the pending retry (from SUPERSEDED with every counter reset; from PLAN_LIMITED the plan-limit episode continues, so a renewed 402 sends no second notice).private static AgentConnection.ReconnectOutcomeprivate voidprivate voidretryLong(String reason, long retryAfterSeconds, AgentConnection.Effects out) Under the monitor: a long-class failure; a 429 waits at least itsRetry-After.private voidretryNormal(String reason, AgentConnection.Effects out) private voidrunRetry(long token) private voidscheduleHeartbeat(long id) Under the monitor: the next heartbeat tick of the established connectionid.private booleanscheduleRetry(AgentConnection.State next, long delayMs, String reason, Level level, AgentConnection.Effects out) Under the monitor: ends the attempt and schedules the next one; logs the retry line unlessreasonis null (the caller logs its own line).private static longseconds(long millis) (package private) booleanSends a text frame.private booleansilent()(package private) voidstart()Starts a fresh cycle from IDLE, STOPPED or a terminal state, with every counter reset.(package private) AgentConnection.Statestate()(package private) voidstop()Stops: cancels the pending retry and closes the socket with 1000.(package private) voidStops for good because thewelcomenamed another server id than the linked one (contract answer 21, SEMANTICS 16): the socket is closed with 1000, no retry is scheduled and the owner is told once throughAgentConnection.Listener.onServerIdMismatch(); the credential is kept.private voidsuperseded(boolean afterLongLivedConnection, AgentConnection.Effects out) Under the monitor: close 4000.private voidterminal(AgentConnection.State terminalState, Runnable callback, AgentConnection.Effects out) Under the monitor: enters a terminal state.
-
Field Details
-
HTTP_INVALID_CREDENTIAL
static final int HTTP_INVALID_CREDENTIALUpgrade refused: the credential is invalid. Stop and delete the credential.- See Also:
-
HTTP_PLAN_LIMIT
static final int HTTP_PLAN_LIMITUpgrade refused: the server is suspended for exceeding its plan. Keep the credential.- See Also:
-
HTTP_UPGRADE_REQUIRED
static final int HTTP_UPGRADE_REQUIREDUpgrade refused: protocol or framework too old. Stop and keep the credential.- See Also:
-
HTTP_TOO_MANY_REQUESTS
static final int HTTP_TOO_MANY_REQUESTSUpgrade refused: rate limited. Long backoff, at leastRetry-After.- See Also:
-
HTTP_CLIENT_ERROR_MIN
static final int HTTP_CLIENT_ERROR_MINThe first 4xx status; every 4xx not named above backs off long.- See Also:
-
HTTP_CLIENT_ERROR_MAX
static final int HTTP_CLIENT_ERROR_MAXThe last 4xx status.- See Also:
-
CLOSE_PLAN_LIMIT
static final int CLOSE_PLAN_LIMITClosed: the server's plan limit applies; same handling as HTTP 402.- See Also:
-
CLOSE_SUPERSEDED
static final int CLOSE_SUPERSEDEDClosed: another connection with the same credential replaced this one.- See Also:
-
CLOSE_INVALID_HELLO
static final int CLOSE_INVALID_HELLOClosed: an invalid hello, or a frame before welcome.- See Also:
-
CLOSE_REVOKED
static final int CLOSE_REVOKEDClosed: the credential was revoked.- See Also:
-
CLOSE_NO_HELLO
static final int CLOSE_NO_HELLOClosed: no hello within the gateway's deadline.- See Also:
-
CLOSE_PROTOCOL_MISMATCH
static final int CLOSE_PROTOCOL_MISMATCHClosed: the hello's protocol does not match the upgrade header.- See Also:
-
CLOSE_ABUSE_RATE
static final int CLOSE_ABUSE_RATEClosed: abuse rate (including too many invalid frames).- See Also:
-
CLOSE_NORMAL
static final int CLOSE_NORMALThe normal close code this side uses for its own closes.- See Also:
-
CLOSE_NEVER_CONNECTED
static final int CLOSE_NEVER_CONNECTEDJava-WebSocket's code for a socket that never connected.- See Also:
-
SUPERSEDED_STOP_AFTER
static final int SUPERSEDED_STOP_AFTERConsecutive 4000 closes, with no long-lived connection between them, that end retrying.- See Also:
-
PLAN_LIMIT_RETRY_MS
static final long PLAN_LIMIT_RETRY_MSFixed plan-limit retry interval: 15 minutes, no jitter, no growth.- See Also:
-
RETRY_AFTER_MAX_SECONDS
static final long RETRY_AFTER_MAX_SECONDSLargestRetry-Afterhonoured, in seconds.- See Also:
-
SUPERSEDED_RESET_MS
static final long SUPERSEDED_RESET_MSA connection open at least this long resets the consecutive-4000 count.- See Also:
-
HANDSHAKE_DEADLINE_MS
static final long HANDSHAKE_DEADLINE_MSDeadline for an attempt to be established: upgraded and answered with a validwelcome.- See Also:
-
CONNECT_TIMEOUT_MS
static final int CONNECT_TIMEOUT_MSTCP connect timeout handed to the socket's constructor.- See Also:
-
HEARTBEAT_INTERVAL_MS
static final long HEARTBEAT_INTERVAL_MSHeartbeat: the text frameHEARTBEAT_PINGis sent this often once established.- See Also:
-
HEARTBEAT_TIMEOUT_MS
static final long HEARTBEAT_TIMEOUT_MSHeartbeat: without aHEARTBEAT_PONGfor this long the connection is closed.- See Also:
-
HEARTBEAT_PING
The heartbeat request text frame.- See Also:
-
HEARTBEAT_PONG
The heartbeat reply text frame.- See Also:
-
AUTHORIZATION_HEADER
Upgrade header carrying the credential.- See Also:
-
PROTOCOL_HEADER
Upgrade header carrying the protocol major version.- See Also:
-
FRAMEWORK_HEADER
Upgrade header carrying the running framework version.- See Also:
-
MIN_FRAMEWORK_HEADER
426 response header carrying the minimum framework version.- See Also:
-
PROTOCOL_VERSION
The protocol version this client speaks.- See Also:
-
endpoint
-
bearerSupplier
-
frameworkVersion
-
scheduler
-
listener
-
sockets
-
policy
-
clock
-
timing
-
log
-
state
-
generation
private long generationIdentifies the live attempt or pending retry; only ever increases. -
handle
-
attemptsSinceEstablished
private int attemptsSinceEstablished -
currentAttemptNumber
private int currentAttemptNumber -
normalFailures
private int normalFailures -
longFailures
private int longFailures -
consecutiveSuperseded
private int consecutiveSuperseded -
openedAtMillis
private long openedAtMillis -
established
private boolean establishedWhether the open attempt was answered with a validwelcome(markEstablished()). -
retryFuture
-
deadlineFuture
-
heartbeatFuture
-
lastPongMillis
private long lastPongMillis -
retryAtMillis
private long retryAtMillis -
planEpisode
private boolean planEpisode -
planNoticeSent
private boolean planNoticeSent
-
-
Constructor Details
-
AgentConnection
AgentConnection(URI endpoint, Supplier<String> bearerSupplier, String frameworkVersion, ScheduledExecutorService scheduler, AgentConnection.Listener listener) The production connection.- Parameters:
endpoint- the agent endpoint, fromAgentEndpoint.fromApiUrl(java.lang.String)bearerSupplier- reads the current credential;nullmeans noneframeworkVersion- the running framework versionscheduler- the single-thread agent schedulerlistener- the owner's callbacks
-
AgentConnection
AgentConnection(URI endpoint, Supplier<String> bearerSupplier, String frameworkVersion, ScheduledExecutorService scheduler, AgentConnection.Listener listener, AgentConnection.SocketFactory sockets, ReconnectPolicy policy, LongSupplier clock, AgentConnection.Timing timing, BiConsumer<Level, String> log) The full constructor, for tests.
-
-
Method Details
-
forRefusal
Classifies a refused upgrade.- Parameters:
httpStatus- the HTTP status of the refused upgrade- Returns:
- its disposition; another 4xx retries long, a 5xx (and anything else) normally
-
forClose
Classifies a close of an upgraded (or never-upgraded) socket.- Parameters:
code- the close code- Returns:
- its disposition; every code not named here (1000, 1001, 1006, 1009, 1011, 4408, unknown) retries normally
-
realSockets
static AgentConnection.SocketFactory realSockets(int connectTimeoutMs, BiConsumer<Level, String> log) The production socket factory: a freshAgentSocketper attempt, connected with the non-blockingconnect()(neverconnectBlocking). -
buildHeaders
The upgrade headers: exactly the credential, the protocol version and the framework version.- Parameters:
bearer- the credential- Returns:
- the headers, in a fixed order
-
start
void start()Starts a fresh cycle from IDLE, STOPPED or a terminal state, with every counter reset. A no-op while connecting, open, backing off or plan-limited. Without a credential the connection goes to STOPPED. -
stop
void stop()Stops: cancels the pending retry and closes the socket with 1000. Events from older sockets are ignored afterwards. -
reconnectNow
AgentConnection.ReconnectOutcome reconnectNow()Retries at once from BACKOFF, PLAN_LIMITED or SUPERSEDED, cancelling the pending retry (from SUPERSEDED with every counter reset; from PLAN_LIMITED the plan-limit episode continues, so a renewed 402 sends no second notice). Used by/ulticloud reconnect.- Returns:
STARTED, or why nothing was started
-
reconnectRefusal
-
markEstablished
void markEstablished()Marks the open connection as established, called by the protocol layer when a validwelcomeanswers thehello(D-12, contract SEMANTICS 10): resets the normal and long backoff counters and the attempt number, cancels the handshake deadline and starts the text heartbeat. Apingbeforewelcomewould be closed with 4400, so nothing here runs at the upgrade itself. A no-op unless OPEN; never calls out. -
abandonAttempt
Ends the open attempt because the protocol layer refused what the gateway sent (an invalidwelcome, S-51): the socket is closed with 1000 and the normal backoff follows. A no-op unless OPEN.- Parameters:
reason- the reason for the retry line, never carrying payload or credential text
-
reconnectForNewHello
Closes the open attempt with 1000 so the next attempt sends a newhello(22-22 fix: a reload changed the capability set, which is negotiated only at the handshake); the normal backoff follows, which is short after an established connection. Runs on the scheduler, like every socket event, so the close listeners run on the agent thread. A no-op unless the attempt open now is still the open one when it runs.- Parameters:
reason- the reason for the retry line, never carrying payload or credential text
-
stopForServerIdMismatch
void stopForServerIdMismatch()Stops for good because thewelcomenamed another server id than the linked one (contract answer 21, SEMANTICS 16): the socket is closed with 1000, no retry is scheduled and the owner is told once throughAgentConnection.Listener.onServerIdMismatch(); the credential is kept.reconnectNow()does not leave the resulting state. A no-op unless OPEN. -
frameworkVersion
String frameworkVersion()- Returns:
- the running framework version this connection sends in its upgrade headers
-
isEstablished
boolean isEstablished()- Returns:
- whether the open connection was answered with a valid
welcome
-
send
Sends a text frame.- Parameters:
text- the frame- Returns:
- whether it was handed to an open socket; false unless OPEN
-
state
AgentConnection.State state()- Returns:
- the current state
-
attempt
int attempt()- Returns:
- the 1-based number of the current or last attempt since the last established connection
-
nextRetryAtMillis
long nextRetryAtMillis()- Returns:
- when the pending retry runs, from the injected clock; 0 when none is pending
-
isStartable
-
resetCycle
private void resetCycle() -
silent
private boolean silent() -
beginAttempt
Under the monitor: enters CONNECTING and queues the socket open. -
openSocket
Outside the monitor: opens the attempt's socket and keeps it if the attempt is still live. -
handleOpen
private void handleOpen(long id) -
handleText
-
handleRefused
-
handleClosed
private void handleClosed(long id, int code) -
onAttemptFailed
-
planLimitRefusal
private void planLimitRefusal(UpgradeRejection rejection, String reason, AgentConnection.Effects out) Under the monitor: a 402 refusal. -
planLimitClose
Under the monitor: close 4402. The close frame cannot carry the reason or the manage link, so one probe follows after the normal attempt-0 delay; its 402 sends the notice. -
endPlanEpisode
private void endPlanEpisode()Under the monitor: a failure other than the plan limit ends a plan-limit episode. -
retryNormal
-
retryLong
Under the monitor: a long-class failure; a 429 waits at least itsRetry-After. -
superseded
Under the monitor: close 4000. Three in a row with no connection lastingsupersededResetMsin between stop retrying (credential kept); the earlier ones back off long with a warning instead of the retry line. -
scheduleRetry
private boolean scheduleRetry(AgentConnection.State next, long delayMs, String reason, Level level, AgentConnection.Effects out) Under the monitor: ends the attempt and schedules the next one; logs the retry line unlessreasonis null (the caller logs its own line).- Returns:
- whether the retry was scheduled
-
runRetry
private void runRetry(long token) -
terminal
private void terminal(AgentConnection.State terminalState, Runnable callback, AgentConnection.Effects out) Under the monitor: enters a terminal state. -
handshakeDeadline
private void handshakeDeadline(long id) On the scheduler: the attempt's handshake deadline, covering the upgrade and the welcome. -
scheduleHeartbeat
private void scheduleHeartbeat(long id) Under the monitor: the next heartbeat tick of the established connectionid. -
heartbeatTick
private void heartbeatTick(long id) On the scheduler: sends the text frameping, or closes and retries when nopongarrived within the heartbeat timeout (contract answer 2; the library's ping control frames are disabled). -
cancelDeadline
private void cancelDeadline() -
cancelTimers
private void cancelTimers() -
cancelRetry
private void cancelRetry() -
moveTo
-
readBearer
-
post
-
bump
private static int bump(int counter) -
seconds
private static long seconds(long millis)
-