Azure / Azure/azure-functions-host

SuspendedSynchronizationContextScope used dangerously

Open
#1,829 0 comments 0 reactions 1 assignee Claimed by @fabiocav View on GitHub
Dominant language
C#
Stars
2k
Forks
482
Avg merge
2d 12h
Merged PRs (30d)
38

Description

`SuspendedSynchronizationContextScope` is used [here](https://github.com/Azure/azure-webjobs-sdk-script/blob/918b057707acfb842659c9dad3cef0193fae1330/src/WebJobs.Script.WebHost/WebScriptHostManager.cs#L181) to prevent the ASP.NET SyncCtx from being seen by the function.

The problem with its usage is that with its current API, all code within its `using` block must be synchronous. Otherwise, you will [remove an ASP.NET SyncCtx from the request thread and stick it on a random thread pool thread](https://stackoverflow.com/questions/44418761/cross-thread-exception-after-async-call).

I'd recommend changing the `SuspendedSynchronizationContextScope` to enforce correct usage, similar to my [`SynchronizationContextSwitcher`](https://github.com/StephenCleary/AsyncEx/blob/master/src/Nito.AsyncEx.Tasks/SynchronizationContextSwitcher.cs#L36), as such:

public sealed class SuspendedSynchronizationContextScope : IDisposable
{
private readonly SynchronizationContext _oldContext;
private bool _disposed = false;

private SuspendedSynchronizationContextScope()
{
_oldContext = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(null);
}

private void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
SynchronizationContext.SetSynchronizationContext(_oldContext);
}

_disposed = true;
}
}

void IDisposable.Dispose()
{
Dispose(true);
}

public static T NoContext(Func action)
{
using (new SuspendedSynchronizationContextScope())
return action();
}
}

This is safe to use as such:

// Suspend the current synchronization context so we don't pass the ASP.NET
// context down to the function.
SuspendedSynchronizationContextScope.NoContext(async () =>
{
// Add the request to the logging scope. This allows the App Insights logger to
// record details about the request.
ILoggerFactory loggerFactory = _config.HostConfig.GetService();
ILogger logger = loggerFactory.CreateLogger(LogCategories.Function);
var scopeState = new Dictionary()
{
[ScriptConstants.LoggerHttpRequest] = request
};
using (logger.BeginScope(scopeState))
{
await Instance.CallAsync(function.Name, arguments, cancellationToken);
}
});

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.