Skip to main content

Shard Context & Memory Layout

The multi-shard design starts with a simple rule:

every shard must have an explicit owner for every piece of mutable transport and Gateway state.

The top-level definition lives in legacy_shards.inc.

Local pool​

The runtime defines:

LEGACY_SHARD_MAX = 128 local slots
LEGACY_SHARD_TOTAL_MAX = 65535 global shards
LEGACY_SHARD_CTX_SIZE = 256 bytes

The 256-byte descriptor is not the whole shard memory footprint. It is the control record that points at lazily allocated workspaces.

State machine​

A local slot can move through:

FREE
↓
ASSIGNED
↓
STARTING
↓
READY
↓
BACKOFF
└────────→ STARTING

fatal configuration / unrecoverable state
↓
FATAL

The states are intentionally coarse. Protocol-specific detail remains in the TCP/TLS/WebSocket/Discord workspaces.

Top-level descriptor​

Important fields include:

shard ID
global shard count
state
Gateway sequence
heartbeat interval
flags
lease generation
last tick
session generation
heartbeat deadline
heartbeat counters
Identify bucket
TCP context pointer
TLS context pointer
WebSocket context pointer
Discord context pointer
RX/TX buffer pointers
RX/TX lengths
reconnect count
lease deadline
last error
reconnect deadline
session pointer

This shape makes shard ownership visible from one address.

Descriptor vs workspace

The descriptor answers "where is this shard's state?" The pointed-to pages contain the large protocol buffers and cryptographic workspaces.

Flags​

Flags represent orthogonal state that does not fit cleanly into the coarse lifecycle state.

Examples include:

  • sequence valid
  • awaiting heartbeat ACK
  • WebSocket HTTP upgrade in progress
  • WebSocket open
  • Identify sent
  • READY observed
  • Identify pending
  • resuming
  • connection allowed

That avoids multiplying the lifecycle enum into dozens of combinations.

Local plan application​

A node owns a contiguous range defined by:

global total shard count
local start shard
local shard count
plan generation
Identify max concurrency

Applying a plan:

  1. validates/clamps global total
  2. validates the local start
  3. bounds local count by remaining global shards
  4. caps local count at 128
  5. clears the local descriptor pool
  6. assigns global shard IDs to local slots
  7. calculates each shard's Identify bucket
  8. resets per-plan scheduler bookkeeping

A fresh plan therefore establishes one coherent local ownership generation.

Global shard ID vs local slot​

These are deliberately separate concepts.

For example:

global shard IDs owned by node: 32..47
local slots: 0..15

The runtime provides conversion logic so internal arrays can stay compact while Discord-visible shard IDs remain global.

Guild routing​

Discord guild routing uses:

(guild_id >> 22) % total_shards
Code example of guild-to-shard routing
; RCX = Discord guild snowflake
legacy_shard_for_guild64:
mov rax, rcx
shr rax, 22
mov ecx, [rel shard_total_global]
test ecx, ecx
jz .zero
xor edx, edx
div rcx
mov eax, edx
ret
.zero:
xor eax, eax
ret

The bare-metal runtime contains this routing primitive directly.

That matters because routing is useful both for local decisions and for the external controller's node lookup.

Lazy allocation​

A local descriptor does not immediately allocate every large workspace for all 128 possible slots.

Individual protocol layers allocate private pages when a shard actually starts.

Examples:

  • TCP: one page plus reliable retransmission storage
  • TLS: 45 pages
  • WebSocket: one page
  • Discord workspace: separately allocated context
  • RX/TX buffers: per-shard storage as needed

This keeps the 128-slot planning structure cheap while allowing active shards to own large isolated protocol memory.

TLS dominates per-shard memory​

The TLS context alone reserves:

45 × 4096 bytes = 184,320 bytes

That workspace contains areas for:

  • wire records
  • handshake accumulation
  • transcript
  • plaintext/ciphertext buffers
  • ClientHello
  • randoms
  • ECDHE parameters
  • signatures
  • hashes
  • master secret and key block
  • traffic keys and IVs
  • verification data
  • certificate/RSA state

That is why "128 slots" should not be confused with "128 tiny structs."

