Files
gh-geoffjay-claude-plugins-…/commands/tokio-migrate.md
2025-11-29 18:28:15 +08:00

9.0 KiB

name, description
name description
tokio-migrate Migrate synchronous code to async Tokio or upgrade between Tokio versions

Tokio Migrate Command

This command assists with migrating synchronous code to async Tokio, upgrading between Tokio versions, or converting from other async runtimes.

Arguments

  • $1 - Migration type: sync-to-async, tokio-upgrade, runtime-switch (required)
  • $2 - Target file or directory (optional, defaults to current directory)
  • $3 - Additional context: Tokio version for upgrades, or source runtime for switches (optional)

Usage

/rust-tokio-expert:tokio-migrate sync-to-async src/handlers/
/rust-tokio-expert:tokio-migrate tokio-upgrade src/ 1.0
/rust-tokio-expert:tokio-migrate runtime-switch src/ async-std

Workflow

1. Sync to Async Migration

When migrating synchronous code to async Tokio:

Analysis Phase

  1. Scan Target Files

    • Use Glob to find all Rust files in target
    • Read files and identify synchronous operations
    • Detect blocking I/O operations
    • Find CPU-intensive operations
    • Identify thread spawning
  2. Identify Conversion Candidates

    • Functions with I/O operations (network, file, database)
    • Functions that spawn threads
    • Functions with sleep/delays
    • Functions with synchronous HTTP clients
    • Functions with blocking mutex operations
  3. Analyze Dependencies

    • Check Cargo.toml for sync crates
    • Identify replacements (e.g., reqwest blocking → async)
    • Find database drivers needing async versions

Migration Phase

  1. Invoke Agent

    • Use Task tool with subagent_type="rust-tokio-expert:tokio-pro"
    • Provide code context and migration plan
  2. Convert Functions to Async

    The agent should transform:

    Synchronous Function:

    use std::fs::File;
    use std::io::Read;
    
    fn read_config(path: &str) -> Result<String, Error> {
        let mut file = File::open(path)?;
        let mut contents = String::new();
        file.read_to_string(&mut contents)?;
        Ok(contents)
    }
    

    To Async:

    use tokio::fs::File;
    use tokio::io::AsyncReadExt;
    
    async fn read_config(path: &str) -> Result<String, Error> {
        let mut file = File::open(path).await?;
        let mut contents = String::new();
        file.read_to_string(&mut contents).await?;
        Ok(contents)
    }
    
  3. Replace Blocking Operations

    Convert common patterns:

    Thread Sleep → Async Sleep:

    // Before
    use std::thread;
    use std::time::Duration;
    
    fn wait() {
        thread::sleep(Duration::from_secs(1));
    }
    
    // After
    use tokio::time::{sleep, Duration};
    
    async fn wait() {
        sleep(Duration::from_secs(1)).await;
    }
    

    Std Mutex → Tokio Mutex:

    // Before
    use std::sync::Mutex;
    
    fn update_state(mutex: &Mutex<State>) {
        let mut state = mutex.lock().unwrap();
        state.update();
    }
    
    // After
    use tokio::sync::Mutex;
    
    async fn update_state(mutex: &Mutex<State>) {
        let mut state = mutex.lock().await;
        state.update();
    }
    

    Thread Spawning → Task Spawning:

    // Before
    use std::thread;
    
    fn spawn_worker() {
        thread::spawn(|| {
            do_work();
        });
    }
    
    // After
    use tokio::task;
    
    async fn spawn_worker() {
        task::spawn(async {
            do_work().await;
        });
    }
    
  4. Update Dependencies in Cargo.toml

    Replace sync crates:

    # Before
    [dependencies]
    reqwest = { version = "0.11", features = ["blocking"] }
    
    # After
    [dependencies]
    reqwest = "0.11"
    tokio = { version = "1", features = ["full"] }
    
  5. Add Runtime Setup

    Add to main.rs:

    #[tokio::main]
    async fn main() -> Result<(), Box<dyn std::error::Error>> {
        // Your async code
        Ok(())
    }
    
  6. Handle CPU-Intensive Operations

    Wrap in spawn_blocking:

    async fn process_data(data: Vec<u8>) -> Result<Vec<u8>, Error> {
        // CPU-intensive work
        let result = tokio::task::spawn_blocking(move || {
            expensive_computation(data)
        }).await?;
    
        Ok(result)
    }
    

2. Tokio Version Upgrade

When upgrading between Tokio versions (e.g., 0.2 → 1.x):

Analysis Phase

  1. Detect Current Version

    • Read Cargo.toml
    • Identify current Tokio version
    • Check dependent crates versions
  2. Identify Breaking Changes

    • Scan for deprecated APIs
    • Find removed features
    • Detect renamed functions

