Lambda encountered an UnobservedTaskException - Cannot access a disposed object - c#

I've been noticing this exception in the CloudWatch logs for an AWS Lambda.
Everything seems to get processed so I think it is an exception within the AWS code (as opposed to something I've written) that is created after the Lambda has finished executing.
Since functionally it works I've been ignoring it but I'm concerned that there might be problems that I haven't noticed.
Lambda encountered an UnobservedTaskException via
'TaskScheduler.UnobservedTaskException' event:
{
"errorType": "AggregateException",
"errorMessage": "A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread. (Cannot access a disposed object.\nObject name: 'System.Net.Sockets.UdpClient'.)",
"cause": {
"errorType": "ObjectDisposedException",
"errorMessage": "Cannot access a disposed object.\nObject name: 'System.Net.Sockets.UdpClient'.",
"stackTrace": [
"at System.Net.Sockets.UdpClient.EndReceive(IAsyncResult asyncResult, IPEndPoint& remoteEP)",
"at System.Net.Sockets.UdpClient.<>c.<ReceiveAsync>b__56_1(IAsyncResult asyncResult)",
"at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)"
]
},
"causes": [ {
"errorType": "ObjectDisposedException",
"errorMessage": "Cannot access a disposed object.\nObject name: 'System.Net.Sockets.UdpClient'.",
"stackTrace": [
"at System.Net.Sockets.UdpClient.EndReceive(IAsyncResult asyncResult, IPEndPoint& remoteEP)",
"at System.Net.Sockets.UdpClient.<>c.<ReceiveAsync>b__56_1(IAsyncResult asyncResult)",
"at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)"
]
}]
}
The lambda code is pretty simple: It adds these SNS messages into an SQL database using Dapper.
I'm thinking that there might be some problem with how I'm doing async in the Fucntion handler. Any ideas?
public class Function
{
private static string _connectionString;
public async Task<IEnumerable<InsertSnsResult>> FunctionHandler(SNSEvent #event, ILambdaContext context)
{
try
{
context.Logger.LogLine("Adding SNS Messages");
_connectionString = _connectionString ?? await DecryptHelper.DecryptEnvironmentVariableAsync("ConnectionString").ConfigureAwait(false);
var handler = new AddSnsMessageHandler(new SnsMessagesRepository(_connectionString, context.Logger));
return await handler.AddSnsEvents(#event).ConfigureAwait(false);
}
catch (Exception e)
{
context.Logger.LogLine(e.Message);
throw;
}
finally
{
context.Logger.LogLine("Finished SNS Adding Messages");
}
}
}
[Edit]
Just be clear here, this exception does not get caught in the try/catch block. If it did it wouldn't be an UnobservedTaskException. That's why I'm having trouble getting to the root of the problem.
And this is the repository code
public async Task<List<InsertSnsResult>> InsertSnsMessages(IEnumerable<SnsEvent> records)
{
using (var connection = new SqlConnection(_connectionString))
{
await connection.OpenAsync().ConfigureAwait(false);
var results = new List<InsertSnsResult>();
foreach (var record in records)
{
try
{
await connection.ExecuteAsync(InsertEventCommand, record).ConfigureAwait(false);
results.Add(new InsertSnsResult(record.CorrelationId, true));
}
catch (Exception ex)
{
_logger.LogLine($"InsertSns failed for {record.Id}. {ex.Message}");
results.Add(new InsertSnsResult(record.CorrelationId, false));
}
}
return results;
}
}

The log messages are straightforward and explain what is happening:
You have an asynchronous task
That asynchronous task is accessing an object that has already been disposed, probably because you have some race condition in your workflow whereby an object in the asynchronous workflow is disposed out-of-order with another portion of the workflow that needs it. This means something is seriously broken in this workflow.
The asynchronous task is never waited on, either asynchronously with await, or (don't do this!) synchronously with Result or Wait. That means that the exceptional continuation path is never taken, and the task notices this when it is collected. Again, something is probably seriously broken in your workflow if you have a task that you never wait for the result. Combine this fact with the fact from the previous point: we now have two pieces of evidence that reinforce each other that there is something seriously broken in this workflow and that it involves a task that is not awaited when it should be to ensure an ordering constraint.
And therefore you get an exception on your finalizer thread, which is really bad.
Since functionally it works I've been ignoring it
I heard once that when a factory catches fire and burns to the ground, on average there were seven different safety systems that people ignored or disabled. Get out of this habit of thinking that it works, so it must be safe. Maybe it's nothing but I would consider these messages to be indicative of a serious problem until I had evidence otherwise.

I too have run into a place where I have a 3rd party library causing errors. I wanted log it outside of CloudWatch. In order to prevent Lambda from logging these I was able to do some evil reflection to reset the event handler.
Here is the code to do this yourself. Please note this is evil code. It is fragile and will break when code is changed in the CLR or even if the compiler does optimizations (which recently happened). However, it was the only way I could find to opt-out of this functionality provided by Lambda
private void ReplaceLambdaDefaultUnobservedTaskException()
{
try
{
var bindingFlags = BindingFlags.NonPublic | BindingFlags.Static;
Type type = typeof(TaskScheduler);
var field = type.GetField("_unobservedTaskException", bindingFlags);
if (field == null)
{
field = type.GetField("UnobservedTaskException", bindingFlags);
}
var handler = new EventHandler<UnobservedTaskExceptionEventArgs>(TaskSchedulerOnUnobservedTaskException);
field.SetValue(null, handler);
}
catch (Exception ex)
{
logger.Warning(ex, "Unable to do evil reflection.");
}
TaskScheduler.UnobservedTaskException += TaskSchedulerOnUnobservedTaskException;
}
private void TaskSchedulerOnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
e.SetObserved();
logger.Error(e.Exception, "Lambda threw an UnobservedTaskException");
}

Related

Exception in loaded plugin crashes parent application

I'm writing a .NET 6 application for which users can create plugins. In some situations however, when a plugin throws an unhandled exception, my own application crashes as well. That should not happen, no matter what. The plugin may stop working, it may unload, it may whatever, just leave the parent app alive.
Loading happens like this:
public static ServiceInfo? LoadService(string relativePath)
{
var loadContext = new ServiceLoadContext(relativePath);
_alcs.Add(loadContext);
try
{
var assembly = loadContext.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(relativePath)));
var shouldLoadDll = false;
foreach (var type in assembly.GetTypes())
{
if (typeof(IMonitorableService).IsAssignableFrom(type))
{
var directoryName = new FileInfo(relativePath).Directory!.FullName;
if (Activator.CreateInstance(type, new object[] { directoryName }) is IMonitorableService result)
{
shouldLoadDll = true;
return new ServiceInfo
{
Category = Directory.GetParent(relativePath)!.Name,
Name = Path.GetFileNameWithoutExtension(relativePath),
AssemblyPath = relativePath,
Service = result!
};
}
}
}
if (!shouldLoadDll)
{
loadContext.Unload();
}
}
catch (Exception)
{
// This is handled, but this won't catch the exception in the plugin
}
return null;
}
I have my share of try/catch phrases, and since these IMonitorableServices are BackgroundServices, they're started like
public async Task StartAsync(CancellationToken cancellationToken)
{
foreach (var service in _options.Services)
{
try
{
await service.Service.StartAsync(cancellationToken);
}
catch (Exception ex)
{
// This is handled, but it won't catch the exception in the plugin
}
}
}
Now I doubt that it's really relevant to provide the specific error, but just in case: it's a
'System.InvalidOperationException: 'Collection was modified; enumeration operation may not execute',
following an operation on event subscriptions. I know how to solve that in the plugin, but I could never trust my future plugin writers to always handle their exceptions (or prevent them from happening). I need some way to catch absolutely everything in my own application. I've been breaking my head over this and I can find many considerations on plugins loaded in AppDomains, but they're from the .NET Framework era...
Who has an idea how to solve this? I could hardly imagine this is something that has been overlooked in .NET Core/6 development.
Update: I find that other type of exceptions actually are caught within the StartAsync method. So it might have something to do with the exception being raised from an event in the plugin (don't want to put you on the wrong track though). I must add, the event is registered from within the StartAsync method, but it seems to bypass the regular catch.

throw original exception inside Parallel methods instead of aggregate exception

I have two CPU-intensive methods inside a Parallel.Invoke call:
Parallel.Invoke(
() => { GetMaxRateDict(tradeOffObj); },
() => { GetMinRateDict(tradeOffObj); }
);
For a MCVE, assume:
public void GetMaxRateDict(object junk)
{
throw new Exception("Max exception raised, do foo...");
}
public void GetMinRateDict(object moreJunk)
{
throw new Exception("Min exception raised, do bar...")
}
I throw different exceptions in each of these inner methods. However, if one of these gets thrown, the Parallel wrapper throws a more generic exception: "One or more errors occurred", which is specific enough to show in my UI layer.
Can I grab the original exception somehow and throw it instead?
I would like the Parallel task to stop entirely if possible to raise the inner exception, but if that's not possible, at least being able to raise it once the two methods complete is what I need. Thanks.
Can I grab the original exception somehow and throw it instead?
"It" implies that there will only be on exception. Even though that's probably true, because you're executing actions in parallel you can't 100% rule out the possibility that multiple actions throw exceptions even if you attempt to cancel the others after the first exception. If you're okay with that, we can go from the assumption that we only expect one exception and we're okay with only catching one. (If you allow the other invocation to continue after one throws an exception the possibility of having two exceptions increases.)
You can use a cancellation token. If one of the invocations below throws an exception, it should catch that exception, place it in a variable or queue, and then call
source.Cancel;
Doing so will cause the entire Parallel.Invoke to throw an OperationCanceledException. You can catch that exception, retrieve the exception that was set, and rethrow that.
I'm going to go with the other answer's suggestion of a ConcurrentQueue just as a matter of practice because I don't think we can rule out the remote possibility that a second thread could throw an exception before being canceled.
This started off seeming small, but eventually it got so involved that I separated it into its own class. This makes me question whether my approach is needlessly complex. The main intent was to keep the messy cancellation logic from polluting your GetMaxRateDict and GetMinRateDict methods.
In addition to keeping your original methods unpolluted and testable, this class is itself testable.
I suppose I'll find out from the other responses whether this is a decent approach or there's something much simpler. I can't say I'm particularly excited about this solution. I just thought it was interesting and wanted to write something that did what you asked.
public class ParallelInvokesMultipleInvocationsAndThrowsOneException //names are hard
{
public void InvokeActions(params Action[] actions)
{
using (CancellationTokenSource source = new CancellationTokenSource())
{
// The invocations can put their exceptions here.
var exceptions = new ConcurrentQueue<Exception>();
var wrappedActions = actions
.Select(action => new Action(() =>
InvokeAndCancelOthersOnException(action, source, exceptions)))
.ToArray();
try
{
Parallel.Invoke(new ParallelOptions{CancellationToken = source.Token},
wrappedActions)
}
// if any of the invocations throw an exception,
// the parallel invocation will get canceled and
// throw an OperationCanceledException;
catch (OperationCanceledException ex)
{
Exception invocationException;
if (exceptions.TryDequeue(out invocationException))
{
//rethrow however you wish.
throw new Exception(ex.Message, invocationException);
}
// You shouldn't reach this point, but if you do, throw something else.
// In the unlikely but possible event that you get more
// than one exception, you'll lose all but one.
}
}
}
private void InvokeAndCancelOthersOnException(Action action,
CancellationTokenSource cancellationTokenSource,
ConcurrentQueue<Exception> exceptions)
{
// Try to invoke the action. If it throws an exception,
// capture the exception and then cancel the entire Parallel.Invoke.
try
{
action.Invoke();
}
catch (Exception ex)
{
exceptions.Enqueue(ex);
cancellationTokenSource.Cancel();
}
}
}
The usage would then be
var thingThatInvokes = new ParallelInvokesMultipleInvocationsAndThrowsOneException();
thingThatInvokes.InvokeActions(
()=> GetMaxRateDict(tradeOffObj),
() => GetMinRateDict(tradeOffObj));
If it throws an exception, it will be a single exception from one invocation failure, not an aggregate exception.
Not quite sure whether given example would answer your question, but it might improve overall solution:
private static void ProcessDataInParallel(byte[] data)
{
// use ConcurrentQueue to enable safe enqueueing from multiple threads.
var exceptions = new ConcurrentQueue<Exception>();
// execute the complete loop and capture all exceptions
Parallel.ForEach(data, d =>
{
try
{
// something that might fail goes here...
}
// accumulate stuff, be patient ;)
catch (Exception e) { exceptions.Enqueue(e); }
});
// check whether something failed?..
if (exceptions.Count > 0) // do whatever you like ;
}
Such an approach gives additional freedom in terms of collecting different kinds of exceptions into different queues (if necessary) or re-throwing aggregated exception further (such that no sensitive info bubbled up or you may convey particular exception with a user-friendly description of possible reasons, etc.).
Generally, that is correct way of exception management with parallelization. Not only in C#.

IIS is throwing an HTML page error saying "An asynchronous operation cannot be started ..."

I have found a couple examples of this error occurring in the forums but I don't apparently have the technical knowledge to make my case be truly 'async all the way' - if this is even the problem.
The error when caught is:
An asynchronous operation cannot be started at this time. Asynchronous
operations may only be started within an asynchronous handler or
module or during certain events in the Page lifecycle. If this
exception occurred while executing a Page, ensure that the Page is
marked <%# Page Async="true" %>. This exception may also indicate an
attempt to call an "async void" method, which is generally unsupported
within ASP.NET request processing. Instead, the asynchronous method
should return a Task, and the caller should await it.
at System.Web.AspNetSynchronizationContext.OperationStarted() at
System.Runtime.CompilerServices.AsyncVoidMethodBuilder.Create() at
myCompany.System.Turhh.Turhh.<>c__DisplayClass18_0.b__1(Object
sender, GetInfoCompletedEventArgs e) at
myCompany.System.TurhhWebServices.InfoServiceClient.OnGetInfoCompleted(Object
state)
The code:
public void GetInfoAsync(GetInfoRq request, object Infotate)
{
if ((this.onBeginGetInfoDelegate == null))
{
this.onBeginGetInfoDelegate = new BeginOperationDelegate(this.OnBeginGetInfo);
}
if ((this.onEndGetInfoDelegate == null))
{
this.onEndGetInfoDelegate = new EndOperationDelegate(this.OnEndGetInfo);
}
if ((this.onGetInfoCompletedDelegate == null))
{
this.onGetInfoCompletedDelegate = new System.Threading.SendOrPostCallback(this.OnGetInfoCompleted);
}
base.InvokeAsync(this.onBeginGetInfoDelegate, new object[] {
request}, this.onEndGetInfoDelegate, this.onGetInfoCompletedDelegate, Infotate);
}
private BeginOperationDelegate onBeginGetInfoDelegate;
private EndOperationDelegate onEndGetInfoDelegate;
private System.Threading.SendOrPostCallback onGetInfoCompletedDelegate;
public event System.EventHandler<GetInfoCompletedEventArgs> GetInfoCompleted;
public System.IAsyncResult BeginGetInfo(GetInfoRq request, System.AsyncCallback callback, object asyncState)
{
return base.Channel.BeginGetInfo(request, callback, asyncState);
}
public GetInfoRs EndGetInfo(System.IAsyncResult result)
{
return base.Channel.EndGetInfo(result);
}
private System.IAsyncResult OnBeginGetInfo(object[] inValues, System.AsyncCallback callback, object asyncState)
{
return this.BeginGetInfo(rq, callback, asyncState);
}
private object[] OnEndGetInfo(System.IAsyncResult result)
{
GetInfoRs retVal = this.EndGetInfo(result);
return new object[] {retVal};
}
private void OnGetInfoCompleted(object state)
{
if ((this.GetInfoCompleted != null))
{
InvokeAsyncCompletedEventArgs e = ((InvokeAsyncCompletedEventArgs)(state));
var ea = new GetInfoCompletedEventArgs(e.Results, e.Error, e.Cancelled, e.Infotate);
try
{
// Note: Encapsulating this in a Task.Run() hides the issue on the server (here)
// but IIS still dumps its HTML page with an await/async error info.
this.GetInfoCompleted(this, ea);
}
catch (InvalidOperationException ex)
{
// HELP:
// Most of the time, this works fine. About 1 in 10 times, this exception is thrown.
// When this exception is thrown (and caught), IIS still dumps its HTML async/await error
System.Diagnostics.Debug.WriteLine(" **** AWAIT/ASYNC EXCEPTION 3 **** ");
}
}
}
This is the code behind the GetInfoCompleted:
channel.GetInfoCompleted += new EventHandler<GetInfoCompletedEventArgs>(async (sender, e) =>
{
await complete(); // complete contains an 'await Task.Run(async() => { some server work here });' job
});
This exception only occurs about 1 out of 10 times running locally. On production, it occurs almost every time. Essentially, on really fast servers, when the async calls happen synchronously rather than asynchronously, this throws the exception and irritates IIS causing it to dump the HTML error page down to the client. When these calls are made asynchronously, some time after IIS has gracefully given a good result to the client and the client moves on to its next task, everything is fine and this error-prone call on the server successfully accomplishes its mission (whether it is successful or not does not matter to the client - this is why it is threaded out as a post-operation task).

Unobserved exception was rethrown despite awaiting the Task and catching Exception

I have code that catches all exceptions that are thrown by a server call as follows:
new public Task SaveAsync()
{
return ServerException.Wrap(base.SaveAsync);
}
Where ServerException.Wrap looks like:
public static async Task<T> Wrap<T>(Func<Task<T>> func)
{
try
{
return await func();
}
catch (Exception ex)
{
// This is an internal error that shouldn't happen.
throw new ServerException(ex);
}
}
public static async Task Wrap(Func<Task> func)
{
await Wrap(async () =>
{
await func();
return true;
});
}
And then I have a function that calls SaveAsync as follows:
try
{
await problem.SaveAsync();
}
catch (ServerException ex)
{
Logger.LogException("Error saving problem.", ex);
}
I have some internal error that generates an exception which I catch in the above line and then it gets logged as follows:
2015-10-20 11:20:44.502 [Line 99] Error saving problem. (Exception:
Exceptions.ServerException: ---> System.ArgumentException: An item
with the same key has already been added. at
System.ThrowHelper.ThrowArgumentException (ExceptionResource resource)
[0x00000] in
/Users/builder/data/lanes/1977/2c66d2fe/source/mono/external/referencesource/mscorlib/system/throwhelper.cs:74
However a few seconds later I get an unhanded exception warning that gets logged:
2015-10-20 11:21:16.352 Warning: Unhandled exception:
System.AggregateException: A Task's exception(s) were not observed
either by Waiting on the Task or accessing its Exception property. As
a result, the unobserved exception was rethrown by the finalizer
thread. ---> System.ArgumentException: An item with the same key has
already been added. at System.ThrowHelper.ThrowArgumentException
(ExceptionResource resource) [0x00000] in
/Users/builder/data/lanes/1977/2c66d2fe/source/mono/external/referencesource/mscorlib/system/throwhelper.cs:74
Why do I get the second unobserved exception, even though I am catching and handling the first exception? This exception seems to be thrown by my ServerException.Wrap method.
I am using MonoTouch.
You need to explicitly set the exception to observed.
For that, you need to subscribe to the TaskScheduler's UnobservedTaskException event, and set it explicitly to observed (call SetObserved() on it).
See here:
UnobservedTaskException being throw...
EDIT:
Of course, you can also just catch AggregateException as well, or use ContinueWith() to observe and resume the task.
See the bottom of the official documentation:
Exception Handling (MSDN)

Getting a Task's unexpected Exception to bring down the application earlier than Garbage Collection

Typically, for code that I don't expect to throw exceptions but does (i.e. a programming error), I want my application to crash (so that it doesn't corrupt data, report invalid data to the user, etc.).
Is there a best practice for getting (closer to) this behavior when using Tasks? We've registered a handler for TaskScheduler.UnobservedTaskException. The problem is that this can occur much later than the causing unexpected exception.
Question:
Which option should I use if any:
Should I wrap my Tasks action in a try/catch and escalate in the catch for exceptions I don't expect? And if so, what should I do to escalate (i.e. I'd like to get it to fire the AppDomain.UnhandledException event and terminate.
Should I attach a continuation (OnlyOnFaulted) on the ui thread (this is a Winforms application) that rethrows the exception if it is not an expected exception?
Is there a better or more standard approach?
Here's what #1 might look like:
var t1 = Task.Factory.StartNew(() =>
{
try
{
string path = null; // Programming error. Should have been a valid string. Will cause System.ArgumentNullException below
using (FileStream fs = File.Create(path))
{
}
}
catch (System.IO.IOException) { throw; } // Expected possible exception
catch (System.UnauthorizedAccessException) { throw; }
catch
{
// Anything caught here is not an expected exception and should be escalated.
// But how?
}
});
Here's what #2 might look like:
TaskScheduler uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
var t1 = Task.Factory.StartNew(() =>
{
string path = null; // Programming error. Should have been a valid string. Will cause System.ArgumentNullException below
using (FileStream fs = File.Create(path))
{
}
});
t1.ContinueWith(t =>
{
Exception ex = t.Exception;
if (ex is IOException || ex is UnauthorizedAccessException) // Expected exceptions (do nothing)
return;
throw ex; // Not expected (escalate by rethrowing)
}, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, uiTaskScheduler);
Attaching a continuation feels like a good approach to me. If you're comfortable with the assumption that you won't be blocking the UI thread for too long for other reasons, forcing the continuation to run on the UI thread seems like a very reasonable option to me. That way you can perform any UI tasks you need to as well, as part of the emergency shutdown.

Categories

Resources