Rewrite std::sync::TaskPool to be load balancing and panic-resistant
The previous implementation was very likely to cause panics during
unwinding through this process:
- child panics, drops its receiver
- taskpool comes back around and sends another job over to that child
- the child receiver has hung up, so the taskpool panics on send
- during unwinding, the taskpool attempts to send a quit message to
the child, causing a panic during unwinding
- panic during unwinding causes a process abort
This meant that TaskPool upgraded any child panic to a full process
abort. This came up in Iron when it caused crashes in long-running
servers.
This implementation uses a single channel to communicate between
spawned tasks and the TaskPool, which significantly reduces the complexity
of the implementation and cuts down on allocation. The TaskPool uses
the channel as a single-producer-multiple-consumer queue.
Additionally, through the use of send_opt and recv_opt instead of
send and recv, this TaskPool is robust on the face of child panics,
both before, during, and after the TaskPool itself is dropped.
Due to the TaskPool no longer using an `init_fn_factory`, this is a
[breaking-change]
otherwise, the API has not changed.
If you used `init_fn_factory` in your code, and this change breaks for
you, you can instead use an `AtomicUint` counter and a channel to
move information into child tasks.
2014-11-13 18:04:28 -08:00
|
|
|
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
|
2012-12-03 16:48:01 -08:00
|
|
|
// file at the top-level directory of this distribution and at
|
|
|
|
// http://rust-lang.org/COPYRIGHT.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
|
|
// option. This file may not be copied, modified, or distributed
|
|
|
|
// except according to those terms.
|
|
|
|
|
2014-12-26 16:04:27 -05:00
|
|
|
//! Abstraction of a thread pool for basic parallelism.
|
2012-10-31 18:29:44 -07:00
|
|
|
|
2015-01-22 18:22:03 -08:00
|
|
|
#![unstable(feature = "std_misc",
|
2015-01-12 18:40:19 -08:00
|
|
|
reason = "the semantics of a failing task and whether a thread is \
|
|
|
|
re-attached to a thread pool are somewhat unclear, and the \
|
|
|
|
utility of this type in `std::sync` is questionable with \
|
|
|
|
respect to the jobs of other primitives")]
|
2014-12-29 15:03:01 -08:00
|
|
|
|
2014-06-07 11:13:26 -07:00
|
|
|
use core::prelude::*;
|
|
|
|
|
Rewrite std::sync::TaskPool to be load balancing and panic-resistant
The previous implementation was very likely to cause panics during
unwinding through this process:
- child panics, drops its receiver
- taskpool comes back around and sends another job over to that child
- the child receiver has hung up, so the taskpool panics on send
- during unwinding, the taskpool attempts to send a quit message to
the child, causing a panic during unwinding
- panic during unwinding causes a process abort
This meant that TaskPool upgraded any child panic to a full process
abort. This came up in Iron when it caused crashes in long-running
servers.
This implementation uses a single channel to communicate between
spawned tasks and the TaskPool, which significantly reduces the complexity
of the implementation and cuts down on allocation. The TaskPool uses
the channel as a single-producer-multiple-consumer queue.
Additionally, through the use of send_opt and recv_opt instead of
send and recv, this TaskPool is robust on the face of child panics,
both before, during, and after the TaskPool itself is dropped.
Due to the TaskPool no longer using an `init_fn_factory`, this is a
[breaking-change]
otherwise, the API has not changed.
If you used `init_fn_factory` in your code, and this change breaks for
you, you can instead use an `AtomicUint` counter and a channel to
move information into child tasks.
2014-11-13 18:04:28 -08:00
|
|
|
use sync::{Arc, Mutex};
|
2014-12-23 11:53:35 -08:00
|
|
|
use sync::mpsc::{channel, Sender, Receiver};
|
|
|
|
use thread::Thread;
|
2014-11-26 08:12:18 -05:00
|
|
|
use thunk::Thunk;
|
2012-10-31 18:29:44 -07:00
|
|
|
|
Rewrite std::sync::TaskPool to be load balancing and panic-resistant
The previous implementation was very likely to cause panics during
unwinding through this process:
- child panics, drops its receiver
- taskpool comes back around and sends another job over to that child
- the child receiver has hung up, so the taskpool panics on send
- during unwinding, the taskpool attempts to send a quit message to
the child, causing a panic during unwinding
- panic during unwinding causes a process abort
This meant that TaskPool upgraded any child panic to a full process
abort. This came up in Iron when it caused crashes in long-running
servers.
This implementation uses a single channel to communicate between
spawned tasks and the TaskPool, which significantly reduces the complexity
of the implementation and cuts down on allocation. The TaskPool uses
the channel as a single-producer-multiple-consumer queue.
Additionally, through the use of send_opt and recv_opt instead of
send and recv, this TaskPool is robust on the face of child panics,
both before, during, and after the TaskPool itself is dropped.
Due to the TaskPool no longer using an `init_fn_factory`, this is a
[breaking-change]
otherwise, the API has not changed.
If you used `init_fn_factory` in your code, and this change breaks for
you, you can instead use an `AtomicUint` counter and a channel to
move information into child tasks.
2014-11-13 18:04:28 -08:00
|
|
|
struct Sentinel<'a> {
|
2014-11-26 08:12:18 -05:00
|
|
|
jobs: &'a Arc<Mutex<Receiver<Thunk>>>,
|
Rewrite std::sync::TaskPool to be load balancing and panic-resistant
The previous implementation was very likely to cause panics during
unwinding through this process:
- child panics, drops its receiver
- taskpool comes back around and sends another job over to that child
- the child receiver has hung up, so the taskpool panics on send
- during unwinding, the taskpool attempts to send a quit message to
the child, causing a panic during unwinding
- panic during unwinding causes a process abort
This meant that TaskPool upgraded any child panic to a full process
abort. This came up in Iron when it caused crashes in long-running
servers.
This implementation uses a single channel to communicate between
spawned tasks and the TaskPool, which significantly reduces the complexity
of the implementation and cuts down on allocation. The TaskPool uses
the channel as a single-producer-multiple-consumer queue.
Additionally, through the use of send_opt and recv_opt instead of
send and recv, this TaskPool is robust on the face of child panics,
both before, during, and after the TaskPool itself is dropped.
Due to the TaskPool no longer using an `init_fn_factory`, this is a
[breaking-change]
otherwise, the API has not changed.
If you used `init_fn_factory` in your code, and this change breaks for
you, you can instead use an `AtomicUint` counter and a channel to
move information into child tasks.
2014-11-13 18:04:28 -08:00
|
|
|
active: bool
|
2012-10-31 18:29:44 -07:00
|
|
|
}
|
|
|
|
|
Rewrite std::sync::TaskPool to be load balancing and panic-resistant
The previous implementation was very likely to cause panics during
unwinding through this process:
- child panics, drops its receiver
- taskpool comes back around and sends another job over to that child
- the child receiver has hung up, so the taskpool panics on send
- during unwinding, the taskpool attempts to send a quit message to
the child, causing a panic during unwinding
- panic during unwinding causes a process abort
This meant that TaskPool upgraded any child panic to a full process
abort. This came up in Iron when it caused crashes in long-running
servers.
This implementation uses a single channel to communicate between
spawned tasks and the TaskPool, which significantly reduces the complexity
of the implementation and cuts down on allocation. The TaskPool uses
the channel as a single-producer-multiple-consumer queue.
Additionally, through the use of send_opt and recv_opt instead of
send and recv, this TaskPool is robust on the face of child panics,
both before, during, and after the TaskPool itself is dropped.
Due to the TaskPool no longer using an `init_fn_factory`, this is a
[breaking-change]
otherwise, the API has not changed.
If you used `init_fn_factory` in your code, and this change breaks for
you, you can instead use an `AtomicUint` counter and a channel to
move information into child tasks.
2014-11-13 18:04:28 -08:00
|
|
|
impl<'a> Sentinel<'a> {
|
2014-11-26 08:12:18 -05:00
|
|
|
fn new(jobs: &Arc<Mutex<Receiver<Thunk>>>) -> Sentinel {
|
Rewrite std::sync::TaskPool to be load balancing and panic-resistant
The previous implementation was very likely to cause panics during
unwinding through this process:
- child panics, drops its receiver
- taskpool comes back around and sends another job over to that child
- the child receiver has hung up, so the taskpool panics on send
- during unwinding, the taskpool attempts to send a quit message to
the child, causing a panic during unwinding
- panic during unwinding causes a process abort
This meant that TaskPool upgraded any child panic to a full process
abort. This came up in Iron when it caused crashes in long-running
servers.
This implementation uses a single channel to communicate between
spawned tasks and the TaskPool, which significantly reduces the complexity
of the implementation and cuts down on allocation. The TaskPool uses
the channel as a single-producer-multiple-consumer queue.
Additionally, through the use of send_opt and recv_opt instead of
send and recv, this TaskPool is robust on the face of child panics,
both before, during, and after the TaskPool itself is dropped.
Due to the TaskPool no longer using an `init_fn_factory`, this is a
[breaking-change]
otherwise, the API has not changed.
If you used `init_fn_factory` in your code, and this change breaks for
you, you can instead use an `AtomicUint` counter and a channel to
move information into child tasks.
2014-11-13 18:04:28 -08:00
|
|
|
Sentinel {
|
|
|
|
jobs: jobs,
|
|
|
|
active: true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cancel and destroy this sentinel.
|
|
|
|
fn cancel(mut self) {
|
|
|
|
self.active = false;
|
|
|
|
}
|
2013-02-27 19:13:53 -05:00
|
|
|
}
|
|
|
|
|
2013-03-20 18:18:57 -07:00
|
|
|
#[unsafe_destructor]
|
Rewrite std::sync::TaskPool to be load balancing and panic-resistant
The previous implementation was very likely to cause panics during
unwinding through this process:
- child panics, drops its receiver
- taskpool comes back around and sends another job over to that child
- the child receiver has hung up, so the taskpool panics on send
- during unwinding, the taskpool attempts to send a quit message to
the child, causing a panic during unwinding
- panic during unwinding causes a process abort
This meant that TaskPool upgraded any child panic to a full process
abort. This came up in Iron when it caused crashes in long-running
servers.
This implementation uses a single channel to communicate between
spawned tasks and the TaskPool, which significantly reduces the complexity
of the implementation and cuts down on allocation. The TaskPool uses
the channel as a single-producer-multiple-consumer queue.
Additionally, through the use of send_opt and recv_opt instead of
send and recv, this TaskPool is robust on the face of child panics,
both before, during, and after the TaskPool itself is dropped.
Due to the TaskPool no longer using an `init_fn_factory`, this is a
[breaking-change]
otherwise, the API has not changed.
If you used `init_fn_factory` in your code, and this change breaks for
you, you can instead use an `AtomicUint` counter and a channel to
move information into child tasks.
2014-11-13 18:04:28 -08:00
|
|
|
impl<'a> Drop for Sentinel<'a> {
|
2013-09-16 21:18:07 -04:00
|
|
|
fn drop(&mut self) {
|
Rewrite std::sync::TaskPool to be load balancing and panic-resistant
The previous implementation was very likely to cause panics during
unwinding through this process:
- child panics, drops its receiver
- taskpool comes back around and sends another job over to that child
- the child receiver has hung up, so the taskpool panics on send
- during unwinding, the taskpool attempts to send a quit message to
the child, causing a panic during unwinding
- panic during unwinding causes a process abort
This meant that TaskPool upgraded any child panic to a full process
abort. This came up in Iron when it caused crashes in long-running
servers.
This implementation uses a single channel to communicate between
spawned tasks and the TaskPool, which significantly reduces the complexity
of the implementation and cuts down on allocation. The TaskPool uses
the channel as a single-producer-multiple-consumer queue.
Additionally, through the use of send_opt and recv_opt instead of
send and recv, this TaskPool is robust on the face of child panics,
both before, during, and after the TaskPool itself is dropped.
Due to the TaskPool no longer using an `init_fn_factory`, this is a
[breaking-change]
otherwise, the API has not changed.
If you used `init_fn_factory` in your code, and this change breaks for
you, you can instead use an `AtomicUint` counter and a channel to
move information into child tasks.
2014-11-13 18:04:28 -08:00
|
|
|
if self.active {
|
|
|
|
spawn_in_pool(self.jobs.clone())
|
2012-10-31 18:29:44 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-12-26 16:04:27 -05:00
|
|
|
/// A thread pool used to execute functions in parallel.
|
Rewrite std::sync::TaskPool to be load balancing and panic-resistant
The previous implementation was very likely to cause panics during
unwinding through this process:
- child panics, drops its receiver
- taskpool comes back around and sends another job over to that child
- the child receiver has hung up, so the taskpool panics on send
- during unwinding, the taskpool attempts to send a quit message to
the child, causing a panic during unwinding
- panic during unwinding causes a process abort
This meant that TaskPool upgraded any child panic to a full process
abort. This came up in Iron when it caused crashes in long-running
servers.
This implementation uses a single channel to communicate between
spawned tasks and the TaskPool, which significantly reduces the complexity
of the implementation and cuts down on allocation. The TaskPool uses
the channel as a single-producer-multiple-consumer queue.
Additionally, through the use of send_opt and recv_opt instead of
send and recv, this TaskPool is robust on the face of child panics,
both before, during, and after the TaskPool itself is dropped.
Due to the TaskPool no longer using an `init_fn_factory`, this is a
[breaking-change]
otherwise, the API has not changed.
If you used `init_fn_factory` in your code, and this change breaks for
you, you can instead use an `AtomicUint` counter and a channel to
move information into child tasks.
2014-11-13 18:04:28 -08:00
|
|
|
///
|
2014-12-26 16:04:27 -05:00
|
|
|
/// Spawns `n` worker threads and replenishes the pool if any worker threads
|
Rewrite std::sync::TaskPool to be load balancing and panic-resistant
The previous implementation was very likely to cause panics during
unwinding through this process:
- child panics, drops its receiver
- taskpool comes back around and sends another job over to that child
- the child receiver has hung up, so the taskpool panics on send
- during unwinding, the taskpool attempts to send a quit message to
the child, causing a panic during unwinding
- panic during unwinding causes a process abort
This meant that TaskPool upgraded any child panic to a full process
abort. This came up in Iron when it caused crashes in long-running
servers.
This implementation uses a single channel to communicate between
spawned tasks and the TaskPool, which significantly reduces the complexity
of the implementation and cuts down on allocation. The TaskPool uses
the channel as a single-producer-multiple-consumer queue.
Additionally, through the use of send_opt and recv_opt instead of
send and recv, this TaskPool is robust on the face of child panics,
both before, during, and after the TaskPool itself is dropped.
Due to the TaskPool no longer using an `init_fn_factory`, this is a
[breaking-change]
otherwise, the API has not changed.
If you used `init_fn_factory` in your code, and this change breaks for
you, you can instead use an `AtomicUint` counter and a channel to
move information into child tasks.
2014-11-13 18:04:28 -08:00
|
|
|
/// panic.
|
|
|
|
///
|
|
|
|
/// # Example
|
|
|
|
///
|
|
|
|
/// ```rust
|
2014-12-22 09:04:23 -08:00
|
|
|
/// use std::sync::TaskPool;
|
|
|
|
/// use std::iter::AdditiveIterator;
|
2014-12-23 11:53:35 -08:00
|
|
|
/// use std::sync::mpsc::channel;
|
Rewrite std::sync::TaskPool to be load balancing and panic-resistant
The previous implementation was very likely to cause panics during
unwinding through this process:
- child panics, drops its receiver
- taskpool comes back around and sends another job over to that child
- the child receiver has hung up, so the taskpool panics on send
- during unwinding, the taskpool attempts to send a quit message to
the child, causing a panic during unwinding
- panic during unwinding causes a process abort
This meant that TaskPool upgraded any child panic to a full process
abort. This came up in Iron when it caused crashes in long-running
servers.
This implementation uses a single channel to communicate between
spawned tasks and the TaskPool, which significantly reduces the complexity
of the implementation and cuts down on allocation. The TaskPool uses
the channel as a single-producer-multiple-consumer queue.
Additionally, through the use of send_opt and recv_opt instead of
send and recv, this TaskPool is robust on the face of child panics,
both before, during, and after the TaskPool itself is dropped.
Due to the TaskPool no longer using an `init_fn_factory`, this is a
[breaking-change]
otherwise, the API has not changed.
If you used `init_fn_factory` in your code, and this change breaks for
you, you can instead use an `AtomicUint` counter and a channel to
move information into child tasks.
2014-11-13 18:04:28 -08:00
|
|
|
///
|
|
|
|
/// let pool = TaskPool::new(4u);
|
|
|
|
///
|
|
|
|
/// let (tx, rx) = channel();
|
2015-01-26 15:46:12 -05:00
|
|
|
/// for _ in 0..8u {
|
Rewrite std::sync::TaskPool to be load balancing and panic-resistant
The previous implementation was very likely to cause panics during
unwinding through this process:
- child panics, drops its receiver
- taskpool comes back around and sends another job over to that child
- the child receiver has hung up, so the taskpool panics on send
- during unwinding, the taskpool attempts to send a quit message to
the child, causing a panic during unwinding
- panic during unwinding causes a process abort
This meant that TaskPool upgraded any child panic to a full process
abort. This came up in Iron when it caused crashes in long-running
servers.
This implementation uses a single channel to communicate between
spawned tasks and the TaskPool, which significantly reduces the complexity
of the implementation and cuts down on allocation. The TaskPool uses
the channel as a single-producer-multiple-consumer queue.
Additionally, through the use of send_opt and recv_opt instead of
send and recv, this TaskPool is robust on the face of child panics,
both before, during, and after the TaskPool itself is dropped.
Due to the TaskPool no longer using an `init_fn_factory`, this is a
[breaking-change]
otherwise, the API has not changed.
If you used `init_fn_factory` in your code, and this change breaks for
you, you can instead use an `AtomicUint` counter and a channel to
move information into child tasks.
2014-11-13 18:04:28 -08:00
|
|
|
/// let tx = tx.clone();
|
2014-11-26 08:12:18 -05:00
|
|
|
/// pool.execute(move|| {
|
2014-12-23 11:53:35 -08:00
|
|
|
/// tx.send(1u).unwrap();
|
Rewrite std::sync::TaskPool to be load balancing and panic-resistant
The previous implementation was very likely to cause panics during
unwinding through this process:
- child panics, drops its receiver
- taskpool comes back around and sends another job over to that child
- the child receiver has hung up, so the taskpool panics on send
- during unwinding, the taskpool attempts to send a quit message to
the child, causing a panic during unwinding
- panic during unwinding causes a process abort
This meant that TaskPool upgraded any child panic to a full process
abort. This came up in Iron when it caused crashes in long-running
servers.
This implementation uses a single channel to communicate between
spawned tasks and the TaskPool, which significantly reduces the complexity
of the implementation and cuts down on allocation. The TaskPool uses
the channel as a single-producer-multiple-consumer queue.
Additionally, through the use of send_opt and recv_opt instead of
send and recv, this TaskPool is robust on the face of child panics,
both before, during, and after the TaskPool itself is dropped.
Due to the TaskPool no longer using an `init_fn_factory`, this is a
[breaking-change]
otherwise, the API has not changed.
If you used `init_fn_factory` in your code, and this change breaks for
you, you can instead use an `AtomicUint` counter and a channel to
move information into child tasks.
2014-11-13 18:04:28 -08:00
|
|
|
/// });
|
|
|
|
/// }
|
|
|
|
///
|
|
|
|
/// assert_eq!(rx.iter().take(8u).sum(), 8u);
|
|
|
|
/// ```
|
|
|
|
pub struct TaskPool {
|
2014-12-26 16:04:27 -05:00
|
|
|
// How the threadpool communicates with subthreads.
|
Rewrite std::sync::TaskPool to be load balancing and panic-resistant
The previous implementation was very likely to cause panics during
unwinding through this process:
- child panics, drops its receiver
- taskpool comes back around and sends another job over to that child
- the child receiver has hung up, so the taskpool panics on send
- during unwinding, the taskpool attempts to send a quit message to
the child, causing a panic during unwinding
- panic during unwinding causes a process abort
This meant that TaskPool upgraded any child panic to a full process
abort. This came up in Iron when it caused crashes in long-running
servers.
This implementation uses a single channel to communicate between
spawned tasks and the TaskPool, which significantly reduces the complexity
of the implementation and cuts down on allocation. The TaskPool uses
the channel as a single-producer-multiple-consumer queue.
Additionally, through the use of send_opt and recv_opt instead of
send and recv, this TaskPool is robust on the face of child panics,
both before, during, and after the TaskPool itself is dropped.
Due to the TaskPool no longer using an `init_fn_factory`, this is a
[breaking-change]
otherwise, the API has not changed.
If you used `init_fn_factory` in your code, and this change breaks for
you, you can instead use an `AtomicUint` counter and a channel to
move information into child tasks.
2014-11-13 18:04:28 -08:00
|
|
|
//
|
2014-12-26 16:04:27 -05:00
|
|
|
// This is the only such Sender, so when it is dropped all subthreads will
|
Rewrite std::sync::TaskPool to be load balancing and panic-resistant
The previous implementation was very likely to cause panics during
unwinding through this process:
- child panics, drops its receiver
- taskpool comes back around and sends another job over to that child
- the child receiver has hung up, so the taskpool panics on send
- during unwinding, the taskpool attempts to send a quit message to
the child, causing a panic during unwinding
- panic during unwinding causes a process abort
This meant that TaskPool upgraded any child panic to a full process
abort. This came up in Iron when it caused crashes in long-running
servers.
This implementation uses a single channel to communicate between
spawned tasks and the TaskPool, which significantly reduces the complexity
of the implementation and cuts down on allocation. The TaskPool uses
the channel as a single-producer-multiple-consumer queue.
Additionally, through the use of send_opt and recv_opt instead of
send and recv, this TaskPool is robust on the face of child panics,
both before, during, and after the TaskPool itself is dropped.
Due to the TaskPool no longer using an `init_fn_factory`, this is a
[breaking-change]
otherwise, the API has not changed.
If you used `init_fn_factory` in your code, and this change breaks for
you, you can instead use an `AtomicUint` counter and a channel to
move information into child tasks.
2014-11-13 18:04:28 -08:00
|
|
|
// quit.
|
2014-11-26 10:10:52 -05:00
|
|
|
jobs: Sender<Thunk>
|
Rewrite std::sync::TaskPool to be load balancing and panic-resistant
The previous implementation was very likely to cause panics during
unwinding through this process:
- child panics, drops its receiver
- taskpool comes back around and sends another job over to that child
- the child receiver has hung up, so the taskpool panics on send
- during unwinding, the taskpool attempts to send a quit message to
the child, causing a panic during unwinding
- panic during unwinding causes a process abort
This meant that TaskPool upgraded any child panic to a full process
abort. This came up in Iron when it caused crashes in long-running
servers.
This implementation uses a single channel to communicate between
spawned tasks and the TaskPool, which significantly reduces the complexity
of the implementation and cuts down on allocation. The TaskPool uses
the channel as a single-producer-multiple-consumer queue.
Additionally, through the use of send_opt and recv_opt instead of
send and recv, this TaskPool is robust on the face of child panics,
both before, during, and after the TaskPool itself is dropped.
Due to the TaskPool no longer using an `init_fn_factory`, this is a
[breaking-change]
otherwise, the API has not changed.
If you used `init_fn_factory` in your code, and this change breaks for
you, you can instead use an `AtomicUint` counter and a channel to
move information into child tasks.
2014-11-13 18:04:28 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl TaskPool {
|
2014-12-26 16:04:27 -05:00
|
|
|
/// Spawns a new thread pool with `threads` threads.
|
2014-06-19 23:17:49 -04:00
|
|
|
///
|
2014-10-09 15:17:22 -04:00
|
|
|
/// # Panics
|
2014-06-19 23:17:49 -04:00
|
|
|
///
|
2014-12-26 16:04:27 -05:00
|
|
|
/// This function will panic if `threads` is 0.
|
|
|
|
pub fn new(threads: uint) -> TaskPool {
|
|
|
|
assert!(threads >= 1);
|
2012-10-31 18:29:44 -07:00
|
|
|
|
2014-11-26 10:10:52 -05:00
|
|
|
let (tx, rx) = channel::<Thunk>();
|
Rewrite std::sync::TaskPool to be load balancing and panic-resistant
The previous implementation was very likely to cause panics during
unwinding through this process:
- child panics, drops its receiver
- taskpool comes back around and sends another job over to that child
- the child receiver has hung up, so the taskpool panics on send
- during unwinding, the taskpool attempts to send a quit message to
the child, causing a panic during unwinding
- panic during unwinding causes a process abort
This meant that TaskPool upgraded any child panic to a full process
abort. This came up in Iron when it caused crashes in long-running
servers.
This implementation uses a single channel to communicate between
spawned tasks and the TaskPool, which significantly reduces the complexity
of the implementation and cuts down on allocation. The TaskPool uses
the channel as a single-producer-multiple-consumer queue.
Additionally, through the use of send_opt and recv_opt instead of
send and recv, this TaskPool is robust on the face of child panics,
both before, during, and after the TaskPool itself is dropped.
Due to the TaskPool no longer using an `init_fn_factory`, this is a
[breaking-change]
otherwise, the API has not changed.
If you used `init_fn_factory` in your code, and this change breaks for
you, you can instead use an `AtomicUint` counter and a channel to
move information into child tasks.
2014-11-13 18:04:28 -08:00
|
|
|
let rx = Arc::new(Mutex::new(rx));
|
2012-10-31 18:29:44 -07:00
|
|
|
|
2014-12-26 16:04:27 -05:00
|
|
|
// Threadpool threads
|
2015-01-26 15:46:12 -05:00
|
|
|
for _ in 0..threads {
|
Rewrite std::sync::TaskPool to be load balancing and panic-resistant
The previous implementation was very likely to cause panics during
unwinding through this process:
- child panics, drops its receiver
- taskpool comes back around and sends another job over to that child
- the child receiver has hung up, so the taskpool panics on send
- during unwinding, the taskpool attempts to send a quit message to
the child, causing a panic during unwinding
- panic during unwinding causes a process abort
This meant that TaskPool upgraded any child panic to a full process
abort. This came up in Iron when it caused crashes in long-running
servers.
This implementation uses a single channel to communicate between
spawned tasks and the TaskPool, which significantly reduces the complexity
of the implementation and cuts down on allocation. The TaskPool uses
the channel as a single-producer-multiple-consumer queue.
Additionally, through the use of send_opt and recv_opt instead of
send and recv, this TaskPool is robust on the face of child panics,
both before, during, and after the TaskPool itself is dropped.
Due to the TaskPool no longer using an `init_fn_factory`, this is a
[breaking-change]
otherwise, the API has not changed.
If you used `init_fn_factory` in your code, and this change breaks for
you, you can instead use an `AtomicUint` counter and a channel to
move information into child tasks.
2014-11-13 18:04:28 -08:00
|
|
|
spawn_in_pool(rx.clone());
|
|
|
|
}
|
2012-10-31 18:29:44 -07:00
|
|
|
|
Rewrite std::sync::TaskPool to be load balancing and panic-resistant
The previous implementation was very likely to cause panics during
unwinding through this process:
- child panics, drops its receiver
- taskpool comes back around and sends another job over to that child
- the child receiver has hung up, so the taskpool panics on send
- during unwinding, the taskpool attempts to send a quit message to
the child, causing a panic during unwinding
- panic during unwinding causes a process abort
This meant that TaskPool upgraded any child panic to a full process
abort. This came up in Iron when it caused crashes in long-running
servers.
This implementation uses a single channel to communicate between
spawned tasks and the TaskPool, which significantly reduces the complexity
of the implementation and cuts down on allocation. The TaskPool uses
the channel as a single-producer-multiple-consumer queue.
Additionally, through the use of send_opt and recv_opt instead of
send and recv, this TaskPool is robust on the face of child panics,
both before, during, and after the TaskPool itself is dropped.
Due to the TaskPool no longer using an `init_fn_factory`, this is a
[breaking-change]
otherwise, the API has not changed.
If you used `init_fn_factory` in your code, and this change breaks for
you, you can instead use an `AtomicUint` counter and a channel to
move information into child tasks.
2014-11-13 18:04:28 -08:00
|
|
|
TaskPool { jobs: tx }
|
2012-10-31 18:29:44 -07:00
|
|
|
}
|
|
|
|
|
2014-12-26 16:04:27 -05:00
|
|
|
/// Executes the function `job` on a thread in the pool.
|
2014-11-26 10:10:52 -05:00
|
|
|
pub fn execute<F>(&self, job: F)
|
|
|
|
where F : FnOnce(), F : Send
|
|
|
|
{
|
2014-12-23 11:53:35 -08:00
|
|
|
self.jobs.send(Thunk::new(job)).unwrap();
|
2012-10-31 18:29:44 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-26 10:10:52 -05:00
|
|
|
fn spawn_in_pool(jobs: Arc<Mutex<Receiver<Thunk>>>) {
|
2015-02-01 12:44:15 -05:00
|
|
|
Thread::spawn(move || {
|
2014-12-26 16:04:27 -05:00
|
|
|
// Will spawn a new thread on panic unless it is cancelled.
|
Rewrite std::sync::TaskPool to be load balancing and panic-resistant
The previous implementation was very likely to cause panics during
unwinding through this process:
- child panics, drops its receiver
- taskpool comes back around and sends another job over to that child
- the child receiver has hung up, so the taskpool panics on send
- during unwinding, the taskpool attempts to send a quit message to
the child, causing a panic during unwinding
- panic during unwinding causes a process abort
This meant that TaskPool upgraded any child panic to a full process
abort. This came up in Iron when it caused crashes in long-running
servers.
This implementation uses a single channel to communicate between
spawned tasks and the TaskPool, which significantly reduces the complexity
of the implementation and cuts down on allocation. The TaskPool uses
the channel as a single-producer-multiple-consumer queue.
Additionally, through the use of send_opt and recv_opt instead of
send and recv, this TaskPool is robust on the face of child panics,
both before, during, and after the TaskPool itself is dropped.
Due to the TaskPool no longer using an `init_fn_factory`, this is a
[breaking-change]
otherwise, the API has not changed.
If you used `init_fn_factory` in your code, and this change breaks for
you, you can instead use an `AtomicUint` counter and a channel to
move information into child tasks.
2014-11-13 18:04:28 -08:00
|
|
|
let sentinel = Sentinel::new(&jobs);
|
|
|
|
|
|
|
|
loop {
|
|
|
|
let message = {
|
|
|
|
// Only lock jobs for the time it takes
|
|
|
|
// to get a job, not run it.
|
2014-12-08 20:20:03 -08:00
|
|
|
let lock = jobs.lock().unwrap();
|
2014-12-23 11:53:35 -08:00
|
|
|
lock.recv()
|
Rewrite std::sync::TaskPool to be load balancing and panic-resistant
The previous implementation was very likely to cause panics during
unwinding through this process:
- child panics, drops its receiver
- taskpool comes back around and sends another job over to that child
- the child receiver has hung up, so the taskpool panics on send
- during unwinding, the taskpool attempts to send a quit message to
the child, causing a panic during unwinding
- panic during unwinding causes a process abort
This meant that TaskPool upgraded any child panic to a full process
abort. This came up in Iron when it caused crashes in long-running
servers.
This implementation uses a single channel to communicate between
spawned tasks and the TaskPool, which significantly reduces the complexity
of the implementation and cuts down on allocation. The TaskPool uses
the channel as a single-producer-multiple-consumer queue.
Additionally, through the use of send_opt and recv_opt instead of
send and recv, this TaskPool is robust on the face of child panics,
both before, during, and after the TaskPool itself is dropped.
Due to the TaskPool no longer using an `init_fn_factory`, this is a
[breaking-change]
otherwise, the API has not changed.
If you used `init_fn_factory` in your code, and this change breaks for
you, you can instead use an `AtomicUint` counter and a channel to
move information into child tasks.
2014-11-13 18:04:28 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
match message {
|
2014-11-26 10:10:52 -05:00
|
|
|
Ok(job) => job.invoke(()),
|
Rewrite std::sync::TaskPool to be load balancing and panic-resistant
The previous implementation was very likely to cause panics during
unwinding through this process:
- child panics, drops its receiver
- taskpool comes back around and sends another job over to that child
- the child receiver has hung up, so the taskpool panics on send
- during unwinding, the taskpool attempts to send a quit message to
the child, causing a panic during unwinding
- panic during unwinding causes a process abort
This meant that TaskPool upgraded any child panic to a full process
abort. This came up in Iron when it caused crashes in long-running
servers.
This implementation uses a single channel to communicate between
spawned tasks and the TaskPool, which significantly reduces the complexity
of the implementation and cuts down on allocation. The TaskPool uses
the channel as a single-producer-multiple-consumer queue.
Additionally, through the use of send_opt and recv_opt instead of
send and recv, this TaskPool is robust on the face of child panics,
both before, during, and after the TaskPool itself is dropped.
Due to the TaskPool no longer using an `init_fn_factory`, this is a
[breaking-change]
otherwise, the API has not changed.
If you used `init_fn_factory` in your code, and this change breaks for
you, you can instead use an `AtomicUint` counter and a channel to
move information into child tasks.
2014-11-13 18:04:28 -08:00
|
|
|
|
|
|
|
// The Taskpool was dropped.
|
|
|
|
Err(..) => break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sentinel.cancel();
|
2015-01-05 21:59:45 -08:00
|
|
|
});
|
2012-10-31 18:29:44 -07:00
|
|
|
}
|
2014-06-19 23:17:49 -04:00
|
|
|
|
Rewrite std::sync::TaskPool to be load balancing and panic-resistant
The previous implementation was very likely to cause panics during
unwinding through this process:
- child panics, drops its receiver
- taskpool comes back around and sends another job over to that child
- the child receiver has hung up, so the taskpool panics on send
- during unwinding, the taskpool attempts to send a quit message to
the child, causing a panic during unwinding
- panic during unwinding causes a process abort
This meant that TaskPool upgraded any child panic to a full process
abort. This came up in Iron when it caused crashes in long-running
servers.
This implementation uses a single channel to communicate between
spawned tasks and the TaskPool, which significantly reduces the complexity
of the implementation and cuts down on allocation. The TaskPool uses
the channel as a single-producer-multiple-consumer queue.
Additionally, through the use of send_opt and recv_opt instead of
send and recv, this TaskPool is robust on the face of child panics,
both before, during, and after the TaskPool itself is dropped.
Due to the TaskPool no longer using an `init_fn_factory`, this is a
[breaking-change]
otherwise, the API has not changed.
If you used `init_fn_factory` in your code, and this change breaks for
you, you can instead use an `AtomicUint` counter and a channel to
move information into child tasks.
2014-11-13 18:04:28 -08:00
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
2014-12-22 09:04:23 -08:00
|
|
|
use prelude::v1::*;
|
Rewrite std::sync::TaskPool to be load balancing and panic-resistant
The previous implementation was very likely to cause panics during
unwinding through this process:
- child panics, drops its receiver
- taskpool comes back around and sends another job over to that child
- the child receiver has hung up, so the taskpool panics on send
- during unwinding, the taskpool attempts to send a quit message to
the child, causing a panic during unwinding
- panic during unwinding causes a process abort
This meant that TaskPool upgraded any child panic to a full process
abort. This came up in Iron when it caused crashes in long-running
servers.
This implementation uses a single channel to communicate between
spawned tasks and the TaskPool, which significantly reduces the complexity
of the implementation and cuts down on allocation. The TaskPool uses
the channel as a single-producer-multiple-consumer queue.
Additionally, through the use of send_opt and recv_opt instead of
send and recv, this TaskPool is robust on the face of child panics,
both before, during, and after the TaskPool itself is dropped.
Due to the TaskPool no longer using an `init_fn_factory`, this is a
[breaking-change]
otherwise, the API has not changed.
If you used `init_fn_factory` in your code, and this change breaks for
you, you can instead use an `AtomicUint` counter and a channel to
move information into child tasks.
2014-11-13 18:04:28 -08:00
|
|
|
use super::*;
|
2014-12-23 11:53:35 -08:00
|
|
|
use sync::mpsc::channel;
|
Rewrite std::sync::TaskPool to be load balancing and panic-resistant
The previous implementation was very likely to cause panics during
unwinding through this process:
- child panics, drops its receiver
- taskpool comes back around and sends another job over to that child
- the child receiver has hung up, so the taskpool panics on send
- during unwinding, the taskpool attempts to send a quit message to
the child, causing a panic during unwinding
- panic during unwinding causes a process abort
This meant that TaskPool upgraded any child panic to a full process
abort. This came up in Iron when it caused crashes in long-running
servers.
This implementation uses a single channel to communicate between
spawned tasks and the TaskPool, which significantly reduces the complexity
of the implementation and cuts down on allocation. The TaskPool uses
the channel as a single-producer-multiple-consumer queue.
Additionally, through the use of send_opt and recv_opt instead of
send and recv, this TaskPool is robust on the face of child panics,
both before, during, and after the TaskPool itself is dropped.
Due to the TaskPool no longer using an `init_fn_factory`, this is a
[breaking-change]
otherwise, the API has not changed.
If you used `init_fn_factory` in your code, and this change breaks for
you, you can instead use an `AtomicUint` counter and a channel to
move information into child tasks.
2014-11-13 18:04:28 -08:00
|
|
|
|
|
|
|
const TEST_TASKS: uint = 4u;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_works() {
|
|
|
|
use iter::AdditiveIterator;
|
|
|
|
|
|
|
|
let pool = TaskPool::new(TEST_TASKS);
|
|
|
|
|
|
|
|
let (tx, rx) = channel();
|
2015-01-26 15:46:12 -05:00
|
|
|
for _ in 0..TEST_TASKS {
|
Rewrite std::sync::TaskPool to be load balancing and panic-resistant
The previous implementation was very likely to cause panics during
unwinding through this process:
- child panics, drops its receiver
- taskpool comes back around and sends another job over to that child
- the child receiver has hung up, so the taskpool panics on send
- during unwinding, the taskpool attempts to send a quit message to
the child, causing a panic during unwinding
- panic during unwinding causes a process abort
This meant that TaskPool upgraded any child panic to a full process
abort. This came up in Iron when it caused crashes in long-running
servers.
This implementation uses a single channel to communicate between
spawned tasks and the TaskPool, which significantly reduces the complexity
of the implementation and cuts down on allocation. The TaskPool uses
the channel as a single-producer-multiple-consumer queue.
Additionally, through the use of send_opt and recv_opt instead of
send and recv, this TaskPool is robust on the face of child panics,
both before, during, and after the TaskPool itself is dropped.
Due to the TaskPool no longer using an `init_fn_factory`, this is a
[breaking-change]
otherwise, the API has not changed.
If you used `init_fn_factory` in your code, and this change breaks for
you, you can instead use an `AtomicUint` counter and a channel to
move information into child tasks.
2014-11-13 18:04:28 -08:00
|
|
|
let tx = tx.clone();
|
2014-11-26 08:12:18 -05:00
|
|
|
pool.execute(move|| {
|
2014-12-23 11:53:35 -08:00
|
|
|
tx.send(1u).unwrap();
|
Rewrite std::sync::TaskPool to be load balancing and panic-resistant
The previous implementation was very likely to cause panics during
unwinding through this process:
- child panics, drops its receiver
- taskpool comes back around and sends another job over to that child
- the child receiver has hung up, so the taskpool panics on send
- during unwinding, the taskpool attempts to send a quit message to
the child, causing a panic during unwinding
- panic during unwinding causes a process abort
This meant that TaskPool upgraded any child panic to a full process
abort. This came up in Iron when it caused crashes in long-running
servers.
This implementation uses a single channel to communicate between
spawned tasks and the TaskPool, which significantly reduces the complexity
of the implementation and cuts down on allocation. The TaskPool uses
the channel as a single-producer-multiple-consumer queue.
Additionally, through the use of send_opt and recv_opt instead of
send and recv, this TaskPool is robust on the face of child panics,
both before, during, and after the TaskPool itself is dropped.
Due to the TaskPool no longer using an `init_fn_factory`, this is a
[breaking-change]
otherwise, the API has not changed.
If you used `init_fn_factory` in your code, and this change breaks for
you, you can instead use an `AtomicUint` counter and a channel to
move information into child tasks.
2014-11-13 18:04:28 -08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
assert_eq!(rx.iter().take(TEST_TASKS).sum(), TEST_TASKS);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[should_fail]
|
|
|
|
fn test_zero_tasks_panic() {
|
|
|
|
TaskPool::new(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_recovery_from_subtask_panic() {
|
|
|
|
use iter::AdditiveIterator;
|
|
|
|
|
|
|
|
let pool = TaskPool::new(TEST_TASKS);
|
|
|
|
|
2014-12-26 16:04:27 -05:00
|
|
|
// Panic all the existing threads.
|
2015-01-26 15:46:12 -05:00
|
|
|
for _ in 0..TEST_TASKS {
|
2014-11-26 08:12:18 -05:00
|
|
|
pool.execute(move|| -> () { panic!() });
|
Rewrite std::sync::TaskPool to be load balancing and panic-resistant
The previous implementation was very likely to cause panics during
unwinding through this process:
- child panics, drops its receiver
- taskpool comes back around and sends another job over to that child
- the child receiver has hung up, so the taskpool panics on send
- during unwinding, the taskpool attempts to send a quit message to
the child, causing a panic during unwinding
- panic during unwinding causes a process abort
This meant that TaskPool upgraded any child panic to a full process
abort. This came up in Iron when it caused crashes in long-running
servers.
This implementation uses a single channel to communicate between
spawned tasks and the TaskPool, which significantly reduces the complexity
of the implementation and cuts down on allocation. The TaskPool uses
the channel as a single-producer-multiple-consumer queue.
Additionally, through the use of send_opt and recv_opt instead of
send and recv, this TaskPool is robust on the face of child panics,
both before, during, and after the TaskPool itself is dropped.
Due to the TaskPool no longer using an `init_fn_factory`, this is a
[breaking-change]
otherwise, the API has not changed.
If you used `init_fn_factory` in your code, and this change breaks for
you, you can instead use an `AtomicUint` counter and a channel to
move information into child tasks.
2014-11-13 18:04:28 -08:00
|
|
|
}
|
|
|
|
|
2014-12-26 16:04:27 -05:00
|
|
|
// Ensure new threads were spawned to compensate.
|
Rewrite std::sync::TaskPool to be load balancing and panic-resistant
The previous implementation was very likely to cause panics during
unwinding through this process:
- child panics, drops its receiver
- taskpool comes back around and sends another job over to that child
- the child receiver has hung up, so the taskpool panics on send
- during unwinding, the taskpool attempts to send a quit message to
the child, causing a panic during unwinding
- panic during unwinding causes a process abort
This meant that TaskPool upgraded any child panic to a full process
abort. This came up in Iron when it caused crashes in long-running
servers.
This implementation uses a single channel to communicate between
spawned tasks and the TaskPool, which significantly reduces the complexity
of the implementation and cuts down on allocation. The TaskPool uses
the channel as a single-producer-multiple-consumer queue.
Additionally, through the use of send_opt and recv_opt instead of
send and recv, this TaskPool is robust on the face of child panics,
both before, during, and after the TaskPool itself is dropped.
Due to the TaskPool no longer using an `init_fn_factory`, this is a
[breaking-change]
otherwise, the API has not changed.
If you used `init_fn_factory` in your code, and this change breaks for
you, you can instead use an `AtomicUint` counter and a channel to
move information into child tasks.
2014-11-13 18:04:28 -08:00
|
|
|
let (tx, rx) = channel();
|
2015-01-26 15:46:12 -05:00
|
|
|
for _ in 0..TEST_TASKS {
|
Rewrite std::sync::TaskPool to be load balancing and panic-resistant
The previous implementation was very likely to cause panics during
unwinding through this process:
- child panics, drops its receiver
- taskpool comes back around and sends another job over to that child
- the child receiver has hung up, so the taskpool panics on send
- during unwinding, the taskpool attempts to send a quit message to
the child, causing a panic during unwinding
- panic during unwinding causes a process abort
This meant that TaskPool upgraded any child panic to a full process
abort. This came up in Iron when it caused crashes in long-running
servers.
This implementation uses a single channel to communicate between
spawned tasks and the TaskPool, which significantly reduces the complexity
of the implementation and cuts down on allocation. The TaskPool uses
the channel as a single-producer-multiple-consumer queue.
Additionally, through the use of send_opt and recv_opt instead of
send and recv, this TaskPool is robust on the face of child panics,
both before, during, and after the TaskPool itself is dropped.
Due to the TaskPool no longer using an `init_fn_factory`, this is a
[breaking-change]
otherwise, the API has not changed.
If you used `init_fn_factory` in your code, and this change breaks for
you, you can instead use an `AtomicUint` counter and a channel to
move information into child tasks.
2014-11-13 18:04:28 -08:00
|
|
|
let tx = tx.clone();
|
2014-11-26 08:12:18 -05:00
|
|
|
pool.execute(move|| {
|
2014-12-23 11:53:35 -08:00
|
|
|
tx.send(1u).unwrap();
|
Rewrite std::sync::TaskPool to be load balancing and panic-resistant
The previous implementation was very likely to cause panics during
unwinding through this process:
- child panics, drops its receiver
- taskpool comes back around and sends another job over to that child
- the child receiver has hung up, so the taskpool panics on send
- during unwinding, the taskpool attempts to send a quit message to
the child, causing a panic during unwinding
- panic during unwinding causes a process abort
This meant that TaskPool upgraded any child panic to a full process
abort. This came up in Iron when it caused crashes in long-running
servers.
This implementation uses a single channel to communicate between
spawned tasks and the TaskPool, which significantly reduces the complexity
of the implementation and cuts down on allocation. The TaskPool uses
the channel as a single-producer-multiple-consumer queue.
Additionally, through the use of send_opt and recv_opt instead of
send and recv, this TaskPool is robust on the face of child panics,
both before, during, and after the TaskPool itself is dropped.
Due to the TaskPool no longer using an `init_fn_factory`, this is a
[breaking-change]
otherwise, the API has not changed.
If you used `init_fn_factory` in your code, and this change breaks for
you, you can instead use an `AtomicUint` counter and a channel to
move information into child tasks.
2014-11-13 18:04:28 -08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
assert_eq!(rx.iter().take(TEST_TASKS).sum(), TEST_TASKS);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_should_not_panic_on_drop_if_subtasks_panic_after_drop() {
|
|
|
|
use sync::{Arc, Barrier};
|
|
|
|
|
|
|
|
let pool = TaskPool::new(TEST_TASKS);
|
|
|
|
let waiter = Arc::new(Barrier::new(TEST_TASKS + 1));
|
|
|
|
|
2014-12-26 16:04:27 -05:00
|
|
|
// Panic all the existing threads in a bit.
|
2015-01-26 15:46:12 -05:00
|
|
|
for _ in 0..TEST_TASKS {
|
Rewrite std::sync::TaskPool to be load balancing and panic-resistant
The previous implementation was very likely to cause panics during
unwinding through this process:
- child panics, drops its receiver
- taskpool comes back around and sends another job over to that child
- the child receiver has hung up, so the taskpool panics on send
- during unwinding, the taskpool attempts to send a quit message to
the child, causing a panic during unwinding
- panic during unwinding causes a process abort
This meant that TaskPool upgraded any child panic to a full process
abort. This came up in Iron when it caused crashes in long-running
servers.
This implementation uses a single channel to communicate between
spawned tasks and the TaskPool, which significantly reduces the complexity
of the implementation and cuts down on allocation. The TaskPool uses
the channel as a single-producer-multiple-consumer queue.
Additionally, through the use of send_opt and recv_opt instead of
send and recv, this TaskPool is robust on the face of child panics,
both before, during, and after the TaskPool itself is dropped.
Due to the TaskPool no longer using an `init_fn_factory`, this is a
[breaking-change]
otherwise, the API has not changed.
If you used `init_fn_factory` in your code, and this change breaks for
you, you can instead use an `AtomicUint` counter and a channel to
move information into child tasks.
2014-11-13 18:04:28 -08:00
|
|
|
let waiter = waiter.clone();
|
2014-11-26 08:12:18 -05:00
|
|
|
pool.execute(move|| {
|
Rewrite std::sync::TaskPool to be load balancing and panic-resistant
The previous implementation was very likely to cause panics during
unwinding through this process:
- child panics, drops its receiver
- taskpool comes back around and sends another job over to that child
- the child receiver has hung up, so the taskpool panics on send
- during unwinding, the taskpool attempts to send a quit message to
the child, causing a panic during unwinding
- panic during unwinding causes a process abort
This meant that TaskPool upgraded any child panic to a full process
abort. This came up in Iron when it caused crashes in long-running
servers.
This implementation uses a single channel to communicate between
spawned tasks and the TaskPool, which significantly reduces the complexity
of the implementation and cuts down on allocation. The TaskPool uses
the channel as a single-producer-multiple-consumer queue.
Additionally, through the use of send_opt and recv_opt instead of
send and recv, this TaskPool is robust on the face of child panics,
both before, during, and after the TaskPool itself is dropped.
Due to the TaskPool no longer using an `init_fn_factory`, this is a
[breaking-change]
otherwise, the API has not changed.
If you used `init_fn_factory` in your code, and this change breaks for
you, you can instead use an `AtomicUint` counter and a channel to
move information into child tasks.
2014-11-13 18:04:28 -08:00
|
|
|
waiter.wait();
|
|
|
|
panic!();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
drop(pool);
|
|
|
|
|
|
|
|
// Kick off the failure.
|
|
|
|
waiter.wait();
|
|
|
|
}
|
2014-06-19 23:17:49 -04:00
|
|
|
}
|