What is a V8 Isolate?
A V8 isolate is an independent instance of the V8 JavaScript engine:- Separate heap: Each isolate has its own memory allocation
- No shared state: Isolates cannot access each other’s variables or functions
- Independent execution: Code in one isolate cannot interfere with another
- Resource limits: CPU and memory usage can be controlled per isolate
V8 is the JavaScript engine that powers Chrome, Node.js, and Deno. flora uses Deno Core as a wrapper around V8.
Per-Guild Isolation
flora maintains a strict one isolate per guild model:- Guild ID is extracted from the Discord event
- Runtime looks up the guild’s isolate in the map
- Event is dispatched to that specific isolate
- No other guild’s code can observe or intercept the event
Isolation Guarantees
| Property | Isolated? | Notes |
|---|---|---|
| Global variables | Yes | Each guild has its own globalThis |
| Imported modules | Yes | Module cache is per-isolate |
| Timers/promises | Yes | Event loop is per-isolate |
| Memory | Yes | Separate heap per isolate |
| Errors | Yes | Unhandled errors don’t crash other guilds |
| Secrets | Yes | Secrets are injected per-guild via ops |
| KV storage | Yes | KV ops filter by guild ID at runtime |
Isolate Lifecycle
Initialization
When a guild script is deployed (apps/runtime/src/runtime/worker.rs:152-217):new_js_runtime() function:
- Creates a new
JsRuntimewith the flora ops extension - Injects guild-scoped state into
OpState - Executes the runtime prelude (SDK initialization code)
- Returns the ready-to-use isolate
Script Loading
After initialization, the guild script is loaded:- Evaluates the bundled JavaScript code
- Registers event handlers via
on()and slash commands viaslash() - Extracts the dispatch function for later event routing
- Runs the event loop until quiescence (all pending promises resolved)
Event Dispatch
When a Discord event arrives for the guild:- Enters the isolate’s V8 context
- Calls the cached dispatch function with the event data
- Runs the event loop with a timeout
- Returns control to the worker thread
Termination
Isolates are terminated when:- A new deployment replaces the guild’s script
- The bot leaves the guild
- A fatal error occurs (e.g., repeated timeouts)
- The worker thread shuts down
v8::Isolate::terminate_execution() to forcefully stop all pending operations.
Resource Limits
Each isolate is subject to configurable limits:Timeout Enforcement
Timeouts are enforced using Tokio’stimeout() wrapper:
terminate_execution().
Script Size Limits
The bundled script size is checked before loading:RUNTIME_MAX_SCRIPT_BYTES).
Worker Thread Model
Workers use a single-threaded Tokio runtime per worker thread (apps/runtime/src/runtime/worker.rs:94-97):Why Single-Threaded?
V8 isolates are not thread-safe. They can only be accessed from one thread at a time. Using a current-thread runtime ensures:- No race conditions on isolate state
- No need for complex locking
- Predictable execution order
- Lower overhead than multi-threaded runtime
Worker Pool Scaling
To handle more guilds, increase the worker pool size:Shared State via Ops
While isolates are isolated, they can access shared services via ops (operations):OpState to ensure proper isolation:
- Guild ID: Injected when the isolate is created
- HTTP client: Shared
Arc<Http>for Discord API calls - KV service: Shared
KvServicethat filters by guild ID - Secrets: Guild-scoped secrets from
SecretService
Ops are the only way for isolate code to interact with the outside world. This provides a security boundary.
Default Runtime
Each worker also maintains a default runtime (apps/runtime/src/runtime/worker.rs:101):- Hosts the SDK bundle (loaded once per worker)
- Does not have a guild ID
- Used for testing and SDK initialization
- Shares the same ops as guild runtimes (but with no guild context)
Migration Between Workers
Guild runtimes can be migrated between workers for load balancing (apps/runtime/src/runtime/mod.rs:112-177):- Source worker serializes the deployment state into a
MigrationEnvelope - Source worker terminates the guild isolate
- Target worker receives the envelope
- Target worker creates a new isolate with the guild’s script
- Queued events are replayed on the new isolate
Migration does not preserve in-memory state (global variables, timers, etc.). Only the deployment (script code) is transferred.