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

[Parameter Capturing] Make probe management APIs asynchronous #5125

Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
"trce",
"tstr",
"ukwn",
"uninitialize",
"Uninstallation",
"uniquifier",
"Unlocalized",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Diagnostics.Monitoring.HostingStartup.ParameterCapturing.FunctionProbes
{
Expand All @@ -25,50 +28,172 @@ private static extern void RequestFunctionProbeInstallation(
uint count,
[MarshalAs(UnmanagedType.LPArray)] uint[] boxingTokens,
[MarshalAs(UnmanagedType.LPArray)] uint[] boxingTokenCounts);

private readonly object _requestLocker = new();

private delegate void FunctionProbeRegistrationCallback(int hresult);
private delegate void FunctionProbeInstallationCallback(int hresult);
private delegate void FunctionProbeUninstallationCallback(int hresult);
private delegate void FunctionProbeFaultCallback(ulong uniquifier);

[DllImport(ProfilerIdentifiers.MutatingProfiler.LibraryRootFileName, CallingConvention = CallingConvention.StdCall, PreserveSig = false)]
private static extern void RegisterFunctionProbeCallbacks(
FunctionProbeRegistrationCallback onRegistration,
FunctionProbeInstallationCallback onInstallation,
FunctionProbeUninstallationCallback onUninstallation,
FunctionProbeFaultCallback onFault);

[DllImport(ProfilerIdentifiers.MutatingProfiler.LibraryRootFileName, CallingConvention = CallingConvention.StdCall, PreserveSig = false)]
private static extern void UnregisterFunctionProbeCallbacks();

private long _probeState;
private const long ProbeStateUninitialized = default(long);
private const long ProbeStateUninstalled = 1;
private const long ProbeStateUninstalling = 2;
private const long ProbeStateInstalling = 3;
private const long ProbeStateInstalled = 4;
private const long ProbeStateUnrecoverable = 5;

private readonly TaskCompletionSource _probeRegistrationTaskSource = new(TaskCreationOptions.RunContinuationsAsynchronously);
private TaskCompletionSource? _installationTaskSource;
private TaskCompletionSource? _uninstallationTaskSource;

private long _disposedState;
private bool _isCapturing;

public event EventHandler<InstrumentedMethod>? OnProbeFault;

public FunctionProbesManager(IFunctionProbes probes)
{
ProfilerResolver.InitializeResolver<FunctionProbesManager>();

RegisterFunctionProbeCallbacks(OnRegistration, OnInstallation, OnUninstallation, OnFault);
schmittjoseph marked this conversation as resolved.
Show resolved Hide resolved
RequestFunctionProbeRegistration(FunctionProbesStub.GetProbeFunctionId());

FunctionProbesStub.Instance = probes;
}

public void StopCapturing()
private void OnRegistration(int hresult)
{
TransitionStateFromHr(_probeRegistrationTaskSource, hresult,
expectedState: ProbeStateUninitialized,
succeededState: ProbeStateUninstalled,
failedState: ProbeStateUnrecoverable);
}

private void OnInstallation(int hresult)
{
TransitionStateFromHr(_installationTaskSource, hresult,
expectedState: ProbeStateInstalling,
succeededState: ProbeStateInstalled,
failedState: ProbeStateUninstalled);
}

private void OnUninstallation(int hresult)
{
TransitionStateFromHr(_uninstallationTaskSource, hresult,
expectedState: ProbeStateUninstalling,
succeededState: ProbeStateUninstalled,
failedState: ProbeStateInstalled);
}

private void OnFault(ulong uniquifier)
{
lock (_requestLocker)
var methodCache = FunctionProbesStub.InstrumentedMethodCache;
if (methodCache == null ||
!methodCache.TryGetValue(uniquifier, out InstrumentedMethod? instrumentedMethod))
{
if (!_isCapturing)
{
return;
}
//
// The probe fault occurred in a method that is no longer actively instrumented, ignore.
// This can happen when we request uninstallation of function probes and there's still a thread
// actively in one of the instrumented methods and it happens to fault.
//
return;
}

FunctionProbesStub.InstrumentedMethodCache = null;
RequestFunctionProbeUninstallation();
OnProbeFault?.Invoke(this, instrumentedMethod);
schmittjoseph marked this conversation as resolved.
Show resolved Hide resolved
}

private void TransitionStateFromHr(TaskCompletionSource? taskCompletionSource, int hresult, long expectedState, long succeededState, long failedState)
{
Exception? ex = Marshal.GetExceptionForHR(hresult);
long newState = (ex == null) ? succeededState : failedState;

if (expectedState != Interlocked.CompareExchange(ref _probeState, newState, expectedState))
{
// Unexpected, the profiler is in a different state than us.
StateMismatch(expectedState);
return;
}

if (ex == null)
{
_ = taskCompletionSource?.TrySetResult();
}
else
{
_ = taskCompletionSource?.TrySetException(ex);
}
}

private void StateMismatch(long expected)
{
InvalidOperationException ex = new(string.Format(CultureInfo.InvariantCulture, ParameterCapturingStrings.ErrorMessage_ProbeStateMismatchFormatString, expected, _probeState));

_probeState = ProbeStateUnrecoverable;
_ = _installationTaskSource?.TrySetException(ex);
_ = _uninstallationTaskSource?.TrySetException(ex);
_ = _probeRegistrationTaskSource?.TrySetException(ex);
}

public async Task StopCapturingAsync(CancellationToken token)
{
if (ProbeStateInstalled != Interlocked.CompareExchange(ref _probeState, ProbeStateUninstalling, ProbeStateInstalled))
{
throw new InvalidOperationException();
schmittjoseph marked this conversation as resolved.
Show resolved Hide resolved
}

_isCapturing = false;
_uninstallationTaskSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
try
{
StopCapturingCore();
}
catch
{
_uninstallationTaskSource = null;
_probeState = ProbeStateInstalled;
throw;
}

await _uninstallationTaskSource.Task.WaitAsync(token).ConfigureAwait(false);
}
schmittjoseph marked this conversation as resolved.
Show resolved Hide resolved

public void StartCapturing(IList<MethodInfo> methods)
private void StopCapturingCore()
{
if (_probeState == ProbeStateUninstalled)
{
return;
}

FunctionProbesStub.InstrumentedMethodCache = null;
RequestFunctionProbeUninstallation();
}


public async Task StartCapturingAsync(IList<MethodInfo> methods, CancellationToken token)
{
if (methods.Count == 0)
{
schmittjoseph marked this conversation as resolved.
Show resolved Hide resolved
throw new ArgumentException(nameof(methods));
}

Dictionary<ulong, InstrumentedMethod> newMethodCache = new(methods.Count);
lock (_requestLocker)
await _probeRegistrationTaskSource.Task.WaitAsync(token).ConfigureAwait(false);

if (ProbeStateUninstalled != Interlocked.CompareExchange(ref _probeState, ProbeStateInstalling, ProbeStateUninstalled))
{
if (_isCapturing)
{
throw new InvalidOperationException();
}
throw new InvalidOperationException();
}

try
{
Dictionary<ulong, InstrumentedMethod> newMethodCache = new(methods.Count);
List<ulong> functionIds = new(methods.Count);
List<uint> argumentCounts = new(methods.Count);
List<uint> boxingTokens = new();
Expand All @@ -78,7 +203,7 @@ public void StartCapturing(IList<MethodInfo> methods)
ulong functionId = method.GetFunctionId();
if (functionId == 0)
{
return;
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, ParameterCapturingStrings.ErrorMessage_FunctionDoesNotHaveIdFormatString, method.Name));
}

uint[] methodBoxingTokens = BoxingTokens.GetBoxingTokens(method);
Expand All @@ -94,14 +219,23 @@ public void StartCapturing(IList<MethodInfo> methods)
}

