diff --git a/src/Backends/DotCompute.Backends.CUDA/Persistent/CudaPersistentKernelManager.cs b/src/Backends/DotCompute.Backends.CUDA/Persistent/CudaPersistentKernelManager.cs
index 7b12a0e8f..29541d154 100644
--- a/src/Backends/DotCompute.Backends.CUDA/Persistent/CudaPersistentKernelManager.cs
+++ b/src/Backends/DotCompute.Backends.CUDA/Persistent/CudaPersistentKernelManager.cs
@@ -14,7 +14,7 @@ namespace DotCompute.Backends.CUDA.Persistent
///
/// Manages persistent, grid-resident CUDA kernels for long-running computations.
///
- public sealed partial class CudaPersistentKernelManager : IDisposable
+ public sealed partial class CudaPersistentKernelManager : IDisposable, IAsyncDisposable
{
#region LoggerMessage Delegates
@@ -337,6 +337,43 @@ public void Dispose()
_ringBufferAllocator?.Dispose();
_disposed = true;
}
+
+ ///
+ /// Asynchronously disposes the persistent kernel manager, awaiting the
+ /// shutdown of every active persistent kernel before releasing the
+ /// ring-buffer allocator.
+ ///
+ ///
+ /// Prefer this over in async teardown: each
+ /// StopKernelAsync is awaited rather than resolved via
+ /// GetAwaiter().GetResult(), so a kernel that takes non-trivial
+ /// time to drain its command queue does not block a thread-pool thread.
+ ///
+ 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);
+ }
///
/// A class that represents persistent kernel state.
///
@@ -401,7 +438,7 @@ public void Dispose()
///
/// Handle for interacting with a running persistent kernel.
///
- public interface IPersistentKernelHandle : IDisposable
+ public interface IPersistentKernelHandle : IDisposable, IAsyncDisposable
{
///
/// Gets or sets the kernel identifier.
@@ -512,6 +549,24 @@ public void Dispose()
// Swallow exceptions during dispose
}
}
+
+ ///
+ /// Asynchronously stops the kernel and releases resources without
+ /// blocking the caller's thread.
+ ///
+ public async ValueTask DisposeAsync()
+ {
+ try
+ {
+ await StopAsync().ConfigureAwait(false);
+ }
+ catch (Exception)
+ {
+ // Swallow exceptions during dispose
+ }
+
+ GC.SuppressFinalize(this);
+ }
}
///
diff --git a/src/Backends/DotCompute.Backends.CUDA/Profiling/CudaPerformanceProfiler.cs b/src/Backends/DotCompute.Backends.CUDA/Profiling/CudaPerformanceProfiler.cs
index c574cc53a..d0413983a 100644
--- a/src/Backends/DotCompute.Backends.CUDA/Profiling/CudaPerformanceProfiler.cs
+++ b/src/Backends/DotCompute.Backends.CUDA/Profiling/CudaPerformanceProfiler.cs
@@ -16,7 +16,7 @@ namespace DotCompute.Backends.CUDA.Profiling
/// Production-grade CUDA performance profiler with CUPTI integration,
/// metrics collection, and detailed performance analysis.
///
- public sealed partial class CudaPerformanceProfiler : IDisposable
+ public sealed partial class CudaPerformanceProfiler : IDisposable, IAsyncDisposable
{
private readonly ILogger _logger;
private readonly ConcurrentDictionary _kernelProfiles;
@@ -786,5 +786,56 @@ public void Dispose()
_disposed = true;
}
+
+ ///
+ /// Asynchronously disposes the profiler, awaiting any active profiling
+ /// session's stop-and-drain instead of blocking the calling thread.
+ ///
+ ///
+ /// Prefer this over in async shutdown paths: when
+ /// profiling is active, StopProfilingAsync is awaited rather than
+ /// resolved via GetAwaiter().GetResult(), so a long-running flush
+ /// of CUPTI buffers does not stall the caller's thread pool worker.
+ ///
+ 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);
+ }
}
}
diff --git a/src/Backends/DotCompute.Backends.Metal/MetalBackend.LoggerMessages.cs b/src/Backends/DotCompute.Backends.Metal/MetalBackend.LoggerMessages.cs
index 0a175f956..69cd1fb46 100644
--- a/src/Backends/DotCompute.Backends.Metal/MetalBackend.LoggerMessages.cs
+++ b/src/Backends/DotCompute.Backends.Metal/MetalBackend.LoggerMessages.cs
@@ -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);
}
diff --git a/src/Backends/DotCompute.Backends.Metal/MetalBackend.cs b/src/Backends/DotCompute.Backends.Metal/MetalBackend.cs
index 1ed4b8793..573ac43a9 100644
--- a/src/Backends/DotCompute.Backends.Metal/MetalBackend.cs
+++ b/src/Backends/DotCompute.Backends.Metal/MetalBackend.cs
@@ -17,7 +17,7 @@ namespace DotCompute.Backends.Metal;
///
/// Main entry point for Metal compute backend
///
-public sealed partial class MetalBackend : IDisposable
+public sealed partial class MetalBackend : IDisposable, IAsyncDisposable
{
private readonly ILogger _logger;
private readonly ILoggerFactory _loggerFactory;
@@ -499,4 +499,44 @@ public void Dispose()
_disposed = true;
GC.SuppressFinalize(this);
}
+
+ ///
+ /// Asynchronously disposes the backend by awaiting each Metal accelerator's
+ /// in sequence.
+ ///
+ ///
+ /// Prefer this over in async hosts: Metal accelerators
+ /// hold command queues, buffer pools, and MPS handles whose clean shutdown
+ /// involves asynchronous work. Awaiting here avoids the
+ /// AsTask().GetAwaiter().GetResult() sync-over-async pattern and
+ /// prevents thread-pool starvation during multi-GPU teardown.
+ ///
+ 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);
+ }
}
diff --git a/src/Core/DotCompute.Core/Logging/LogBuffer.cs b/src/Core/DotCompute.Core/Logging/LogBuffer.cs
index ccbabe83d..f9098499c 100644
--- a/src/Core/DotCompute.Core/Logging/LogBuffer.cs
+++ b/src/Core/DotCompute.Core/Logging/LogBuffer.cs
@@ -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.
///
-public sealed partial class LogBuffer : IDisposable
+public sealed partial class LogBuffer : IDisposable, IAsyncDisposable
{
#region LoggerMessage Delegates
@@ -621,6 +621,87 @@ public void Dispose()
_cancellationTokenSource?.Dispose();
}
}
+
+ ///
+ /// Asynchronously disposes the log buffer, completing the channel writer,
+ /// awaiting the processing task, and performing a final flush without
+ /// blocking the calling thread.
+ ///
+ ///
+ /// Prefer this over when shutting down inside an
+ /// async scope (host teardown, test cleanup): the background processing
+ /// task is awaited rather than Wait(TimeSpan)'d, and the final
+ /// flush happens asynchronously, so shutdown does not stall an async
+ /// thread pool worker.
+ ///
+ 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);
+ }
}
///
/// An i log sink interface.
diff --git a/src/Core/DotCompute.Core/Logging/StructuredLogger.cs b/src/Core/DotCompute.Core/Logging/StructuredLogger.cs
index 6129bc3ed..5606aa758 100644
--- a/src/Core/DotCompute.Core/Logging/StructuredLogger.cs
+++ b/src/Core/DotCompute.Core/Logging/StructuredLogger.cs
@@ -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.
///
-public sealed partial class StructuredLogger : ILogger, IDisposable
+public sealed partial class StructuredLogger : ILogger, IDisposable, IAsyncDisposable
{
private readonly string _categoryName;
private readonly ILogger _baseLogger;
@@ -609,6 +609,40 @@ public void Dispose()
_flushTimer?.Dispose();
}
+
+ ///
+ /// Asynchronously disposes the logger, performing a final flush of the underlying
+ /// without blocking the calling thread.
+ ///
+ ///
+ /// Prefer this over in async hosts: the buffered channel is
+ /// drained via rather than a sync-over-async
+ /// FlushAsync().GetAwaiter().GetResult(), so the processing task can complete
+ /// cleanly without starving the thread pool during shutdown.
+ ///
+ 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);
+ }
}
///
/// A class that represents structured logging options.
diff --git a/src/Core/DotCompute.Core/Security/MemoryProtection.cs b/src/Core/DotCompute.Core/Security/MemoryProtection.cs
index ca4501f40..b51795472 100644
--- a/src/Core/DotCompute.Core/Security/MemoryProtection.cs
+++ b/src/Core/DotCompute.Core/Security/MemoryProtection.cs
@@ -18,7 +18,7 @@ namespace DotCompute.Core.Security;
/// Provides comprehensive memory protection services including bounds checking,
/// guard pages, and secure memory management with defense against common memory vulnerabilities.
///
-public sealed partial class MemoryProtection : IDisposable
+public sealed partial class MemoryProtection : IDisposable, IAsyncDisposable
{
private readonly ILogger _logger;
private readonly MemoryProtectionConfiguration _configuration;
@@ -896,6 +896,52 @@ public void Dispose()
_logger.LogInfoMessage($"MemoryProtection disposed. Final statistics: Allocations={_allocations.Count}, Violations={_violationCount}");
}
+
+ ///
+ /// Asynchronously disposes the memory protection service, awaiting secure
+ /// wipes of any protected regions before releasing their backing memory.
+ ///
+ ///
+ /// Prefer this over when disposing during async
+ /// shutdown: each region's secure wipe is awaited rather than resolved via
+ /// GetAwaiter().GetResult(), which avoids blocking the thread pool
+ /// when many regions need to be scrubbed.
+ ///
+ public async ValueTask DisposeAsync()
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ _disposed = true;
+
+ _integrityCheckTimer?.Dispose();
+ _allocationLock?.Dispose();
+
+ foreach (var region in _protectedRegions.Values)
+ {
+ try
+ {
+ if (_configuration.EnableSecureWiping)
+ {
+ await SecureWipeMemoryAsync(region).ConfigureAwait(false);
+ }
+ FreeRawMemory(region.BaseAddress, region.TotalSize);
+ }
+ catch (Exception ex)
+ {
+ MemoryRegionDisposeError(_logger, ex, region.Identifier);
+ }
+ }
+
+ _protectedRegions.Clear();
+ _allocations.Clear();
+
+ _logger.LogInfoMessage($"MemoryProtection disposed. Final statistics: Allocations={_allocations.Count}, Violations={_violationCount}");
+
+ GC.SuppressFinalize(this);
+ }
}
#region Supporting Types
diff --git a/src/Core/DotCompute.Core/Security/MemorySanitizer.cs b/src/Core/DotCompute.Core/Security/MemorySanitizer.cs
index d6d629822..b5db88486 100644
--- a/src/Core/DotCompute.Core/Security/MemorySanitizer.cs
+++ b/src/Core/DotCompute.Core/Security/MemorySanitizer.cs
@@ -22,7 +22,7 @@ namespace DotCompute.Core.Security;
/// with advanced security features for protecting sensitive data in memory.
/// Implements production-grade memory safety with hardware-accelerated validation.
///
-public sealed partial class MemorySanitizer : IDisposable
+public sealed partial class MemorySanitizer : IDisposable, IAsyncDisposable
{
private readonly ILogger _logger;
private readonly MemorySanitizerConfiguration _configuration;
@@ -1085,5 +1085,55 @@ public void Dispose()
var stats = GetStatistics();
LogSanitizerDisposed(_logger, stats.TotalAllocations, stats.TotalViolations);
}
+
+ ///
+ /// Asynchronously disposes the sanitizer, awaiting secure wipes of any
+ /// tracked allocations before releasing their backing memory.
+ ///
+ ///
+ /// Prefer this over in async shutdown paths: each
+ /// tracked allocation's secure wipe is awaited rather than resolved via
+ /// GetAwaiter().GetResult(), which prevents starving the thread pool
+ /// when many allocations need to be scrubbed at once.
+ ///
+ public async ValueTask DisposeAsync()
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ _disposed = true;
+
+ // Secure-wipe + free each tracked allocation asynchronously.
+ foreach (var allocation in _trackedAllocations.Values)
+ {
+ try
+ {
+ if (_configuration.EnableSecureWiping)
+ {
+ await PerformSecureWipeAsync(allocation).ConfigureAwait(false);
+ }
+ FreeRawMemory(allocation.BaseAddress, allocation.TotalSize);
+ }
+ catch (Exception ex)
+ {
+ LogAllocationDisposeError(_logger, ex, allocation.Identifier);
+ }
+ }
+
+ _trackedAllocations.Clear();
+ _freeHistory.Clear();
+
+ _leakDetectionTimer?.Dispose();
+ _integrityCheckTimer?.Dispose();
+ _operationLock?.Dispose();
+ _randomGenerator?.Dispose();
+
+ var stats = GetStatistics();
+ LogSanitizerDisposed(_logger, stats.TotalAllocations, stats.TotalViolations);
+
+ GC.SuppressFinalize(this);
+ }
}
diff --git a/src/Extensions/DotCompute.Algorithms/Security/KernelSandbox.cs b/src/Extensions/DotCompute.Algorithms/Security/KernelSandbox.cs
index eaad1ff00..2c1f18269 100644
--- a/src/Extensions/DotCompute.Algorithms/Security/KernelSandbox.cs
+++ b/src/Extensions/DotCompute.Algorithms/Security/KernelSandbox.cs
@@ -15,7 +15,7 @@ namespace DotCompute.Algorithms.Security;
/// Provides sandboxed execution environment for untrusted kernels with comprehensive security controls.
/// Implements process isolation, resource limits, and execution monitoring.
///
-public sealed partial class KernelSandbox : IDisposable
+public sealed partial class KernelSandbox : IDisposable, IAsyncDisposable
{
private readonly ILogger _logger;
private readonly SandboxConfiguration _configuration;
@@ -546,6 +546,54 @@ public void Dispose()
_logger.LogInfoMessage("KernelSandbox disposed");
}
+ ///
+ /// Asynchronously disposes the sandbox, awaiting each active sandbox's
+ /// cooperative teardown before releasing the monitoring timer and
+ /// creation lock.
+ ///
+ ///
+ /// Prefer this over in async shutdown paths:
+ /// destroying each sandbox spawns a process-kill-and-cleanup task; awaiting
+ /// them via instead of
+ /// avoids parking a
+ /// thread-pool worker for up to 30 seconds while many sandboxes unwind.
+ ///
+ public async ValueTask DisposeAsync()
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ _disposed = true;
+
+ _monitoringTimer?.Dispose();
+ _creationLock?.Dispose();
+
+ var sandboxTasks = _activeSandboxes.Keys
+ .Select(DestroySandboxInstanceAsync)
+ .ToArray();
+
+ try
+ {
+ await Task.WhenAll(sandboxTasks)
+ .WaitAsync(TimeSpan.FromSeconds(30))
+ .ConfigureAwait(false);
+ }
+ catch (TimeoutException ex)
+ {
+ _logger.LogErrorMessage(ex, "Timeout async-disposing sandboxes");
+ }
+ catch (Exception ex)
+ {
+ _logger.LogErrorMessage(ex, "Error async-disposing sandboxes");
+ }
+
+ _logger.LogInfoMessage("KernelSandbox async-disposed");
+
+ GC.SuppressFinalize(this);
+ }
+
#region LoggerMessage Delegates
[LoggerMessage(Level = LogLevel.Warning, Message = "Failed to set directory permissions for: {TempPath}")]
diff --git a/src/Extensions/DotCompute.Linq/CodeGeneration/CompilationPipeline.cs b/src/Extensions/DotCompute.Linq/CodeGeneration/CompilationPipeline.cs
index 3785da72d..d6c88f1b7 100644
--- a/src/Extensions/DotCompute.Linq/CodeGeneration/CompilationPipeline.cs
+++ b/src/Extensions/DotCompute.Linq/CodeGeneration/CompilationPipeline.cs
@@ -44,7 +44,7 @@ namespace DotCompute.Linq.CodeGeneration;
/// The internal Roslyn workspace is reused across compilations for efficiency.
///
///
-public sealed class CompilationPipeline : IDisposable
+public sealed class CompilationPipeline : IDisposable, IAsyncDisposable
{
private readonly IKernelCache _kernelCache;
private readonly CpuKernelGenerator _kernelGenerator;
@@ -987,6 +987,47 @@ public void Dispose()
Interlocked.Read(ref _compilationFailures),
Interlocked.Read(ref _fallbackCount));
}
+
+ ///
+ /// Asynchronously disposes the compilation pipeline, awaiting the CUDA
+ /// and Metal runtime compilers' cooperative shutdown so their semaphores
+ /// and cached kernels release without blocking the calling thread.
+ ///
+ ///
+ /// Prefer this over in async shutdown paths: the
+ /// CUDA and Metal compilers each own a guarding
+ /// compilation state and a dictionary of cached compiled kernels that may
+ /// implement . Awaiting their
+ /// drains both cooperatively.
+ ///
+ public async ValueTask DisposeAsync()
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ _disposed = true;
+
+ if (_cudaCompiler is not null)
+ {
+ await _cudaCompiler.DisposeAsync().ConfigureAwait(false);
+ }
+
+ if (_metalCompiler is not null)
+ {
+ await _metalCompiler.DisposeAsync().ConfigureAwait(false);
+ }
+
+ _logger?.LogInformation(
+ "CompilationPipeline async-disposed. Stats - Total: {Total}, Cache Hits: {Hits}, Failures: {Failures}, Fallbacks: {Fallbacks}",
+ Interlocked.Read(ref _totalCompilations),
+ Interlocked.Read(ref _cacheHits),
+ Interlocked.Read(ref _compilationFailures),
+ Interlocked.Read(ref _fallbackCount));
+
+ GC.SuppressFinalize(this);
+ }
}
///
diff --git a/src/Extensions/DotCompute.Linq/CodeGeneration/RuntimeExecutor.cs b/src/Extensions/DotCompute.Linq/CodeGeneration/RuntimeExecutor.cs
index 20b6951aa..2612a4a58 100644
--- a/src/Extensions/DotCompute.Linq/CodeGeneration/RuntimeExecutor.cs
+++ b/src/Extensions/DotCompute.Linq/CodeGeneration/RuntimeExecutor.cs
@@ -37,7 +37,7 @@ namespace DotCompute.Linq.CodeGeneration;
/// - Execute compiled GPU kernels on the appropriate accelerator
///
///
-public sealed class RuntimeExecutor : IDisposable
+public sealed class RuntimeExecutor : IDisposable, IAsyncDisposable
{
private readonly ILogger _logger;
private readonly Dictionary _accelerators = new();
@@ -543,4 +543,39 @@ public void Dispose()
_logger.LogInformation("RuntimeExecutor disposed");
}
+
+ ///
+ /// Asynchronously disposes all accelerators and releases resources.
+ ///
+ ///
+ /// Prefer this over in async shutdown paths: each
+ /// accelerator's is awaited
+ /// directly instead of resolved via AsTask().Wait(), so long-running
+ /// resource cleanup (stream synchronization, memory pool trimming) does
+ /// not block the calling thread.
+ ///
+ public async ValueTask DisposeAsync()
+ {
+ if (_disposed) return;
+
+ foreach (var accelerator in _accelerators.Values)
+ {
+ try
+ {
+ await accelerator.DisposeAsync().ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error async-disposing accelerator: {Name}", accelerator.Info.Name);
+ }
+ }
+
+ _accelerators.Clear();
+ _initLock.Dispose();
+ _disposed = true;
+
+ _logger.LogInformation("RuntimeExecutor async-disposed");
+
+ GC.SuppressFinalize(this);
+ }
}
diff --git a/src/Extensions/DotCompute.Linq/Compilation/CudaRuntimeKernelCompiler.cs b/src/Extensions/DotCompute.Linq/Compilation/CudaRuntimeKernelCompiler.cs
index 500660d0a..74ecb0dfb 100644
--- a/src/Extensions/DotCompute.Linq/Compilation/CudaRuntimeKernelCompiler.cs
+++ b/src/Extensions/DotCompute.Linq/Compilation/CudaRuntimeKernelCompiler.cs
@@ -45,7 +45,7 @@ namespace DotCompute.Linq.Compilation;
/// - Production-ready error handling and fallback to CPU
///
///
-public sealed class CudaRuntimeKernelCompiler : IGpuKernelCompiler, IDisposable
+public sealed class CudaRuntimeKernelCompiler : IGpuKernelCompiler, IDisposable, IAsyncDisposable
{
private readonly ILogger _logger;
private readonly CudaAccelerator _accelerator;
@@ -311,6 +311,54 @@ public void Dispose()
_disposed = true;
}
}
+
+ ///
+ /// Asynchronously disposes the CUDA runtime kernel compiler, acquiring
+ /// the compilation semaphore cooperatively so in-flight compilation calls
+ /// can drain before cached kernels are released.
+ ///
+ ///
+ /// Prefer this over in async shutdown paths: the
+ /// compilation semaphore is acquired via
+ /// instead of a blocking
+ /// , and any cached kernels that
+ /// implement are awaited rather than
+ /// sync-disposed. This avoids starving the thread pool when a concurrent
+ /// CompileKernelAsync holds the semaphore.
+ ///
+ public async ValueTask DisposeAsync()
+ {
+ if (_disposed) return;
+
+ await _compilationLock.WaitAsync().ConfigureAwait(false);
+ try
+ {
+ foreach (var kernel in _compiledKernels.Values)
+ {
+ if (kernel is IAsyncDisposable asyncKernel)
+ {
+ await asyncKernel.DisposeAsync().ConfigureAwait(false);
+ }
+ else
+ {
+ (kernel as IDisposable)?.Dispose();
+ }
+ }
+
+ _compiledKernels.Clear();
+
+ _logger.LogInformation("CudaRuntimeKernelCompiler async-disposed ({Count} cached kernels cleaned up)",
+ _compiledKernels.Count);
+ }
+ finally
+ {
+ _ = _compilationLock.Release();
+ _compilationLock.Dispose();
+ _disposed = true;
+ }
+
+ GC.SuppressFinalize(this);
+ }
}
///
diff --git a/src/Extensions/DotCompute.Linq/Compilation/MetalRuntimeKernelCompiler.cs b/src/Extensions/DotCompute.Linq/Compilation/MetalRuntimeKernelCompiler.cs
index a04420f64..f8eae1bb5 100644
--- a/src/Extensions/DotCompute.Linq/Compilation/MetalRuntimeKernelCompiler.cs
+++ b/src/Extensions/DotCompute.Linq/Compilation/MetalRuntimeKernelCompiler.cs
@@ -44,7 +44,7 @@ namespace DotCompute.Linq.Compilation;
/// - iOS/iPadOS devices (A-series chips)
///
///
-public sealed class MetalRuntimeKernelCompiler : IGpuKernelCompiler, IDisposable
+public sealed class MetalRuntimeKernelCompiler : IGpuKernelCompiler, IDisposable, IAsyncDisposable
{
private readonly ILogger _logger;
private readonly MetalAccelerator _accelerator;
@@ -305,6 +305,53 @@ public void Dispose()
_disposed = true;
}
}
+
+ ///
+ /// Asynchronously disposes the Metal runtime kernel compiler, acquiring
+ /// the compilation semaphore cooperatively so in-flight compilation calls
+ /// can drain before cached kernels are released.
+ ///
+ ///
+ /// Prefer this over in async shutdown paths: the
+ /// compilation semaphore is acquired via
+ /// instead of a blocking
+ /// , and any cached kernels that
+ /// implement are awaited rather than
+ /// sync-disposed.
+ ///
+ public async ValueTask DisposeAsync()
+ {
+ if (_disposed) return;
+
+ await _compilationLock.WaitAsync().ConfigureAwait(false);
+ try
+ {
+ foreach (var kernel in _compiledKernels.Values)
+ {
+ if (kernel is IAsyncDisposable asyncKernel)
+ {
+ await asyncKernel.DisposeAsync().ConfigureAwait(false);
+ }
+ else
+ {
+ (kernel as IDisposable)?.Dispose();
+ }
+ }
+
+ _compiledKernels.Clear();
+
+ _logger.LogInformation("MetalRuntimeKernelCompiler async-disposed ({Count} cached kernels cleaned up)",
+ _compiledKernels.Count);
+ }
+ finally
+ {
+ _ = _compilationLock.Release();
+ _compilationLock.Dispose();
+ _disposed = true;
+ }
+
+ GC.SuppressFinalize(this);
+ }
}
///
diff --git a/src/Extensions/DotCompute.Linq/Reactive/BackpressureManager.cs b/src/Extensions/DotCompute.Linq/Reactive/BackpressureManager.cs
index 5269e324a..c0df42b1c 100644
--- a/src/Extensions/DotCompute.Linq/Reactive/BackpressureManager.cs
+++ b/src/Extensions/DotCompute.Linq/Reactive/BackpressureManager.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Concurrent;
using System.Threading;
+using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace DotCompute.Linq.Reactive;
@@ -20,7 +21,7 @@ namespace DotCompute.Linq.Reactive;
/// - Block: Block producer thread until space available
/// - Sample: Keep only most recent item
///
-public sealed class BackpressureManager : IBackpressureManager, IDisposable
+public sealed class BackpressureManager : IBackpressureManager, IDisposable, IAsyncDisposable
{
private readonly ILogger _logger;
@@ -361,19 +362,81 @@ public void ResetStatistics()
#endregion
- #region IDisposable
+ #region IDisposable / IAsyncDisposable
+
+ private int _disposed;
///
/// Disposes resources used by the backpressure manager.
///
public void Dispose()
{
- _cancellationSource.Cancel();
+ if (Interlocked.Exchange(ref _disposed, 1) != 0)
+ {
+ return;
+ }
+
+ DisposeCore();
+ }
+
+ ///
+ /// Asynchronously disposes resources used by the backpressure manager.
+ ///
+ ///
+ /// Cancels the cancellation source, unblocks any producer threads waiting on
+ /// , and drains the pending buffer before
+ /// releasing resources. Prefer this over when running inside
+ /// async contexts so producers observe cancellation cleanly before shutdown.
+ ///
+ public async ValueTask DisposeAsync()
+ {
+ if (Interlocked.Exchange(ref _disposed, 1) != 0)
+ {
+ return;
+ }
+
+ try
+ {
+ await _cancellationSource.CancelAsync().ConfigureAwait(false);
+ }
+ catch (ObjectDisposedException)
+ {
+ // Already disposed - nothing to cancel.
+ }
+
+ // Wake any producers blocked in HandleBlock so they can observe cancellation
+ // before we tear the manager down.
+ UnblockProducer();
+
+ // Drain pending items so subscribers that still hold a reference see an
+ // empty buffer rather than a half-populated snapshot after disposal.
+ while (_buffer.TryDequeue(out _))
+ {
+ }
+
_cancellationSource.Dispose();
+ _logger.LogInformation("BackpressureManager disposed. " +
+ "Total processed: {Processed}, Total dropped: {Dropped}",
+ _totalItemsProcessed, _totalItemsDropped);
+ }
+
+ private void DisposeCore()
+ {
+ try
+ {
+ _cancellationSource.Cancel();
+ }
+ catch (ObjectDisposedException)
+ {
+ // Already disposed - nothing to cancel.
+ }
+
// Unblock any waiting threads
UnblockProducer();
+ _cancellationSource.Dispose();
+
_logger.LogInformation("BackpressureManager disposed. " +
"Total processed: {Processed}, Total dropped: {Dropped}",
_totalItemsProcessed, _totalItemsDropped);
diff --git a/src/Runtime/DotCompute.Plugins/Managers/NuGetPluginManager.cs b/src/Runtime/DotCompute.Plugins/Managers/NuGetPluginManager.cs
index 38609d9ff..5996e672b 100644
--- a/src/Runtime/DotCompute.Plugins/Managers/NuGetPluginManager.cs
+++ b/src/Runtime/DotCompute.Plugins/Managers/NuGetPluginManager.cs
@@ -17,7 +17,7 @@ namespace DotCompute.Plugins.Managers;
///
/// Advanced NuGet plugin manager with comprehensive lifecycle management, hot reloading, and monitoring.
///
-public class NuGetPluginManager : IDisposable
+public class NuGetPluginManager : IDisposable, IAsyncDisposable
{
private readonly ILogger _logger;
private readonly NuGetPluginLoader _pluginLoader;
@@ -676,6 +676,49 @@ public void Dispose()
GC.SuppressFinalize(this);
}
+
+ ///
+ /// Asynchronously disposes the plugin manager, awaiting each plugin's
+ /// unload before tearing down the loader, health monitor, and metrics
+ /// collector.
+ ///
+ ///
+ /// Prefer this over during async host shutdown:
+ /// UnloadPluginAsync is awaited rather than resolved via
+ /// GetAwaiter().GetResult(), so long-running unload hooks (plugin
+ /// finalisation, terminator scripts) do not block a thread-pool worker.
+ ///
+ public async ValueTask DisposeAsync()
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ _disposed = true;
+
+ _periodicMaintenanceTimer?.Dispose();
+
+ // Unload all plugins cooperatively.
+ foreach (var (pluginId, _) in _managedPlugins.ToList())
+ {
+ try
+ {
+ _ = await UnloadPluginAsync(pluginId).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogErrorMessage(ex, $"Error unloading plugin during async dispose: {pluginId}");
+ }
+ }
+
+ _pluginLoader?.Dispose();
+ _healthMonitor?.Dispose();
+ _metricsCollector?.Dispose();
+ _operationSemaphore?.Dispose();
+
+ GC.SuppressFinalize(this);
+ }
}
///
diff --git a/src/Runtime/DotCompute.Plugins/Security/PluginSandbox.cs b/src/Runtime/DotCompute.Plugins/Security/PluginSandbox.cs
index 0b23928d5..7431ebc72 100644
--- a/src/Runtime/DotCompute.Plugins/Security/PluginSandbox.cs
+++ b/src/Runtime/DotCompute.Plugins/Security/PluginSandbox.cs
@@ -12,7 +12,7 @@ namespace DotCompute.Plugins.Security;
///
/// Provides secure sandboxing and isolation for plugin execution with controlled permissions.
///
-public class PluginSandbox : IDisposable
+public class PluginSandbox : IDisposable, IAsyncDisposable
{
private readonly ILogger _logger;
private readonly SandboxConfiguration _configuration;
@@ -467,4 +467,42 @@ public void Dispose()
_resourceMonitor?.Dispose();
_securityManager?.Dispose();
}
+
+ ///
+ /// Asynchronously disposes the sandbox, awaiting each sandboxed plugin's
+ /// termination before releasing the security manager and resource monitor.
+ ///
+ ///
+ /// Prefer this over in async contexts: plugin
+ /// termination runs cooperative cleanup hooks that previously resolved via
+ /// GetAwaiter().GetResult(), which can deadlock under hosts with a
+ /// captured synchronization context and starve the thread pool when many
+ /// plugins are active.
+ ///
+ public async ValueTask DisposeAsync()
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ _disposed = true;
+ GC.SuppressFinalize(this);
+
+ foreach (var plugin in _sandboxedPlugins.Values)
+ {
+ try
+ {
+ await plugin.TerminateAsync().ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogErrorMessage(ex, $"Error async-disposing sandboxed plugin {plugin.Id}");
+ }
+ }
+
+ _sandboxedPlugins.Clear();
+ _resourceMonitor?.Dispose();
+ _securityManager?.Dispose();
+ }
}
diff --git a/src/Runtime/DotCompute.Plugins/Security/SandboxedPlugin.cs b/src/Runtime/DotCompute.Plugins/Security/SandboxedPlugin.cs
index 2e50a6d19..ab482b3fa 100644
--- a/src/Runtime/DotCompute.Plugins/Security/SandboxedPlugin.cs
+++ b/src/Runtime/DotCompute.Plugins/Security/SandboxedPlugin.cs
@@ -6,7 +6,7 @@ namespace DotCompute.Plugins.Security;
///
/// Represents a plugin running in a secure sandbox with restricted permissions.
///
-public class SandboxedPlugin : IDisposable
+public class SandboxedPlugin : IDisposable, IAsyncDisposable
{
private readonly ResourceMonitor _resourceMonitor;
private bool _disposed;
@@ -175,6 +175,37 @@ public void Dispose()
TerminateAsync().GetAwaiter().GetResult();
GC.SuppressFinalize(this);
}
+
+ ///
+ /// Asynchronously disposes the sandboxed plugin, awaiting its termination
+ /// hooks before releasing the isolated load context and resource monitor.
+ ///
+ ///
+ /// Prefer this over in async contexts: plugin
+ /// termination runs sensitive cleanup (flushing outstanding operations,
+ /// clearing cached data) that previously resolved via
+ /// GetAwaiter().GetResult(), which can deadlock on hosts with a
+ /// captured synchronization context.
+ ///
+ public async ValueTask DisposeAsync()
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ _disposed = true;
+ try
+ {
+ await TerminateAsync().ConfigureAwait(false);
+ }
+ catch
+ {
+ // Swallow exceptions during async disposal - matches the sync path.
+ }
+
+ GC.SuppressFinalize(this);
+ }
}
///
diff --git a/tests/Unit/DotCompute.Core.Tests/Security/MemoryProtectionTests.cs b/tests/Unit/DotCompute.Core.Tests/Security/MemoryProtectionTests.cs
index dd79891e9..1dafa759b 100644
--- a/tests/Unit/DotCompute.Core.Tests/Security/MemoryProtectionTests.cs
+++ b/tests/Unit/DotCompute.Core.Tests/Security/MemoryProtectionTests.cs
@@ -713,7 +713,7 @@ public async Task AllocateProtectedMemoryAsync_AfterDispose_ShouldThrowObjectDis
{
// Arrange
var protection = new MemoryProtection(_logger);
- protection.Dispose();
+ await protection.DisposeAsync();
// Act
var action = async () => await protection.AllocateProtectedMemoryAsync(1024);
diff --git a/tests/Unit/DotCompute.Core.Tests/Security/MemorySanitizerTests.cs b/tests/Unit/DotCompute.Core.Tests/Security/MemorySanitizerTests.cs
index 59029e9d8..b7291b361 100644
--- a/tests/Unit/DotCompute.Core.Tests/Security/MemorySanitizerTests.cs
+++ b/tests/Unit/DotCompute.Core.Tests/Security/MemorySanitizerTests.cs
@@ -718,7 +718,7 @@ public async Task AllocateSanitizedMemoryAsync_AfterDispose_ShouldThrowObjectDis
{
// Arrange
var sanitizer = new MemorySanitizer(_logger);
- sanitizer.Dispose();
+ await sanitizer.DisposeAsync();
// Act
var action = async () => await sanitizer.AllocateSanitizedMemoryAsync(1024);
@@ -762,7 +762,7 @@ public async Task DetectMemoryLeaksAsync_AfterDispose_ShouldThrowObjectDisposedE
{
// Arrange
var sanitizer = new MemorySanitizer(_logger);
- sanitizer.Dispose();
+ await sanitizer.DisposeAsync();
// Act
var action = sanitizer.DetectMemoryLeaksAsync;
diff --git a/tests/Unit/DotCompute.Plugins.Tests/Security/PluginSandboxTests.cs b/tests/Unit/DotCompute.Plugins.Tests/Security/PluginSandboxTests.cs
index 334fc6d3c..f1b4d13d3 100644
--- a/tests/Unit/DotCompute.Plugins.Tests/Security/PluginSandboxTests.cs
+++ b/tests/Unit/DotCompute.Plugins.Tests/Security/PluginSandboxTests.cs
@@ -119,7 +119,7 @@ public async Task CreateSandboxedPluginAsync_WhenDisposed_ShouldThrowObjectDispo
{
// Arrange
_sandbox = new PluginSandbox(_mockLogger);
- _sandbox.Dispose();
+ await _sandbox.DisposeAsync();
var permissions = new SandboxPermissions();
// Act
@@ -174,7 +174,7 @@ public async Task TerminatePluginAsync_WhenDisposed_ShouldThrowObjectDisposedExc
{
// Arrange
_sandbox = new PluginSandbox(_mockLogger);
- _sandbox.Dispose();
+ await _sandbox.DisposeAsync();
// Act
var act = async () => await _sandbox.TerminatePluginAsync(Guid.NewGuid());