< 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@ca54e35c77799b76774b3805b6f075cdbc0c5fbe
Line coverage
83%
Covered lines: 65
Uncovered lines: 13
Coverable lines: 78
Total lines: 145
Line coverage: 83.3%
Branch coverage
62%
Covered branches: 15
Total branches: 24
Branch coverage: 62.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 09/08/2025 - 20:34:03 Line coverage: 100% (52/52) Branch coverage: 92.3% (24/26) Total lines: 125 Tag: Kestrun/Kestrun@3790ee5884494a7a2a829344a47743e0bf492e7209/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@10d476bee71c71ad215bb8ab59f219887b5b4a5e01/02/2026 - 00:16:25 Line coverage: 84.2% (64/76) Branch coverage: 63.6% (14/22) Total lines: 140 Tag: Kestrun/Kestrun@8405dc23b786b9d436fba0d65fb80baa4171e1d001/24/2026 - 19:35:59 Line coverage: 83.3% (65/78) Branch coverage: 62.5% (15/24) Total lines: 145 Tag: Kestrun/Kestrun@f59dcba478ea75f69584d696e5f1fb1cfa40aa51 09/08/2025 - 20:34:03 Line coverage: 100% (52/52) Branch coverage: 92.3% (24/26) Total lines: 125 Tag: Kestrun/Kestrun@3790ee5884494a7a2a829344a47743e0bf492e7209/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@10d476bee71c71ad215bb8ab59f219887b5b4a5e01/02/2026 - 00:16:25 Line coverage: 84.2% (64/76) Branch coverage: 63.6% (14/22) Total lines: 140 Tag: Kestrun/Kestrun@8405dc23b786b9d436fba0d65fb80baa4171e1d001/24/2026 - 19:35:59 Line coverage: 83.3% (65/78) Branch coverage: 62.5% (15/24) Total lines: 145 Tag: Kestrun/Kestrun@f59dcba478ea75f69584d696e5f1fb1cfa40aa51

Metrics

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

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;
 59
 60            // Store the PowerShell instance in the context for later use
 361            context.Items[PowerShellDelegateBuilder.PS_INSTANCE_KEY] = ps;
 62
 363            var kestrunContext = new KestrunContext(Host, context);
 64            // Set the KestrunContext in the HttpContext.Items for later use
 365            context.Items[PowerShellDelegateBuilder.KR_CONTEXT_KEY] = kestrunContext;
 66
 367            if (Log.IsEnabled(LogEventLevel.Debug))
 68            {
 069                Log.DebugSanitized("PowerShellRunspaceMiddleware - Setting KestrunContext in HttpContext.Items for {Path
 70            }
 71
 372            Log.Verbose("Setting PowerShell variables for Request and Response in the runspace.");
 73            // Set the PowerShell variables for the request and response
 374            var ss = ps.Runspace.SessionStateProxy;
 375            ss.SetVariable("Context", kestrunContext);
 76
 377            if (context.Items.TryGetValue("KrLocalizer", out var localizer))
 78            {
 079                ss.SetVariable("Localizer", localizer);
 80            }
 81
 82            // Defer cleanup until the response is fully completed. This ensures
 83            // post-endpoint middleware (e.g., StatusCodePages) can still access the runspace.
 384            context.Response.OnCompleted(() =>
 385            {
 386                try
 387                {
 188                    if (Log.IsEnabled(LogEventLevel.Debug))
 389                    {
 090                        Log.Debug("OnCompleted: Returning runspace to pool: {RunspaceId} {name} {id}", ps.Runspace.Insta
 391                    }
 192                    _pool.Release(ps.Runspace);
 193                }
 094                catch (Exception ex)
 395                {
 096                    Log.Debug(ex, "OnCompleted: Error returning runspace to pool");
 097                }
 398                finally
 399                {
 3100                    try
 3101                    {
 1102                        if (Log.IsEnabled(LogEventLevel.Debug))
 3103                        {
 0104                            Log.Debug("OnCompleted: Disposing PowerShell instance: {InstanceId}", ps.InstanceId);
 3105                        }
 1106                        ps.Dispose();
 1107                    }
 0108                    catch (Exception ex)
 3109                    {
 0110                        Log.Debug(ex, "OnCompleted: Error disposing PowerShell instance");
 0111                    }
 1112                    _ = context.Items.Remove(PowerShellDelegateBuilder.PS_INSTANCE_KEY);
 1113                    _ = context.Items.Remove(PowerShellDelegateBuilder.KR_CONTEXT_KEY);
 1114                }
 1115                return Task.CompletedTask;
 3116            });
 117
 3118            if (Log.IsEnabled(LogEventLevel.Debug))
 119            {
 0120                Log.DebugSanitized("PowerShellRunspaceMiddleware - Continuing Pipeline  for {Path}", context.Request.Pat
 121            }
 122
 3123            await _next(context); // continue the pipeline
 3124            if (Log.IsEnabled(LogEventLevel.Debug))
 125            {
 0126                Log.DebugSanitized("PowerShellRunspaceMiddleware completed for {Path}", context.Request.Path);
 127            }
 3128        }
 1129        catch (Exception ex)
 130        {
 1131            Log.Error(ex, "Error occurred in PowerShellRunspaceMiddleware");
 1132            throw; // allow ExceptionHandler to catch and handle (re-exec or JSON)
 133        }
 134        finally
 135        {
 4136            var remaining = Interlocked.Decrement(ref _inFlight);
 4137            var durationMs = (DateTime.UtcNow - start).TotalMilliseconds;
 4138            if (Log.IsEnabled(LogEventLevel.Debug))
 139            {
 1140                Log.DebugSanitized("PowerShellRunspaceMiddleware ended for {Path} durationMs={durationMs} inFlight={rema
 1141                    context.Request.Path, durationMs, remaining);
 142            }
 143        }
 3144    }
 145}