Skip to content

Operations

  • Warm, leased, recycled. Sessions are lease-exclusive (one caller per slot at a time) and recycled on age / request-count / health, with hard per-cmdlet and connect timeouts and off-path recovery of wedged slots.
  • Idle-slot sweeper. A background sweeper proactively recycles aged idle slots that the lease-time recycle would otherwise miss - closing an EXO per-runspace memory-growth path on low-traffic pools. It participates in the pool’s permit protocol so it can never race a lease.
  • Per-principal token bucket - 100 tokens, replenished 100 every 5 min (so a 100-request burst, then ≈20 req/min sustained). Partitioned oid → appid → anonymous IP, so an app and an interactive user never share a bucket. QueueLimit 0: over-limit is an instant 429 + Retry-After: 300 + the canonical Throttled envelope.
  • EXO throttles surface as 429 with a conservative Retry-After so callers back off instead of retry-storming.

Measured ceiling (2026-06-02, live tenant):

Probe Result Interpretation
200 concurrent requests, fresh bucket exactly 100 × 200 + 100 × 429 confirms the bucket size; 429 carries Retry-After: 300
6 / 12 concurrent EXO-backed reads 2 / 4 clean waves; ≈7.7 req/s effective confirms runspace PoolSize = 3 is the concurrency gate
single EXO-backed read ~0.37 s median per-request latency floor (in-process cmdlet + EXO round-trip)

The raw engine tops out near ~450 req/min (pool of 3 × ~2.6 reads/s). The 20/min policy sits well below that by design - it mirrors EXO’s per-tenant cmdlet quota, not the server’s capacity. The throughput levers are RunspaceHostOptions.PoolSize (concurrency) and the token-bucket settings (TokenLimit / TokensPerPeriod / ReplenishmentPeriod).

  • /healthz (liveness) and /readyz (real per-slot pool readiness).
  • Security headers on every response (X-Content-Type-Options: nosniff, X-Frame-Options: SAMEORIGIN, Referrer-Policy: no-referrer; HSTS outside Development).
  • Module hygiene - the EOM module floor is pinned to the documented 3.9.x baseline; an opt-in EXOModuleBasePath controls where the module’s temp copy lives on long-lived hosts.
  • EOM < 3.0 is unsupported; 3.5.0 breaks in-process hosting - use 3.9.x.
  • The REST-based Get-EXOMailbox can’t run in-process (an OData assembly clash); controllers route through the classic Get-Mailbox, which works.
  • Mailbox-scoped cmdlets return a clean 404 when the identity isn’t a mailbox - expected.