private static async Task WorkerAsync( BlockingCollection <SentryEvent> queue, BackgroundWorkerOptions options, ITransport transport, CancellationToken cancellation) { var shutdownTimeout = new CancellationTokenSource(); var shutdownRequested = false; while (!shutdownTimeout.IsCancellationRequested) { // If the cancellation was signaled, // set the latest we can keep reading off the queue (while there's still stuff to read) if (!shutdownRequested && cancellation.IsCancellationRequested) { shutdownTimeout.CancelAfter(options.ShutdownTimeout); shutdownRequested = true; } if (queue.TryTake(out var @event)) { try { // Optionally we can keep multiple requests in-flight concurrently: // instead of awaiting here, keep reading from the queue while less than // N events are being sent await transport.CaptureEventAsync(@event, shutdownTimeout.Token).ConfigureAwait(false); } catch (Exception exception) { // TODO: Notify error handler Debug.WriteLine(exception.ToString()); } } else { if (shutdownRequested) { break; // Shutdown requested and queue is empty. ready to quit. } // Queue is empty, wait asynchronously before polling again try { await Task.Delay(options.EmptyQueueDelay, cancellation).ConfigureAwait(false); } // Cancellation requested, scheduled shutdown but loop again in case there are more items catch (OperationCanceledException) { shutdownTimeout.CancelAfter(options.ShutdownTimeout); shutdownRequested = true; } } } }
private static async Task WorkerAsync( IProducerConsumerCollection <SentryEvent> queue, SentryOptions options, ITransport transport, SemaphoreSlim inSemaphore, SemaphoreSlim outSemaphore, CancellationToken cancellation) { var shutdownTimeout = new CancellationTokenSource(); var shutdownRequested = false; try { while (!shutdownTimeout.IsCancellationRequested) { // If the cancellation was signaled, // set the latest we can keep reading off of the queue (while there's still stuff to read) // No longer synchronized with outSemaphore (Enqueue will throw object disposed), // run until the end of the queue or shutdownTimeout if (!shutdownRequested) { try { await outSemaphore.WaitAsync(cancellation).ConfigureAwait(false); } // Cancellation requested, scheduled shutdown but continue in case there are more items catch (OperationCanceledException) { if (options.ShutdownTimeout == TimeSpan.Zero) { options.DiagnosticLogger?.LogDebug("Exiting immediately due to 0 shutdown timeout. #{0} in queue.", queue.Count); return; } else { options.DiagnosticLogger?.LogDebug("Shutdown scheduled. Stopping by: {0}. #{1} in queue.", options.ShutdownTimeout, queue.Count); shutdownTimeout.CancelAfter(options.ShutdownTimeout); } shutdownRequested = true; } } if (queue.TryTake(out var @event)) { inSemaphore.Release(); try { // Optionally we can keep multiple requests in-flight concurrently: // instead of awaiting here, keep reading from the queue while less than // N events are being sent var task = transport.CaptureEventAsync(@event, shutdownTimeout.Token).ConfigureAwait(false); options.DiagnosticLogger?.LogDebug("Event {0} in-flight to Sentry. #{1} in queue.", @event.EventId, queue.Count); await task; } catch (OperationCanceledException) { options.DiagnosticLogger?.LogInfo("Shutdown token triggered. Time to exit. #{0} in queue.", queue.Count); return; } catch (Exception exception) { options.DiagnosticLogger?.LogError("Error while processing event {1}: {0}. #{2} in queue.", exception, @event.EventId, queue.Count); } } else { Debug.Assert(shutdownRequested); options.DiagnosticLogger?.LogInfo("Exiting the worker with an empty queue."); // Empty queue. Exit. return; } } } finally { inSemaphore.Dispose(); outSemaphore.Dispose(); } }
private async Task WorkerAsync( ConcurrentQueue <SentryEvent> queue, SentryOptions options, ITransport transport, SemaphoreSlim queuedEventSemaphore, CancellationToken cancellation) { var shutdownTimeout = new CancellationTokenSource(); var shutdownRequested = false; try { while (!shutdownTimeout.IsCancellationRequested) { // If the cancellation was signaled, // set the latest we can keep reading off of the queue (while there's still stuff to read) // No longer synchronized with queuedEventSemaphore (Enqueue will throw object disposed), // run until the end of the queue or shutdownTimeout if (!shutdownRequested) { try { await queuedEventSemaphore.WaitAsync(cancellation).ConfigureAwait(false); } // Cancellation requested, scheduled shutdown but continue in case there are more items catch (OperationCanceledException) { if (options.ShutdownTimeout == TimeSpan.Zero) { options.DiagnosticLogger?.LogDebug( "Exiting immediately due to 0 shutdown timeout. #{0} in queue.", queue.Count); return; } else { options.DiagnosticLogger?.LogDebug( "Shutdown scheduled. Stopping by: {0}. #{1} in queue.", options.ShutdownTimeout, queue.Count); shutdownTimeout.CancelAfter(options.ShutdownTimeout); } shutdownRequested = true; } } if (queue.TryPeek(out var @event)) // Work with the event while it's in the queue { try { var task = transport.CaptureEventAsync(@event, shutdownTimeout.Token); options.DiagnosticLogger?.LogDebug("Event {0} in-flight to Sentry. #{1} in queue.", @event.EventId, queue.Count); await task.ConfigureAwait(false); } catch (OperationCanceledException) { options.DiagnosticLogger?.LogInfo("Shutdown token triggered. Time to exit. #{0} in queue.", queue.Count); return; } catch (Exception exception) { options.DiagnosticLogger?.LogError("Error while processing event {1}: {0}. #{2} in queue.", exception, @event.EventId, queue.Count); } finally { _ = queue.TryDequeue(out _); _ = Interlocked.Decrement(ref _currentItems); OnFlushObjectReceived?.Invoke(@event, EventArgs.Empty); } } else { Debug.Assert(shutdownRequested); options.DiagnosticLogger?.LogInfo("Exiting the worker with an empty queue."); // Empty queue. Exit. return; } } } catch (Exception e) { options.DiagnosticLogger?.LogFatal("Exception in the background worker.", e); throw; } finally { queuedEventSemaphore.Dispose(); } }
private static async Task WorkerAsync( IProducerConsumerCollection <SentryEvent> queue, BackgroundWorkerOptions options, ITransport transport, SemaphoreSlim inSemaphore, SemaphoreSlim outSemaphore, CancellationToken cancellation) { var shutdownTimeout = new CancellationTokenSource(); var shutdownRequested = false; try { while (!shutdownTimeout.IsCancellationRequested) { // If the cancellation was signaled, // set the latest we can keep reading off of the queue (while there's still stuff to read) // No longer synchronized with outSemaphore (Enqueue will throw object disposed), // run until the end of the queue or shutdownTimeout if (!shutdownRequested) { try { await outSemaphore.WaitAsync(cancellation).ConfigureAwait(false); } // Cancellation requested, scheduled shutdown but continue in case there are more items catch (OperationCanceledException) { if (options.ShutdownTimeout == TimeSpan.Zero) { return; } else { shutdownTimeout.CancelAfter(options.ShutdownTimeout); } shutdownRequested = true; } } if (queue.TryTake(out var @event)) { inSemaphore.Release(); try { // Optionally we can keep multiple requests in-flight concurrently: // instead of awaiting here, keep reading from the queue while less than // N events are being sent await transport.CaptureEventAsync(@event, shutdownTimeout.Token).ConfigureAwait(false); } catch (OperationCanceledException) { // Shutdown token triggered. Time to exit. return; } catch (Exception exception) { // TODO: Notify error handler Debug.WriteLine(exception.ToString()); } } else { Debug.Assert(shutdownRequested); // Empty queue. Exit. return; } } } finally { inSemaphore.Dispose(); outSemaphore.Dispose(); } }