Freigeben über


Why doesn't CTRL-C stop NET USE?

 John Vert’s been griping about this issue to me for literally 14 years now.

I do a NET USE * \\MYSERVER\SERVERSSHARE from the CMD.EXE prompt and the console hangs. No amount of hitting CTRL-C will get it back until that silly application decides to give up.

Why on earth is this? Why can’t I just control-C and have my application stop?

It turns out that this issue comes because of a bunch of different behaviors that combine to give a less than optimal user experience.

The first is how CTRL-C is implemented in console applications. When an application calls SetConsoleCtrlHandler, then the console subsystem remembers the callback address. When the user hits CTRL-C on the console, then the console subsystem creates a brand new thread in the user’s application, and calls into the user’s specified Ctrl-C handler. If there are multiple processes in the console window (which happens when CMD.EXE launches a process), then the console subsystem calls them in the order that they were registered, and doesn’t stop until one of the handlers returns TRUE (indicating that the handler’s dealt with the signal).

If an app doesn’t call SetConsoleCtrlHandler, then CTRL-C is redirected to a handler that calls ExitProcess.

Now CMD.EXE has a CTRL-C handler, but NET.EXE (the external command executed for NET USE) doesn’t. So the system calls ExitProcess on NET.EXE when you hit CTRL-C. So far so good.

But there’s a problem. You see, the main thread of NET.EXE is blocked calling WNetAddConnection2 API. That API in turn is blocked issuing a synchronous IOCTL into the network filesystem, and the IOCTL’s blocked waiting on DNS name resolution. And since ExitProcess guarantees that the process has cleaned up before it actually removes the process, it has to wait until that IOCTL completes.

That seems silly, why doesn’t NT have a mechanism to cancel this outstanding I/O? Well it does. That’s what the CancelIo API’s all about. It takes a file handle and cancels all outstanding I/O’s for that handle

But if you look at the documentation for CancelIo carefully, it clearly says that CancelIo only cancels I/O’s that were initiated on the thread that called CanceIo. And remember – the console subsystem created a brand new thread to execute the control C handler. There aren’t any I/O’s outstanding on that thread, all of the I/O’s in the application are outstanding on the main thread of the application.

And that thread’s blocked on a synchronous IOCTL call into the network filesystem. Which won’t complete until it’s done doing its work. And that might take a while.

The really horrible thing is that there isn’t any good solution to the problem. The I/O system has really good reasons for implementing I/O cancellation the way it does, they’re deeply embedded in the design of I/O completion. The WNetAddConnection2 API is a synchronous system API (for ease of use), so it issues synchronous I/O’s to its driver. And they can’t add a console control C handler into the WNetAddConnection2 handler because if they did, it would override the intentions of the application – what if the application had indicated to the system that it NEVER wanted to be terminated by CTRL-C? If WNetAddConnection2 somehow managed to cancel its I/O when the user hit CTRL-C, then it would cause the application to malfunction.

This problem could be handled by CMD.EXE, except CMD.EXE doesn’t get control when the user hits CTRL-C, since it’s not the foreground process (NET.EXE is).

So you wait. And wait. And every time John runs into me in the hall, he asks me when I’m going to fix CTRL-C.

Comments

  • Anonymous
    March 19, 2004
    The problem really is that WNetAddConnection2 is a synchronous call that cannot be canceled. Ctrl+C handling is just a side effect.
  • Anonymous
    March 19, 2004
    The comment has been removed
  • Anonymous
    June 07, 2004
    As a side note in the synchronous vs asynchronous API thing, I had to do some Mac programming for the first time in my life recently. I was shocked to discover that virtually all APIs that can block have asynchronous equivalents. This probably came from necessity in the cooperative multitasking days of Mac OS <10. I thought it was pretty cool though, I always thought UNIX and Windows needed just a litte more support for asynchronous operations.
  • Anonymous
    April 19, 2006
    Way back when, back in the very early days of this blog (actually it was the 3rd post to my blog), I...