вторник, 3 августа 2010 г.

Почему Ctrl+C не прекращает работу NET USE?

Это перевод Why doesn't CTRL-C stop NET USE? Автор: Ларри Остерман.

John Vert пристаёт ко мне с этой проблемой вот уже, буквально, 14 лет.

Когда я делаю NET USE * \\MYSERVER\SERVERSSHARE из CMD.EXE и консоль подвисает - я нажимаю Ctrl+C, но сколько бы раз я это ни делал, это не помогает: приложение отвисает только когда оно сдаётся само.

Ну почему это так? Почему я просто не могу нажать Ctrl+C для остановки приложения?

Оказывается, что этот вопрос возникает из-за кучи различных поведений, которые в совокупности дают менее оптимальное юзабилити.

Первое слагаемое - то, как Ctrl+C реализовано в консольных приложениях. Когда приложение вызывает SetConsoleCtrlHandler, то подсистема консоли запоминает адрес функции обратного вызова. Когда пользователь нажимает Ctrl+C, то подсистема консоли создаёт новый поток в приложении и вызывает ранее указанную функцию обратного вызова (обработчик Ctrl+C). Если в окне консоли работает несколько процессов (что и происходит, когда CMD.EXE запускает процесс), то подсистема консоли вызывает обработчики в том порядке, в котором они были зарегистрированы, не останавливаясь, пока один из обработчиков не вернёт True (указывая, что обработчик разберётся с нажатием Ctrl+C).

Если же приложение не вызывает SetConsoleCtrlHandler, то Ctrl+C обрабатывается обработчиком по-умолчанию, который просто вызывает ExitProcess.

Итак, в CMD.EXE есть обработчик Ctrl+C, но NET.EXE (внешняя команда, запускаемая по "NET USE") его не имеет. Поэтому система вызывает ExitProcess для NET.EXE, когда вы нажимаете Ctrl+C. Пока всё отлично.

Но тут есть проблема. Видите ли, главный поток NET.EXE блокирован вызовом WNetAddConnection2. Эта функция, в свою очередь, блокирована синхронным IOCTL-вызовом в сетевую файловую систему, которая блокирует IOCTL-запрос разрешением DNS имени. А поскольку ExitProcess гарантирует, что процесс не оставит после себя следов, то она вынуждена ждать, пока этот IOCTL не завершится.

Теперь это выглядит немного глупо: почему в NT нет механизма, чтобы отменить этот уже не нужный запрос? Ну, вообще-то он есть. Это именно то, что делает CancelIo. Она берёт файловый описатель и отменяет все текущие I/O-запросы для этого описателя.

Но если вы посмотрите описание CancelIo, то увидите, что CancelIo отменяет только запросы, которые были сделаны тем же потоком, что вызывает CanceIo. Но вспомните, что для выполнения обработчика Ctrl+C консольная подсистема создаёт новый поток. У этого потока нет никаких I/O запросов. Запросы делал главный поток приложения.

Но этот поток блокирован синхронным вызовом IOCTL и не может вызвать CancelIo. А запрос не завершится, пока он не сделает всю свою работу. Что может занять время.

Действительно ужасная вещь в этой истории - это то, что у вас нет хорошего решения этой проблемы. Система ввода-вывода имеет веские причины для такой реализации отмены - они глубоко внедрены в дизайн завершения ввода-вывода (I/O completion). А функция WNetAddConnection2 реализована синхронно (чтобы её было проще использовать), поэтому она генерирует синхронные запросы драйверу. И в неё нельзя добавить обработчик Ctrl+C: ведь это будет игнорирование желаний приложения: что если оно не хочет закрываться по Ctrl+C? Если бы WNetAddConnection2 как-то удавалось отменять запросы по нажатию Ctrl+C, то это могло бы привести к неверной работе приложения.

Эта проблема могла бы быть обработана CMD.EXE, но CMD.EXE не получает управления, когда пользователь нажимает Ctrl+C, потому что он не является активным процессом (активный процесс - это NET.EXE).

Поэтому вам приходится ждать. И ждать. И каждый раз, когда John встречает меня в коридоре, он спрашивает меня, когда же я исправлю этот Ctrl+C.

Прим.пер.: в Windows Vista появляется новая функция CancelIoEx и теперь Ctrl+C отменяет NET USE.

Комментариев нет:

Отправить комментарий

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.