mirror of
https://github.com/stablyai/orca.git
synced 2026-09-24 00:02:24 +00:00
* feat(relay): declare Asia cell c30 at the c27 shape Adds production-gce-c30 in asia-east2-a at the reviewed Asia shape (6,000 request units, 3,000/60 connection limits, 16-connection pool, disabled) and the rehome trust the other Asia cells carry. Every Asia enumeration now knows C30. The topology, admission, and director tools treat it as its own reviewed wave so its plan and registration never touch the live launch cells. C30 promotion requires C27 general and fresh staging evidence. The topology and director validators now pin the committed production pool of 16 instead of the stale 10, which had made the topology workflow reject the committed launch cells. * fix(relay): plan C30 at live images and prove it with its own canary The shared URL map pulls every cell into the C30 topology plan, so the workflow now plans each non-target cell at the image its live template serves, and the validator names any change to a cell outside the wave. C30 promotion runs the same five-minute production canary and automatic rollback C27 used, with the load report proving the canary control was placed on C30, instead of relying on staging evidence. C30 leaves the shadow gate's fleet pool list until it serves, rollback rejects mixed partial sets, and a budget test pins the mixed-Asia-pool refusal. * fix(relay): pin C30 to the production director's live image digest C30 promotion requires the director and C30 to report one digest, so C30 takes the director's sha256:4158d8a2 (read 2026-09-22). C27-C29 keep their committed lines; every Asia check compares only the cells named in a run. * fix(relay): read the committed cell map from a plan, not console terraform console evaluates every output against state, and the Relay deployments output indexes each cell's MIG, so it fails with Invalid index while C30 is declared but not created. Read the map from a no-refresh, unlocked plan over the same targets instead, and refuse empty overlay input. * fix(relay): keep console readers working and C30 migration-only until promotion relay_gce_cell_deployments indexed each cell's MIG, backend, and template, so once C30 is declared but not applied every production terraform console reader printed a warning to stdout and broke its jq parse. Wrap those six lookups in try(..., null). Same-cap listed C30 as general, so a rollback dispatch on a migration-only C30 would restore it with activate and skip its canary. List it with the migration-only cells until the promotion follow-up moves it.
576 lines
22 KiB
JavaScript
576 lines
22 KiB
JavaScript
import { createHash, createPrivateKey, createPublicKey } from 'node:crypto'
|
|
import { readFileSync } from 'node:fs'
|
|
import { monitorEventLoopDelay } from 'node:perf_hooks'
|
|
import { setTimeout as delay } from 'node:timers/promises'
|
|
import { RelayLoadControlPeer } from './relay-load-control-peer.mjs'
|
|
import { requestGitHubSmokeTokens } from './github-smoke-token.mjs'
|
|
import { relayLoadFailureReason } from './relay-load-connection-failure.mjs'
|
|
import {
|
|
assertRelayLoadDirectorCapacityToken,
|
|
waitForRelayLoadDirectorCapacity,
|
|
waitForRelayLoadRequestUnits
|
|
} from './relay-load-director-capacity-gate.mjs'
|
|
import { waitForRelayLoadPhaseBarrier } from './relay-load-phase-barrier.mjs'
|
|
import {
|
|
proveRelayLoadPlacementBoundary,
|
|
proveRelayLoadRegionalFallback
|
|
} from './relay-load-placement-boundary.mjs'
|
|
import {
|
|
proveRelayLoadRebindBoundary,
|
|
waitForRelayLoadRebindGate
|
|
} from './relay-load-rebind-boundary.mjs'
|
|
import { proveRelayLoadRegionBehavior } from './relay-load-region-behavior.mjs'
|
|
import {
|
|
openRelayLoadInviteOffers,
|
|
proveRelayLoadRequestUnitBoundary
|
|
} from './relay-load-request-unit-boundary.mjs'
|
|
import {
|
|
assertRelayLoadRampAccepted,
|
|
relayLoadRunHasDisallowedFailures,
|
|
runRelayLoadWithShutdown
|
|
} from './relay-load-run-lifecycle.mjs'
|
|
import { createRelayLoadReaderEvidence } from './relay-load-reader-evidence.mjs'
|
|
import {
|
|
parseRelayLoadArguments,
|
|
relayLoadPrincipalIndex,
|
|
relayLoadReaderEvidenceError,
|
|
relayLoadSpliceIndexes,
|
|
relayLoadSpliceProfile,
|
|
relayLoadSpliceStartDelayMs
|
|
} from './relay-load-profile.mjs'
|
|
|
|
function signingKey(path) {
|
|
if (!path) return {}
|
|
const key = createPrivateKey(readFileSync(path, 'utf8'))
|
|
const signingKeyId = createHash('sha256')
|
|
.update(createPublicKey(key).export({ type: 'spki', format: 'der' }))
|
|
.digest('base64url')
|
|
.slice(0, 16)
|
|
return { signingKey: key, signingKeyId }
|
|
}
|
|
|
|
function report(state, final = false) {
|
|
const elapsedSeconds = Math.max(1, (Date.now() - state.startedAt) / 1000)
|
|
const memory = process.memoryUsage()
|
|
const cpu = process.cpuUsage(state.generatorBaselineCpu)
|
|
const rssMiB = memory.rss / 1_048_576
|
|
state.generatorPeakRssMiB = Math.max(state.generatorPeakRssMiB, rssMiB)
|
|
const readerQueueEvidence = state.readerEvidence?.snapshot() ?? []
|
|
const output = {
|
|
event: final ? 'relay_load_complete' : 'relay_load_progress',
|
|
controls: state.controls,
|
|
shardCount: state.shardCount,
|
|
shardIndex: state.shardIndex,
|
|
configuredRampSeconds: state.rampMs / 1000,
|
|
configuredSteadySeconds: state.durationMs / 1000,
|
|
configuredSpliceHoldSeconds: state.spliceHoldMs / 1000,
|
|
requiredLeaseHorizons: state.requiredLeaseHorizons,
|
|
configuredSplices: state.splices,
|
|
configuredSlowReaderSplices: state.slowReaderSplices,
|
|
configuredWedgedReaderSplices: state.wedgedReaderSplices,
|
|
active: state.active.size,
|
|
peakActive: state.peakActive,
|
|
steadyMinimumActive: state.steadyMinimumActive,
|
|
connected: state.connected,
|
|
connectionFailures: state.connectionFailures,
|
|
rampConnectionFailures: state.rampConnectionFailures,
|
|
steadyConnectionFailures: state.steadyConnectionFailures,
|
|
transitionConnectionFailures: state.transitionConnectionFailures,
|
|
connectionFailuresByReason: state.connectionFailuresByReason,
|
|
closes: state.closes,
|
|
unexpectedCloses: state.unexpectedCloses,
|
|
unexpectedClosesByCode: state.unexpectedClosesByCode,
|
|
drains: state.drains,
|
|
pings: state.pings,
|
|
pingRate: Number((state.pings / elapsedSeconds).toFixed(2)),
|
|
tokens: state.tokens,
|
|
tokenRate: Number((state.tokens / elapsedSeconds).toFixed(2)),
|
|
refreshes: state.refreshes,
|
|
refreshErrors: state.refreshErrors,
|
|
protocolErrors: state.protocolErrors,
|
|
socketErrors: state.socketErrors,
|
|
rebindProbesOpened: state.rebindProbesOpened,
|
|
rebindOverflowReason: state.rebindOverflowReason,
|
|
placementOverflowReason: state.placementOverflowReason,
|
|
regionalFallbacksProved: state.regionalFallbacksProved,
|
|
oldClientUsFirstProved: state.oldClientUsFirstProved,
|
|
stickyAssignmentProved: state.stickyAssignmentProved,
|
|
requestUnitInvitesOpened: state.requestUnitInvitesOpened,
|
|
requestUnitPrincipalCount: state.requestUnitPrincipalCount,
|
|
relayAsiaLoadPrincipalCount: state.relayAsiaLoadPrincipalCount,
|
|
requestUnitOverflowReason: state.requestUnitOverflowReason,
|
|
requestUnitCleanupProved: state.requestUnitCleanupProved,
|
|
phaseBarrierPassed: state.phaseBarrierPassed,
|
|
activeSplices: state.activeSplices,
|
|
peakActiveSplices: state.peakActiveSplices,
|
|
completedSplices: state.completedSplices,
|
|
failedSplices: state.failedSplices,
|
|
slowReaderSplicesCompleted: state.slowReaderSplicesCompleted,
|
|
wedgedReaderSplicesClosed: state.wedgedReaderSplicesClosed,
|
|
readerQueueEvidence,
|
|
readerQueuedBytesPeak: Math.max(
|
|
0,
|
|
...readerQueueEvidence.map(({ increaseBytes }) => increaseBytes)
|
|
),
|
|
readerClosesByCode: state.readerClosesByCode,
|
|
// Every cell a control connected to, so a canary can prove its load reached the target.
|
|
assignedCellOrigins: [...state.assignedCellOrigins].sort(),
|
|
controlHeadroom: Math.max(0, state.controls - state.active.size),
|
|
generatorRssMiB: Number(rssMiB.toFixed(1)),
|
|
generatorPeakRssMiB: Number(state.generatorPeakRssMiB.toFixed(1)),
|
|
generatorRssGrowthMiB: Number(
|
|
Math.max(0, state.generatorPeakRssMiB - state.generatorBaselineRssMiB).toFixed(1)
|
|
),
|
|
generatorHeapUsedMiB: Number((memory.heapUsed / 1_048_576).toFixed(1)),
|
|
generatorCpuPercent: Number(
|
|
(((cpu.user + cpu.system) / 1_000_000 / elapsedSeconds) * 100).toFixed(1)
|
|
),
|
|
generatorEventLoopP99Ms: Number((state.eventLoopDelay.percentile(99) / 1_000_000).toFixed(2)),
|
|
shutdownEvidence: final ? state.shutdownEvidence : undefined,
|
|
elapsedSeconds: Number(elapsedSeconds.toFixed(1))
|
|
}
|
|
console.log(JSON.stringify(output))
|
|
return output
|
|
}
|
|
|
|
const config = parseRelayLoadArguments(process.argv.slice(2))
|
|
let accessToken = process.env.ORCA_RELAY_LOAD_ACCESS_TOKEN
|
|
let accessTokenProviderForIndex
|
|
const adminToken = process.env.ORCA_RELAY_ADMIN_ID_TOKEN
|
|
if (
|
|
config.placementOverflowProbes > 0 || config.regionalFallbackProbes > 0 ||
|
|
config.slowReaderSplices + config.wedgedReaderSplices > 0 ||
|
|
config.requestUnitOverflowProbes > 0 || config.requestUnitCleanupTimeoutMs > 0
|
|
) {
|
|
assertRelayLoadDirectorCapacityToken({
|
|
directorOrigin: config.directorOrigin,
|
|
adminToken
|
|
}, Date.now,
|
|
config.rampMs + config.durationMs + config.wedgedReaderHoldMs +
|
|
(config.phaseBarrierDir ? 2 * config.phaseBarrierTimeoutMs : 0) +
|
|
config.spliceRampMs + config.requestUnitCleanupTimeoutMs + 120_000)
|
|
}
|
|
const key = signingKey(config.signingKeyFile)
|
|
if (!accessToken && !key.signingKey && process.env.ACTIONS_ID_TOKEN_REQUEST_URL &&
|
|
process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN) {
|
|
let tokens
|
|
let refresh
|
|
const loadOptions = config.relayAsiaLoadPrincipalCount > 0
|
|
? {
|
|
relayAsiaLoad: {
|
|
shardIndex: config.shardIndex,
|
|
principalCount: config.relayAsiaLoadPrincipalCount
|
|
}
|
|
}
|
|
: undefined
|
|
const smokeTokens = async () => {
|
|
const expiresAt = config.relayAsiaLoadPrincipalCount > 0
|
|
? tokens?.relayAsiaLoadPrincipals?.[0]?.expiresAt
|
|
: tokens?.owner?.expiresAt
|
|
if (expiresAt > Date.now() + 60_000) return tokens
|
|
refresh ??= requestGitHubSmokeTokens(
|
|
config.authOrigin,
|
|
fetch,
|
|
process.env,
|
|
loadOptions
|
|
)
|
|
try {
|
|
tokens = await refresh
|
|
return tokens
|
|
} finally {
|
|
refresh = undefined
|
|
}
|
|
}
|
|
accessTokenProviderForIndex = (index) => async () => {
|
|
const current = await smokeTokens()
|
|
return config.relayAsiaLoadPrincipalCount > 0
|
|
? current.relayAsiaLoadPrincipals[
|
|
relayLoadPrincipalIndex(
|
|
index,
|
|
config.shardCount,
|
|
current.relayAsiaLoadPrincipals.length
|
|
)
|
|
].accessToken
|
|
: current.owner.accessToken
|
|
}
|
|
await smokeTokens()
|
|
}
|
|
if (!accessToken && !accessTokenProviderForIndex && !key.signingKey) {
|
|
throw new Error('provide GitHub OIDC, ORCA_RELAY_LOAD_ACCESS_TOKEN, or --signing-key-file')
|
|
}
|
|
const eventLoopDelay = monitorEventLoopDelay({ resolution: 20 })
|
|
eventLoopDelay.enable()
|
|
const generatorBaselineRssMiB = process.memoryUsage().rss / 1_048_576
|
|
const generatorBaselineCpu = process.cpuUsage()
|
|
const state = {
|
|
...config,
|
|
startedAt: Date.now(),
|
|
active: new Set(),
|
|
peakActive: 0,
|
|
steadyMinimumActive: null,
|
|
steadyStarted: false,
|
|
connected: 0,
|
|
connectionFailures: 0,
|
|
rampConnectionFailures: 0,
|
|
steadyConnectionFailures: 0,
|
|
transitionConnectionFailures: 0,
|
|
connectionFailuresByReason: {},
|
|
closes: 0,
|
|
unexpectedCloses: 0,
|
|
unexpectedClosesByCode: {},
|
|
drains: 0,
|
|
pings: 0,
|
|
tokens: 0,
|
|
refreshes: 0,
|
|
refreshErrors: 0,
|
|
protocolErrors: 0,
|
|
socketErrors: 0,
|
|
rebindProbesOpened: 0,
|
|
rebindOverflowReason: null,
|
|
placementOverflowReason: null,
|
|
regionalFallbacksProved: 0,
|
|
oldClientUsFirstProved: 0,
|
|
stickyAssignmentProved: 0,
|
|
requestUnitInvitesOpened: 0,
|
|
requestUnitOverflowReason: null,
|
|
requestUnitCleanupProved: 0,
|
|
phaseBarrierPassed: false,
|
|
activeSplices: 0,
|
|
peakActiveSplices: 0,
|
|
completedSplices: 0,
|
|
failedSplices: 0,
|
|
slowReaderSplicesCompleted: 0,
|
|
wedgedReaderSplicesClosed: 0,
|
|
readerEvidence: null,
|
|
readerClosesByCode: {},
|
|
assignedCellOrigins: new Set(),
|
|
generatorBaselineRssMiB,
|
|
generatorBaselineCpu,
|
|
generatorPeakRssMiB: generatorBaselineRssMiB,
|
|
peerShutdowns: 0,
|
|
shutdownEvidence: null,
|
|
eventLoopDelay,
|
|
stopping: false,
|
|
transitionWindow: false
|
|
}
|
|
const peers = new Map()
|
|
const reconnectTimers = new Set()
|
|
|
|
async function readRuntimeQueuedBytes(origin) {
|
|
const response = await fetch(`${origin}/v1/admin/runtime-status`, {
|
|
method: 'POST',
|
|
headers: { authorization: `Bearer ${adminToken}`, 'content-type': 'application/json' },
|
|
body: JSON.stringify({ v: 1 }),
|
|
signal: AbortSignal.timeout(5_000)
|
|
})
|
|
if (response.status === 401 || response.status === 403) {
|
|
throw new Error('reader evidence identity was rejected')
|
|
}
|
|
if (!response.ok) throw new Error(`reader runtime status returned ${response.status}`)
|
|
const status = await response.json()
|
|
const queuedBytes = status?.runtime?.queuedBytes
|
|
if (!Number.isSafeInteger(queuedBytes) || queuedBytes < 0) {
|
|
throw new Error('reader runtime queued bytes are invalid')
|
|
}
|
|
return queuedBytes
|
|
}
|
|
|
|
async function observeReaderPressure(input) {
|
|
if (!state.readerEvidence) throw new Error('reader evidence baseline is unavailable')
|
|
await state.readerEvidence.observe(input)
|
|
state.generatorPeakRssMiB = Math.max(
|
|
state.generatorPeakRssMiB,
|
|
process.memoryUsage().rss / 1_048_576
|
|
)
|
|
}
|
|
|
|
function recordSteadyMinimum() {
|
|
if (!state.steadyStarted || state.stopping) return
|
|
state.steadyMinimumActive = Math.min(state.steadyMinimumActive, state.active.size)
|
|
}
|
|
|
|
function scheduleReconnect(peer) {
|
|
if (state.stopping) return
|
|
const timeout = setTimeout(() => {
|
|
reconnectTimers.delete(timeout)
|
|
void connect(peer)
|
|
}, Math.floor(Math.random() * (config.reconnectMaxMs + 1)))
|
|
reconnectTimers.add(timeout)
|
|
}
|
|
|
|
function observe(type, detail) {
|
|
if (type === 'connected') {
|
|
state.active.add(detail.index)
|
|
state.connected++
|
|
const cellOrigin = peers.get(detail.index)?.assignedCellUrl()
|
|
if (cellOrigin) state.assignedCellOrigins.add(cellOrigin)
|
|
state.peakActive = Math.max(state.peakActive, state.active.size)
|
|
recordSteadyMinimum()
|
|
} else if (type === 'closed') {
|
|
state.active.delete(detail.index)
|
|
state.closes++
|
|
if (!detail.stopped && !detail.expectedDrain) {
|
|
state.unexpectedCloses++
|
|
const code = String(detail.code)
|
|
state.unexpectedClosesByCode[code] = (state.unexpectedClosesByCode[code] ?? 0) + 1
|
|
}
|
|
if (!detail.stopped) scheduleReconnect(peers.get(detail.index))
|
|
recordSteadyMinimum()
|
|
} else if (type === 'drain') state.drains++
|
|
else if (type === 'ping') state.pings++
|
|
else if (type === 'token') state.tokens++
|
|
else if (type === 'refresh') state.refreshes++
|
|
else if (type === 'refreshError') state.refreshErrors++
|
|
else if (type === 'protocolError') state.protocolErrors++
|
|
else if (type === 'socketError') state.socketErrors++
|
|
else if (type === 'spliceOpened') {
|
|
state.activeSplices++
|
|
state.peakActiveSplices = Math.max(state.peakActiveSplices, state.activeSplices)
|
|
} else if (type === 'spliceCompleted') {
|
|
state.completedSplices++
|
|
if (detail.readerMode === 'slow') state.slowReaderSplicesCompleted++
|
|
} else if (type === 'spliceWedged') {
|
|
state.wedgedReaderSplicesClosed++
|
|
const code = String(detail.code)
|
|
state.readerClosesByCode[code] = (state.readerClosesByCode[code] ?? 0) + 1
|
|
} else if (type === 'spliceClosed') state.activeSplices--
|
|
else if (type === 'spliceFailed') state.failedSplices++
|
|
else if (type === 'shutdown') state.peerShutdowns++
|
|
}
|
|
|
|
async function connect(peer) {
|
|
try {
|
|
await peer.connect()
|
|
} catch (error) {
|
|
state.connectionFailures++
|
|
if (state.steadyStarted) state.steadyConnectionFailures++
|
|
else if (state.transitionWindow) state.transitionConnectionFailures++
|
|
else state.rampConnectionFailures++
|
|
const reason = relayLoadFailureReason(error)
|
|
state.connectionFailuresByReason[reason] =
|
|
(state.connectionFailuresByReason[reason] ?? 0) + 1
|
|
scheduleReconnect(peer)
|
|
}
|
|
}
|
|
|
|
const peerOptions = (index, overrides = {}) => ({
|
|
...config,
|
|
...key,
|
|
accessToken,
|
|
...(accessTokenProviderForIndex
|
|
? { accessTokenProvider: accessTokenProviderForIndex(index) }
|
|
: {}),
|
|
seed: 0x4f524341 ^ config.shardIndex,
|
|
...overrides
|
|
})
|
|
if (config.regionBehaviorProbes > 0) {
|
|
const proofIndex = config.controls * config.shardCount + 10_000
|
|
const regionProof = await proveRelayLoadRegionBehavior({
|
|
oldClientPeer: new RelayLoadControlPeer(
|
|
proofIndex,
|
|
peerOptions(proofIndex, { preferredRegion: undefined }),
|
|
() => undefined
|
|
),
|
|
stickyPeer: new RelayLoadControlPeer(
|
|
proofIndex + 1,
|
|
peerOptions(proofIndex + 1, { preferredRegion: 'asia-east2' }),
|
|
() => undefined
|
|
),
|
|
asiaOrigin: config.capacityCellOrigin
|
|
})
|
|
state.oldClientUsFirstProved = regionProof.oldClientUsFirst ? 1 : 0
|
|
state.stickyAssignmentProved = regionProof.stickyAssignmentPreserved ? 1 : 0
|
|
}
|
|
const initialConnections = []
|
|
for (let localIndex = 0; localIndex < config.controls; localIndex++) {
|
|
const globalIndex = localIndex * config.shardCount + config.shardIndex
|
|
const peer = new RelayLoadControlPeer(globalIndex, peerOptions(globalIndex), observe)
|
|
peers.set(globalIndex, peer)
|
|
const rampOffset =
|
|
config.controls === 1 ? 0 : Math.floor((localIndex / (config.controls - 1)) * config.rampMs)
|
|
const offset = config.rampStartDelayMs + rampOffset
|
|
initialConnections.push(delay(offset).then(() => connect(peer)))
|
|
}
|
|
const progressTimer = setInterval(() => report(state), 10_000)
|
|
progressTimer.unref()
|
|
await runRelayLoadWithShutdown(async () => {
|
|
await Promise.all(initialConnections)
|
|
assertRelayLoadRampAccepted(state.rampConnectionFailures, config.maxRampConnectionFailures)
|
|
if (
|
|
config.rebindProbes > 0 || config.placementOverflowProbes > 0 ||
|
|
config.regionalFallbackProbes > 0
|
|
) {
|
|
state.transitionWindow = config.rebindDelayMs > 0
|
|
await waitForRelayLoadRebindGate({
|
|
delay,
|
|
delayMs: config.rebindDelayMs,
|
|
activeCount: () => state.active.size,
|
|
requiredCount: config.controls
|
|
})
|
|
state.transitionWindow = false
|
|
}
|
|
if (config.placementOverflowProbes > 0 || config.regionalFallbackProbes > 0) {
|
|
const closesBeforeBoundary = state.closes
|
|
await waitForRelayLoadDirectorCapacity({
|
|
directorOrigin: config.directorOrigin,
|
|
adminToken,
|
|
cellId: config.capacityCellId,
|
|
hardCap: config.capacityHardCap,
|
|
unobservedBound: config.capacityUnobservedBound,
|
|
requiredConnections: config.aggregateControls
|
|
})
|
|
if (state.active.size !== config.controls || state.closes !== closesBeforeBoundary) {
|
|
throw new Error('ordinary controls changed during the director capacity gate')
|
|
}
|
|
const overflowIndex = config.controls * config.shardCount + config.shardIndex
|
|
if (config.placementOverflowProbes > 0) {
|
|
state.placementOverflowReason = await proveRelayLoadPlacementBoundary({
|
|
peer: new RelayLoadControlPeer(overflowIndex, peerOptions(overflowIndex), observe),
|
|
failureReason: relayLoadFailureReason
|
|
})
|
|
}
|
|
if (config.regionalFallbackProbes > 0) {
|
|
await proveRelayLoadRegionalFallback({
|
|
peer: new RelayLoadControlPeer(overflowIndex, peerOptions(overflowIndex), () => undefined),
|
|
blockedOrigin: config.capacityCellOrigin
|
|
})
|
|
state.regionalFallbacksProved = 1
|
|
}
|
|
if (state.active.size !== config.controls || state.closes !== closesBeforeBoundary) {
|
|
throw new Error('ordinary controls changed during the placement boundary probe')
|
|
}
|
|
await waitForRelayLoadDirectorCapacity({
|
|
directorOrigin: config.directorOrigin,
|
|
adminToken,
|
|
cellId: config.capacityCellId,
|
|
hardCap: config.capacityHardCap,
|
|
unobservedBound: config.capacityUnobservedBound,
|
|
requiredConnections: config.aggregateControls,
|
|
requiredSamples: 1
|
|
})
|
|
if (state.active.size !== config.controls || state.closes !== closesBeforeBoundary) {
|
|
throw new Error('ordinary controls changed before post-probe capacity verification')
|
|
}
|
|
}
|
|
const rebindResult = await proveRelayLoadRebindBoundary({
|
|
peers: [...state.active].map((index) => peers.get(index)),
|
|
probeCount: config.rebindProbes,
|
|
holdMs: config.rebindHoldMs,
|
|
delay,
|
|
failureReason: relayLoadFailureReason,
|
|
requireOverflow: config.requireRebindOverflow
|
|
})
|
|
state.rebindProbesOpened = rebindResult.opened
|
|
state.rebindOverflowReason = rebindResult.overflowReason
|
|
if (config.requestUnitInvites > 0) {
|
|
state.requestUnitInvitesOpened = await openRelayLoadInviteOffers({
|
|
peers: [...state.active].sort((left, right) => left - right).map((index) => peers.get(index)),
|
|
count: config.requestUnitInvites,
|
|
ratePerSecond: config.requestUnitInvitesPerSecond
|
|
})
|
|
}
|
|
if (config.requestUnitOverflowProbes > 0) {
|
|
await waitForRelayLoadRequestUnits({
|
|
directorOrigin: config.directorOrigin,
|
|
adminToken,
|
|
cellId: config.capacityCellId,
|
|
capacityRequests: config.requestUnitCapacity,
|
|
expectedRequestUnits: config.requestUnitCapacity,
|
|
expectedActivityLeases: config.requestUnitCapacity
|
|
})
|
|
state.requestUnitOverflowReason = await proveRelayLoadRequestUnitBoundary(
|
|
peers.get([...state.active][0])
|
|
)
|
|
}
|
|
if (config.phaseBarrierDir) {
|
|
await waitForRelayLoadPhaseBarrier({
|
|
directory: config.phaseBarrierDir,
|
|
shardCount: config.shardCount,
|
|
shardIndex: config.shardIndex,
|
|
timeoutMs: config.phaseBarrierTimeoutMs
|
|
})
|
|
state.phaseBarrierPassed = true
|
|
}
|
|
state.steadyStarted = true
|
|
state.steadyMinimumActive = state.active.size
|
|
const spliceIndexes = relayLoadSpliceIndexes(config)
|
|
const readerOrigins = spliceIndexes.flatMap((index, spliceIndex) =>
|
|
relayLoadSpliceProfile(config, spliceIndex).readerMode === 'normal'
|
|
? []
|
|
: [peers.get(index).lastAssignment.cellUrl]
|
|
)
|
|
state.readerEvidence = await createRelayLoadReaderEvidence(readerOrigins, {
|
|
readQueuedBytes: readRuntimeQueuedBytes,
|
|
delay
|
|
})
|
|
if (config.phaseBarrierDir) {
|
|
await waitForRelayLoadPhaseBarrier({
|
|
directory: `${config.phaseBarrierDir}-splices`,
|
|
shardCount: config.shardCount,
|
|
shardIndex: config.shardIndex,
|
|
timeoutMs: config.phaseBarrierTimeoutMs
|
|
})
|
|
}
|
|
const splicePromises = spliceIndexes.map((index, spliceIndex) =>
|
|
delay(relayLoadSpliceStartDelayMs(config, spliceIndex)).then(() =>
|
|
peers.get(index).openSplice({
|
|
payloadBytes: config.splicePayloadBytes,
|
|
...relayLoadSpliceProfile(config, spliceIndex),
|
|
observeReaderPressure,
|
|
holdMs: config.spliceHoldMs
|
|
})
|
|
)
|
|
)
|
|
await Promise.all([...splicePromises, delay(config.durationMs)])
|
|
}, async () => {
|
|
state.stopping = true
|
|
clearInterval(progressTimer)
|
|
for (const timeout of reconnectTimers) clearTimeout(timeout)
|
|
reconnectTimers.clear()
|
|
await Promise.all([...peers.values()].map((peer) => peer.shutdown()))
|
|
eventLoopDelay.disable()
|
|
state.shutdownEvidence = {
|
|
peerShutdowns: state.peerShutdowns,
|
|
activeControls: state.active.size,
|
|
activeSplices: state.activeSplices,
|
|
reconnectTimers: reconnectTimers.size
|
|
}
|
|
})
|
|
if (config.requestUnitCleanupTimeoutMs > 0) {
|
|
await waitForRelayLoadRequestUnits({
|
|
directorOrigin: config.directorOrigin,
|
|
adminToken,
|
|
cellId: config.capacityCellId,
|
|
capacityRequests: config.requestUnitCapacity,
|
|
expectedRequestUnits: 0,
|
|
expectedActivityLeases: 0,
|
|
timeoutMs: config.requestUnitCleanupTimeoutMs
|
|
})
|
|
state.requestUnitCleanupProved = 1
|
|
}
|
|
const result = report(state, true)
|
|
const minimumPeak = config.allowPartial ? 1 : Math.ceil(config.controls * 0.95)
|
|
if (result.peakActive < minimumPeak) {
|
|
throw new Error(`peak active controls ${result.peakActive} below required ${minimumPeak}`)
|
|
}
|
|
if (result.steadyMinimumActive < minimumPeak) {
|
|
throw new Error(
|
|
`steady minimum active controls ${result.steadyMinimumActive} below required ${minimumPeak}`
|
|
)
|
|
}
|
|
if (relayLoadRunHasDisallowedFailures(result, config)) {
|
|
throw new Error('relay load run observed connection, protocol, refresh, or socket errors')
|
|
}
|
|
const readerEvidenceError = relayLoadReaderEvidenceError(result, config)
|
|
if (readerEvidenceError) throw new Error(readerEvidenceError)
|
|
if (
|
|
result.failedSplices > 0 ||
|
|
result.completedSplices + result.wedgedReaderSplicesClosed !== config.splices ||
|
|
result.shutdownEvidence.peerShutdowns !== config.controls ||
|
|
result.shutdownEvidence.activeControls !== 0 ||
|
|
result.shutdownEvidence.activeSplices !== 0 ||
|
|
result.shutdownEvidence.reconnectTimers !== 0
|
|
) {
|
|
throw new Error('relay load run did not complete splices or shut down cleanly')
|
|
}
|