FunctionProbesStub.InstrumentedMethodCache = new ReadOnlyDictionary<ulong, InstrumentedMethod>(newMethodCache);

_installationTaskSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
schmittjoseph marked this conversation as resolved.
Show resolved Hide resolved
RequestFunctionProbeInstallation(
functionIds.ToArray(),
(uint)functionIds.Count,
boxingTokens.ToArray(),
argumentCounts.ToArray());

_isCapturing = true;
}
catch
{
FunctionProbesStub.InstrumentedMethodCache = null;
_probeState = ProbeStateUninstalled;
_installationTaskSource = null;
throw;
}

await _installationTaskSource.Task.WaitAsync(token).ConfigureAwait(false);
}
schmittjoseph marked this conversation as resolved.
Show resolved Hide resolved

public void Dispose()
Expand All @@ -110,13 +244,17 @@ public void Dispose()
return;

FunctionProbesStub.Instance = null;

_ = _installationTaskSource?.TrySetCanceled();
schmittjoseph marked this conversation as resolved.
Show resolved Hide resolved
_ = _uninstallationTaskSource?.TrySetCanceled();

try
{
StopCapturing();
UnregisterFunctionProbeCallbacks();
StopCapturingCore();
}
catch
{

}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Diagnostics.Monitoring.HostingStartup.ParameterCapturing.FunctionProbes
{
internal interface IFunctionProbesManager : IDisposable
{
public void StartCapturing(IList<MethodInfo> methods);
public Task StartCapturingAsync(IList<MethodInfo> methods, CancellationToken token);

public void StopCapturing();
public Task StopCapturingAsync(CancellationToken token);

public event EventHandler<InstrumentedMethod> OnProbeFault;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public sealed class InstrumentedMethod

public InstrumentedMethod(MethodInfo method, uint[] boxingTokens)
{
FunctionId = method.GetFunctionId();
SupportedParameters = BoxingTokens.AreParametersSupported(boxingTokens);
MethodWithParametersTemplateString = PrettyPrinter.ConstructTemplateStringFromMethod(method, SupportedParameters);
foreach (bool isParameterSupported in SupportedParameters)
Expand Down Expand Up @@ -67,5 +68,7 @@ private static ParameterCaptureMode ComputeCaptureMode(string? typeName)
/// The number of format items equals NumberOfSupportedParameters.
/// </summary>
public string MethodWithParametersTemplateString { get; }

public ulong FunctionId { get; }
}
}
Loading