Azure / Azure/azure-functions-host
SuspendedSynchronizationContextScope used dangerously
- 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
Assessment
This issue has not been assessed yet.