Private/Functions/Server/Init/New-ListenerThread.ps1
function New-ListenerThread { <# .SYNOPSIS Creates the listener thread .DESCRIPTION The listener thread listens for incoming requests then hands them off to a thread for processing. .PARAMETER ThreadArguments A block of arguments to pass to the listener thread .OUTPUTS [PowerShell] Configured listener thread. #> param ( [object]$ThreadArguments ) # ScriptBlock that defines the code that the listener thread executes. [scriptblock]$listenerTask = { param ( [object]$ThreadArguments ) Set-Variable -Name SharedVariables -Option AllScope -Value @{ CanLogEvents = $ThreadArguments.CanLogEvents ServerName = [IO.Path]::GetFileNameWithoutExtension($ThreadArguments.ModulePath) } # dot source event logging $privateDirectory = [IO.Path]::Combine((Split-Path -Parent $ThreadArguments.ModulePath), 'Private') ('00-Enums.ps1', 'Write-OperatingSystemLogEntry.ps1', 'Write-SyslogEntry.ps1', 'Write-EventlogEntry.ps1') | Foreach-Object { . (Get-ChildItem -Recurse -Path $privateDirectory -Filter $_).FullName } # Class to manage request handling threads. class RequestHandlerTask { # List of currently running jobs hidden static [System.Collections.Generic.List[RequestHandlerTask]]$RunningTasks = [System.Collections.Generic.List[RequestHandlerTask]]::new() # Thread running this request hidden [powershell]$Job # Result of the job hidden [System.IAsyncResult]$AsyncResult RequestHanderTask() { } # Starts a new request handling job [void]Start([System.Net.Sockets.TcpClient]$tcpClient, [object]$threadArguments) { $this.Job = [powershell]::Create() $this.Job.RunspacePool = $threadArguments.RequestPool $this.Job. AddCommand('Resolve-Request'). AddParameter('TcpClient', $tcpClient). AddParameter('CancellationTokenSource', $threadArguments.CancellationTokenSource) | Out-Null $this.AsyncResult = $this.Job.BeginInvoke() [RequestHandlerTask]::RunningTasks.Add($this) } # Tests if this jo9b is complete and cleans up if it is [bool]IsComplete() { if ($this.AsyncResult.AsyncWaitHandle.WaitOne(0)) { # Clean up try { # This throws if there was an unhandled exception in the thread's runspace. $this.Job.EndInvoke($this.AsyncResult) } finally { $this.AsyncResult.AsyncWaitHandle.Close() $this.Job.Dispose() } return $true } return $false } # Garbage collect completed jobs static [void]GarbageCollect() { ( [RequestHandlerTask]::RunningTasks | Where-Object { $_.IsComplete() } ) | ForEach-Object { [RequestHandlerTask]::RunningTasks.Remove($_) } } } try { # Create the listener $listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Parse($ThreadArguments.BoundIp), $ThreadArguments.Port) $listener.Start() Write-OperatingSystemLogEntry -EventId ([EventId]::ServerStarted) -Message "Server up. Listening on port $($ThreadArguments.Port)" $running = $true do { # Wait for a connection $clientWaiter = $listener.AcceptTcpClientAsync() try { $clientWaiter.Wait($threadArguments.CancellationTokenSource.Token) # Connection accepted. Start a job to process it. [RequestHandlerTask]::new().Start($clientWaiter.Result, $threadArguments) # Clean up any jobs that have finished. [RequestHandlerTask]::GarbageCollect() } catch [OperationCanceledException] { # User requested server halt, i.e. TerminateServerException was thrown to cancel the cancellation token. $running = $false } catch { # If anything else caught here, something's badly wrong. # It will usually mean that an unhandled exception propagated out of a request han dling thread and was thrown at EndInvoke() # All exceptions in user code should have already been handled. # Cancel the cancellation token so other threads waiting on it also exit $threadArguments.CancellationTokenSource.Cancel() Write-OperatingSystemLogEntry -EventId ([EventId]::Exception) -Message "FATAL:`n$($_.Exception.Message)`n$($_.ScriptStacktrace)" $running = $false } } while ($running) } catch { # Something went wrong prior to or during opening the socket # Cancel the cancellation token so other threads waiting on it also exit $threadArguments.CancellationTokenSource.Cancel() Write-OperatingSystemLogEntry -EventId ([EventId]::Exception) -Message "FATAL:`n$($_.Exception.Message)`n$($_.ScriptStacktrace)" } finally { if ($listener) { # Close the listener socket $listener.Stop() } } } # Create listener thread Write-OperatingSystemLogEntry -EventId ([EventId]::InitializationStep) -Message "Creating listener thread." $powershell = [powershell]::Create() # Add it's code from the scriptblock $powershell.AddScript($listenerTask.ToString()).AddArgument($threadArguments) | Out-Null # Return the thread $powershell } |