Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tokio: Enable trace subscriber propagation in the runtime #966

Merged
merged 3 commits into from Mar 13, 2019

Conversation

hawkw
Copy link
Member

@hawkw hawkw commented Mar 8, 2019

Motivation

One of the biggest pain points in the current tokio-trace API is that
the subscriber context must be propagated manually. For example, if a
user tokio::spawns a future, the spawning thread's subscriber will not
be propagated to the worker thread the spawned future actually executes
on.

Users can currently get around this one of two ways:

  • Instrument the future with a span:
     use tokio_trace_futures::Instrument;
    
     let fut = do_stuff()
         .instrument(span!("do stuff"));
     tokio::spawn(Box::new(fut));
    Since entering a span sets the dispatcher to that span's dispatcher
    while inside the span, this will both propagate the dispatcher
    context and create a span for the future. However, if the span is
    disabled, then the dispatcher will not be set, which can lead to
    surprising and unexpected behavior.
  • Explicitly attach a subscriber context to the future:
    use tokio_trace_futures::WithSubscriber;
    let dispatch = tokio_trace::dispatcher::with(|current| current.clone());
    
    let fut = do_stuff()
        .instrument(span!("do stuff")) // optionally
        .with_subscriber(dispatch);
    tokio::spawn(Box::new(fut));
    This will work regardless of whether or not the future is
    instrumented with a span, and regardless of whether or not any spans
    are enabled. However, it requires some extra noise and is perhaps
    non-obvious.

Additionally, the following issues apply to both of these approaches:

  • They both require the tokio-trace-futures crate, which is in the
    nursery and thus less stable than the core tokio-trace crates.
  • If library code also uses tokio::spawn or similar functions (a
    reasonably common pattern!) there is nothing that user code can do
    to ensure that futures spawned by libraries also propagate the
    subscriber context correctly.

Solution

This branch modifies runtime::Builder::build so that the current
trace subscriber context is captured, and adds a call to
tokio_trace_core::dispatcher::with_default in the closure passed
to around_worker in the runtime builder's threadpool builder. This
results in the dispatcher context being propagated to all of the worker
threads in the runtime.

Notes

  • This branch requires tokio to depend on tokio-trace-core.
    Therefore, we should not merge this branch until a 0.1 release of
    tokio-trace-core is published to crates.io.
  • Currently, the user cannot explicitly configure the runtime to use a
    provided dispatcher. Adding a function to allow this would require a
    dependency on tokio-trace-core in tokio's public API surface,
    and I believe that we haven't yet decided that tokio-trace-core is
    stable enough for that. This can be added later.

Fixes: #947

Signed-off-by: Eliza Weisman eliza@buoyant.io

@hawkw hawkw added C-enhancement Category: A PR with an enhancement or bugfix. tokio-trace labels Mar 8, 2019
@hawkw hawkw self-assigned this Mar 8, 2019
@hawkw hawkw requested a review from carllerche March 8, 2019 21:45
@LucioFranco
Copy link
Member

I'm +1 on this, though would we want to put it behind a trace feature flag?

@carllerche
Copy link
Member

@LucioFranco The thinking re: feature flag is adding a feature flag includes it permanently in the public dependency (feature flags are public).

Copy link
Member

@carllerche carllerche left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine w/ me. I would like to get reviews from @seanmonstar and @stjepang.

