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

Runtime context API #948

Merged
merged 6 commits into from Jul 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/OpenTelemetry.Api/CHANGELOG.md
Expand Up @@ -2,6 +2,15 @@

## Unreleased

* Introduced `RuntimeContext` API
([#948](https://github.com/open-telemetry/opentelemetry-dotnet/pull/948))

## 0.4.0-beta.2

Released 2020-07-24

* First beta release

## 0.3.0-beta

Released 2020-07-23
Expand Down
53 changes: 53 additions & 0 deletions src/OpenTelemetry.Api/Context/AsyncLocalRuntimeContextSlot.cs
@@ -0,0 +1,53 @@
// <copyright file="AsyncLocalRuntimeContextSlot.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

#if !NET452
using System.Threading;

namespace OpenTelemetry.Context
{
/// <summary>
/// The async local implementation of context slot.
/// </summary>
/// <typeparam name="T">The type of the underlying value.</typeparam>
public class AsyncLocalRuntimeContextSlot<T> : RuntimeContextSlot<T>
{
private readonly AsyncLocal<T> slot;

/// <summary>
/// Initializes a new instance of the <see cref="AsyncLocalRuntimeContextSlot{T}"/> class.
/// </summary>
/// <param name="name">The name of the context slot.</param>
public AsyncLocalRuntimeContextSlot(string name)
: base(name)
{
this.slot = new AsyncLocal<T>();
}

/// <inheritdoc/>
public override T Get()
{
return this.slot.Value;
}

/// <inheritdoc/>
public override void Set(T value)
{
this.slot.Value = value;
}
}
}
#endif
74 changes: 74 additions & 0 deletions src/OpenTelemetry.Api/Context/RemotingRuntimeContextSlot.cs
@@ -0,0 +1,74 @@
// <copyright file="RemotingRuntimeContextSlot.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

#if NET452
using System.Collections;
using System.Reflection;
using System.Runtime.Remoting.Messaging;

namespace OpenTelemetry.Context
{
/// <summary>
/// The .NET Remoting implementation of context slot.
/// </summary>
/// <typeparam name="T">The type of the underlying value.</typeparam>
public class RemotingRuntimeContextSlot<T> : RuntimeContextSlot<T>
{
// A special workaround to suppress context propagation cross AppDomains.
//
// By default the value added to System.Runtime.Remoting.Messaging.CallContext
// will be marshalled/unmarshalled across AppDomain boundary. This will cause
// serious issue if the destination AppDomain doesn't have the corresponding type
// to unmarshal data.
// The worst case is AppDomain crash with ReflectionLoadTypeException.
//
// The workaround is to use a well known type that exists in all AppDomains, and
// put the actual payload as a non-public field so the field is ignored during
// marshalling.
private static readonly FieldInfo WrapperField = typeof(BitArray).GetField("_syncRoot", BindingFlags.Instance | BindingFlags.NonPublic);

/// <summary>
/// Initializes a new instance of the <see cref="RemotingRuntimeContextSlot{T}"/> class.
/// </summary>
/// <param name="name">The name of the context slot.</param>
public RemotingRuntimeContextSlot(string name)
: base(name)
{
}

/// <inheritdoc/>
public override T Get()
{
var wrapper = CallContext.LogicalGetData(this.Name) as BitArray;

if (wrapper == null)
{
return default(T);
}

return (T)WrapperField.GetValue(wrapper);
}

/// <inheritdoc/>
public override void Set(T value)
{
var wrapper = new BitArray(0);
WrapperField.SetValue(wrapper, value);
CallContext.LogicalSetData(this.Name, wrapper);
}
}
}
#endif
110 changes: 110 additions & 0 deletions src/OpenTelemetry.Api/Context/RuntimeContext.cs
@@ -0,0 +1,110 @@
// <copyright file="RuntimeContext.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;

namespace OpenTelemetry.Context
{
/// <summary>
/// Generic runtime context management API.
/// </summary>
public sealed class RuntimeContext
{
private static readonly ConcurrentDictionary<string, object> Slots = new ConcurrentDictionary<string, object>();

/// <summary>
/// Gets or sets the actual context carrier implementation.
/// </summary>
#if !NET452
public static Type ContextSlotType { get; set; } = typeof(AsyncLocalRuntimeContextSlot<>);
#else
public static Type ContextSlotType { get; set; } = typeof(RemotingRuntimeContextSlot<>);
#endif

/// <summary>
/// Register a named context slot.
/// </summary>
/// <param name="name">The name of the context slot.</param>
/// <typeparam name="T">The type of the underlying value.</typeparam>
public static void RegisterSlot<T>(string name)
{
lock (Slots)
{
if (Slots.ContainsKey(name))
{
throw new InvalidOperationException($"The context slot {name} is already registered.");
}

var type = ContextSlotType.MakeGenericType(typeof(T));
var ctor = type.GetConstructor(new Type[] { typeof(string) });
Slots[name] = ctor.Invoke(new object[] { name });
}
}

/*
public static void Apply(IDictionary<string, object> snapshot)
{
foreach (var entry in snapshot)
{
// TODO: revisit this part if we want Snapshot() to be used on critical paths
dynamic value = entry.Value;
SetValue(entry.Key, value);
}
}

public static IDictionary<string, object> Snapshot()
{
var retval = new Dictionary<string, object>();
foreach (var entry in Slots)
{
// TODO: revisit this part if we want Snapshot() to be used on critical paths
dynamic slot = entry.Value;
retval[entry.Key] = slot.Get();
}
return retval;
}
*/

/// <summary>
/// Sets the value to a registered slot.
/// </summary>
/// <param name="name">The name of the context slot.</param>
/// <param name="value">The value to be set.</param>
/// <typeparam name="T">The type of the value.</typeparam>
public static void SetValue<T>(string name, T value)
{
var slot = (RuntimeContextSlot<T>)Slots[name];
slot.Set(value);
}

/// <summary>
/// Gets the value from a registered slot.
/// </summary>
/// <param name="name">The name of the context slot.</param>
/// <typeparam name="T">The type of the value.</typeparam>
/// <returns>The value retrieved from the context slot.</returns>
public static T GetValue<T>(string name)
{
var slot = (RuntimeContextSlot<T>)Slots[name];
return slot.Get();
}

// For testing purpose
// private static Clear
}
}
51 changes: 51 additions & 0 deletions src/OpenTelemetry.Api/Context/RuntimeContextSlot.cs
@@ -0,0 +1,51 @@
// <copyright file="RuntimeContextSlot.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

namespace OpenTelemetry.Context
{
/// <summary>
/// The abstract context slot.
/// </summary>
/// <typeparam name="T">The type of the underlying value.</typeparam>
public abstract class RuntimeContextSlot<T>
{
/// <summary>
/// Initializes a new instance of the <see cref="RuntimeContextSlot{T}"/> class.
/// </summary>
/// <param name="name">The name of the context slot.</param>
public RuntimeContextSlot(string name)
{
this.Name = name;
}

/// <summary>
/// Gets the name of the context slot.
/// </summary>
public string Name { get; private set; }

/// <summary>
/// Get the value from the context slot.
/// </summary>
/// <returns>The value retrieved from the context slot.</returns>
public abstract T Get();

/// <summary>
/// Set the value to the context slot.
/// </summary>
/// <param name="value">The value to be set.</param>
public abstract void Set(T value);
}
}
51 changes: 51 additions & 0 deletions src/OpenTelemetry.Api/Context/ThreadLocalRuntimeContextSlot.cs
@@ -0,0 +1,51 @@
// <copyright file="ThreadLocalRuntimeContextSlot.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

using System.Threading;

namespace OpenTelemetry.Context
{
/// <summary>
/// The thread local (TLS) implementation of context slot.
/// </summary>
/// <typeparam name="T">The type of the underlying value.</typeparam>
public class ThreadLocalRuntimeContextSlot<T> : RuntimeContextSlot<T>
{
private readonly ThreadLocal<T> slot;

/// <summary>
/// Initializes a new instance of the <see cref="ThreadLocalRuntimeContextSlot{T}"/> class.
/// </summary>
/// <param name="name">The name of the context slot.</param>
public ThreadLocalRuntimeContextSlot(string name)
: base(name)
{
this.slot = new ThreadLocal<T>();
}

/// <inheritdoc/>
public override T Get()
{
return this.slot.Value;
}

/// <inheritdoc/>
public override void Set(T value)
{
this.slot.Value = value;
}
}
}