Runtime context API (#948)

* runtime context API

* fix comments

* rename

* changelog

* changelog
This commit is contained in:
Reiley Yang 2020-07-30 20:45:28 -07:00 committed by GitHub
parent d03dff2435
commit 3333957c12
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 348 additions and 0 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}