memlink-runtime 0.2.0

Dynamic module loading framework with circuit breaker, caching, pooling, health checks, versioning, and auto-discovery
Documentation

Memlink Runtime

Dynamic module loading and execution framework for Rust


Overview

Memlink Runtime is a high-performance dynamic module loading system that enables plugin architectures, hot-reloadable code, and safe FFI execution. Load shared libraries at runtime, call methods on them, and unload them without restarting your application.

Key Features

  • ๐Ÿ”Œ Dynamic Loading - Load .so, .dll, .dylib files at runtime
  • ๐Ÿ›ก๏ธ Panic Isolation - Module panics don't crash your application
  • ๐Ÿ”ฅ Hot Reload - Replace modules with zero downtime
  • ๐Ÿ“Š Prometheus Metrics - Built-in observability
  • ๐Ÿงต Thread-Safe - Concurrent module calls from multiple threads
  • ๐Ÿ“ฆ Multi-Module - Load and manage multiple modules simultaneously
  • โšก Circuit Breaker - Prevent cascading failures with automatic recovery
  • ๐Ÿ—„๏ธ Request Caching - Cache responses for improved performance
  • ๐ŸŠ Module Pooling - Multiple instances with load balancing
  • โค๏ธ Health Checks - Liveness and readiness probes
  • ๐Ÿ”— Dependency Management - Track module dependencies and load order
  • ๐Ÿ“ฆ Version Management - Side-by-side versions with gradual migration
  • ๐Ÿญ Request Batching - Combine requests for better throughput
  • ๐Ÿ”’ Sandboxing - Resource limits and permission control
  • ๐Ÿ“ก Event System - Lifecycle hooks and middleware
  • ๐Ÿ” Auto-Discovery - Watch directories and auto-load modules

Quick Start

Basic Example

use memlink_runtime::runtime::{Runtime, ModuleRuntime};
use memlink_runtime::resolver::ModuleRef;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create runtime
    let runtime = Runtime::with_local_resolver();
    
    // Load a module
    let handle = runtime.load(
        ModuleRef::parse("./my_module.so")?
    )?;
    
    // Call a method
    let result = runtime.call(handle, "process", b"input data")?;
    println!("Result: {:?}", String::from_utf8_lossy(&result));
    
    // Unload when done
    runtime.unload(handle)?;
    
    Ok(())
}

Creating Modules

Modules are shared libraries that export three required functions:

Minimal Module (C)

#include <stdint.h>
#include <string.h>

__attribute__((visibility("default")))
int memlink_init(const unsigned char* config, unsigned long config_len) {
    (void)config;
    (void)config_len;
    return 0;
}

__attribute__((visibility("default")))
int memlink_call(unsigned int method_id, const unsigned char* args,
                unsigned long args_len, unsigned char* output) {
    (void)method_id;
    // Echo input back
    if (args_len > 0 && args != NULL) {
        memcpy(output, args, args_len);
    }
    return 0;
}

__attribute__((visibility("default")))
int memlink_shutdown(void) {
    return 0;
}

Build Commands

Platform Command
Linux cc -shared -fPIC -O2 -o my_module.so my_module.c
Windows cl /LD my_module.c /Fe:my_module.dll
macOS cc -shared -fPIC -O2 -o my_module.dylib my_module.c

See ABI Documentation for full specification.


Advanced Usage

Loading Multiple Modules

use memlink_runtime::runtime::{Runtime, ModuleRuntime};
use memlink_runtime::resolver::ModuleRef;
use std::sync::Arc;
use std::thread;

let runtime = Arc::new(Runtime::with_local_resolver());

// Load multiple modules
let math = runtime.load(ModuleRef::parse("./math.so")?)?;
let string = runtime.load(ModuleRef::parse("./string.so")?)?;
let crypto = runtime.load(ModuleRef::parse("./crypto.so")?)?;

// Call concurrently from different threads
let mut handles = vec![];

