Skip to content

Commit 342c42b

Browse files
feat: update exception handling / addtl logging
1 parent a79e614 commit 342c42b

File tree

1 file changed

+74
-57
lines changed

1 file changed

+74
-57
lines changed

src/Arius.Core/Features/Commands/Archive/ArchiveCommandHandler.cs

Lines changed: 74 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -103,19 +103,78 @@ internal async ValueTask<Result<ArchiveCommandResult>> Handle(HandlerContext han
103103

104104
// Attach cancellation logic
105105
tasks.Values.RaiseCancellationOnFault(errorCancellationTokenSource);
106+
107+
try
106108
{
109+
await Task.WhenAll(tasks.Values);
110+
}
111+
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested && !errorCancellationTokenSource.IsCancellationRequested)
112+
{
113+
// User-triggered cancellation
114+
logger.LogInformation("Archive operation cancelled by user");
115+
return Result.Fail("Archive operation was cancelled by user");
116+
}
117+
catch (Exception ex)
118+
{
119+
// Either a task failed with an exception or error-triggered cancellation occurred
120+
logger.LogError(ex, "Unhandled exception: Either a task failed with an exception or error-triggered cancellation occurred");
121+
122+
var faultedTasks = tasks.Where(kvp => kvp.Value.IsFaulted).Select(kvp => (Name: kvp.Key, Exception: kvp.Value.Exception!.GetBaseException())).ToArray();
123+
124+
// Trigger error-driven cancellation to signal other tasks to stop gracefully
125+
errorCancellationTokenSource.Cancel();
126+
127+
// Wait for all tasks to complete gracefully
128+
await Task.WhenAll(tasks.Values.Select(async t =>
107129
{
130+
try
108131
{
132+
await t;
109133
}
134+
catch
135+
{
136+
/* Ignore exceptions during graceful shutdown */
137+
}
138+
}));
139+
140+
// Observe all task exceptions to prevent UnobservedTaskException
141+
foreach (var task in tasks.Values.Where(t => t.IsFaulted))
142+
{
143+
_ = task.Exception;
144+
}
145+
146+
// Log cancelled tasks (debug level)
147+
var cancelledTaskNames = tasks.Where(kvp => kvp.Value.IsCanceled).Select(kvp => kvp.Key).ToArray();
148+
if (cancelledTaskNames.Any())
149+
{
150+
logger.LogDebug("Tasks cancelled during graceful shutdown: {TaskNames}", string.Join(", ", cancelledTaskNames));
151+
}
152+
153+
// Log and handle failed tasks (error level)
154+
if (faultedTasks is { Length: 1 } && faultedTasks.Single() is var faultedTask)
155+
{
156+
// Single faulted task - return the exception
157+
var msg = faultedTask.Exception?.GetBaseException().Message ?? "UNKNOWN";
158+
logger.LogError(faultedTask.Exception, "Task '{TaskName}' failed with exception '{Exception}'", faultedTask.Name, msg);
159+
return Result.Fail($"Archive operation failed: {faultedTask.Name} failed with {msg}").WithError(new ExceptionalError(faultedTask.Exception));
160+
}
161+
else
162+
{
163+
// Multiple faulted tasks - return aggregate exception
164+
var exceptions = faultedTasks.Select(ft => ft.Exception).ToArray();
165+
var aggregateException = new AggregateException("Multiple tasks failed during archive operation", exceptions);
166+
var faultedTaskNames = string.Join(", ", faultedTasks.Select(ft => ft.Name));
167+
logger.LogError(aggregateException, "Tasks failed: {FaultedTaskNames}", faultedTaskNames);
168+
return Result.Fail($"Archive operation failed: {faultedTaskNames} tasks failed").WithError(new ExceptionalError(aggregateException));
169+
}
110170
}
111171

