Skip to content

Commit bcb05a0

Browse files
committed
Auto merge of #55043 - oliver-giersch:unchecked_thread_spawning, r=alexcrichton
Unchecked thread spawning # Summary Add an unsafe interface for spawning lifetime-unrestricted threads for library authors to build less-contrived, less-hacky safe abstractions on. # Motivation So a few years back scoped threads were entirely removed from the Rust stdlib, the reason being that it was possible to leak the scoped thread's join guards without resorting to unsafe code, which meant the concept was not completely safe, either. Only a maximally-restrictive safe API for thread spawning was kept in the stdlib, that requires `'static` lifetime bounds on both the thread closure and its return type. A number of 3rd party libraries sprung up to offer their implementations for safe scoped threads implementations. These work by essentially hiding the join guards from the user, thus forcing them to join at the end of an (internal) function scope. However, since these libraries have to use the maximally restrictive thread spawning API, they have to resort to some very contrived manipulations and subversions of Rust's type system to basically achieve what this commit does with some minimal restructuring of the current code and exposing a new unsafe function signature for spawning threads without lifetime restrictions. Obviously this is unsafe, but its main use would be to allow library authors to write safe abstractions with and around it. To further illustrate my point, here's a quick summary of the hoops that, for instance `crossbeam`, has to jump through to spawn a lifetime unrestricted thread, all of which would not be necessary if an unsafe API existed as part of the stdlib: 1. Allocate an `Arc<Option<T>>` on the heap where the result with type `T: 'a` will go (in practice requires `Mutex` or `UnsafeCell` as well). 2. Wrap the desired thread closure with lifetime bound `'a` into another closure (also `..: 'a`) that returns `()`, executes the inner closure and writes its result into the pre-allocated `Option<T>`. 3. Box the wrapping closure, cast it to a trait object (`FnBox`) and (unsafely) transmute its lifetime bound from `'a` to `'static`. So while this new `spawn_unchecked` function is certainly not very relevant for general use, since scoped threads are so common I think it makes sense to expose an interface for libraries implementing these to build on. The changes implemented are also very minimal: The current `spawn` function (which internally contains unsafe code) is moved into an unsafe `spawn_unchecked` function, which the safe function then wraps around. # Issues - ~~so far, no documentation for the new function (yet)~~ - the name of the function might be controversial, as `*_unchecked` more commonly indicates that some sort of runtime check is omitted (`unrestricted` may be more fitting) - if accepted, it might make sense to add a freestanding `thread::spawn_unchecked` function similar to the current `thread::spawn` for convenience.
2 parents 96064eb + 7849aed commit bcb05a0

File tree

1 file changed

+78
-13
lines changed

1 file changed

+78
-13
lines changed

src/libstd/thread/mod.rs

+78-13
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,74 @@ impl Builder {
386386
#[stable(feature = "rust1", since = "1.0.0")]
387387
pub fn spawn<F, T>(self, f: F) -> io::Result<JoinHandle<T>> where
388388
F: FnOnce() -> T, F: Send + 'static, T: Send + 'static
389+
{
390+
unsafe { self.spawn_unchecked(f) }
391+
}
392+
393+
/// Spawns a new thread without any lifetime restrictions by taking ownership
394+
/// of the `Builder`, and returns an [`io::Result`] to its [`JoinHandle`].
395+
///
396+
/// The spawned thread may outlive the caller (unless the caller thread
397+
/// is the main thread; the whole process is terminated when the main
398+
/// thread finishes). The join handle can be used to block on
399+
/// termination of the child thread, including recovering its panics.
400+
///
401+
/// This method is identical to [`thread::Builder::spawn`][`Builder::spawn`],
402+
/// except for the relaxed lifetime bounds, which render it unsafe.
403+
/// For a more complete documentation see [`thread::spawn`][`spawn`].
404+
///
405+
/// # Errors
406+
///
407+
/// Unlike the [`spawn`] free function, this method yields an
408+
/// [`io::Result`] to capture any failure to create the thread at
409+
/// the OS level.
410+
///
411+
/// # Panics
412+
///
413+
/// Panics if a thread name was set and it contained null bytes.
414+
///
415+
/// # Safety
416+
///
417+
/// The caller has to ensure that no references in the supplied thread closure
418+
/// or its return type can outlive the spawned thread's lifetime. This can be
419+
/// guaranteed in two ways:
420+
///
421+
/// - ensure that [`join`][`JoinHandle::join`] is called before any referenced
422+
/// data is dropped
423+
/// - use only types with `'static` lifetime bounds, i.e. those with no or only
424+
/// `'static` references (both [`thread::Builder::spawn`][`Builder::spawn`]
425+
/// and [`thread::spawn`][`spawn`] enforce this property statically)
426+
///
427+
/// # Examples
428+
///
429+
/// ```
430+
/// #![feature(thread_spawn_unchecked)]
431+
/// use std::thread;
432+
///
433+
/// let builder = thread::Builder::new();
434+
///
435+
/// let x = 1;
436+
/// let thread_x = &x;
437+
///
438+
/// let handler = unsafe {
439+
/// builder.spawn_unchecked(move || {
440+
/// println!("x = {}", *thread_x);
441+
/// }).unwrap()
442+
/// };
443+
///
444+
/// // caller has to ensure `join()` is called, otherwise
445+
/// // it is possible to access freed memory if `x` gets
446+
/// // dropped before the thread closure is executed!
447+
/// handler.join().unwrap();
448+
/// ```
449+
///
450+
/// [`spawn`]: ../../std/thread/fn.spawn.html
451+
/// [`Builder::spawn`]: ../../std/thread/struct.Builder.html#method.spawn
452+
/// [`io::Result`]: ../../std/io/type.Result.html
453+
/// [`JoinHandle`]: ../../std/thread/struct.JoinHandle.html
454+
#[unstable(feature = "thread_spawn_unchecked", issue = "55132")]
455+
pub unsafe fn spawn_unchecked<F, T>(self, f: F) -> io::Result<JoinHandle<T>> where
456+
F: FnOnce() -> T, F: Send, T: Send
389457
{
390458
let Builder { name, stack_size } = self;
391459

@@ -402,22 +470,19 @@ impl Builder {
402470
if let Some(name) = their_thread.cname() {
403471
imp::Thread::set_name(name);
404472
}
405-
unsafe {
406-
thread_info::set(imp::guard::current(), their_thread);
407-
#[cfg(feature = "backtrace")]
408-
let try_result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
409-
::sys_common::backtrace::__rust_begin_short_backtrace(f)
410-
}));
411-
#[cfg(not(feature = "backtrace"))]
412-
let try_result = panic::catch_unwind(panic::AssertUnwindSafe(f));
413-
*their_packet.get() = Some(try_result);
414-
}
473+
474+
thread_info::set(imp::guard::current(), their_thread);
475+
#[cfg(feature = "backtrace")]
476+
let try_result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
477+
::sys_common::backtrace::__rust_begin_short_backtrace(f)
478+
}));
479+
#[cfg(not(feature = "backtrace"))]
480+
let try_result = panic::catch_unwind(panic::AssertUnwindSafe(f));
481+
*their_packet.get() = Some(try_result);
415482
};
416483

417484
Ok(JoinHandle(JoinInner {
418-
native: unsafe {
419-
Some(imp::Thread::new(stack_size, Box::new(main))?)
420-
},
485+
native: Some(imp::Thread::new(stack_size, Box::new(main))?),
421486
thread: my_thread,
422487
packet: Packet(my_packet),
423488
}))

0 commit comments

Comments
 (0)