Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

## [0.6.1] - 2025-11-02
- add optional `track-creation-site` feature to store and print the location of `SendWrapper::new` during panics, to help debugging.

## [0.5.0] - 2020-10-14
- add transparent wrapping of `Future` and `Stream` types via new cargo feature `futures`

Expand Down
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "send_wrapper"
version = "0.6.0"
version = "0.6.1"
edition = "2018"
authors = ["Thomas Keh"]
license = "MIT/Apache-2.0"
Expand All @@ -17,6 +17,7 @@ categories = ["rust-patterns"]

[features]
futures = ["futures-core"]
track-creation-site = []

[dependencies]
futures-core = { version = "0.3", optional = true }
Expand Down
80 changes: 61 additions & 19 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@
//! // This would panic (because of dereferencing in wrong thread):
//! // let value = wrapped_value.deref();
//!
//! // Move SendWrapper back to main thread, so it can be dropped from there.
//! // If you leave this out the thread will panic because of dropping from wrong thread.
//! sender.send(wrapped_value).unwrap();
//! // Move SendWrapper back to main thread, so it can be dropped from there.
//! // If you leave this out the thread will panic because of dropping from wrong thread.
//! sender.send(wrapped_value).unwrap();
//!
//! });
//!
Expand Down Expand Up @@ -99,22 +99,28 @@ mod futures;
use std::fmt;
use std::mem::{self, ManuallyDrop};
use std::ops::{Deref, DerefMut, Drop};
use std::panic::Location;
use std::thread::{self, ThreadId};

/// A wrapper which allows you to move around non-[`Send`]-types between threads, as long as you access the contained
/// value only from within the original thread and make sure that it is dropped from within the original thread.
pub struct SendWrapper<T> {
data: ManuallyDrop<T>,
thread_id: ThreadId,
#[cfg(feature = "track-creation-site")]
location: &'static Location<'static>,
}

impl<T> SendWrapper<T> {
/// Create a `SendWrapper<T>` wrapper around a value of type `T`.
/// The wrapper takes ownership of the value.
#[track_caller]
pub fn new(data: T) -> SendWrapper<T> {
SendWrapper {
data: ManuallyDrop::new(data),
thread_id: thread::current().id(),
#[cfg(feature = "track-creation-site")]
location: Location::caller(),
}
}

Expand Down Expand Up @@ -145,14 +151,24 @@ impl<T> SendWrapper<T> {
#[track_caller]
fn assert_valid_for_deref(&self) {
if !self.valid() {
invalid_deref()
invalid_deref(
#[cfg(feature = "track-creation-site")]
Some(self.location),
#[cfg(not(feature = "track-creation-site"))]
None,
)
}
}

#[track_caller]
fn assert_valid_for_poll(&self) {
if !self.valid() {
invalid_poll()
invalid_poll(
#[cfg(feature = "track-creation-site")]
Some(self.location),
#[cfg(not(feature = "track-creation-site"))]
None,
)
}
}
}
Expand All @@ -176,7 +192,7 @@ impl<T> Deref for SendWrapper<T> {
// Access the value.
//
// Safety: We just checked that it is valid to access `T` on the current thread.
&*self.data
&self.data
}
}

Expand All @@ -194,7 +210,7 @@ impl<T> DerefMut for SendWrapper<T> {
// Access the value.
//
// Safety: We just checked that it is valid to access `T` on the current thread.
&mut *self.data
&mut self.data
}
}

Expand Down Expand Up @@ -227,7 +243,12 @@ impl<T> Drop for SendWrapper<T> {
ManuallyDrop::drop(&mut self.data);
}
} else {
invalid_drop()
invalid_drop(
#[cfg(feature = "track-creation-site")]
Some(self.location),
#[cfg(not(feature = "track-creation-site"))]
None,
);
}
}
}
Expand Down Expand Up @@ -264,31 +285,52 @@ impl<T: Clone> Clone for SendWrapper<T> {
#[cold]
#[inline(never)]
#[track_caller]
fn invalid_deref() -> ! {
const DEREF_ERROR: &'static str = "Dereferenced SendWrapper<T> variable from a thread different to the one it has been created with.";

panic!("{}", DEREF_ERROR)
fn invalid_deref(creation_site: Option<&Location<'static>>) -> ! {
const DEREF_ERROR: &str = "Dereferenced SendWrapper<T> variable from a thread different to the one it has been created with.";

panic!(
"{}{}",
DEREF_ERROR,
match creation_site {
Some(loc) => format!(" SendWrapper<T> created at {}", loc),
None => String::new(),
}
)
}

#[cold]
#[inline(never)]
#[track_caller]
fn invalid_poll() -> ! {
const POLL_ERROR: &'static str = "Polling SendWrapper<T> variable from a thread different to the one it has been created with.";

panic!("{}", POLL_ERROR)
fn invalid_poll(src: Option<&Location<'static>>) -> ! {
const POLL_ERROR: &str = "Polling SendWrapper<T> variable from a thread different to the one it has been created with.";

panic!(
"{}{}",
POLL_ERROR,
match src {
Some(loc) => format!(" SendWrapper<T> created at {}", loc),
None => String::new(),
}
)
}

#[cold]
#[inline(never)]
#[track_caller]
fn invalid_drop() {
const DROP_ERROR: &'static str = "Dropped SendWrapper<T> variable from a thread different to the one it has been created with.";
fn invalid_drop(creation_site: Option<&Location<'static>>) {
const DROP_ERROR: &str = "Dropped SendWrapper<T> variable from a thread different to the one it has been created with.";

if !std::thread::panicking() {
// panic because of dropping from wrong thread
// only do this while not unwinding (could be caused by deref from wrong thread)
panic!("{}", DROP_ERROR)
panic!(
"{}{}",
DROP_ERROR,
match creation_site {
Some(loc) => format!(" SendWrapper<T> created at {}", loc),
None => String::new(),
}
)
}
}

Expand Down