Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace DotCompute.Backends.CUDA.Persistent
/// <summary>
/// Manages persistent, grid-resident CUDA kernels for long-running computations.
/// </summary>
public sealed partial class CudaPersistentKernelManager : IDisposable
public sealed partial class CudaPersistentKernelManager : IDisposable, IAsyncDisposable
{
#region LoggerMessage Delegates

Expand Down Expand Up @@ -337,6 +337,43 @@ public void Dispose()
_ringBufferAllocator?.Dispose();
_disposed = true;
}

/// <summary>
/// Asynchronously disposes the persistent kernel manager, awaiting the
/// shutdown of every active persistent kernel before releasing the
/// ring-buffer allocator.
/// </summary>
/// <remarks>
/// Prefer this over <see cref="Dispose"/> in async teardown: each
/// <c>StopKernelAsync</c> is awaited rather than resolved via
/// <c>GetAwaiter().GetResult()</c>, so a kernel that takes non-trivial
/// time to drain its command queue does not block a thread-pool thread.
/// </remarks>
public async ValueTask DisposeAsync()
{
if (_disposed)
{
return;
}

// Stop all active kernels cooperatively.
foreach (var kernelId in _activeKernels.Keys)
{
try
{
await StopKernelAsync(kernelId).ConfigureAwait(false);
}
catch (Exception ex)
{
LogKernelStopError(_logger, ex);
}
}

_ringBufferAllocator?.Dispose();
_disposed = true;

GC.SuppressFinalize(this);
}
/// <summary>
/// A class that represents persistent kernel state.
/// </summary>
Expand Down Expand Up @@ -401,7 +438,7 @@ public void Dispose()
/// <summary>
/// Handle for interacting with a running persistent kernel.
/// </summary>
public interface IPersistentKernelHandle : IDisposable
public interface IPersistentKernelHandle : IDisposable, IAsyncDisposable
{
/// <summary>
/// Gets or sets the kernel identifier.
Expand Down Expand Up @@ -512,6 +549,24 @@ public void Dispose()
// Swallow exceptions during dispose
}
}

/// <summary>
/// Asynchronously stops the kernel and releases resources without
/// blocking the caller's thread.
/// </summary>
public async ValueTask DisposeAsync()
{
try
{
await StopAsync().ConfigureAwait(false);
}
catch (Exception)
{
// Swallow exceptions during dispose
}

GC.SuppressFinalize(this);
}
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ namespace DotCompute.Backends.CUDA.Profiling
/// Production-grade CUDA performance profiler with CUPTI integration,
/// metrics collection, and detailed performance analysis.
/// </summary>
public sealed partial class CudaPerformanceProfiler : IDisposable
public sealed partial class CudaPerformanceProfiler : IDisposable, IAsyncDisposable
{
private readonly ILogger<CudaPerformanceProfiler> _logger;
private readonly ConcurrentDictionary<string, KernelProfile> _kernelProfiles;
Expand Down Expand Up @@ -786,5 +786,56 @@ public void Dispose()

_disposed = true;
}

/// <summary>
/// Asynchronously disposes the profiler, awaiting any active profiling
/// session's stop-and-drain instead of blocking the calling thread.
/// </summary>
/// <remarks>
/// Prefer this over <see cref="Dispose"/> in async shutdown paths: when
/// profiling is active, <c>StopProfilingAsync</c> is awaited rather than
/// resolved via <c>GetAwaiter().GetResult()</c>, so a long-running flush
/// of CUPTI buffers does not stall the caller's thread pool worker.
/// </remarks>
public async ValueTask DisposeAsync()
{
if (_disposed)
{
return;
}

_metricsTimer?.Dispose();
_profilingLock?.Dispose();

if (_isProfilingActive)
{
try
{
_ = await StopProfilingAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
Trace.TraceWarning($"StopProfilingAsync failed during async disposal: {ex.Message}");
}
}

if (_cuptiSubscriber != IntPtr.Zero)
{
_ = cuptiUnsubscribe(_cuptiSubscriber);
}

try
{
_ = nvmlShutdown();
}
catch (Exception ex)
{
Trace.TraceWarning($"NVML shutdown failed: {ex.Message}");
}

_disposed = true;

GC.SuppressFinalize(this);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -166,4 +166,10 @@ public partial class MetalBackend
Level = LogLevel.Error,
Message = "Failed to create Metal accelerator for device {DeviceIndex}")]
private static partial void LogAcceleratorCreationError(ILogger logger, Exception ex, int deviceIndex);

[LoggerMessage(
EventId = 3002,
Level = LogLevel.Warning,
Message = "Failed to dispose Metal accelerator during backend shutdown")]
private static partial void LogAcceleratorDisposeError(ILogger logger, Exception ex);
}
42 changes: 41 additions & 1 deletion src/Backends/DotCompute.Backends.Metal/MetalBackend.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace DotCompute.Backends.Metal;
/// <summary>
/// Main entry point for Metal compute backend
/// </summary>
public sealed partial class MetalBackend : IDisposable
public sealed partial class MetalBackend : IDisposable, IAsyncDisposable
{
private readonly ILogger<MetalBackend> _logger;
private readonly ILoggerFactory _loggerFactory;
Expand Down Expand Up @@ -499,4 +499,44 @@ public void Dispose()
_disposed = true;
GC.SuppressFinalize(this);
}