112172
try
113173
{
114-
await Task.WhenAll(tasks.Values);
115-
116174
// 6. Remove PointerFileEntries that do not exist on disk
117-
logger.LogDebug("Cleaning up pointer file entries that no longer exist on disk");
175+
logger.LogInformation("Cleaning up pointer file entries that no longer exist on disk...");
118176
pointerFileEntriesDeleted = handlerContext.StateRepository.DeletePointerFileEntries(pfe => !handlerContext.FileSystem.FileExists(pfe.RelativeName));
177+
logger.LogInformation("Cleaning up pointer file entries that no longer exist on disk... {count} deleted", pointerFileEntriesDeleted);
119178

120179
// 7. Upload the new state file to blob storage
121180
string? newStateName = null;
@@ -150,9 +209,9 @@ internal async ValueTask<Result<ArchiveCommandResult>> Handle(HandlerContext han
150209
TotalLocalFiles = totalLocalFiles,
151210
ExistingPointerFiles = existingPointerFiles,
152211

153-
ChunksBeforeOperation = statisticsBefore.ChunkCount,
154-
BinariesBeforeOperation = statisticsBefore.BinaryCount,
155-
ArchivedSizeBeforeOperation = statisticsBefore.ArchivedSize,
212+
ChunksBeforeOperation = statisticsBefore.ChunkCount,
213+
BinariesBeforeOperation = statisticsBefore.BinaryCount,
214+
ArchivedSizeBeforeOperation = statisticsBefore.ArchivedSize,
156215

157216
UniqueBinariesUploaded = uniqueBinariesUploaded,
158217
UniqueChunksUploaded = uniqueChunksUploaded,
@@ -161,13 +220,13 @@ internal async ValueTask<Result<ArchiveCommandResult>> Handle(HandlerContext han
161220
PointerFilesCreated = pointerFilesCreated,
162221
PointerFileEntriesDeleted = pointerFileEntriesDeleted,
163222

164-
ChunksAfterOperation = statisticsAfter.ChunkCount,
165-
BinariesAfterOperation = statisticsAfter.BinaryCount,
166-
ArchivedSizeAfterOperation = statisticsAfter.ArchivedSize,
223+
ChunksAfterOperation = statisticsAfter.ChunkCount,
224+
BinariesAfterOperation = statisticsAfter.BinaryCount,
225+
ArchivedSizeAfterOperation = statisticsAfter.ArchivedSize,
167226

168-
NewStateName = newStateName,
169-
Warnings = warnings.ToArray(),
170-
FilesSkipped = filesSkipped
227+
NewStateName = newStateName,
228+
Warnings = warnings.ToArray(),
229+
FilesSkipped = filesSkipped
171230
});
172231
}
173232
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested && !errorCancellationTokenSource.IsCancellationRequested)
@@ -176,52 +235,10 @@ internal async ValueTask<Result<ArchiveCommandResult>> Handle(HandlerContext han
176235
logger.LogInformation("Archive operation cancelled by user");
177236
return Result.Fail("Archive operation was cancelled by user");
178237
}
179-
catch (Exception ex)
238+
catch (Exception e)
180239
{
181-
// Either a task failed with an exception or error-triggered cancellation occurred
182-
183-
var faultedTasks = tasks.Where(kvp => kvp.Value.IsFaulted).Select(kvp => (Name: kvp.Key, Exception: kvp.Value.Exception!.GetBaseException())).ToArray();
184-
185-
// Trigger error-driven cancellation to signal other tasks to stop gracefully
186-
errorCancellationTokenSource.Cancel();
187-
188-
// Wait for all tasks to complete gracefully
189-
await Task.WhenAll(tasks.Values.Select(async t =>
190-
{
191-
try { await t; }
192-
catch { /* Ignore exceptions during graceful shutdown */ }
193-
}));
194-
195-
// Observe all task exceptions to prevent UnobservedTaskException
196-
foreach (var task in tasks.Values.Where(t => t.IsFaulted))
197-
{
198-
_ = task.Exception;
199-
}
200-
201-
// Log cancelled tasks (debug level)
202-
var cancelledTaskNames = tasks.Where(kvp => kvp.Value.IsCanceled).Select(kvp => kvp.Key).ToArray();
203-
if (cancelledTaskNames.Any())
204-
{
205-
logger.LogDebug("Tasks cancelled during graceful shutdown: {TaskNames}", string.Join(", ", cancelledTaskNames));
206-
}
207-
208-
// Log and handle failed tasks (error level)
209-
if (faultedTasks is { Length: 1 } && faultedTasks.Single() is var faultedTask)
210-
{
211-
// Single faulted task - return the exception
212-
var msg = faultedTask.Exception?.GetBaseException().Message ?? "UNKNOWN";
213-
logger.LogError(faultedTask.Exception, "Task '{TaskName}' failed with exception '{Exception}'", faultedTask.Name, msg);
214-
return Result.Fail($"Archive operation failed: {faultedTask.Name} failed with {msg}").WithError(new ExceptionalError(faultedTask.Exception));
215-
}
216-
else
217-
{
218-
// Multiple faulted tasks - return aggregate exception
219-
var exceptions = faultedTasks.Select(ft => ft.Exception).ToArray();
220-
var aggregateException = new AggregateException("Multiple tasks failed during archive operation", exceptions);
221-
var faultedTaskNames = string.Join(", ", faultedTasks.Select(ft => ft.Name));
222-
logger.LogError(aggregateException, "Tasks failed: {FaultedTaskNames}", faultedTaskNames);
223-
return Result.Fail($"Archive operation failed: {faultedTaskNames} tasks failed").WithError(new ExceptionalError(aggregateException));
224-
}
240+
logger.LogError(e, "Unhandled exception");
241+
return Result.Fail($"Archive operation failed: {e}").WithError(new ExceptionalError(e));
225242
}
226243
}
227244

0 commit comments

Comments
 (0)