Reset semantics​

Connection reset does not necessarily free and recreate every allocation.

For example, the lifecycle code can:

  • zero a TCP page while retaining its separately allocated reliable queue pointer
  • zero the TLS workspace
  • zero the WebSocket workspace
  • zero Discord protocol state
  • preserve resumable Gateway sequence/session data when valid

This reduces allocation churn while maintaining state isolation.

Why explicit pointers beat a giant monolith​

The project could have created one enormous fixed-size struct containing every possible buffer.

It instead uses a small stable descriptor plus pointers because:

  • protocol workspaces have very different sizes
  • some resources are needed only when a shard becomes active
  • reset behavior differs by layer
  • debugging pointer ownership is clearer
  • a future allocator can change without rewriting the descriptor layout

Acceptance implications​

The two-shard test does not accept "both shard IDs reached READY" as sufficient.

It checks that shard 0 and shard 1 do not reuse the same:

  • TCP context
  • local TCP port
  • TLS workspace
  • WebSocket workspace
  • Discord workspace

This directly tests the memory-ownership model described on this page.

Local shard-context materialization​

A controller/local plan becomes a contiguous set of 256-byte descriptors.

Context before the block
Symbol / registerMeaning
R12DFirst global shard ID assigned to this node.
R13DNumber of local shard contexts to populate.
EBXCurrent local slot index.
shard_contextsBase address of the contiguous 128-slot descriptor pool.
SHARD_OFF_*Byte offsets into one 256-byte descriptor.
shard_identify_concurrencyDiscord Identify concurrency bucket count.
xor ebx, ebx ; EBX = local slot index 0.

.assign:
cmp ebx, r13d ; Have all requested local slots been populated?
jae .done ; Yes: finish plan application.

mov eax, ebx ; EAX = local slot index.
shl rax, 8 ; Multiply by 256 bytes (descriptor size).
lea rdi, [rel shard_contexts] ; RDI = base of descriptor pool.
add rdi, rax ; RDI = address of this slot's descriptor.

mov eax, r12d ; EAX = first global shard ID in this node's lease.
add eax, ebx ; Add local slot index to derive global shard ID.
mov [rdi + SHARD_OFF_ID], eax ; Store global shard ID.

mov eax, [rel shard_total_global] ; EAX = bot's global shard count.
mov [rdi + SHARD_OFF_TOTAL], eax ; Store global total for Identify/routing.
mov dword [rdi + SHARD_OFF_STATE], SHARD_STATE_ASSIGNED ; Slot now belongs to the plan.
mov dword [rdi + SHARD_OFF_SEQUENCE], 0xFFFFFFFF ; No valid Gateway sequence yet.

mov rax, [rel shard_plan_generation] ; RAX = current ownership-plan generation.
mov [rdi + SHARD_OFF_LEASE_EPOCH], rax; Stamp descriptor with that generation.

mov eax, [rdi + SHARD_OFF_ID] ; EAX = global shard ID.
xor edx, edx ; Clear high dividend for 32-bit DIV.
mov ecx, [rel shard_identify_concurrency] ; ECX = Discord max_concurrency.
test ecx, ecx ; Did Discord/controller provide a nonzero value?
jnz .bucket_div ; Yes: use it.
mov ecx, 1 ; No: normalize invalid/zero concurrency to one bucket.

.bucket_div:
div ecx ; EDX = shard_id % max_concurrency.
mov [rdi + SHARD_OFF_IDENTIFY_BUCKET], edx ; Persist Identify bucket in descriptor.

inc ebx ; Advance to next local slot.
jmp .assign ; Populate next descriptor.
Descriptor-size coupling

shl rax, 8 is a hard-coded multiply-by-256. If LEGACY_SHARD_CTX_SIZE changes without updating this addressing logic, every descriptor after slot zero is indexed incorrectly.

Success condition

Every active local slot contains the correct global shard ID, total shard count, lease generation, invalid initial sequence marker, and Identify bucket.

State after the block​

The descriptors are assigned but not connected. TCP/TLS/WebSocket workspaces remain lazily allocated later when the scheduler starts each shard.

Source​