< 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@9d3a582b2d63930269564a7591aa77ef297cadeb
Line coverage
100%
Covered lines: 52
Uncovered lines: 0
Coverable lines: 52
Total lines: 125
Line coverage: 100%
Branch coverage
92%
Covered branches: 24
Total branches: 26
Branch coverage: 92.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)50%44100%
InvokeAsync()100%2222100%

File(s)

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

#LineLine coverage
 1using System.Management.Automation;
 2using System.Diagnostics;
 3using Kestrun.Hosting;
 4using Kestrun.Languages;
 5using Kestrun.Models;
 6using Kestrun.Scripting;
 7using Serilog;
 8using Serilog.Events;
 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
 23    /// <summary>
 24    /// Processes an HTTP request using a PowerShell runspace from the pool.
 25    /// </summary>
 26    /// <param name="context">The HTTP context for the current request.</param>
 27    public async Task InvokeAsync(HttpContext context)
 28    {
 29        // Concurrency diagnostics
 430        var start = DateTime.UtcNow;
 431        var threadId = Environment.CurrentManagedThreadId;
 432        var current = Interlocked.Increment(ref _inFlight);
 433        if (Log.IsEnabled(LogEventLevel.Debug))
 34        {
 435            Log.Debug("ENTER InvokeAsync path={Path} inFlight={InFlight} thread={Thread} time={Start}",
 436                context.Request.Path, current, threadId, start.ToString("O"));
 37        }
 38
 39        try
 40        {
 441            if (Log.IsEnabled(LogEventLevel.Debug))
 42            {
 443                Log.Debug("PowerShellRunspaceMiddleware started for {Path}", context.Request.Path);
 44            }
 45
 46            // Acquire a runspace from the pool asynchronously (avoid blocking thread pool while waiting)
 447            var acquireStart = Stopwatch.GetTimestamp();
 448            var runspace = await _pool.AcquireAsync(context.RequestAborted);
 349            var acquireMs = (Stopwatch.GetTimestamp() - acquireStart) * 1000.0 / Stopwatch.Frequency;
 350            if (Log.IsEnabled(LogEventLevel.Debug))
 51            {
 352                Log.Debug("Runspace acquired for {Path} in {AcquireMs} ms (inFlight={InFlight})", context.Request.Path, 
 53            }
 54
 355            using var ps = PowerShell.Create();
 356            ps.Runspace = runspace;
 357            var krRequest = await KestrunRequest.NewRequest(context);
 358            var krResponse = new KestrunResponse(krRequest);
 59
 60            // Store the PowerShell instance in the context for later use
 361            context.Items[PowerShellDelegateBuilder.PS_INSTANCE_KEY] = ps;
 62
 363            KestrunContext kestrunContext = new(krRequest, krResponse, 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            {
 369                Log.Debug("PowerShellRunspaceMiddleware - Setting KestrunContext in HttpContext.Items for {Path}", conte
 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
 77            try
 78            {
 379                if (Log.IsEnabled(LogEventLevel.Debug))
 80                {
 381                    Log.Debug("PowerShellRunspaceMiddleware - Continuing Pipeline  for {Path}", context.Request.Path);
 82                }
 83
 384                await _next(context); // continue the pipeline
 385                if (Log.IsEnabled(LogEventLevel.Debug))
 86                {
 387                    Log.Debug("PowerShellRunspaceMiddleware completed for {Path}", context.Request.Path);
 88                }
 389            }
 90            finally
 91            {
 392                if (ps != null)
 93                {
 394                    if (Log.IsEnabled(LogEventLevel.Debug))
 95                    {
 396                        Log.Debug("Returning runspace to pool: {RunspaceId}", ps.Runspace.InstanceId);
 97                    }
 98
 399                    _pool.Release(ps.Runspace); // return the runspace to the pool
 3100                    if (Log.IsEnabled(LogEventLevel.Debug))
 101                    {
 3102                        Log.Debug("Disposing PowerShell instance: {InstanceId}", ps.InstanceId);
 103                    }
 104                    // Dispose the PowerShell instance
 3105                    ps.Dispose();
 3106                    _ = context.Items.Remove(PowerShellDelegateBuilder.PS_INSTANCE_KEY); // just in case someone re-uses
 107                }
 108            }
 3109        }
 1110        catch (Exception ex)
 111        {
 1112            Log.Error(ex, "Error occurred in PowerShellRunspaceMiddleware");
 1113        }
 114        finally
 115        {
 4116            var remaining = Interlocked.Decrement(ref _inFlight);
 4117            var durationMs = (DateTime.UtcNow - start).TotalMilliseconds;
 4118            if (Log.IsEnabled(LogEventLevel.Debug))
 119            {
 4120                Log.Debug("PowerShellRunspaceMiddleware ended for {Path} durationMs={Duration} inFlight={InFlight}",
 4121                    context.Request.Path, durationMs, remaining);
 122            }
 123        }
 4124    }
 125}