crossbeam icon indicating copy to clipboard operation
crossbeam copied to clipboard

A method to query the scope if anything panicked

Open vorner opened this issue 6 years ago • 7 comments

Let's say I have a code like this:

thread::scope(|scope| {
    scope.thread(|| for item in long_vector {
        do_stuff(item);
    });
    scope.thread(|| for item in another_long_vector {
        do_stuff(item);
    });
});

Now, if one of the threads panics, the scope has little choice than wait for the other to terminate as well, which could be a long time (or, in certain cases, might never happen, because it relies on the existence of the dead thread).

It would still be useful to at least be able to query the scope if any of the threads panicked and let the thread terminate on its own will, eg:

scope.thread(|| for item in long_vector {
    if scope.anything_panicked() {
      break;
    }
    do_stuff(item);
});

vorner avatar Jul 24 '18 07:07 vorner

It seems this should be easy to implement manually, perhaps like this:

let panicked = AtomicBool::new(false);
crossbeam::scope(|scope| {
    let spawn = |f| {
        scope.spawn(move || {
            match panic::catch_unwind(f) => {
                Ok(r) => r,
                Err(e) => {
                    panicked.store(true, SeqCst);
                    panic::resume_unwind(e);
                }
            }
        })
    };

    spawn(|| {
        for item in long_vector {
            if panicked.load(SeqCst) {
                break;
            }
            do_stuff(item);
        }
    });
});

But it might be worth implementing this functionality in crossbeam. Here's a suggestion:

impl Scope<'_> {
    fn running_threads() -> usize;
    fn panicked_threads() -> usize;
    fn completed_threads() -> usize;
}

ghost avatar Jul 24 '18 10:07 ghost

Implementing it manually in every single one thread might be quite tedious I think. It would be nice to already have such support ‒ if it would not be too much work to do it inside (which it probably shouldn't be).

Yes, the proposed methods actually look better, they are more general.

vorner avatar Jul 24 '18 10:07 vorner

My last suggestion on how to implement this manually was not very ergonomic, but here's a better idea using scopeguard and the new AtomicCell:

let panicked = AtomicCell::new(false);

thread::scope(|scope| {
    scope.thread(|| {
        defer_on_unwind! { panicked.store(true); }

        for item in long_vector {
            do_stuff(item);
        }
    });

    scope.thread(|| {
        for item in another_long_vector {
            if panicked.load() {
                break;
            }
            do_stuff(item);
        }
    });
});

It's even easy to do counters:

let finished = AtomicCell::new(0);
let panicked = AtomicCell::new(0);

thread::scope(|scope| {
    scope.thread(|| {
        defer! { finished.fetch_add(1); }
        defer_on_unwind! { panicked.fetch_add(1); }

        for item in long_vector {
            do_stuff(item);
        }
    });
});

Given the simplicity of this solution, I wonder whether it's worth having built-in counters in Scope at all.

Note that this way you also have more precise control over which threads are actually tracked. For example, you might want to check for panics only in some of the threads rather than in all threads spawned within the scope.

ghost avatar Sep 29 '18 00:09 ghost

The scopeguard trick certainly lowers the cost of implementing it. Though I still wonder if it would be more natural to be able to query the scope for this.

Just out of curiosity, what is the advantage of AtomicCell over AtomicBool/AtomicUsize here?

vorner avatar Sep 29 '18 09:09 vorner

Just out of curiosity, what is the advantage of AtomicCell over AtomicBool/AtomicUsize here?

AtomicCell is more ergonomic because you don't have to specify orderings.

ghost avatar Sep 30 '18 10:09 ghost

I agree with @stjepang on this issue. We can implement status trackers on top of scoped threads and other utilities, so I don't see much necessity for adding it as a feature. Furthermore, tracking the status will add a slight overhead, which I would like to avoid in a general-purpose library.

jeehoonkang avatar Mar 03 '19 13:03 jeehoonkang

I came across a similar problem today: I have a producer thread and a consumer thread in a crossbeam::scope and if one side panics the entire thing deadlocks permanently. This is a pretty annoying footgun.

khuey avatar Dec 15 '21 20:12 khuey

crossbeam's scope threads have been soft-deprecated in favor of the more efficient std::thread::scope that stabilized on Rust 1.63, so there are no plans to accept new APIs to it.

taiki-e avatar Dec 02 '23 11:12 taiki-e