/// <summary>
/// Asynchronously disposes the backend by awaiting each Metal accelerator's
/// <see cref="IAsyncDisposable.DisposeAsync"/> in sequence.
/// </summary>
/// <remarks>
/// Prefer this over <see cref="Dispose"/> in async hosts: Metal accelerators
/// hold command queues, buffer pools, and MPS handles whose clean shutdown
/// involves asynchronous work. Awaiting here avoids the
/// <c>AsTask().GetAwaiter().GetResult()</c> sync-over-async pattern and
/// prevents thread-pool starvation during multi-GPU teardown.
/// </remarks>
public async ValueTask DisposeAsync()
{
if (_disposed)
{
return;
}

foreach (var accelerator in _accelerators)
{
if (accelerator is null)
{
continue;
}

try
{
await accelerator.DisposeAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
LogAcceleratorDisposeError(_logger, ex);
}
}

_accelerators.Clear();
_disposed = true;
GC.SuppressFinalize(this);
}
}
83 changes: 82 additions & 1 deletion src/Core/DotCompute.Core/Logging/LogBuffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace DotCompute.Core.Logging;
/// High-performance asynchronous log buffer with batching, compression, and multiple sink support.
/// Designed to minimize performance impact on the main application thread while ensuring reliable log delivery.
/// </summary>
public sealed partial class LogBuffer : IDisposable
public sealed partial class LogBuffer : IDisposable, IAsyncDisposable
{
#region LoggerMessage Delegates

Expand Down Expand Up @@ -621,6 +621,87 @@ public void Dispose()
_cancellationTokenSource?.Dispose();
}
}

/// <summary>
/// Asynchronously disposes the log buffer, completing the channel writer,
/// awaiting the processing task, and performing a final flush without
/// blocking the calling thread.
/// </summary>
/// <remarks>
/// Prefer this over <see cref="Dispose"/> when shutting down inside an
/// async scope (host teardown, test cleanup): the background processing
/// task is awaited rather than <c>Wait(TimeSpan)</c>'d, and the final
/// flush happens asynchronously, so shutdown does not stall an async
/// thread pool worker.
/// </remarks>
public async ValueTask DisposeAsync()
{
if (_disposed)
{
return;
}

_disposed = true;

try
{
// Stop accepting new entries.
_writer.Complete();

// Cancel background processing.
await _cancellationTokenSource.CancelAsync().ConfigureAwait(false);

// Await the background processing task (bounded by a timeout so
// a wedged processor cannot hang shutdown).
try
{
await _processingTask.WaitAsync(TimeSpan.FromSeconds(30)).ConfigureAwait(false);
}
catch (TimeoutException)
{
_logger.LogWarningMessage("Log processing task did not complete within timeout");
}
catch (OperationCanceledException)
{
// Expected when the processing task observes cancellation.
}

// Final flush, awaited rather than sync-over-async.
try
{
await FlushAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.LogErrorMessage(ex, "Error during final LogBuffer flush");
}

// Dispose sinks. ILogSink is IDisposable today; this matches Dispose().
foreach (var sink in _sinks)
{
try
{
sink.Dispose();
}
catch (Exception ex)
{
_logger.LogErrorMessage(ex, $"Error disposing sink: {sink.GetType().Name}");
}
}
}
catch (Exception ex)
{
_logger.LogErrorMessage(ex, "Error during LogBuffer async disposal");
}
finally
{
_batchTimer?.Dispose();
_flushSemaphore?.Dispose();
_cancellationTokenSource?.Dispose();
}

GC.SuppressFinalize(this);
}
}
/// <summary>
/// An i log sink interface.
Expand Down
36 changes: 35 additions & 1 deletion src/Core/DotCompute.Core/Logging/StructuredLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace DotCompute.Core.Logging;
/// Production-grade structured logger with semantic properties, correlation IDs, and performance metrics.
/// Provides async, buffered logging with configurable sinks and minimal performance impact.
/// </summary>
public sealed partial class StructuredLogger : ILogger, IDisposable
public sealed partial class StructuredLogger : ILogger, IDisposable, IAsyncDisposable
{
private readonly string _categoryName;
private readonly ILogger _baseLogger;
Expand Down Expand Up @@ -609,6 +609,40 @@ public void Dispose()

_flushTimer?.Dispose();
}

/// <summary>
/// Asynchronously disposes the logger, performing a final flush of the underlying
/// <see cref="LogBuffer"/> without blocking the calling thread.
/// </summary>
/// <remarks>
/// Prefer this over <see cref="Dispose"/> in async hosts: the buffered channel is
/// drained via <see cref="LogBuffer.DisposeAsync"/> rather than a sync-over-async
/// <c>FlushAsync().GetAwaiter().GetResult()</c>, so the processing task can complete
/// cleanly without starving the thread pool during shutdown.
/// </remarks>
public async ValueTask DisposeAsync()
{
if (_disposed)
{
return;
}

_disposed = true;

try
{
// Flush and shut down the buffer cooperatively.
await _logBuffer.DisposeAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
LogFinalFlushFailed(_baseLogger, ex);
}

_flushTimer?.Dispose();

GC.SuppressFinalize(this);
}
}
/// <summary>
/// A class that represents structured logging options.
Expand Down
Loading
Loading