< Summary - Kestrun — Combined Coverage

Information
Class: Kestrun.Middleware.PowerShellRunspaceMiddleware
Assembly: Kestrun
File(s): /home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Middleware/PowerShellRunspaceMiddleware.cs
Tag: Kestrun/Kestrun@2d87023b37eb91155071c91dd3d6a2eeb3004705
Line coverage
84%
Covered lines: 66
Uncovered lines: 12
Coverable lines: 78
Total lines: 142
Line coverage: 84.6%
Branch coverage
63%
Covered branches: 14
Total branches: 22
Branch coverage: 63.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 08/26/2025 - 01:25:22 Line coverage: 86.4% (32/37) Branch coverage: 61.1% (11/18) Total lines: 95 Tag: Kestrun/Kestrun@07f821172e5dc3657f1be7e6818f18d6721cf38a09/02/2025 - 17:53:35 Line coverage: 88.4% (46/52) Branch coverage: 69.2% (18/26) Total lines: 125 Tag: Kestrun/Kestrun@58c69eb1b344fcfa1f4b9c597030af85fac9e31b09/06/2025 - 18:30:33 Line coverage: 100% (52/52) Branch coverage: 92.3% (24/26) Total lines: 125 Tag: Kestrun/Kestrun@aeddbedb8a96e9137aac94c2d5edd011b57ac87109/12/2025 - 03:43:11 Line coverage: 100% (52/52) Branch coverage: 92.3% (24/26) Total lines: 124 Tag: Kestrun/Kestrun@d160286e3020330b1eb862d66a37db2e26fc904210/13/2025 - 16:52:37 Line coverage: 84.6% (66/78) Branch coverage: 63.6% (14/22) Total lines: 142 Tag: Kestrun/Kestrun@10d476bee71c71ad215bb8ab59f219887b5b4a5e 08/26/2025 - 01:25:22 Line coverage: 86.4% (32/37) Branch coverage: 61.1% (11/18) Total lines: 95 Tag: Kestrun/Kestrun@07f821172e5dc3657f1be7e6818f18d6721cf38a09/02/2025 - 17:53:35 Line coverage: 88.4% (46/52) Branch coverage: 69.2% (18/26) Total lines: 125 Tag: Kestrun/Kestrun@58c69eb1b344fcfa1f4b9c597030af85fac9e31b09/06/2025 - 18:30:33 Line coverage: 100% (52/52) Branch coverage: 92.3% (24/26) Total lines: 125 Tag: Kestrun/Kestrun@aeddbedb8a96e9137aac94c2d5edd011b57ac87109/12/2025 - 03:43:11 Line coverage: 100% (52/52) Branch coverage: 92.3% (24/26) Total lines: 124 Tag: Kestrun/Kestrun@d160286e3020330b1eb862d66a37db2e26fc904210/13/2025 - 16:52:37 Line coverage: 84.6% (66/78) Branch coverage: 63.6% (14/22) Total lines: 142 Tag: Kestrun/Kestrun@10d476bee71c71ad215bb8ab59f219887b5b4a5e

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)50%44100%
get_Host()100%11100%
get_Log()100%11100%
InvokeAsync()71.42%141492.72%

File(s)

/home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Middleware/PowerShellRunspaceMiddleware.cs