.around_worker(move |w, enter| {
let index = w.id().to_usize();
let dispatch = dispatch.clone();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this clone required? It seems like most of the other with_default things are fine with a reference.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@seanmonstar A Dispatch is internally just an Arc --- we could potentially make with_default take an &Dispatch and perform the clone itself. I thought it was better to take it by value since the dispatcher might not always need to be cloned when with_default is used in other cases`...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That’s a good point. with_dispatch should probably take a ref to follow the pattern.

The fn can clone internally.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've opened #971 to make this change. As I had suspected, it has the downside of requiring the arc to be cloned twice when entering a span using the tokio-trace::Span type. If we decide this is acceptable, I can rebase onto that branch.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting the dispatcher every time a span is entered is unexpected to me. Can you expand on that decision?

Copy link
Member Author

@hawkw hawkw Mar 11, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@carllerche

Setting the dispatcher every time a span is entered is unexpected to me. Can you expand on that decision?

Setting the default dispatcher on span entry is a relic of when spans tracked their parent's ID. At that time, it was necessary to ensure that any spans created inside a span were observed by the same subscriber that originally provided the entered span with an ID, as otherwise, new spans would be created with parent IDs that did not originate from that subscriber.

Now that spans don't track their parent ID, this is no longer necessary. However, removing this behavior does mean that if a span is entered outside of the subscriber context it was created in, any subsequent spans will be observed by the current default subscriber and thus will not be part of the original span's trace tree. Since subscribers are not expected to change frequently, and spans are not expected to move between them, this is likely acceptable.

I've removed this on my branch for #971 in commit 345567d.

Not setting the dispatcher on entry does make span entry/exit significantly faster. Here are the results of running a benchmark that enters a span, does nothing, and immediately exits it, before this change:

test enter_span              ... bench:          93 ns/iter (+/- 14)

...and after:

test enter_span              ... bench:          51 ns/iter (+/- 9)

@hawkw
Copy link
Member Author

hawkw commented Mar 11, 2019

I'm +1 on this, though would we want to put it behind a trace feature flag?

@LucioFranco The thinking re: feature flag is adding a feature flag includes it permanently in the public dependency (feature flags are public).

To expand just a little bit on Carl's comment, I just wanted to point out that since the dispatcher propagation is (currently) only done implicitly, this branch doesn't actually expose anything from tokio-trace-core in the public API surface. If Runtime users aren't using tokio-trace themselves, the added code will just silently do nothing; and since types and functions from tokio-trace-core aren't exposed publicly by tokio, any API changes in tokio-trace-core won't break code that doesn't depend on it explicitly.

In this case, the main reason users might want to disable a trace feature flag is to reduce the number of dependencies. This is valid but it's probably fine to wait until we have a public dependency on tokio-trace-core to introduce a feature flag.

hawkw added a commit that referenced this pull request Mar 11, 2019
* trace-core: Pass dispatcher by ref to `dispatcher::with_default`

As requested by @carllerche in #966 (comment), this branch changes the
`dispatcher::with_default` function in `tokio-trace-core` to take the
dispatcher by ref and perform the clone internally. This makes this
function more consistant with other `with_default` functions in other
crates.

Signed-off-by: Eliza Weisman <eliza@buoyant.io>

* trace: Don't set the default dispatcher on entering a span

Setting the default dispatcher on span entry is a relic of when spans
tracked their parent's ID. At that time, it was necessary to ensure that
any spans created inside a span were observed by the same subscriber
that originally provided the entered span with an ID, as otherwise, new
spans would be created with parent IDs that did not originate from that
subscriber.

Now that spans don't track their parent ID, this is no longer necessary.
However, removing this behavior does mean that if a span is entered
outside of the subscriber context it was created in, any subsequent
spans will be observed by the current default subscriber and thus will
not be part of the original span's trace tree. Since subscribers are not
expected to change frequently, and spans are not expected to move
between them, this is likely acceptable.

I've removed the tests for the old behavior.

Note that this change improves the performance of span entry/exit fairly
significantly. Here are the results of running a benchmark that enters
a span, does nothing, and immediately exits it, before this change:

```
test enter_span              ... bench:          93 ns/iter (+/- 14)
```

...and after:

```
test enter_span              ... bench:          51 ns/iter (+/- 9)
```

Signed-off-by: Eliza Weisman <eliza@buoyant.io>
@hawkw hawkw force-pushed the eliza/runtime-trace-compat branch from f4e2ff1 to d289d35 Compare March 11, 2019 23:19
@hawkw hawkw changed the title [WIP] tokio: Enable trace subscriber propagation in the runtime tokio: Enable trace subscriber propagation in the runtime Mar 11, 2019
@hawkw hawkw marked this pull request as ready for review March 11, 2019 23:19
@hawkw
Copy link
Member Author

hawkw commented Mar 11, 2019

I've rebased this branch onto master and marked it as "ready for review", but I think we'll want to actually get a tokio-trace-core 0.1 release on crates.io prior to merging this PR (and update the dependency to use that).

Signed-off-by: Eliza Weisman <eliza@buoyant.io>

tokio_reactor::with_default(&reactor_handles[index], enter, |enter| {
clock::with_default(&clock, enter, |enter| {
timer::with_default(&timer_handles[index], enter, |_| {
w.run();
dispatcher::with_default(dispatch, || {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just noting, out of context, "dispatcher" sounds like a task dispatcher, not something about traces...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@seanmonstar in f409c12, I changed the way the Dispatch type and the dispatcher module are imported so that they're all namespaced as trace::dispatcher --- hopefully that's clearer!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do agree w/ sean... trace::dispatcher helps some. IMO it would be better if it was trace::subscriber, but I also know why it isn't subscriber (Dispatcher::new(subscriber)).

I'm not going to make a request either way. I just wanted to provide these thoughts to @seanmonstar and @hawkw

Signed-off-by: Eliza Weisman <eliza@buoyant.io>
@hawkw hawkw force-pushed the eliza/runtime-trace-compat branch from 87b6ec3 to e31d5d1 Compare March 12, 2019 17:32
Signed-off-by: Eliza Weisman <eliza@buoyant.io>
@hawkw hawkw force-pushed the eliza/runtime-trace-compat branch from e31d5d1 to f409c12 Compare March 12, 2019 17:33
@carllerche carllerche merged commit acd08eb into master Mar 13, 2019
@carllerche carllerche deleted the eliza/runtime-trace-compat branch March 13, 2019 17:40
hawkw added a commit to tokio-rs/tracing that referenced this pull request Jun 25, 2019
* trace-core: Pass dispatcher by ref to `dispatcher::with_default`

As requested by @carllerche in tokio-rs/tokio#966 (comment), this branch changes the
`dispatcher::with_default` function in `tokio-trace-core` to take the
dispatcher by ref and perform the clone internally. This makes this
function more consistant with other `with_default` functions in other
crates.

Signed-off-by: Eliza Weisman <eliza@buoyant.io>

* trace: Don't set the default dispatcher on entering a span

Setting the default dispatcher on span entry is a relic of when spans
tracked their parent's ID. At that time, it was necessary to ensure that
any spans created inside a span were observed by the same subscriber
that originally provided the entered span with an ID, as otherwise, new
spans would be created with parent IDs that did not originate from that
subscriber.

Now that spans don't track their parent ID, this is no longer necessary.
However, removing this behavior does mean that if a span is entered
outside of the subscriber context it was created in, any subsequent
spans will be observed by the current default subscriber and thus will
not be part of the original span's trace tree. Since subscribers are not
expected to change frequently, and spans are not expected to move
between them, this is likely acceptable.

I've removed the tests for the old behavior.

Note that this change improves the performance of span entry/exit fairly
significantly. Here are the results of running a benchmark that enters
a span, does nothing, and immediately exits it, before this change:

```
test enter_span              ... bench:          93 ns/iter (+/- 14)
```

...and after:

```
test enter_span              ... bench:          51 ns/iter (+/- 9)
```

Signed-off-by: Eliza Weisman <eliza@buoyant.io>
hawkw added a commit to tokio-rs/tracing that referenced this pull request Jun 25, 2019
* trace-core: Pass dispatcher by ref to `dispatcher::with_default`

As requested by @carllerche in tokio-rs/tokio#966 (comment), this branch changes the
`dispatcher::with_default` function in `tokio-trace-core` to take the
dispatcher by ref and perform the clone internally. This makes this
function more consistant with other `with_default` functions in other
crates.

Signed-off-by: Eliza Weisman <eliza@buoyant.io>

* trace: Don't set the default dispatcher on entering a span

Setting the default dispatcher on span entry is a relic of when spans
tracked their parent's ID. At that time, it was necessary to ensure that
any spans created inside a span were observed by the same subscriber
that originally provided the entered span with an ID, as otherwise, new
spans would be created with parent IDs that did not originate from that
subscriber.

Now that spans don't track their parent ID, this is no longer necessary.
However, removing this behavior does mean that if a span is entered
outside of the subscriber context it was created in, any subsequent
spans will be observed by the current default subscriber and thus will
not be part of the original span's trace tree. Since subscribers are not
expected to change frequently, and spans are not expected to move
between them, this is likely acceptable.

I've removed the tests for the old behavior.

Note that this change improves the performance of span entry/exit fairly
significantly. Here are the results of running a benchmark that enters
a span, does nothing, and immediately exits it, before this change:

```
test enter_span              ... bench:          93 ns/iter (+/- 14)
```

...and after:

```
test enter_span              ... bench:          51 ns/iter (+/- 9)
```

Signed-off-by: Eliza Weisman <eliza@buoyant.io>
hawkw added a commit to tokio-rs/tracing that referenced this pull request Jun 25, 2019
* trace-core: Pass dispatcher by ref to `dispatcher::with_default`

As requested by @carllerche in tokio-rs/tokio#966 (comment), this branch changes the
`dispatcher::with_default` function in `tokio-trace-core` to take the
dispatcher by ref and perform the clone internally. This makes this
function more consistant with other `with_default` functions in other
crates.

Signed-off-by: Eliza Weisman <eliza@buoyant.io>

* trace: Don't set the default dispatcher on entering a span

Setting the default dispatcher on span entry is a relic of when spans
tracked their parent's ID. At that time, it was necessary to ensure that
any spans created inside a span were observed by the same subscriber
that originally provided the entered span with an ID, as otherwise, new
spans would be created with parent IDs that did not originate from that
subscriber.

Now that spans don't track their parent ID, this is no longer necessary.
However, removing this behavior does mean that if a span is entered
outside of the subscriber context it was created in, any subsequent
spans will be observed by the current default subscriber and thus will
not be part of the original span's trace tree. Since subscribers are not
expected to change frequently, and spans are not expected to move
between them, this is likely acceptable.

I've removed the tests for the old behavior.

Note that this change improves the performance of span entry/exit fairly
significantly. Here are the results of running a benchmark that enters
a span, does nothing, and immediately exits it, before this change:

```
test enter_span              ... bench:          93 ns/iter (+/- 14)
```

...and after:

```
test enter_span              ... bench:          51 ns/iter (+/- 9)
```

Signed-off-by: Eliza Weisman <eliza@buoyant.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-enhancement Category: A PR with an enhancement or bugfix.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants