Flattening of AggregateExceptions for Processing

JNYRanger picture JNYRanger · Apr 4, 2014 · Viewed 26.4k times · Source

I'm running into a few issues where I call flatten on an AggregateException, but inside there is still ANOTHER AggregateException! This obviously means that they are being propagated up the chain and being rolled into another AggregateException. Is there a way to recursively flatten ALL inner AggregateExceptions? Usually, I'll use the handle delegate to process these, but it returns false if there is another inner AggregateExceeption. Am I not handling these properly?

EDIT: Since I already am calling Flatten, it appears that the issue is that it's not being caught until way later in the callstack. Here is the code where I'm calling Flatten(). For use in the stack trace this method is called WriteExceptionRecord(string, FileInfo):

do
{
    try
    {
        using (var stream = file.Open(FileMode.Append, FileAccess.Write, FileShare.None))
        {
            using (StreamWriter writer = new StreamWriter(stream))
            {
                await writer.WriteLineAsync(data);
            }
        }
    }
    catch (AggregateException ex)
    {
        ex.Flatten().Handle((x) =>
        {
            if (x is IOException)
            {
                retryNeeded = true;
                retryLeft--;
                Thread.Sleep(500);
                return true;
            }

            logger.ErrorException("Could not write to exception file: " + data, ex);
            return false;
        });
    }
}
while (retryNeeded && retryLeft > 0);

However, the stack trace shows that it's not being caught here. Instead it's being caught way later up the call stack. Below is the trace with some identifying information removed for security reasons:

System.AggregateException: One or more errors occurred. --->      
System.AggregateException: One or more errors occurred. --->
System.IO.IOException: The process cannot access the file 'X:\Production\ProductionBatches\DataEntry\J\PD\Exception.csv' because it is being used by another process.    
   at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)    
   at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)    
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share)    
   at System.IO.FileInfo.Open(FileMode mode, FileAccess access, FileShare share)    
   at PDI.LoadFileProcessing.<WriteExceptionRecord>d__21.MoveNext() in c:\Users\XYZ\Development\PDI\PDI\LoadFileProcessing.cs:line 328

--- End of stack trace from previous location where exception was thrown ---  
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at PDI.LoadFileProcessing.<ExceptionRecordProcessing>d__17.MoveNext() in c:\Users\XYZ\Development\PDI\PDI\LoadFileProcessing.cs:line 316

--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at PDI.ProcessPipeline.<>c__DisplayClass9.<<ProcessBatch>b__2>d__13.MoveNext() in c:\Users\XYZ\Development\PDI\PDI\ProcessPipeline.cs:line 61

   --- End of inner exception stack trace ---

   --- End of inner exception stack trace ---

---> (Inner Exception #0) System.AggregateException: One or more errors occurred. ---> System.IO.IOException: The process cannot access the file 'X:\Production\ProductionBatches\DataEntry\J\PD\Exception.csv' because it is being used by another process.
   at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
   at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share)
   at System.IO.FileInfo.Open(FileMode mode, FileAccess access, FileShare share)
   at PeopleDocImporter.LoadFileProcessing.<WriteExceptionRecord>d__21.MoveNext() in c:\Users\XYZ\Development\PDI\PDI\LoadFileProcessing.cs:line 328

--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at PDI.LoadFileProcessing.<ExceptionRecordProcessing>d__17.MoveNext() in c:\Users\XYZ\Development\PDI\PDI\LoadFileProcessing.cs:line 316

--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at PDI.ProcessPipeline.<>c__DisplayClass9.<<ProcessBatch>b__2>d__13.MoveNext() in c:\Users\XYZ\Development\PDI\PDI\ProcessPipeline.cs:line 61

   --- End of inner exception stack trace ---
---> (Inner Exception #0) System.IO.IOException: The process cannot access the file 'X:\Production\ProductionBatches\DataEntry\J\PD\Exception.csv' because it is being used by another process.
   at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
   at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share)
   at System.IO.FileInfo.Open(FileMode mode, FileAccess access, FileShare share)
   at PDI.LoadFileProcessing.<WriteExceptionRecord>d__21.MoveNext() in c:\Users\XYZ\Development\PDI\PDI\LoadFileProcessing.cs:line 328

--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at PDI.LoadFileProcessing.<ExceptionRecordProcessing>d__17.MoveNext() in c:\Users\XYZ\Development\PDI\PDI\LoadFileProcessing.cs:line 316

--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)    
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()    
   at PDI.ProcessPipeline.<>c__DisplayClass9.<<ProcessBatch>b__2>d__13.MoveNext() in c:\Users\XYZ\Development\PDI\PDI\ProcessPipeline.cs:line 61<---

<---

System.AggregateException: One or more errors occurred. ---> System.AggregateException: One or more errors occurred. ---> System.IO.IOException: The process cannot access the file 'X:\Production\ProductionBatches\DataEntry\J\PD\Exception.csv' because it is being used by another process.    
   at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)    
   at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)    
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share)    
   at System.IO.FileInfo.Open(FileMode mode, FileAccess access, FileShare share)    
   at PDI.LoadFileProcessing.<WriteExceptionRecord>d__21.MoveNext() in c:\Users\XYZ\Development\PDI\PDI\LoadFileProcessing.cs:line 328

