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

AsyncLocal based scope manager (ported from Java) #63

Merged
merged 2 commits into from
Feb 13, 2018
Merged

Conversation

cwe1ss
Copy link
Member

@cwe1ss cwe1ss commented Feb 10, 2018

This is a port from opentracing-java's ThreadLocalScopeManager.

  • For storage it either uses AsyncLocal (.NET 4.6+, .NET Core) or the CallContext (.NET 4.5). This code is based on ASP.NET Core's HttpContextAccessor.
  • Since I had to fiddle around with #if-defs I'm now targeting netstandard1.3 and net452 as described in Target netstandard1.3 and net45 #62 - I'll change the targets here should that issue decide differently.
  • The unit tests are also ported from Java.
  • I also had to slightly change the build script (as the tests now target multiple frameworks) and I've updated the nuget dependencies.
  • I have NOT ported Java's AutoFinishScopeManager as I'm not sure if that's required in .NET (due to async/await). I'd say we port that later if someone really needs it.
  • The project structure assumes only one OpenTracing package - I'll change this as well should we decide differently in 0.11 Release plan #55.

@cwe1ss cwe1ss added this to the 0.11.0 milestone Feb 10, 2018
@cwe1ss cwe1ss mentioned this pull request Feb 10, 2018
10 tasks
@cwe1ss
Copy link
Member Author

cwe1ss commented Feb 10, 2018

fyi: I'll probably merge this on Tuesday morning (UTC) if there's no contrary comments.

private readonly bool _finishOnDispose;
private readonly IScope _scopeToRestore;

public AsyncLocalScope(AsyncLocalScopeManager scopeManager, ISpan wrappedSpan, bool finishOnDispose)
Copy link
Contributor

Choose a reason for hiding this comment

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

[nit] it looks like the only place wrappedSpan is supplied here it's not wrapped in anything?

Copy link
Contributor

Choose a reason for hiding this comment

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

Oooh it's the span that is BEING wrapped, not a span that IS wrapped. Obviously your naming is fine now that I realize this, but given I was confused for a bit suggests possibility for better naming (subjective, feel free to leave if you disagree)

Copy link
Member Author

Choose a reason for hiding this comment

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

Naming is from Java, except that I named it wrappedSpan instead of just wrapped.

{
ISpan span = Substitute.For<ISpan>();

using (IScope scope = _source.Activate(span, false))
Copy link
Contributor

Choose a reason for hiding this comment

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

[nit] I generally prefer named args for booleans (or nulls) because it makes the call site make a lot more sense.
E.g. in this place you'd be able to see we expect no Finish() because we said finishOnDispose: false
Without it you require more contextual brain-knowledge to be able to understand the test

Copy link
Member Author

Choose a reason for hiding this comment

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

fixed

/// that relies on C#'s AsyncLocal/CallContext storage primitive.
/// </summary>
/// <seealso cref="IScopeManager"/>
public class AsyncLocalScope : IScope
Copy link
Contributor

Choose a reason for hiding this comment

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

This relates to the other review comment I made regarding the APIs. Using this structure removes the ability for any implementation to expect that the Scope they get back will be the one they pushed out of their APIs, which can cause some weird issues.

Do not have a good suggestion at this time, so this is not a comment to request change but just to start your brain pondering over solutions.

Copy link
Member Author

Choose a reason for hiding this comment

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

Could you clarify this? I do not understand what you mean.

{
if (_scopeManager.Active != this)
{
// This shouldn't happen if users call methods in the expected order. Bail out.
Copy link
Contributor

Choose a reason for hiding this comment

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

Throw InvalidOperationException?

The exception that is thrown when a method call is invalid for the object's current state

Copy link
Contributor

Choose a reason for hiding this comment

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

Depends on your views... Throw from Dispose is generally frowned upon, but which is worse:

  1. Failing silently leaving a bug to go potentially unnoticed for quite some time and hard to debug
  2. Throwing from Dispose on code errors

The reason Throw on Dispose is frowned upon is because the following

try
{
 using (var span = /* ... */)
 {
   // Do stuff
   throw new IOException();
 }
}
catch (IOException)
{
 // Not hit if (non-IOException) thrown from Dispose, and the exception being thrown is not available to `Dispose` so it can't even be wrapped
}

But I generally feel that's okay because you only loose an exception if you're 1) already in an exceptional state and 2) have done something exceptional with the library that suggests a developer bug (I avoid it when it's a state-based/concurrency-based bug but when it's something that's likely just a coding error it seems better to fail-fast to me)

Copy link
Member Author

Choose a reason for hiding this comment

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

AFAIK not throwing an exception here has been a deliberate decision in Java as tracers are not meant to break the application (in the same way that loggers are not meant to break it). So if you want to have this changed we should discuss it in a separate issue.

_wrappedSpan = wrappedSpan;
_finishOnDispose = finishOnDispose;

_scopeToRestore = scopeManager.Active;
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to explicitly set the Parent here? I notice my InMemory implementation also does not (at this location) but I'm curious if we both missed part of the spec

Copy link
Member Author

Choose a reason for hiding this comment

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

Could you clarify this? I do not understand what you mean.

@ndrwrbgs
Copy link
Contributor

ndrwrbgs commented Feb 11, 2018 via email

@cwe1ss cwe1ss merged commit 5eaa1a1 into master Feb 13, 2018
@cwe1ss cwe1ss deleted the cweiss/ScopeUtil branch February 13, 2018 08:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants