Overview
Theisolate crate (crates/isolate/) implements:
- V8 isolate management and lifecycle
- JavaScript-to-Rust syscall bridge
- Built-in API implementations
- Module loading and resolution
- Security boundaries and resource limits
- Error handling and stack traces
Architecture
V8 isolate management
Isolate lifecycle
Each function execution runs in a V8 isolate:- Creation: New isolate is created or retrieved from pool
- Initialization: Runtime environment is set up
- Module loading: User code and dependencies are loaded
- Execution: Function runs with syscall access
- Cleanup: Resources are released and isolate returned to pool
Isolate pooling
To amortize initialization cost:- Pool of pre-initialized isolates
- Reuse isolates across executions
- Eviction based on memory pressure
- Isolation between different deployments
Memory management
V8 heap management:- Configurable heap size limits
- Garbage collection tuning
- Memory pressure detection
- Out-of-memory handling
UDF runtime integration
The UDF runtime (npm-packages/udf-runtime/) provides the JavaScript environment:
Built-in APIs
Provides Convex-specific APIs:Syscall interface
JavaScript calls Rust through syscalls:Environment setup
The runtime configures:- Global objects and APIs
- Module resolution rules
- Import maps for dependencies
- Polyfills for Node.js compatibility
Syscall implementation
Syscall categories
Database syscalls
db/query: Read documents from a tabledb/insert: Insert a new documentdb/patch: Update existing documentdb/replace: Replace entire documentdb/delete: Remove a documentdb/get: Get document by ID
Storage syscalls
storage/getUrl: Get URL for stored filestorage/getMetadata: Get file metadatastorage/store: Store a filestorage/delete: Delete a file
Scheduler syscalls
scheduler/runAfter: Schedule delayed executionscheduler/runAt: Schedule at specific timescheduler/cancel: Cancel scheduled function
Action syscalls
fetch: HTTP requests (actions only)crypto: Cryptographic operationsrandom: Random number generation
Syscall execution flow
- JavaScript calls syscall function
- Deno Core marshals arguments to Rust
- Isolate runtime validates the syscall
- Appropriate handler is invoked
- Handler interacts with database/storage
- Result is marshaled back to JavaScript
- JavaScript receives the result
Type conversion
Between JavaScript and Rust:- Primitives: Direct mapping (number, string, boolean, null)
- Objects: Converted to
ConvexObject - Arrays: Converted to
ConvexArray - Dates: Converted to timestamp values
- Functions: Not directly transferable
- Errors: Special error object conversion
Module system
Module loading
Supports ES modules:Module resolution
- Parse import specifier
- Resolve to file path
- Check module cache
- Load and compile if needed
- Execute module initialization
- Return module exports
Built-in modules
Provided by the runtime:convex/server: Server-side APIsconvex/values: Type validators- Node.js polyfills (limited set)
Source maps
For TypeScript debugging:- Source maps are preserved
- Stack traces map back to TypeScript
- Error messages reference original code
Security and isolation
Sandbox boundaries
Isolates are isolated from:- File system access (no direct access)
- Network access (only through syscalls)
- Process spawning
- Native code execution
- Other isolates and deployments
Resource limits
Enforced limits:Syscall permissions
Different function types have different permissions:- Queries: Read-only database access, no side effects
- Mutations: Read-write database access, deterministic
- Actions: Full network access, non-deterministic operations
- HTTP actions: HTTP request/response handling
Error handling
JavaScript errors
Errors in user code are caught:Stack traces
Stack trace processing:- V8 captures raw stack trace
- Source maps are applied
- Internal frames are filtered
- User-friendly stack trace is generated
- Error is returned to client
System errors
Rust-side errors:- Out of memory
- Execution timeout
- Syscall errors
- Invalid arguments
Performance optimization
Compilation caching
Compiled code is cached:- JavaScript is compiled to V8 bytecode
- Bytecode is cached by module hash
- Warm start for repeated executions
- Cache eviction based on LRU
Snapshot support
V8 snapshots for fast startup:- Runtime environment pre-initialized
- Snapshot saved to disk
- New isolates restore from snapshot
- Reduces initialization time
JIT optimization
V8’s JIT compiler:- Hot functions are optimized
- Inline caching for property access
- Type feedback for optimization
- Deoptimization when assumptions break
Integration with function runner
The function runner coordinates isolate usage:Execution flow
- Function runner receives request
- Acquires isolate from pool
- Sets up execution context
- Invokes function in isolate
- Collects results or errors
- Returns isolate to pool
Transaction coordination
For queries and mutations:- Database transaction is started before execution
- Syscalls operate within transaction
- Transaction commits after successful execution
- Rollback on errors
Streaming results
For large result sets:- Results stream back to client
- Backpressure handling
- Chunked processing
Deno Core integration
Why Deno Core
Deno Core provides:- Rust-to-V8 bindings
- Module system implementation
- Promise/async integration
- Op (syscall) infrastructure
- Resource management
Op interface
Deno’s op system for syscalls:Extension system
Convex defines extensions:Testing
Unit tests
Test individual syscalls:Integration tests
Test full function execution:Property-based tests
Test syscall robustness:Debugging and observability
Logging
Function execution logging:- Console.log statements captured
- Structured log format
- Streaming to dashboard
- Log level filtering
Tracing
Distributed tracing:- Syscall tracing
- Execution time tracking
- Span correlation
- Performance profiling
Metrics
Isolate metrics:- Execution time histograms
- Memory usage tracking
- Error rates
- Syscall counts
Known limitations
Node.js compatibility
Limited Node.js API support:- No native modules
- Subset of standard library
- Some APIs not available in queries/mutations
Memory constraints
Heap size limits may affect:- Large data processing
- Deep recursion
- Memory-intensive operations
Execution time limits
Long-running operations:- Queries/mutations: 10 second limit
- Actions: 15 minute limit
- Use scheduling for longer tasks
Next steps
- Function runner component - Orchestration layer
- Database engine component - Database integration
- Rust backend architecture - Overall system architecture