for (name, module) in [("math", math), ("string", string), ("crypto", crypto)] {
    let rt = Arc::clone(&runtime);
    let h = thread::spawn(move || {
        for i in 0..100 {
            rt.call(module, "process", format!("{}_{}", name, i).as_bytes()).unwrap();
        }
    });
    handles.push(h);
}

for h in handles {
    h.join().unwrap();
}

Hot Reload

use memlink_runtime::reload::ReloadConfig;
use std::time::Duration;

// Reload a module with new version
let config = ReloadConfig::default()
    .with_drain_timeout(Duration::from_secs(30))
    .with_state_preservation();

let reload_state = runtime.reload_with_config(
    old_handle,
    ModuleRef::parse("./my_module_v2.so")?,
    config
)?;

// Old module drains in-flight calls before unloading

Metrics and Monitoring

// Get usage statistics
let usage = runtime.get_usage(handle).unwrap();
println!("Calls: {}", usage.call_count);
println!("Arena: {} bytes ({:.2}%)", 
    usage.arena_bytes, 
    usage.arena_usage * 100.0
);

// Export Prometheus metrics
let metrics = RuntimeMetrics::new();
// ... after operations ...
println!("{}", metrics.prometheus_export());

Circuit Breaker Pattern

use memlink_runtime::{CircuitRegistry, CircuitConfig};

let registry = CircuitRegistry::new();

// Configure circuit breaker
let config = CircuitConfig {
    failure_threshold: 5,
    success_threshold: 3,
    open_timeout: std::time::Duration::from_secs(30),
    ..Default::default()
};
registry.register("risky_module", config);

// Check before calling
if registry.can_execute("risky_module") {
    match runtime.call(handle, "risky_op", data) {
        Ok(result) => registry.record_success("risky_module"),
        Err(_) => registry.record_failure("risky_module"),
    }
} else {
    // Circuit is open, reject immediately
    eprintln!("Request rejected due to circuit open");
}

Request Caching

use memlink_runtime::{RequestCache, CacheConfig};

let config = CacheConfig {
    default_ttl: std::time::Duration::from_secs(60),
    max_entries: 10000,
    max_memory_bytes: 256 * 1024 * 1024,
};
let cache = RequestCache::new(config);

let key = RequestCache::generate_key("expensive_method", &input);

// Check cache first
if let Some(cached) = cache.get(key) {
    return Ok(cached);
}

// Cache miss - compute and cache
let result = runtime.call(handle, "expensive_method", &input)?;
cache.set(key, result.clone(), None);
Ok(result)

Module Pooling & Load Balancing

use memlink_runtime::{ModulePool, PoolConfig, LoadBalanceStrategy};

let config = PoolConfig {
    min_instances: 2,
    max_instances: 8,
    target_load: 0.6,
    scale_up_threshold: 0.8,
    ..Default::default()
};

let pool = ModulePool::new("worker".to_string(), config);

// Add multiple instances
for path in &["worker1.so", "worker2.so", "worker3.so"] {
    let instance = runtime.load(ModuleRef::parse(*path)?)?;
    pool.add_instance(instance);
}

// Select instance based on load balancing strategy
if let Some(instance) = pool.select_instance() {
    let result = runtime.call(instance, "work", input)?;
}

Health Checks

use memlink_runtime::{HealthRegistry, HealthConfig};

let config = HealthConfig {
    check_interval: std::time::Duration::from_secs(10),
    timeout: std::time::Duration::from_secs(5),
    failure_threshold: 3,
    ..Default::default()
};

let registry = HealthRegistry::new();
let tracker = registry.get_or_create("database_module");

// Record health check results
tracker.record_success(latency_us).await;
// or
tracker.record_failure(Some("connection timeout".to_string())).await;

// Check health status
match tracker.status().await {
    HealthStatus::Healthy => println!("Module is healthy"),
    HealthStatus::Unhealthy => println!("Module is unhealthy!"),
    HealthStatus::Unknown => println!("Health unknown"),
}

Dependency Management

use memlink_runtime::{DependencyGraph, ModuleDependency};

