Accurate timeout for threads in runspace pool

Just a POC right now, but it seems to work:

function Invoke-ScriptAsync {
    [cmdletbinding()]
    param(
        [Parameter(Mandatory=$true,position=0)]
        [ScriptBlock] $ScriptBlock,

        [Parameter(Mandatory=$true)]
        [array]$ArgumentList,

        [Parameter()]
        [int]$MaxThreads = 5,

        [Parameter()]
        [int]$Timeout = 15,

        [Parameter()]
        [int]$RefreshInterval = 1
  )

$MaxRunTime = New-TimeSpan -Seconds $Timeout

$RunspacePool = [RunspaceFactory ]::CreateRunspacePool(1, $MaxThreads)
$RunspacePool.Open()

$LogStart =
@'
   &{ 
      $DebugPreference = 'Continue'
      Write-Debug "Start(Ticks) = $((get-date).Ticks)"
    }
'@

$LogEnd =
@'
   &{
      $DebugPreference = 'Continue' 
      Write-Debug "End(Ticks) = $((get-date).Ticks)"
    }
'@

$JobScript = 
[Scriptblock]::Create($LogStart + $Scriptblock + $LogEnd)

$SaveJobData = 
          {
            #Save Pipeline Streams and information

             $Job.HadErrors = $Job.Pipe.HadErrors

             $Job.Error = $Job.Pipe.Streams.Error |
                  foreach { $_.Exception.Message }

            $Job.Warning = $Job.Pipe.Streams.Warning.Readall() | Out-String
            
            $Debug = $Job.Pipe.Streams.Debug
            if ($Debug.count -gt 2)
              { $Job.Debug = $Debug[1..($Debug.count - 2)] | Out-String }

            $Job.Verbose = $Job.Pipe.Streams.Verbose.Readall() | Out-String

            $Job.Duration = '{0:f2} ms' -f ($Job.Ended - $Job.Started).totalmilliseconds

               
          } #End Save Job data

$Sequence = 1 
$Jobs = @()


Foreach ($Argument in $ArgumentList)
 { 
  $Job = [powershell ]::Create().
                        AddScript($JobScript).
                        AddArgument($argument)

  $Job.RunspacePool = $RunspacePool

  $Jobs += New-Object PSObject -Property @{
                                           Pipe        = $Job
                                           Result      = $Job.BeginInvoke()
                                           Sequence    = $Sequence++
                                           Args        = $argument 
                                           Started     = $null
                                           Ended       = $null
                                           Duration    = $null
                                           Finalized   = $False
                                           State       = $null
                                           HadErrors   = $null
                                           Verbose     = $null
                                           Warning     = $null
                                           Error       = $null
                                           Debug       = $null
                                          }                                          

 }


While ($Jobs | Where { $_.Finalized -eq $false })
  {
    foreach ($Job in ($Jobs |  Where { $_.Finalized -eq $False }))
      {
        if (
             ($Job.started -eq $null) -and
             ($job.pipe.Streams.Debug[0].Message -match 'Start')
            )
             {
               $StartTicks = $Job.pipe.Streams.Debug[0].Message -replace '[^0-9]'
               $Job.Started = [Datetime]::MinValue + [TimeSpan]::FromTicks($StartTicks)
             }

        if ($Job.Result.IsCompleted)
          {
            $EndTicks = $Job.pipe.Streams.Debug[-1].Message -replace '[^0-9]'
            $Job.Ended = [Datetime]::MinValue + [TimeSpan]::FromTicks($EndTicks)
            $Job.State = $Job.pipe.InvocationStateInfo.State
            $Job.Finalized = $true
            $Job.HadErrors = $Job.Instance.HadErrors
            .$SaveJobData
            $Job.Pipe.EndInvoke($Job.Result)
            $Job.Pipe.Dispose()
          } 

        if ( ($Job.Started) -and 
             (get-date) -gt ($Job.Started + $MaxRunTime))
          {
            Write-Warning "Job $($job.Pipe.InstanceId) using argument $($job.args) time out."
            $Job.Ended = (Get-Date)
            $Job.Pipe.Stop()
            $Job.State = 'Timeout'
            $Job.Finalized = $True
            .$SaveJobData
            $Job.Pipe.Dispose()
           }

        }

          Start-Sleep -Seconds $RefreshInterval
 }
   

  $RunSpacePool.Close()
  $RunSpacePool.Dispose()

  $Jobs | Select Sequence,Args,Started,Ended,Duration,State,HadErrors,Error,Warning,Debug,Verbose

  } # End function


  #Test Script 

  $ScriptBlock = 
  {
   $VerbosePreference = 'Continue'
   Write-Verbose "Processing $($args[0])"

   If ($args[0] -eq 13){ 
                        Write-Error 'Triskaidekaphobia exception' 
                        Write-Output 'Shaka, when the walls fell.'
                        Return
                       }

   If ($args[0] -eq 1) { Write-Warning 'Warning - one is the loneliest number.' }

   $DebugPreference = 'Continue'
   Write-Debug 'Debug Message'

   Write-Verbose "Sleeping for $($args[0]) seconds at $(Get-Date)"
   Start-Sleep -Seconds $args[0]
   Write-Output "I'm awake! I'm awake! The answer is $($args[0])!"
  }

Invoke-ScriptAsync -ScriptBlock $ScriptBlock -ArgumentList (1..15 | get-random -Count 15) -Timeout 10

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s