Migration Phase

  1. Update Cargo.toml

    # From Tokio 0.2
    [dependencies]
    tokio = { version = "0.2", features = ["macros", "rt-threaded"] }
    
    # To Tokio 1.x
    [dependencies]
    tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
    
  2. Update Runtime Setup

    // Tokio 0.2
    #[tokio::main]
    async fn main() {
        // ...
    }
    
    // Tokio 1.x (same, but verify features)
    #[tokio::main]
    async fn main() {
        // ...
    }
    
  3. Fix API Changes

    Common migrations:

    Timer API:

    // Tokio 0.2
    use tokio::time::delay_for;
    delay_for(Duration::from_secs(1)).await;
    
    // Tokio 1.x
    use tokio::time::sleep;
    sleep(Duration::from_secs(1)).await;
    

    Timeout API:

    // Tokio 0.2
    use tokio::time::timeout_at;
    
    // Tokio 1.x
    use tokio::time::timeout;
    

    Signal Handling:

    // Tokio 0.2
    use tokio::signal::ctrl_c;
    
    // Tokio 1.x (same, but improved)
    use tokio::signal::ctrl_c;
    
  4. Update Feature Flags

    Map old features to new:

    • rt-threadedrt-multi-thread
    • rt-corert
    • tcpnet
    • dns → removed (use async DNS crates)

3. Runtime Switch

When switching from other runtimes (async-std, smol) to Tokio:

Analysis Phase

  1. Identify Runtime-Specific Code
    • Find runtime initialization
    • Detect runtime-specific APIs
    • Identify spawning patterns

Migration Phase

  1. Replace Runtime Setup

    From async-std:

    // Before
    #[async_std::main]
    async fn main() {
        // ...
    }
    
    // After
    #[tokio::main]
    async fn main() {
        // ...
    }
    
  2. Update Spawning

    // async-std
    use async_std::task;
    task::spawn(async { /* ... */ });
    
    // Tokio
    use tokio::task;
    task::spawn(async { /* ... */ });
    
  3. Replace I/O Types

    // async-std
    use async_std::net::TcpListener;
    
    // Tokio
    use tokio::net::TcpListener;
    
  4. Update Dependencies

    Replace runtime-specific crates:

    # Remove
    async-std = "1"
    
    # Add
    tokio = { version = "1", features = ["full"] }
    

Common Migration Tasks

For all migration types:

  1. Update Tests

    // Before
    #[async_std::test]
    async fn test_something() { }
    
    // After
    #[tokio::test]
    async fn test_something() { }
    
  2. Update Error Handling

    • Ensure error types work with async
    • Add proper error context
    • Use ? operator appropriately
  3. Add Tracing

    • Instrument key functions
    • Add structured logging
    • Set up tracing subscriber
  4. Verification

    • Run cargo check
    • Run cargo test
    • Run cargo clippy
    • Verify no blocking operations remain

Migration Checklist

  • All I/O operations are async
  • No std::thread::sleep usage
  • No std::sync::Mutex in async code
  • CPU-intensive work uses spawn_blocking
  • Runtime properly configured
  • Tests updated to use #[tokio::test]
  • Dependencies updated in Cargo.toml
  • Error handling verified
  • Documentation updated
  • Performance tested

Incremental Migration Strategy

For large codebases:

  1. Identify Migration Boundaries

    • Start with leaf functions (no callers)
    • Move up the call graph gradually
    • Create async versions alongside sync
  2. Bridge Sync and Async

    // Call async from sync
    fn sync_wrapper() -> Result<T, Error> {
        let rt = tokio::runtime::Runtime::new()?;
        rt.block_on(async_function())
    }
    
    // Call sync from async (CPU-intensive)
    async fn async_wrapper() -> Result<T, Error> {
        tokio::task::spawn_blocking(|| {
            sync_function()
        }).await?
    }
    
  3. Migration Order

    • I/O layer first
    • Business logic second
    • API/handlers last
    • Tests continuously

Best Practices

  1. Don't Mix Sync and Async I/O: Choose one model
  2. Use spawn_blocking: For blocking operations you can't convert
  3. Test Thoroughly: Async bugs can be subtle
  4. Profile Performance: Measure before and after
  5. Update Documentation: Note async requirements
  6. Handle Cancellation: Implement proper cleanup
  7. Consider Backpressure: Add flow control

Notes

  • Migration is often incremental - don't try to do everything at once
  • Test each migration step thoroughly
  • Consider performance implications of async
  • Some operations may not benefit from async
  • Document breaking changes for API consumers