let graph = DependencyGraph::new();

// Define dependencies
graph.add_module("database", vec![]);
graph.add_module("cache", vec![
    ModuleDependency { name: "database".to_string(), min_version: None, optional: false }
]);
graph.add_module("api", vec![
    ModuleDependency { name: "cache".to_string(), min_version: None, optional: false },
    ModuleDependency { name: "database".to_string(), min_version: None, optional: true }
]);

// Get correct load order
let order = graph.load_order()?; // ["database", "cache", "api"]

// Load in order
for module_name in order {
    runtime.load(ModuleRef::parse(&format!("{}.so", module_name))?)?;
}

Version Management

use memlink_runtime::{VersionManager, ModuleVersion, TrafficRouting};

let manager = VersionManager::new("payment_processor".to_string());

// Register multiple versions
manager.register_version(ModuleVersion::new(1, 0, 0), "/path/v1.so");
manager.register_version(ModuleVersion::new(2, 0, 0), "/path/v2.so");

// Activate v1
manager.activate_version(&ModuleVersion::new(1, 0, 0));

// Configure gradual migration (10% to v2)
let routing = TrafficRouting {
    new_version_percentage: 10,
    min_requests: 100,
    error_threshold: 0.01,
};
manager.set_routing(routing);

// Select version for each request
if let Some(version) = manager.select_version() {
    // Route to appropriate version
}

Event System

use memlink_runtime::{EventRegistry, ModuleEvent, EventListener};

struct MyListener;
impl EventListener for MyListener {
    fn on_event(&self, event: &ModuleEvent) {
        match event {
            ModuleEvent::Loaded { name, duration_ms } => {
                println!("Module {} loaded in {}ms", name, duration_ms);
            }
            ModuleEvent::CallCompleted { module, method, duration_us, success } => {
                println!("{}.{} completed in {}ฮผs (success: {})", 
                    module, method, duration_us, success);
            }
            _ => {}
        }
    }
}

let registry = EventRegistry::new();
registry.register("my_listener", Arc::new(MyListener));

// Events are automatically emitted during module operations

Architecture

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    Your Application                              โ”‚
โ”‚                     (Memlink Runtime)                            โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  Runtime Components                                              โ”‚
โ”‚  โ”œโ”€ Resolver (locate modules)                                    โ”‚
โ”‚  โ”œโ”€ Loader (load shared libs)                                    โ”‚
โ”‚  โ”œโ”€ Instance Manager (track loaded modules)                      โ”‚
โ”‚  โ”œโ”€ Panic Handler (catch module panics)                          โ”‚
โ”‚  โ”œโ”€ Circuit Breaker (fault tolerance)                            โ”‚
โ”‚  โ”œโ”€ Request Cache (performance)                                  โ”‚
โ”‚  โ”œโ”€ Module Pool (load balancing)                                 โ”‚
โ”‚  โ”œโ”€ Health Tracker (liveness/readiness)                          โ”‚
โ”‚  โ”œโ”€ Dependency Graph (load order)                                โ”‚
โ”‚  โ”œโ”€ Version Manager (gradual migration)                          โ”‚
โ”‚  โ”œโ”€ Request Batcher (throughput)                                 โ”‚
โ”‚  โ”œโ”€ Sandbox (resource limits)                                    โ”‚
โ”‚  โ”œโ”€ Event Registry (lifecycle hooks)                             โ”‚
โ”‚  โ”œโ”€ Discovery (auto-loading)                                     โ”‚
โ”‚  โ””โ”€ Metrics (Prometheus-compatible)                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
         โ”‚
         โ”œโ”€โ†’ [module_a.so] โ”€โ”€ memlink_init/call/shutdown
         โ”œโ”€โ†’ [module_b.dll] โ”€โ”€ memlink_init/call/shutdown
         โ””โ”€โ†’ [module_c.dylib] โ”€โ”€ memlink_init/call/shutdown

Components

