Go + Windows = deadlock. Свет в конце тоннеля.
В прошлой статье я рассказывал о редком, но весьма опасном баге: поток под Windows зависал в вызове CancelIoEx
, хотя документация Microsoft утверждает обратное. Суть проблемы — в пересечении синхронного и асинхронного ввода-вывода, где ядро Windows блокирует доставку APC, и поток остаётся навсегда «висящим».
История получила развитие не сама по себе: мы целенаправленно поднимали эту тему через support-кейс в Microsoft. В результате удалось подключить и Escalation Team, и разработчиков Go, ответственных за Windows-порт.
Финальный вывод: стандартная библиотека Go действительно использует неправильный API для отмены синхронных операций. Вместо CancelSynchronousIo
, рекомендованного самой Microsoft, в коде до сих пор вызывается CancelIoEx
.
👀 Сам проблемный вызов:
https://github.com/golang/go/blob/77f911e31c243a8302c086d64dbef340b0c999b8/src/internal/poll/fd_windows.go#L461
Хорошая новость: у команды уже есть рабочий proof-of-concept фикса:
https://go-review.googlesource.com/c/go/+/691395
Менее радостная часть: из-за сложности изменений и их влияния на рантайм правка запланирована только в Go 1.26 (февраль 2026). Бэкпорт в предыдущие версии практически исключён.
Что это значит для разработчиков
Если ваш сервис на Go под Windows внезапно «зависает» в
CancelIoEx
— это следствие бага в стандартной библиотеке, а не ваша ошибка.До релиза Go 1.26 остаются обходные варианты:
не вызывать
CancelIoEx
для синхронных дескрипторов,использовать
CancelSynchronousIo
, если есть возможность управлять потоками,минимизировать использование пайпов в критичных местах.
Итог
Редкий flaky-тест Go (TestPipeIOCloseRace
) оказался симптомом реальной и серьёзной проблемы. Благодаря эскалации через Microsoft Support и совместному разбору мы получили подтверждение, понятное объяснение и официальный фикс в планах.
⚡️ Если ваш Go-код на Windows зависает в CancelIoEx
, теперь вы знаете: проблема признана и исправление уже в пути.