From 89b31f837bc945f587cfc461d88e63dd0a8ce225 Mon Sep 17 00:00:00 2001 From: Arpad Borsos Date: Wed, 19 Oct 2022 16:12:50 +0200 Subject: [PATCH] ref: Improve Hub concurrency docs (#509) This explains the parallelism and concurrency story around Hubs with added examples of parallel multithreading and concurrent futures usage. --- sentry-core/Cargo.toml | 2 + sentry-core/src/hub.rs | 3 +- sentry-core/src/lib.rs | 68 ++++++++++++++++++++++++--- sentry-types/src/protocol/envelope.rs | 1 - sentry/src/lib.rs | 4 +- 5 files changed, 69 insertions(+), 9 deletions(-) diff --git a/sentry-core/Cargo.toml b/sentry-core/Cargo.toml index 9da4b0b6..46782a39 100644 --- a/sentry-core/Cargo.toml +++ b/sentry-core/Cargo.toml @@ -55,4 +55,6 @@ sentry = { path = "../sentry", default-features = false, features = ["test", "tr thiserror = "1.0.15" anyhow = "1.0.30" tokio = { version = "1.0", features = ["rt", "rt-multi-thread", "macros"] } +futures = "0.3.24" +rayon = "1.5.3" criterion = "0.3" diff --git a/sentry-core/src/hub.rs b/sentry-core/src/hub.rs index b5bc513b..3fe0134b 100644 --- a/sentry-core/src/hub.rs +++ b/sentry-core/src/hub.rs @@ -77,7 +77,8 @@ impl HubImpl { /// toplevel convenience functions are expose that will automatically dispatch /// to the thread-local ([`Hub::current`]) hub. In some situations this might not be /// possible in which case it might become necessary to manually work with the -/// hub. This is for instance the case when working with async code. +/// hub. See the main [`crate`] docs for some common use-cases and pitfalls +/// related to parallel, concurrent or async code. /// /// Hubs that are wrapped in [`Arc`]s can be bound to the current thread with /// the `run` static method. diff --git a/sentry-core/src/lib.rs b/sentry-core/src/lib.rs index e92d1c0a..0d0d9a4d 100644 --- a/sentry-core/src/lib.rs +++ b/sentry-core/src/lib.rs @@ -14,6 +14,68 @@ //! the concepts of [`Client`], [`Hub`] and [`Scope`], as well as the extension //! points via the [`Integration`], [`Transport`] and [`TransportFactory`] traits. //! +//! # Parallelism, Concurrency and Async +//! +//! The main concurrency primitive is the [`Hub`]. In general, all concurrent +//! code, no matter if multithreaded parallelism or futures concurrency, needs +//! to run with its own copy of a [`Hub`]. Even though the [`Hub`] is internally +//! synchronized, using it concurrently may lead to unexpected results up to +//! panics. +//! +//! For threads or tasks that are running concurrently or outlive the current +//! execution context, a new [`Hub`] needs to be created and bound for the computation. +//! +//! ```rust +//! # let rt = tokio::runtime::Runtime::new().unwrap(); +//! # rt.block_on(async { +//! use rayon::prelude::*; +//! use sentry::{Hub, SentryFutureExt}; +//! use std::sync::Arc; +//! +//! // Parallel multithreaded code: +//! let outer_hub = Hub::current(); +//! let results: Vec<_> = [1_u32, 2, 3] +//! .into_par_iter() +//! .map(|num| { +//! let thread_hub = Arc::new(Hub::new_from_top(&outer_hub)); +//! Hub::run(thread_hub, || num * num) +//! }) +//! .collect(); +//! +//! assert_eq!(&results, &[1, 4, 9]); +//! +//! // Concurrent futures code: +//! let futures = [1_u32, 2, 3] +//! .into_iter() +//! .map(|num| async move { num * num }.bind_hub(Hub::new_from_top(Hub::current()))); +//! let results = futures::future::join_all(futures).await; +//! +//! assert_eq!(&results, &[1, 4, 9]); +//! # }); +//! ``` +//! +//! For tasks that are not concurrent and do not outlive the current execution +//! context, no *new* [`Hub`] needs to be created, but the current [`Hub`] has +//! to be bound. +//! +//! ```rust +//! # let rt = tokio::runtime::Runtime::new().unwrap(); +//! # rt.block_on(async { +//! use sentry::{Hub, SentryFutureExt}; +//! +//! // Spawned thread that is being joined: +//! let hub = Hub::current(); +//! let result = std::thread::spawn(|| Hub::run(hub, || 1_u32)).join(); +//! +//! assert_eq!(result.unwrap(), 1); +//! +//! // Spawned future that is being awaited: +//! let result = tokio::spawn(async { 1_u32 }.bind_hub(Hub::current())).await; +//! +//! assert_eq!(result.unwrap(), 1); +//! # }); +//! ``` +//! //! # Minimal API //! //! By default, this crate comes with a so-called "minimal" mode. This mode will @@ -38,12 +100,6 @@ //! [Sentry]: https://sentry.io/ //! [`sentry`]: https://crates.io/crates/sentry //! [Unified API]: https://develop.sentry.dev/sdk/unified-api/ -//! [`Client`]: struct.Client.html -//! [`Hub`]: struct.Hub.html -//! [`Scope`]: struct.Scope.html -//! [`Integration`]: trait.Integration.html -//! [`Transport`]: trait.Transport.html -//! [`TransportFactory`]: trait.TransportFactory.html //! [`test`]: test/index.html #![doc(html_favicon_url = "https://sentry-brand.storage.googleapis.com/favicon.ico")] diff --git a/sentry-types/src/protocol/envelope.rs b/sentry-types/src/protocol/envelope.rs index ea6620d0..ac38cdf7 100644 --- a/sentry-types/src/protocol/envelope.rs +++ b/sentry-types/src/protocol/envelope.rs @@ -108,7 +108,6 @@ pub enum EnvelopeItem { /// for more details. Attachment(Attachment), /// An Profile Item. - /// Profile(SampleProfile), // TODO: // etc… diff --git a/sentry/src/lib.rs b/sentry/src/lib.rs index 5cfe255f..f9f26ecc 100644 --- a/sentry/src/lib.rs +++ b/sentry/src/lib.rs @@ -8,7 +8,8 @@ //! //! The most convenient way to use this library is via the [`sentry::init`] function, //! which starts a sentry client with a default set of integrations, and binds -//! it to the current [`Hub`]. +//! it to the current [`Hub`]. More Information on how to use Sentry in parallel, +//! concurrent and async scenarios can be found on the [`Hub`] docs as well. //! //! The [`sentry::init`] function returns a guard that when dropped will flush Events that were not //! yet sent to the sentry service. It has a two second deadline for this so shutdown of @@ -110,6 +111,7 @@ //! //! ## Integrations //! - `tower`: Enables support for the `tower` crate and those using it. + #![doc(html_favicon_url = "https://sentry-brand.storage.googleapis.com/favicon.ico")] #![doc(html_logo_url = "https://sentry-brand.storage.googleapis.com/sentry-glyph-black.png")] #![warn(missing_docs)]