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.
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:
- validates/clamps global total
- validates the local start
- bounds local count by remaining global shards
- caps local count at 128
- clears the local descriptor pool
- assigns global shard IDs to local slots
- calculates each shard's Identify bucket
- 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
- NASM original
- Execution order
- Rust
- C
- Python
; 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
1. Copy the guild snowflake.
2. Shift right by 22 bits to discard Discord's timestamp/worker packing used by the routing formula.
3. Read the current global shard count.
4. Return shard zero if the total is invalid/zero.
5. Divide the shifted snowflake by the global shard count.
6. Return the remainder as the global shard ID.
fn shard_for_guild(guild_id: u64, total_shards: u32) -> u32 {
if total_shards == 0 {
return 0;
}
((guild_id >> 22) % u64::from(total_shards)) as u32
}
uint32_t shard_for_guild(uint64_t guild_id, uint32_t total_shards) {
if (total_shards == 0)
return 0;
return (uint32_t)((guild_id >> 22) % total_shards);
}
def shard_for_guild(guild_id: int, total_shards: int) -> int:
if total_shards == 0:
return 0
return (guild_id >> 22) % total_shards
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.
- NASM original
- Execution order
- Rust equivalent
- C equivalent
- Python model
| Symbol / register | Meaning |
|---|---|
R12D | First global shard ID assigned to this node. |
R13D | Number of local shard contexts to populate. |
EBX | Current local slot index. |
shard_contexts | Base address of the contiguous 128-slot descriptor pool. |
SHARD_OFF_* | Byte offsets into one 256-byte descriptor. |
shard_identify_concurrency | Discord 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.
1. Start at local slot zero.
2. Convert the slot index into a 256-byte descriptor offset.
3. Derive and store the global shard ID.
4. Store total-shard count, ASSIGNED state, and invalid initial sequence.
5. Stamp the descriptor with the current lease generation.
6. Compute shard_id modulo Identify concurrency and store the bucket.
7. Advance to the next local slot until the local plan is populated.
fn apply_plan(
contexts: &mut [ShardContext],
start: u32,
count: u32,
total: u32,
max_concurrency: u32,
generation: u64,
) {
contexts.fill(ShardContext::default());
let concurrency = max_concurrency.max(1);
for (slot, ctx) in contexts.iter_mut().take(count as usize).enumerate() {
ctx.id = start + slot as u32;
ctx.total = total;
ctx.state = ShardState::Assigned;
ctx.sequence = 0xFFFF_FFFF;
ctx.lease_epoch = generation;
ctx.identify_bucket = ctx.id % concurrency;
}
}
void apply_plan(struct shard_ctx *contexts,
uint32_t start,
uint32_t count,
uint32_t total,
uint32_t max_concurrency,
uint64_t generation) {
memset(contexts, 0, LEGACY_SHARD_MAX * sizeof(*contexts));
uint32_t concurrency = max_concurrency ? max_concurrency : 1;
for (uint32_t slot = 0; slot < count; ++slot) {
struct shard_ctx *ctx = &contexts[slot];
ctx->id = start + slot;
ctx->total = total;
ctx->state = SHARD_STATE_ASSIGNED;
ctx->sequence = 0xFFFFFFFFu;
ctx->lease_epoch = generation;
ctx->identify_bucket = ctx->id % concurrency;
}
}
def apply_plan(contexts, start, count, total, max_concurrency, generation):
for ctx in contexts:
ctx.clear()
concurrency = max(1, max_concurrency)
for slot in range(count):
ctx = contexts[slot]
ctx.id = start + slot
ctx.total = total
ctx.state = "ASSIGNED"
ctx.sequence = 0xFFFFFFFF
ctx.lease_epoch = generation
ctx.identify_bucket = ctx.id % concurrency
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.
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.