Skip to content

Commit

Permalink
Add an extension that is able to diverge from the compiler model and …
Browse files Browse the repository at this point in the history
…match the mental model made by the IEnumerable `yield` syntax in CSharp.

Addresses opentracing/opentracing-csharp#106 via an extension
  • Loading branch information
ndrwrbgs committed Apr 1, 2019
1 parent 7f9ced1 commit b4e937b
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 0 deletions.
146 changes: 146 additions & 0 deletions src/Library/EnumerableYieldExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
namespace OpenTracing.Contrib.Extensions
{
using System;
using System.Collections.Generic;

using JetBrains.Annotations;

using OpenTracing.Util;

/// <summary>
/// Extensions for when inside an IEnumerable and, even though the compiler makes it that way, you don't want
/// the caller code to 'belong to' or appear to be 'called by' your internal spans.
///
/// <see href="https://github.com/opentracing/opentracing-csharp/issues/106">Instrumentation inside an IEnumerable</see>
/// </summary>
[PublicAPI]
public static class EnumerableYieldExtensions
{
/// <summary>
/// Wraps the enumeration with the specified <see cref="spanBuilder"/>, returning to the parent context (removing
/// the 'enumerate' scope from <see cref="IScopeManager.Active"/>) before each yield return to the caller.
/// </summary>
public static IEnumerable<T> WrapWithTracing<T>(
this IEnumerable<T> source,
Func<ISpanBuilder> spanBuilder,
IScopeManager scopeManager)
{
using (IYieldReadyScope scope = spanBuilder().StartActiveYieldReadyScope(scopeManager))
{
foreach (var item in source)
{
using (scope.Yielding())
yield return item;
}
}
}

/// <summary>
/// Wraps the enumeration with the specified <see cref="spanBuilder"/>, returning to the parent context (removing
/// the 'enumerate' scope from <see cref="IScopeManager.Active"/>) before each yield return to the caller.
/// </summary>
public static IEnumerable<T> WrapWithTracing<T>(
this IEnumerable<T> source,
Func<ISpanBuilder> spanBuilder)
{
using (IYieldReadyScope scope = spanBuilder().StartActiveYieldReadyScope())
{
foreach (var item in source)
{
using (scope.Yielding())
yield return item;
}
}
}

public static IYieldReadyScope StartActiveYieldReadyScope(
this ISpanBuilder spanBuilder,
bool finishOnDispose = true)
{
return StartActiveYieldReadyScope(spanBuilder, finishOnDispose, GlobalTracer.Instance.ScopeManager);
}

public static IYieldReadyScope StartActiveYieldReadyScope(
this ISpanBuilder spanBuilder,
IScopeManager scopeManager)
{
return StartActiveYieldReadyScope(spanBuilder, true, scopeManager);
}

public static IYieldReadyScope StartActiveYieldReadyScope(
this ISpanBuilder spanBuilder,
bool finishOnDispose,
IScopeManager scopeManager)
{
IScope startActive = spanBuilder.StartActive(false);
return new YieldReadyScope(startActive, finishOnDispose, scopeManager);
}

private sealed class YieldReadyScope : IYieldReadyScope
{
private IScope currentlyActiveScope;
private readonly bool finishOnDispose;
private readonly IScopeManager scopeManager;

public YieldReadyScope(
IScope startActive,
bool finishOnDispose,
IScopeManager scopeManager)
{
this.currentlyActiveScope = startActive;
this.finishOnDispose = finishOnDispose;
this.scopeManager = scopeManager;

this.Span = this.currentlyActiveScope.Span;
}

public void Dispose()
{
// Finish of the overall Scope
if (this.finishOnDispose)
{
this.currentlyActiveScope.Span.Finish();
}

// Disposes the SCOPE not finishing the
this.currentlyActiveScope.Dispose();
}

public ISpan Span { get; }

public IDisposable Yielding()
{
// Disposes the SCOPE not finishing the SPAN
this.currentlyActiveScope.Dispose();

return new ActivateSpanOnDispose(
this.Span,
this.scopeManager,
setCurrentlyActiveOn: this);
}

private sealed class ActivateSpanOnDispose : IDisposable
{
private readonly ISpan span;
private readonly IScopeManager scopeManager;
private readonly YieldReadyScope setCurrentlyActiveOn;

public ActivateSpanOnDispose(
ISpan span,
IScopeManager scopeManager,
YieldReadyScope setCurrentlyActiveOn)
{
this.span = span;
this.scopeManager = scopeManager;
this.setCurrentlyActiveOn = setCurrentlyActiveOn;
}

public void Dispose()
{
var newScope = this.scopeManager.Activate(this.span, false);
this.setCurrentlyActiveOn.currentlyActiveScope = newScope;
}
}
}
}
}
13 changes: 13 additions & 0 deletions src/Library/IYieldReadyScope.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace OpenTracing.Contrib.Extensions
{
using System;

public interface IYieldReadyScope : IScope
{
/// <summary>
/// To be called right before and disposed right after a `yield` in an IEnumerable.
/// Stashes this <see cref="IScope"/> for later resumption after the caller handles the IEnumerable item.
/// </summary>
IDisposable Yielding();
}
}

0 comments on commit b4e937b

Please sign in to comment.