glyde/gateway/close

Gateway close codes and what to do about them.

Main gateway only. Voice reuses the numbers for other things: voice 4014 is a disconnected call, gateway 4014 is a bot that must never reconnect.

A close we send has no disposition here. The decision was made before the frame went out, and it travels as an Intent: code picks the number, intent_keeps_session says what we still hold. from_gateway answers only for closes we did not choose, and the echo of our own close never reaches it, because the state machine has already moved on from that connection.

Types

A close code Discord can send. Every number this module reasons about is named here, so parse is the only table of numbers and everything else matches on the name.

pub type CloseCode {
  NormalClosure
  GoingAway
  NoStatusReceived
  AbnormalClosure
  ServiceRestart
  UnknownError
  UnknownOpcode
  DecodeError
  NotAuthenticated
  AlreadyAuthenticated
  InvalidSeq
  RateLimited
  SessionTimedOut
  Unfixable(reason: Reason)
  UnknownCode(code: Int)
}

Constructors

  • NormalClosure

    From Discord this still leaves the session resumable. The rule that 1000 ends a session is about closes we send.

  • GoingAway
  • NoStatusReceived

    The peer closed with no code in the frame.

  • AbnormalClosure

    The socket died without a close frame.

  • ServiceRestart
  • UnknownError
  • UnknownOpcode
  • DecodeError

    We sent something Discord could not read.

  • NotAuthenticated

    A command before IDENTIFY, or a session Discord has invalidated.

  • AlreadyAuthenticated

    A second IDENTIFY on one connection.

  • InvalidSeq
  • RateLimited

    We flooded the command budget.

  • SessionTimedOut
  • Unfixable(reason: Reason)

    A code no reconnect can fix. The fatal set lives in one variant so the same six numbers cannot be listed twice and drift apart.

  • UnknownCode(code: Int)

    Not a gateway code, including anything Discord adds after this list.

What to do next.

pub type Disposition {
  ReconnectResume
  ReconnectThrottled
  ReconnectFresh
  Fatal(Reason)
}

Constructors

  • ReconnectResume

    Open a new connection and resume. Missed events get replayed.

  • ReconnectThrottled

    Resume, but wait before redialling and throw the queued commands away. Discord closes a command flood with 4008, and redialling straight into the same backlog earns the next one.

  • ReconnectFresh

    Open a new connection and identify from scratch. The session is gone, so a resume would only earn an INVALID_SESSION.

  • Fatal(Reason)

    Stop. Reconnecting cannot fix this.

What we mean to happen when we close the connection ourselves.

pub type Intent {
  Reconnect
  FreshIdentify
  Terminal
}

Constructors

  • Reconnect

    Drop this socket, keep the session, resume on the next one.

  • FreshIdentify

    Drop the session too and identify from scratch.

  • Terminal

    Shut down for good.

Why the connection can never succeed as configured.

pub type Reason {
  BadToken
  InvalidShard
  ShardingRequired
  InvalidApiVersion
  InvalidIntents
  DisallowedIntents
}

Constructors

  • BadToken

    The bot token is wrong.

  • InvalidShard

    The shard id or shard count in IDENTIFY was invalid.

  • ShardingRequired

    Too many guilds for one connection. The bot has to shard.

  • InvalidApiVersion

    The gateway version in the URL is not supported.

  • InvalidIntents

    The intents bitfield contained a bit Discord does not recognise.

  • DisallowedIntents

    The intents included a privileged one that is not enabled for this application. Enable it in the developer portal.

Values

pub fn code(intent: Intent) -> Int

The code to send for an intent. 1000 tells Discord the session is over and marks the bot offline; anything else leaves it resumable, and 4000 reads as “unknown error”.

pub fn describe(code: CloseCode) -> String

Discord’s name for a code, for logs and error messages.

pub fn from_gateway(code: option.Option(Int)) -> Disposition

Decide what to do about a connection Discord closed. None is a transport that died with no close frame, which Discord documents as resumable.

Resume by default. A wrong resume costs one INVALID_SESSION round trip, a wrong identify spends from a budget of 1000 a day.

pub fn intent_keeps_session(intent: Intent) -> Bool

Whether we still hold a session after closing with this intent. Not a property of the code: FreshIdentify sends a code Discord would let us resume with and throws the session away anyway.

pub fn parse(code: Int) -> CloseCode

Classify a raw close code. The only place a close number is written down.

pub fn remedy(reason: Reason) -> String

What an operator should do about a fatal close.

pub fn session_survives(disposition: Disposition) -> Bool

Whether a close leaves the session usable, for a caller that only wants to know “can I resume”.

Search Document