Skip to content

Commit

Permalink
ref: Improve Hub concurrency docs (#509)
Browse files Browse the repository at this point in the history
This explains the parallelism and concurrency story around Hubs with added examples
of parallel multithreading and concurrent futures usage.
  • Loading branch information
Swatinem committed Oct 19, 2022
1 parent e11ab3b commit 89b31f8
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 9 deletions.
2 changes: 2 additions & 0 deletions sentry-core/Cargo.toml
Expand Up @@ -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"
3 changes: 2 additions & 1 deletion sentry-core/src/hub.rs
Expand Up @@ -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.
Expand Down
68 changes: 62 additions & 6 deletions sentry-core/src/lib.rs
Expand Up @@ -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
Expand All @@ -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")]
Expand Down
1 change: 0 additions & 1 deletion sentry-types/src/protocol/envelope.rs
Expand Up @@ -108,7 +108,6 @@ pub enum EnvelopeItem {
/// for more details.
Attachment(Attachment),
/// An Profile Item.
///
Profile(SampleProfile),
// TODO:
// etc…
Expand Down
4 changes: 3 additions & 1 deletion sentry/src/lib.rs
Expand Up @@ -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
Expand Down Expand Up @@ -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)]
Expand Down

0 comments on commit 89b31f8

Please sign in to comment.