--- End of stack trace from previous location where exception was thrown ---    
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)    
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)    
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()    
   at PDI.LoadFileProcessing.<ExceptionRecordProcessing>d__17.MoveNext() in c:\Users\XYZ\Development\PDI\PDI\LoadFileProcessing.cs:line 316

--- End of stack trace from previous location where exception was thrown ---    
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)    
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)    
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()    
   at PDI.ProcessPipeline.<>c__DisplayClass9.<<ProcessBatch>b__2>d__13.MoveNext() in c:\Users\XYZ\Development\PDI\PDI\ProcessPipeline.cs:line 61

   --- End of inner exception stack trace ---

---> (Inner Exception #0) System.AggregateException: One or more errors occurred. ---> System.IO.IOException: The process cannot access the file 'X:\J\PD\Exception.csv' because it is being used by another process.    
   at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)    
   at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)    
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share)    
   at System.IO.FileInfo.Open(FileMode mode, FileAccess access, FileShare share)    
   at PDI.LoadFileProcessing.<WriteExceptionRecord>d__21.MoveNext() in c:\Users\XYZ\Development\PDI\PDI\LoadFileProcessing.cs:line 328

--- End of stack trace from previous location where exception was thrown ---    
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)    
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)    
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()    
   at PDI.LoadFileProcessing.<ExceptionRecordProcessing>d__17.MoveNext() in c:\Users\XYZ\Development\PDI\PDI\LoadFileProcessing.cs:line 316

--- End of stack trace from previous location where exception was thrown ---    
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)    
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)    
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()    
   at PDI.ProcessPipeline.<>c__DisplayClass9.<<ProcessBatch>b__2>d__13.MoveNext() in c:\Users\XYZ\Development\PDI\PDI\ProcessPipeline.cs:line 61

   --- End of inner exception stack trace ---    
---> (Inner Exception #0) System.IO.IOException: The process cannot access the file 'X:\Production\ProductionBatches\DataEntry\J\PD\Exception.csv' because it is being used by another process.    
   at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)    
   at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)    
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share)    
   at System.IO.FileInfo.Open(FileMode mode, FileAccess access, FileShare share)    
   at PDI.LoadFileProcessing.<WriteExceptionRecord>d__21.MoveNext() in c:\Users\XYZ\Development\PDI\PDI\LoadFileProcessing.cs:line 328

--- End of stack trace from previous location where exception was thrown ---    
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)    
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)    
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()    
   at PDI.LoadFileProcessing.<ExceptionRecordProcessing>d__17.MoveNext() in c:\Users\XYZ\Development\PDI\PDI\LoadFileProcessing.cs:line 316

--- End of stack trace from previous location where exception was thrown ---    
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)    
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)    
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()    
   at PDI.ProcessPipeline.<>c__DisplayClass9.<<ProcessBatch>b__2>d__13.MoveNext() in c:\Users\XYZ\Development\PDI\PDI\ProcessPipeline.cs:line 61<---

By the way: This is being called by TPL-Dataflow blocks.

Answer

Timothy John Laird picture Timothy John Laird · Oct 13, 2017

Keep in mind that the 'flatten' method will give you a list of Exceptions, but can still leave you with a flattened InnerExceptions inside of each Exception.

So I found that this was not really sufficient:

try
{
    // something dangerous
}
catch (AggregateException ae)
{ 
    foreach(Exception innerException in ae.Flatten().InnerExceptions)
    {
        Console.WriteLine(innerException.Message());
    }
}

Because this exception:

System.Net.Http.HttpRequestException: An error occurred while sending the request. ---> System.Net.WebException: Unable to connect to the remote server ---> System.Net.Sockets.SocketException: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond 192.168.42.55:443 at System.Net.Sockets.Socket.EndConnect(IAsyncResult asyncResult) at System.Net.ServicePoint.ConnectSocketInternal(Boolean connectFailure, Socket s4, Socket s6, Socket& socket, IPAddress& address, ConnectSocketState state, IAsyncResult asyncResult, Exception& exception) --- End of inner exception stack trace --- at System.Net.HttpWebRequest.EndGetRequestStream(IAsyncResult asyncResult, TransportContext& context) at System.Net.Http.HttpClientHandler.GetRequestStreamCallback(IAsyncResult ar) --- End of inner exception stack trace ---

Would end up like this:

An error occurred while sending the request.

The fix was something like this:

foreach(Exception exInnerException in aggEx.Flatten().InnerExceptions)
{
    Exception exNestedInnerException = exInnerException;
    do
    {
        if (!string.IsNullOrEmpty(exNestedInnerException.Message))
        {
            Console.WriteLine(exNestedInnerException.Message);
        }
        exNestedInnerException = exNestedInnerException.InnerException;
    }
    while (exNestedInnerException != null);
}

Resulting in:

An error occurred while sending the request.

Unable to connect to the remote server

A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond 192.168.42.54:443

Hope that helps someone.