#LineLine coverage
 1using System.Management.Automation;
 2using System.Diagnostics;
 3using Kestrun.Languages;
 4using Kestrun.Models;
 5using Kestrun.Scripting;
 6using Serilog.Events;
 7using Kestrun.Hosting;
 8using Kestrun.Logging;
 9
 10namespace Kestrun.Middleware;
 11
 12/// <summary>
 13/// Initializes a new instance of the <see cref="PowerShellRunspaceMiddleware"/> class.
 14/// </summary>
 15/// <param name="next">The next middleware in the pipeline.</param>
 16/// <param name="pool">The runspace pool manager.</param>
 417public sealed class PowerShellRunspaceMiddleware(RequestDelegate next, KestrunRunspacePoolManager pool)
 18{
 419    private readonly RequestDelegate _next = next ?? throw new ArgumentNullException(nameof(next));
 420    private readonly KestrunRunspacePoolManager _pool = pool ?? throw new ArgumentNullException(nameof(pool));
 21    private static int _inFlight; // diagnostic concurrency counter
 22
 3623    private KestrunHost Host => _pool.Host;
 3324    private Serilog.ILogger Log => Host.Logger;
 25    /// <summary>
 26    /// Processes an HTTP request using a PowerShell runspace from the pool.
 27    /// </summary>
 28    /// <param name="context">The HTTP context for the current request.</param>
 29    public async Task InvokeAsync(HttpContext context)
 30    {
 31        // Concurrency diagnostics
 432        var start = DateTime.UtcNow;
 433        var threadId = Environment.CurrentManagedThreadId;
 434        var current = Interlocked.Increment(ref _inFlight);
 435        if (Log.IsEnabled(LogEventLevel.Debug))
 36        {
 137            Log.DebugSanitized("ENTER InvokeAsync path={Path} inFlight={InFlight} thread={Thread} time={Start}",
 138                context.Request.Path, current, threadId, start.ToString("O"));
 39        }
 40
 41        try
 42        {
 443            if (Log.IsEnabled(LogEventLevel.Debug))
 44            {
 145                Log.DebugSanitized("PowerShellRunspaceMiddleware started for {Path}", context.Request.Path);
 46            }
 47
 48            // Acquire a runspace from the pool asynchronously (avoid blocking thread pool while waiting)
 449            var acquireStart = Stopwatch.GetTimestamp();
 450            var runspace = await _pool.AcquireAsync(context.RequestAborted);
 351            var acquireMs = (Stopwatch.GetTimestamp() - acquireStart) * 1000.0 / Stopwatch.Frequency;
 352            if (Log.IsEnabled(LogEventLevel.Debug))
 53            {
 054                Log.DebugSanitized("Runspace acquired for {Path} in {AcquireMs} ms (inFlight={InFlight})", context.Reque
 55            }
 56
 357            var ps = PowerShell.Create();
 358            ps.Runspace = runspace;
 359            var krRequest = await KestrunRequest.NewRequest(context);
 360            var krResponse = new KestrunResponse(krRequest);
 61
 62            // Store the PowerShell instance in the context for later use
 363            context.Items[PowerShellDelegateBuilder.PS_INSTANCE_KEY] = ps;
 64
 365            KestrunContext kestrunContext = new(Host, krRequest, krResponse, context);
 66            // Set the KestrunContext in the HttpContext.Items for later use
 367            context.Items[PowerShellDelegateBuilder.KR_CONTEXT_KEY] = kestrunContext;
 68
 369            if (Log.IsEnabled(LogEventLevel.Debug))
 70            {
 071                Log.DebugSanitized("PowerShellRunspaceMiddleware - Setting KestrunContext in HttpContext.Items for {Path
 72            }
 73
 374            Log.Verbose("Setting PowerShell variables for Request and Response in the runspace.");
 75            // Set the PowerShell variables for the request and response
 376            var ss = ps.Runspace.SessionStateProxy;
 377            ss.SetVariable("Context", kestrunContext);
 78
 79            // Defer cleanup until the response is fully completed. This ensures
 80            // post-endpoint middleware (e.g., StatusCodePages) can still access the runspace.
 381            context.Response.OnCompleted(() =>
 382            {
 383                try
 384                {
 185                    if (Log.IsEnabled(LogEventLevel.Debug))
 386                    {
 087                        Log.Debug("OnCompleted: Returning runspace to pool: {RunspaceId}", ps.Runspace.InstanceId);
 388                    }
 189                    _pool.Release(ps.Runspace);
 190                }
 091                catch (Exception ex)
 392                {
 093                    Log.Debug(ex, "OnCompleted: Error returning runspace to pool");
 094                }
 395                finally
 396                {
 397                    try
 398                    {
 199                        if (Log.IsEnabled(LogEventLevel.Debug))
 3100                        {
 0101                            Log.Debug("OnCompleted: Disposing PowerShell instance: {InstanceId}", ps.InstanceId);
 3102                        }
 1103                        ps.Dispose();
 1104                    }
 0105                    catch (Exception ex)
 3106                    {
 0107                        Log.Debug(ex, "OnCompleted: Error disposing PowerShell instance");
 0108                    }
 1109                    _ = context.Items.Remove(PowerShellDelegateBuilder.PS_INSTANCE_KEY);
 1110                    _ = context.Items.Remove(PowerShellDelegateBuilder.KR_CONTEXT_KEY);
 1111                }
 1112                return Task.CompletedTask;
 3113            });
 114
 3115            if (Log.IsEnabled(LogEventLevel.Debug))
 116            {
 0117                Log.DebugSanitized("PowerShellRunspaceMiddleware - Continuing Pipeline  for {Path}", context.Request.Pat
 118            }
 119
 3120            await _next(context); // continue the pipeline
 3121            if (Log.IsEnabled(LogEventLevel.Debug))
 122            {
 0123                Log.DebugSanitized("PowerShellRunspaceMiddleware completed for {Path}", context.Request.Path);
 124            }
 3125        }
 1126        catch (Exception ex)
 127        {
 1128            Log.Error(ex, "Error occurred in PowerShellRunspaceMiddleware");
 1129            throw; // allow ExceptionHandler to catch and handle (re-exec or JSON)
 130        }
 131        finally
 132        {
 4133            var remaining = Interlocked.Decrement(ref _inFlight);
 4134            var durationMs = (DateTime.UtcNow - start).TotalMilliseconds;
 4135            if (Log.IsEnabled(LogEventLevel.Debug))
 136            {
 1137                Log.DebugSanitized("PowerShellRunspaceMiddleware ended for {Path} durationMs={durationMs} inFlight={rema
 1138                    context.Request.Path, durationMs, remaining);
 139            }
 140        }
 3141    }
 142}