This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
Advanced Topics
Loading…
Advanced Topics
Relevant source files
Purpose and Scope
This section covers advanced usage patterns, optimization techniques, and extension points for the muxio framework. Topics include cross-platform deployment strategies, performance tuning, custom transport implementations, and deep integration with JavaScript environments via WASM.
For basic usage patterns, see Overview. For specific platform implementations, see Platform Implementations. For service definition basics, see Service Definitions.
Cross-Platform Deployment
Deployment Architecture
The muxio framework enables a single service definition to be deployed across multiple runtime environments without code duplication. The key enabler is the runtime-agnostic core design, which separates platform-specific concerns (transport, async runtime) from business logic.
Sources : README.md:48-52 README.md:66-84 extensions structure from Cargo.toml
graph TB
subgraph "Shared_Service_Contract"
ServiceDef["example-muxio-rpc-service-definition\nRpcMethodPrebuffered traits\nMETHOD_ID constants\nencode_request/decode_response"]
end
subgraph "Native_Tokio_Deployment"
NativeApp["Native Application\nRust Binary"]
RpcClient["RpcClient\nArc<TokioMutex<RpcDispatcher>>\ntokio-tungstenite WebSocket"]
RpcServer["RpcServer\nAxum HTTP Server\ntokio::spawn task pool"]
NativeTransport["TCP/IP Network\nNative OS Stack"]
NativeApp -->|implements| ServiceDef
NativeApp -->|uses| RpcClient
NativeApp -->|serves| RpcServer
RpcClient <-->|WebSocket frames| NativeTransport
RpcServer <-->|WebSocket frames| NativeTransport
end
subgraph "WASM_Browser_Deployment"
WasmApp["Web Application\nwasm-bindgen Module"]
RpcWasmClient["RpcWasmClient\nMUXIO_STATIC_RPC_CLIENT_REF\nthread_local RefCell"]
JsBridge["static_muxio_write_bytes\nJavaScript Callback\nWebSocket.send"]
BrowserTransport["Browser WebSocket API\nJavaScript Runtime"]
WasmApp -->|implements| ServiceDef
WasmApp -->|uses| RpcWasmClient
RpcWasmClient -->|calls| JsBridge
JsBridge -->|invokes| BrowserTransport
end
NativeTransport <-->|Binary Protocol| BrowserTransport
BuildConfig["Build Configuration\ncargo build --target x86_64\ncargo build --target wasm32"]
BuildConfig -.->|native| NativeApp
BuildConfig -.->|wasm32-unknown-unknown| WasmApp
Shared Service Definition Pattern
Both native and WASM clients use the same service definition crate, ensuring compile-time type safety. The service definition declares methods via the RpcMethodPrebuffered trait, which generates:
METHOD_ID- A compile-timeu64constant derived from the method name viaxxhashencode_request/decode_request- Serialization usingbitcodeencode_response/decode_response- Deserialization usingbitcode
| Component | Native Client | WASM Client | Server |
|---|---|---|---|
| Service Definition | Shared crate dependency | Shared crate dependency | Shared crate dependency |
| Caller Interface | RpcClient implements RpcServiceCallerInterface | RpcWasmClient implements RpcServiceCallerInterface | N/A |
| Endpoint Interface | Optional (bidirectional) | Optional (bidirectional) | RpcServer exposes RpcServiceEndpointInterface |
| Transport | tokio-tungstenite | JavaScript WebSocket | tokio-tungstenite via Axum |
| Async Runtime | Tokio | Browser event loop | Tokio |
Sources : README.md50 README.md:72-74
Build Configuration Strategy
The workspace uses Cargo features and target-specific dependencies to enable cross-platform builds:
The muxio-wasm-rpc-client crate uses conditional compilation to ensure WASM-specific dependencies like wasm-bindgen, js-sys, and web-sys are only included for WASM targets.
Sources : README.md:39-40 workspace structure from Cargo.toml
Performance Considerations
Data Flow and Chunking Strategy
The muxio framework employs a multi-layered data transformation pipeline optimized for low latency and minimal memory overhead. Understanding this pipeline is critical for performance tuning.
Sources : README.md architecture overview, bitcode usage from Cargo.lock, chunking from src/rpc/rpc_internals/rpc_session.rs
graph LR
subgraph "Application_Layer"
AppData["Rust Struct\nVec<f64> or Custom Type"]
end
subgraph "Serialization_Layer"
BitcodeEncode["bitcode::encode\nCompact Binary\n~70% smaller than JSON"]
SerializedBytes["Vec<u8>\nSerialized Payload"]
end
subgraph "RPC_Protocol_Layer"
RpcRequest["RpcRequest struct\nmethod_id: u64\nparams: Vec<u8>"]
RpcHeader["RpcHeader\nmessage_type: MessageType\nflags: HeaderFlags"]
end
subgraph "Chunking_Layer"
ChunkLogic["DEFAULT_MAX_CHUNK_SIZE\n8KB chunks\nRpcStreamEncoder"]
Chunk1["Chunk 1\n8KB"]
Chunk2["Chunk 2\n8KB"]
ChunkN["Chunk N\nRemaining"]
end
subgraph "Multiplexing_Layer"
StreamId["stream_id assignment\nRpcSession allocator"]
FrameHeader["Frame Header\n4 bytes: stream_id + flags"]
Frame1["Frame 1"]
Frame2["Frame 2"]
end
subgraph "Transport_Layer"
WsFrame["WebSocket Binary Frame"]
Network["TCP/IP Network"]
end
AppData -->|serialize| BitcodeEncode
BitcodeEncode --> SerializedBytes
SerializedBytes --> RpcRequest
RpcRequest --> RpcHeader
RpcHeader --> ChunkLogic
ChunkLogic --> Chunk1
ChunkLogic --> Chunk2
ChunkLogic --> ChunkN
Chunk1 --> StreamId
Chunk2 --> StreamId
ChunkN --> StreamId
StreamId --> FrameHeader
FrameHeader --> Frame1
FrameHeader --> Frame2
Frame1 --> WsFrame
Frame2 --> WsFrame
WsFrame --> Network
Chunking Configuration
The default chunk size is defined in the core library and affects memory usage, latency, and throughput trade-offs:
| Chunk Size | Memory Impact | Latency | Throughput | Use Case |
|---|---|---|---|---|
| 4KB | Lower peak memory | Higher (more frames) | Lower | Memory-constrained environments |
| 8KB (default) | Balanced | Balanced | Balanced | General purpose |
| 16KB | Higher peak memory | Lower (fewer frames) | Higher | High-bandwidth scenarios |
| 32KB+ | High peak memory | Lowest | Highest | Large file transfers |
The chunk size is currently a compile-time constant (DEFAULT_MAX_CHUNK_SIZE). Custom transports can override this by implementing custom encoding logic in their RpcStreamEncoder implementations.
Sources : src/rpc/rpc_internals/rpc_session.rs frame chunking logic
Prebuffering vs Streaming Trade-offs
The framework supports two RPC invocation patterns, each with distinct performance characteristics:
Prebuffered Pattern (RpcMethodPrebuffered):
- Entire request payload buffered in memory before processing
- Entire response payload buffered before returning
- Lower latency for small payloads (< 8KB)
- Simpler error handling (atomic success/failure)
- Example: README.md:102-118 handler registrations
Streaming Pattern (dynamic channels):
- Incremental processing via
bounded_channelorunbounded_channel - Memory usage proportional to channel buffer size
- Enables processing before full payload arrives
- Supports backpressure via bounded channels
- Required for payloads > available memory
| Scenario | Recommended Pattern | Rationale |
|---|---|---|
| Small JSON-like data (< 8KB) | Prebuffered | Single allocation, minimal overhead |
| Medium data (8KB - 1MB) | Prebuffered or Streaming | Depends on memory constraints |
| Large data (> 1MB) | Streaming | Prevents OOM, enables backpressure |
| Real-time data feeds | Streaming | Continuous processing required |
| File uploads/downloads | Streaming | Predictable memory usage |
Sources : README.md:102-118 prebuffered examples, streaming concepts from architecture overview
Smart Transport Strategy for Large Payloads
For payloads exceeding several megabytes, consider implementing a hybrid approach:
- Send small metadata message via muxio RPC
- Transfer large payload via alternative channel (HTTP multipart, object storage presigned URL)
- Send completion notification via muxio RPC
This pattern avoids WebSocket frame size limitations and allows specialized optimization for bulk data transfer while maintaining RPC semantics for control flow.
Example Flow:
Client -> Server: UploadRequest { file_id: "abc", size: 500MB }
Server -> Client: UploadResponse { presigned_url: "https://..." }
Client -> Storage: PUT to presigned_url (outside muxio)
Client -> Server: UploadComplete { file_id: "abc" }
Server -> Client: ProcessingResult { ... }
Sources : Design patterns implied by README.md:46-47 low-latency focus
Extending the Framework
graph TB
subgraph "Core_Traits"
CallerInterface["RpcServiceCallerInterface\nasync fn call_prebuffered\nasync fn call_streaming"]
EndpointInterface["RpcServiceEndpointInterface\nasync fn register_prebuffered\nasync fn register_streaming"]
end
subgraph "Transport_Abstraction"
ReadBytes["read_bytes callback\nfn('static [u8])"]
WriteBytes["write_bytes implementation\nfn send(&self Vec<u8>)"]
end
subgraph "Provided_Implementations"
RpcClient["RpcClient\nTokio + WebSocket"]
RpcWasmClient["RpcWasmClient\nwasm-bindgen bridge"]
RpcServer["RpcServer\nAxum + WebSocket"]
end
subgraph "Custom_Implementation_Example"
CustomTransport["CustomRpcClient\nYour transport layer"]
CustomDispatcher["Arc<Mutex<RpcDispatcher>>\nRequest correlation"]
CustomSession["RpcSession\nStream multiplexing"]
CustomSend["Custom send_bytes\ne.g. UDP, IPC, gRPC"]
CustomTransport -->|owns| CustomDispatcher
CustomDispatcher -->|owns| CustomSession
CustomTransport -->|implements| CustomSend
end
RpcClient -.implements.-> CallerInterface
RpcWasmClient -.implements.-> CallerInterface
RpcServer -.implements.-> EndpointInterface
CustomTransport -.implements.-> CallerInterface
CustomTransport -->|uses| ReadBytes
CustomTransport -->|uses| WriteBytes
CallerInterface -.requires.-> ReadBytes
CallerInterface -.requires.-> WriteBytes
Extension Points and Custom Transports
The muxio framework exposes several well-defined extension points for custom implementations:
Sources : README.md48 RpcServiceCallerInterface description, extensions/muxio-rpc-service-caller/src/caller_interface.rs extensions/muxio-rpc-service-endpoint/src/endpoint_interface.rs
Implementing a Custom Transport
To create a custom transport (e.g., for UDP, Unix domain sockets, or custom protocols), implement the following pattern:
Required Components:
- Transport Handler - Manages the underlying I/O
- RpcDispatcher - Handles request correlation (reuse from core)
- RpcSession - Handles stream multiplexing (reuse from core)
- Trait Implementation - Implements
RpcServiceCallerInterface
Key Integration Points:
| Component | Responsibility | Implementation Required |
|---|---|---|
read_bytes callback | Feed received bytes to dispatcher | Yes - transport-specific |
write_bytes function | Send frames to network | Yes - transport-specific |
RpcDispatcher::call() | Initiate RPC requests | No - use core implementation |
RpcDispatcher::read() | Process incoming frames | No - use core implementation |
| State management | Track connection lifecycle | Yes - transport-specific |
Example Structure:
custom_transport/
├── src/
│ ├── lib.rs
│ ├── custom_client.rs # Implements RpcServiceCallerInterface
│ ├── custom_transport.rs # Transport-specific I/O
│ └── custom_framing.rs # Adapts RpcSession to transport
Reference implementations: extensions/muxio-tokio-rpc-client/src/rpc_client.rs for async pattern, extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs for callback-driven pattern.
Sources : README.md:35-36 runtime agnostic design, extensions/muxio-tokio-rpc-client/src/rpc_client.rs client structure
Runtime-Specific Adapters
The core library’s non-async, callback-driven design enables integration with diverse runtime environments:
Tokio Integration Pattern:
- Wrap
RpcDispatcherinArc<TokioMutex<_>> - Spawn background task for
read_bytesprocessing - Use
tokio::spawnfor concurrent request handling - Example: extensions/muxio-tokio-rpc-client/src/rpc_client.rs:30-38
WASM Integration Pattern:
- Store client in
thread_local!RefCell - Use JavaScript callbacks for async operations
- Avoid blocking operations (no
std::sync::Mutex) - Example: extensions/muxio-wasm-rpc-client/src/static_lib/static_client.rs
Custom Single-Threaded Runtime:
- Wrap
RpcDispatcherinRc<RefCell<_>> - Process
read_byteson main thread - Use callback-based async pattern
- No thread spawning required
Custom Multi-Threaded Runtime:
- Wrap
RpcDispatcherinArc<StdMutex<_>> - Create thread pool for request handlers
- Use channels for cross-thread communication
- Example pattern from Tokio implementation
Sources : DRAFT.md:48-52 runtime model description, extensions/muxio-tokio-rpc-client/src/rpc_client.rs extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs
graph TB
subgraph "Rust_WASM_Module"
WasmApp["Application Code\ncalls RPC methods"]
RpcWasmClient["RpcWasmClient\nimplements RpcServiceCallerInterface"]
StaticClient["MUXIO_STATIC_RPC_CLIENT_REF\nthread_local RefCell"]
Dispatcher["RpcDispatcher\nrequest correlation"]
Session["RpcSession\nstream multiplexing"]
WasmApp -->|uses| RpcWasmClient
RpcWasmClient -->|stores in| StaticClient
RpcWasmClient -->|owns| Dispatcher
Dispatcher -->|owns| Session
end
subgraph "FFI_Boundary"
WriteBytes["#[wasm_bindgen]\nstatic_muxio_write_bytes\nfn(Vec<u8>)"]
JsCallback["JavaScript Callback\nwindow.muxioWriteBytes\n= function(bytes)"]
WriteBytes -->|invokes| JsCallback
end
subgraph "JavaScript_Runtime"
WebSocket["WebSocket Instance\nws.send(bytes)"]
EventLoop["Browser Event Loop"]
OnMessage["ws.onmessage handler"]
ReadBytes["Read Path\ncalls Rust static_muxio_read_bytes"]
JsCallback -->|calls| WebSocket
WebSocket -->|send| EventLoop
EventLoop -->|receive| OnMessage
OnMessage -->|invokes| ReadBytes
end
subgraph "Rust_Read_Path"
StaticReadFn["#[wasm_bindgen]\nstatic_muxio_read_bytes\nfn(&[u8])"]
DispatcherRead["dispatcher.read()\nframe decoding"]
ReadBytes -->|calls| StaticReadFn
StaticReadFn -->|delegates to| DispatcherRead
end
Session -->|generates frames| WriteBytes
DispatcherRead -->|delivers responses| WasmApp
JavaScript and WASM Integration
WASM Bridge Architecture
The WASM client integrates with JavaScript through a minimal FFI bridge that passes byte arrays between Rust and JavaScript:
Sources : extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs extensions/muxio-wasm-rpc-client/src/static_lib/static_client.rs README.md52 FFI description
Static Client Pattern
The WASM client uses a static singleton pattern due to WASM’s limitations with owned callbacks:
Problem : JavaScript callbacks cannot capture owned Rust data (no 'static lifetime guarantees)
Solution : Store client in thread-local static storage
Key Characteristics:
thread_local!ensures single-threaded access (WASM is single-threaded)RefCellprovides interior mutabilityOptionallows initialization after module load- All public API functions access the static client
Sources : extensions/muxio-wasm-rpc-client/src/static_lib/static_client.rs:10-12 extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs
JavaScript Interop Patterns
The JavaScript side must implement a minimal bridge to complete the integration:
Required JavaScript Setup:
| Function | Purpose | Implementation |
|---|---|---|
window.muxioWriteBytes | Rust -> JS data transfer | Callback that sends bytes via WebSocket |
static_muxio_read_bytes() | JS -> Rust data transfer | WASM export invoked from onmessage |
static_muxio_create_client() | Initialize WASM client | WASM export called on WebSocket open |
static_muxio_handle_state_change() | Connection lifecycle | WASM export called on WebSocket state changes |
Example JavaScript Integration:
Sources : extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs architecture, README.md52 wasm-bindgen bridge description
Memory Management Across FFI Boundary
The WASM FFI boundary requires careful memory management to prevent leaks and use-after-free issues:
Rust - > JavaScript (Write Path):
Vec<u8>ownership transferred to JavaScript viawasm-bindgen- JavaScript holds
Uint8Arrayview into WASM linear memory - Critical : JavaScript must not retain references after async operations
- Data copied by
WebSocket.send(), safe to release
JavaScript - > Rust (Read Path):
- JavaScript creates
Uint8Arrayfrom WebSocket data - Passes slice reference to Rust via
wasm-bindgen - Rust copies data into owned structures (
Vec<u8>) - JavaScript can release buffer after function returns
Best Practices:
- Always copy data across FFI boundary, never share references
- Use
wasm-bindgentype conversions (Vec<u8>,&[u8]) - Avoid storing JavaScript arrays in Rust (lifetime issues)
- Avoid storing Rust pointers in JavaScript (invalidation risk)
Sources : extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs implementation, wasm-bindgen safety patterns
WASM Build and Deployment
Building and deploying the WASM client requires specific toolchain configuration:
Build Steps:
Integration into Web Application:
Sources : extensions/muxio-wasm-rpc-client/ structure, WASM build patterns from Cargo.toml target configuration