Private/Functions/Server/Init/Start-MultiThreadedServer.ps1

function Start-MultiThreadedServer
{
<#
    .SYNOPSIS
        Start the server multi-threaded.

    .DESCRIPTION
        This is provided primarily for debugging, so that the request handling may be steepd into
        Normal operation should be in multithreaded mode.

    .PARAMETER ClassPath
        Path from which to load user controller classes from.

    .PARAMETER BoundIp
        IP address to bind server to. 0.0.0.0 = all local interfaces.

    .PARAMETER Port
        Port to listen on.

    .PARAMETER ThreadCount
        Number of request processing threads to start.

    .PARAMETER LogFolder
        Path to root of logging folder structure.
        Subdirectories 'HTTP' and 'Error' will be created beneath this.
#>

    param
    (
        [string[]]$ClassPath,

        [string]$BoundIp = '0.0.0.0',

        [UInt16]$Port,

        [int]$ThreadCount,

        [string]$LogFolder
    )

    Write-OperatingSystemLogEntry -EventId ([EventId]::ServerStarted) 'Server starting'

    [System.Threading.Mutex]$pesterMutex = $null

    # Look for integration test mutex
    $SharedVariables.IsPester = [System.Threading.Mutex]::TryOpenExisting('PesterWaitServiceStartMutex', [ref]$pesterMutex)

    # Block of arguments that are passed to threads
    $threadArguments = New-Object PSObject -Property @{
        CancellationTokenSource = [System.Threading.CancellationTokenSource]::new()
        BoundIp                 = $BoundIp
        Port                    = $Port
        LogFolder               = Resolve-Path $LogFolder | Select-Object -ExpandProperty Path
        ModulePath              = $MyInvocation.MyCommand.Module.Path
        CanLogEvents            = $SharedVariables.CanLogEvents
        RequestPool             = New-RunspacePool -MaxThreads $ThreadCount -ClassPath (
                                    $ClassPath |
                                        ForEach-Object {
                                        Resolve-Path $_ | Select-Object -ExpandProperty Path
                                    }
                                )
    }

    try
    {
        # Create listener and logging threads
        $loggingThread = New-LoggingThread -ThreadArguments $threadArguments
        $listenerThread = New-ListenerThread -ThreadArguments $threadArguments

        Write-OperatingSystemLogEntry -EventId ([EventId]::InitializationStep) 'Initialization complete'

        # Start threads
        $loggingAsync = $loggingThread.BeginInvoke()
        $listenerAsync = $listenerThread.BeginInvoke()

        if ($SharedVariables.IsPester)
        {
            # Release mutex to allow integration tests to proceed.
            $pesterMutex.ReleaseMutex() | Out-Null
        }

        # Wait for threads to end
        $listenerThread.EndInvoke($listenerAsync) | Out-Null
        Assert-ThreadErrors -Thread $listenerThread

        $loggingThread.EndInvoke($loggingAsync) | Out-Null
    }
    catch
    {
        # EndInvoke threw, which means an unhandled exception in the listener thread - indicative of a bug.
        Write-OperatingSystemLogEntry -EventId ([EventId]::Exception) "Error starting multithreaded server: $($_.Exception.Message)"
        throw
    }
    finally
    {
        if ($threadArguments.RequestPool)
        {
            $threadArguments.RequestPool.Dispose()
        }

        Write-OperatingSystemLogEntry -EventId ([EventId]::ServerStopped) 'Server Stopped'
    }
}