Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

regression: RefCell<LineWriter<std::io::stdio::StdoutRaw>> cannot be shared between threads safely #127340

Open
Mark-Simulacrum opened this issue Jul 4, 2024 · 7 comments
Labels
A-io Area: std::io, std::fs, std::net and std::path I-libs-nominated The issue / PR has been nominated for discussion during a libs team meeting. regression-from-stable-to-beta Performance or correctness regression from stable to beta. S-has-mcve Status: A Minimal Complete and Verifiable Example has been found for this issue T-libs Relevant to the library team, which will review and decide on the PR/issue.
Milestone

Comments

@Mark-Simulacrum
Copy link
Member

Mark-Simulacrum commented Jul 4, 2024

The error seems probably correct, but it's not clear why this would have started failing now.

[INFO] [stdout] error[E0277]: `RefCell<LineWriter<std::io::stdio::StdoutRaw>>` cannot be shared between threads safely
[INFO] [stdout]    --> src/lib.rs:205:20
[INFO] [stdout]     |
[INFO] [stdout] 205 |                 Ok(Box::new(stdout))
[INFO] [stdout]     |                    ^^^^^^^^^^^^^^^^ `RefCell<LineWriter<std::io::stdio::StdoutRaw>>` cannot be shared between threads safely
[INFO] [stdout]     |
[INFO] [stdout]     = help: the trait `Sync` is not implemented for `RefCell<LineWriter<std::io::stdio::StdoutRaw>>`, which is required by `StdoutLock<'_>: Sync`
[INFO] [stdout]     = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` instead
[INFO] [stdout]     = note: required for `ReentrantLockGuard<'_, RefCell<LineWriter<std::io::stdio::StdoutRaw>>>` to implement `Sync`
[INFO] [stdout] note: required because it appears within the type `StdoutLock<'_>`
[INFO] [stdout]    --> /rustc/64a1fe67112931359c7c9a222f08fd206255c2b5/library/std/src/io/stdio.rs:614:12
[INFO] [stdout]     = note: required for the cast from `Box<StdoutLock<'_>>` to `Box<dyn std::io::Write + Sync>`
@Mark-Simulacrum Mark-Simulacrum added the regression-from-stable-to-beta Performance or correctness regression from stable to beta. label Jul 4, 2024
@Mark-Simulacrum Mark-Simulacrum added this to the 1.80.0 milestone Jul 4, 2024
@rustbot rustbot added I-prioritize Issue: Indicates that prioritization has been requested for this issue. needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. labels Jul 4, 2024
@Mark-Simulacrum Mark-Simulacrum added the E-needs-mcve Call for participation: This issue has a repro, but needs a Minimal Complete and Verifiable Example label Jul 4, 2024
@workingjubilee workingjubilee added T-libs Relevant to the library team, which will review and decide on the PR/issue. and removed T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Jul 5, 2024
@workingjubilee
Copy link
Contributor

Minimized:

use std::io::{self, Write};

pub fn open_stdout() -> io::Result<Box<dyn Write + Sync + 'static>> {
    let stdout = Box::leak(Box::new(io::stdout()));
    let stdout = stdout.lock();
    Ok(Box::new(stdout))
}

@workingjubilee
Copy link
Contributor

workingjubilee commented Jul 5, 2024

cargo-bisect-rustc informs us it is rust-lang-ci@fd225fc

This is collateral damage of

...apparently, std said it can have a little bit of unsoundness, as a treat? It seems like exposing #121440 is proving to be a good idea because otherwise we would not have had this actually audited.

cc @joboet @programmerjake

@workingjubilee workingjubilee added T-libs Relevant to the library team, which will review and decide on the PR/issue. A-io Area: std::io, std::fs, std::net and std::path S-has-mcve Status: A Minimal Complete and Verifiable Example has been found for this issue and removed E-needs-mcve Call for participation: This issue has a repro, but needs a Minimal Complete and Verifiable Example I-prioritize Issue: Indicates that prioritization has been requested for this issue. needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. labels Jul 5, 2024
@workingjubilee
Copy link
Contributor

workingjubilee commented Jul 5, 2024

We have the options of

  1. accepting this regression
    • pros: we don't have to do anything
    • cons: ~2 crates relying on an unsound impl deep inside std are broken
  2. making the internals of stdout() a custom internal reentrant lock type with appropriate internal mutability and stuff (and hoping we get it right)
    • pros: the crates build and we can do whatever since this is internal impl details
    • cons: odds are good we get it wrong again
    • small variation: unsafe impl Sync for StdoutLock, has the same "we got it right... right?" problem
  3. adjusting ReentrantLockGuard until the original impl was correct
    • pros: probably makes the type more useful?
    • cons: maybe impossible1, idk, haven't thought about it deeply
  4. slapping a Mutex into this bad boy
    • pros: known correct
    • cons: deadlocks ahoy? kinda silly

@rustbot label: I-libs-nominated

Footnotes

  1. https://github.com/rust-lang/rust/issues/127340#issuecomment-2210632093

@rustbot rustbot added the I-libs-nominated The issue / PR has been nominated for discussion during a libs team meeting. label Jul 5, 2024
@workingjubilee
Copy link
Contributor

The other one minimizes to this:

use std::io;

fn chunk() -> Result<(), io::Error> {
    let stdout = io::stdout();
    let mut handle = stdout.lock();
    write_csv(&mut handle)
}

pub fn write_csv(_w: &mut (dyn io::Write + Sync)) -> Result<(), io::Error> {
    Ok(())
}

In both cases, we can see that the problem is only caused if someone deliberately erases the type of the StdoutLock and instead makes it a &mut dyn io::Write + Sync, a transition it can no longer fulfill.

@Ciel-MC
Copy link

Ciel-MC commented Jul 5, 2024

Can we try and see how much of crates break if we do 1? 2 feels like too much effort for little gain and 3 is not very pretty.

Disclaimer: Absolute stdlib noob, just offering my first thoughts because it’s brought into my attention

@workingjubilee
Copy link
Contributor

workingjubilee commented Jul 5, 2024

Based on @Mark-Simulacrum's original post, it seems only 2 crates with no dependents on crates.io, one of which seems to be an abandoned toy project. That strongly suggests people generally aren't doing this. There's lots to recommend taking the "shrug and do nothing" option, I just wanted to be thorough.

If 3 ("fix the Guard to be actually-Sync") is doable without sacrificing large parts of ReentrantLock's functionality (or performance?) I would say it's the hands-down winner. I just have given it literally zero thought, though it sounds more like wishful thinking than plausible.

@joboet
Copy link
Contributor

joboet commented Jul 5, 2024

Changing ReentrantLockGuard is out of the question: you can get a &T from a &ReentrantLockGuard<T>, so T must be Sync for ReentrantLockGuard to be.

Fixing StdoutLock doesn't really make sense, as it obviates the point of locking: If we can get a &StdoutLock on multiple threads, we have to make the inner state a Mutex (this won't cause any deadlocks by the way, as the Mutex is only locked while executing std-internal code). So why have a lock method at all, then?

Therefore, I strongly prefer option 1: this was a soundness bug in std, so a breaking change is entirely justified.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-io Area: std::io, std::fs, std::net and std::path I-libs-nominated The issue / PR has been nominated for discussion during a libs team meeting. regression-from-stable-to-beta Performance or correctness regression from stable to beta. S-has-mcve Status: A Minimal Complete and Verifiable Example has been found for this issue T-libs Relevant to the library team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

5 participants