Component Description
Runtime High-level API for module management
Resolver Locates and validates module files
Loader Loads shared libraries and resolves symbols
Instance Represents a loaded module
Arena Fast bump allocator for module memory
Metrics Collects and exports runtime statistics
CircuitBreaker Fault tolerance with automatic recovery
RequestCache Response caching with TTL
ModulePool Instance pooling with load balancing
HealthTracker Liveness and readiness probes
DependencyGraph Module dependency tracking
VersionManager Side-by-side version management
RequestBatcher Request batching for throughput
ResourceTracker Resource usage monitoring
EventRegistry Lifecycle event emission
ModuleDiscovery Directory watching and auto-loading

Performance

Operation Latency Throughput
Module load 92 ฮผs -
Method call (64 bytes) 210 ns 4.7M calls/sec (single thread)
Module unload 52 ฮผs -
Hot reload 237 ฮผs -
Concurrent calls (8 threads) - 2.5M calls/sec
Memory overhead 0.7 MB/module -

Key Highlights:

  • โœ… Sub-microsecond call latency
  • โœ… Linear scalability up to 8 threads
  • โœ… Fast hot reload with zero downtime
  • โœ… Low memory footprint

See Performance Benchmarks for detailed methodology, full results, and optimization recommendations.


Use Cases

Plugin Systems

Load user-created plugins without recompiling your application:

// Load all plugins from a directory
for entry in std::fs::read_dir("./plugins")? {
    let path = entry?.path();
    if path.extension() == Some("so".as_ref()) {
        runtime.load(ModuleRef::parse(path.to_str().unwrap())?)?;
    }
}

Hot-Reloadable Business Logic

Update logic in production without downtime:

// Watch for file changes
notify::Watcher::new(move |event| {
    if event.path.ends_with("business_logic.so") {
        runtime.reload(handle, ModuleRef::parse("./business_logic.so")?)?;
    }
})?;

Sandboxed Execution

Isolate risky code that might panic:

// Module panics are caught and converted to errors
match runtime.call(handle, "risky_operation", data) {
    Ok(result) => println!("Success"),
    Err(Error::ModulePanicked(msg)) => eprintln!("Module panicked: {}", msg),
    Err(e) => eprintln!("Error: {}", e),
}

API Reference

Core Traits

Trait Purpose
ModuleRuntime Main interface for module operations
ModuleResolver Resolve module references to artifacts

Key Types

Type Description
Runtime Default runtime implementation
ModuleHandle Opaque handle to loaded module
ModuleRef Module reference (path or registry)
ModuleUsage Usage statistics per module
ReloadState Tracks hot-reload operations
CircuitBreaker Circuit breaker for fault tolerance
CircuitRegistry Registry of circuit breakers
RequestCache Response cache with TTL
ModulePool Pool of module instances
HealthTracker Health check tracker
HealthRegistry Registry of health trackers
DependencyGraph Module dependency graph
VersionManager Version management
TrafficRouting Gradual migration config
RequestBatcher Request batching
ResourceTracker Resource usage tracking
EventRegistry Event emission
ModuleDiscovery Module auto-discovery

Full API Documentation


Examples

Run the included examples:

# Multi-module concurrent calls

cargo run --example m2


# Build test modules first (Linux/WSL)

cd examples/modules/build

node build.js --linux

See examples/modules/README.md for module documentation.


License

Memlink Runtime is licensed under the Apache License 2.0 (LICENSE.


Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Run cargo test and cargo clippy
  4. Submit a pull request

Development

# Build

cargo build


# Test

cargo test


# Lint

cargo clippy --all-targets


# Format

cargo fmt --all


# Build docs

cargo doc --open


# Run integration tests

cargo test --test integration

Testing

The runtime includes comprehensive test coverage:

  • 41 unit tests - Testing individual components
  • 13 integration tests - Testing all 10 enterprise features
  • 6 platform-specific tests - Linux-only module tests
# Run all tests

cargo test -p memlink-runtime


# Run only integration tests

cargo test --test integration


# Run with output

cargo test -- --nocapture


Related Crates


Support