Search code examples
powershelltcpclientcontrol-c

Powershell AcceptTcpClient() cannot be interrupted by Ctrl-C


I am writing a simple TCP/IP server using Powershell. I notice that Ctrl-C cannot interrupt the AcceptTcpClient() call. Ctrl-C works fine after the call though. I have searched around, nobody reported similar problem so far.

The problem can be repeated by the following simple code. I am using Windows 10, latest patch, with the native Powershell terminal, not Powershell ISE.

$listener=new-object System.Net.Sockets.TcpListener([system.net.ipaddress]::any, 4444)
$listener.start()
write-host "listener started at port 4444"
$tcpConnection = $listener.AcceptTcpClient()
write-host "accepted a client"

This is what happens when I run it

ps1> .\test_ctrl_c.ps1
listener started at port 4444
(Ctrl-C doesn't work here)

Solution

  • (As of PowerShell 7.0) Ctrl-C only works while PowerShell code is executing, not during execution of a .NET method.

    Since most .NET method calls execute quickly, the problem doesn't usually surface.

    See this GitHub issue for a discussion and background information.


    As for possible workarounds:

    • The best approach - if possible - is the one shown in your own answer:

      • Run in a loop that periodically polls for a condition, sleeping between tries, and only invoke the method when the condition being met implies that the method will then execute quickly instead of blocking indefinitely.
    • If this is not an option (if there is no such condition you can test for), you can run the blocking method in a background job, so that it runs in a child process that can be terminated on demand by the caller; do note the limitations of this approach, however:

      • Background jobs are slow and resource-intensive, due to needing to run a new PowerShell instance in a hidden child process.

      • Since cross-process marshaling of inputs to and outputs from the job is necessary:

        • Inputs and output won't be live objects.
        • Complex objects (objects other than instances of primitive .NET types and a few well-known types) will be emulations of the original objects; in essence, objects with static copies of the property values, and no methods - see this answer for background information.

    Here's a simple demonstration:

    # Start the long-running, blocking operation in a background job (child process).
    $jb = Start-Job -ErrorAction Stop {
      # Simulate a long-running, blocking .NET method call.
      [Threading.Thread]::Sleep(5000)
      'Done.'
    }
    
    $completed = $false
    try {
    
      Write-Host -ForegroundColor Yellow "Waiting for background job to finish. Press Ctrl-C to abort."
    
      # Note: The output collected won't be *live* objects, and with complex
      #       objects will be *emulations* of the original objects that have
      #       static copies of their property values and no methods.
      $output = Receive-Job -Wait -Job $jb
    
      $completed = $true
    
    }
    finally { # This block is called even when Ctrl-C has been pressed.
    
      if (-not $completed) { Write-Warning 'Aborting due to Ctrl-C.' }
    
      # Remove the background job.
      #  * If it is still running and we got here due to Ctrl-C, -Force is needed
      #    to forcefully terminate it.
      #  * Otherwise, normal job cleanup is performed.
      Remove-Job -Force $jb
    
      # If we got here due to Ctrl-C, execution stops here.
    }
    
    # Getting here means: Ctrl-C was *not* pressed.
    
    # Show the output received from the job.
    Write-Host -ForegroundColor Yellow "Job output received:"
    $output
    
    • If you execute the above script and do not press Ctrl-C, you'll see:

    no Ctrl-C

    • If you do press Ctrl-C, you'll see:

    Ctrl-C