March Docs

BastionHotDeploy

BastionHotDeploy: connection-draining state machine for zero-downtime deploys.

Implements a simple four-state machine:

Starting → Serving ←→ Draining → Stopped

State is persisted in a Vault table so it survives within the same process and is inspectable from tests without HTTP round-trips.

Storage layout ("hot_deploy_state" Vault table): "status" → String "serving" | "draining" | "starting" | "stopped" "in_flight" → Int number of requests currently being handled "drain_started_at" → Int monotonic timestamp (0 = not draining)

Typical usage:

BastionHotDeploy.start_drain()
-- wait for in-flight requests to finish …
let (code, body) = BastionHotDeploy.health_status(BastionHotDeploy.default_config())

Types

typeDrainStatusDrainStatus = Serving | Draining | Starting | Stopped#
typeDrainConfigDrainConfig = DrainConfig(#

Functions

fnconfig_channel_close_timeout_msconfig_channel_close_timeout_ms(cfg)#
fnconfig_close_connectionsconfig_close_connections(cfg)#
fnconfig_drain_status_codeconfig_drain_status_code(cfg)#
fnconfig_timeout_msconfig_timeout_ms(cfg)#
fndecrement_in_flightdecrement_in_flight()#

Decrement the in-flight request counter by 1 (floored at 0). Call when a request finishes.

  BastionHotDeploy.decrement_in_flight()
fndefault_configdefault_config()#

Return the default DrainConfig.

  BastionHotDeploy.default_config()
  -- DrainConfig(30000, 5000, 503, true)
fndrain_responsedrain_response(config)#

Return (status_code, json_body) to send when rejecting a request during drain. Uses the configured drain_status_code (default 503).

  let (code, body) = BastionHotDeploy.drain_response(config)
fnget_statusget_status()#

Return the current DrainStatus. Defaults to Serving if no state has been set.

  BastionHotDeploy.get_status()
fnhealth_statushealth_status(_config)#

Return (status_code, json_body) appropriate for a health-check endpoint.

Serving  → (200, "{\"status\":\"ok\"}")
Draining → (503, "{\"status\":\"draining\"}")
Starting → (503, "{\"status\":\"starting\"}")
Stopped  → (503, "{\"status\":\"stopped\"}")

  let (code, body) = BastionHotDeploy.health_status(BastionHotDeploy.default_config())
fnin_flight_countin_flight_count()#

Return the current in-flight request count.

  BastionHotDeploy.in_flight_count()
fnincrement_in_flightincrement_in_flight()#

Increment the in-flight request counter by 1. Call at the start of each request.

  BastionHotDeploy.increment_in_flight()
fnis_drainingis_draining()#

Return true when the current status is Draining.

  BastionHotDeploy.is_draining()
fnmark_servingmark_serving()#

Transition to Serving. Clears the drain_started_at timestamp.

fnmark_startingmark_starting()#

Transition to Starting.

fnshould_drain_requestshould_drain_request(_config)#

Return true when the deploy manager should refuse new connections (i.e. status is Draining or Stopped).

  BastionHotDeploy.should_drain_request(config)
fnstart_drainstart_drain()#

Transition to Draining (from Serving). Records the drain start timestamp as 0 (no real monotonic clock in stdlib). Safe to call from any state.

  BastionHotDeploy.start_drain()
fnstop_drainstop_drain()#

Transition to Stopped.