Skip to content

StdoutLock is unsound #805

Closed
Closed
@LunaBorowska

Description

@LunaBorowska

Run the following program:

use async_std::io::prelude::WriteExt;
use async_std::{io as async_io, task};
use std::io::{self, Write};
use std::thread;

#[async_std::main]
async fn main() -> io::Result<()> {
    let mut async_locked = async_io::stdout().lock().await;
    let fut = task::spawn_blocking(|| {
        let stdout = io::stdout();
        let mut locked = stdout.lock();
        writeln!(locked, "sync stdout in thread {:?}", thread::current())?;
        locked.flush()?;
        Ok(())
    });
    async_std::writeln!(
        async_locked,
        "async stdout in thread {:?}",
        thread::current(),
    )
    .await?;
    async_locked.flush().await?;
    fut.await
}

Cargo.toml dependencies

[dependencies]
async-std = { version = "1.6.0", features = [ "attributes", "unstable" ] }

This program violates the invariant of StdoutLock (there must never be two instances of StdoutLock owned by different threads), leading to UB and panics like the following.

sync stdout in thread Thread { id: ThreadId(10), name: None }
thread 'main' panicked at 'already borrowed: BorrowMutError', /rustc/8d69840ab92ea7f4d323420088dd8c9775f180cd/src/libcore/cell.rs:878:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Considering lock is an unstable API, I would recommend removing the API, and suggest usage of spawn_blocking instead. I don't think it's possible for async StdoutLock to ever be sound with threaded executors

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions