Клиент сообщил, что их программа рано или поздно полностью встаёт, а все её потоки (750 штук) зависают при вызове
WinHttpGetProxyForUrl
со следующим стеком вызова:ntdll!ZwWaitForSingleObject+0x14 KERNELBASE!WaitForSingleObjectEx+0x8f winhttp!OutProcGetProxyForUrl+0x160 winhttp!WinHttpGetProxyForUrl+0x349 contoso!submit_web_request+0x232 ntdll!TppWorkpExecuteCallback+0x35e ntdll!TppWorkerThread+0x474 kernel32!BaseThreadInitThunk+0x14 ntdll!RtlUserThreadStart+0x21(я упростил стек вызова для простоты объяснения)
В программе происходит следующее: вы помещаете некоторое задание (work item) в пул потоков, и это задание вызывает
WinHttpGetProxyForUrl
. Эта функция является синхронной, но ей нужно делать сетевые запросы HTTP - которые являются асинхронными. Чтобы устранить это противоречие, функция WinHttpGetProxyForUrl
выполняет синхронное ожидание завершения асинхронной работы.И я предполагаю, что для этого функция
WinHttpGetProxyForUrl
использует пул потоков.Происходит следующее: программа заполняет пул потоков заданиями
submit_web_request
. Эти задания вызывают функцию WinHttpGetProxyForUrl
, которая добавляет в очередь свои собственные задания и ожидает их завершения. Но эти задания не могут быть запущены, потому что все потоки пула потоков заняты обработкой заданий submit_web_request
.Рано или поздно пул потоков может понять, что он ничего не делает, и запустит новый поток, чтобы справиться с накопившимися заданиями. Возможно, этот поток завершит задание для
WinHttpGetProxyForUrl
- что позволит продолжить один из потоков для submit_web_request
. Как только этот новый поток пула потоков завершает задание от WinHttpGetProxyForUrl
, он извлекает следующее задание из очереди - и есть вероятность, что он получит очередное задание submit_web_request
, так что теперь мы вернулись к тому, с чего начали, за исключением того, что мы только что создали ещё один застрявший поток в пуле потоков.Если задания
submit_web_request
приходят быстрее, чем WinHttpGetProxyForUrl
может обработать свои собственные задания, пул потоков начнёт заполнятся потоками, заблокированными в submit_web_request
. В итоге пул потоков достигнет своего предела потоков, и всё остановится.По сути, вы перегружаете пул потоков, заполняя его запросами, которые сами требуют пул потоков. Все потоки пула потоков зависают на обработку ваших запросов, и ни один из них не выполняет задания, сгенерированные вашими запросами.
Это как если бы у вас было много тяжёлого оборудования, которое вам нужно перевезти, поэтому вы нанимаете для перевозки все транспортные компании в городе. Появляется компания А, и они говорят: "Хм, это слишком тяжёлое, чтобы перевезти нашими силами. Давайте мы свяжемся с компаний Б, может быть, они нам помогут". Компания Б говорит: "Извините, мы не можем сейчас вам помочь. Мы только что получили крупный заказ на перевозку". Делая заказ во всех доступных транспортных компаниях, вам удаётся помешать любой из них выполнить работу.
Я подозреваю, что эта программа работает в сетевой среде, где WPAD работает медленно. Т.е. задания от
WinHttpGetProxyForUrl
будут выполнять свою работу дольше, что повышает вероятность того, что задания submit_web_request
будут добавляться быстрее, чем завершаться задания от WinHttpGetProxyForUrl
.Теперь, когда мы диагностировали проблему: что мы можем сделать, чтобы решить её?
Одна из идей состоит в том, чтобы нанять только одну транспортную компанию и позволить ей решать, сколько ещё компаний им нужно. Поместите все свои вызовы
submit_web_request
в один поток и обрабатывайте их по одному. Это займёт только один поток в пуле, оставляя другие потоки доступными. С другой стороны, это означает, что запросы не могут обрабатываться параллельно.Лучшим решением будет изменить способ использования пула потоков, чтобы вы не держали его в заложниках надолго.
Я не эксперт по WinHttp, но у других людей были некоторые идеи, как это сделать.
Вы можете переключиться на функцию
WinHttpGetProxyForUrlEx
, которая возвращает управление немедленно и вызывает вашу функцию обратного вызова, когда у неё появляется ответ. Тогда функция submit_web_request
может вызвать WinHttpGetProxyForUrlEx
и тоже немедленно выйти. Это освободит поток пула потоков для выполнения другой работы - возможно, даже того самого задания, которое функция WinHttpGetProxyForUrlEx
должна выполнить для своего завершения. Когда WinHttpGetProxyForUrlEx
выполнит свою асинхронную работу, она вызовет функцию обратного вызова, которая доделает любую работу, которую изначально планировала выполнить submit_web_request
после получения информации о прокси.Короче говоря, нужно идти асинхронно до конца. Это не будет таким уж необоснованным подходом для этой программы, так как сама
submit_web_request
моделирует асинхронный запрос: она инициирует запрос и вызывает функцию обратного вызова, предоставляемую вызывающим, передавая в неё ответ от сервера. Поскольку она уже ведёт себя асинхронно, вы можете просто сделать её ещё более асинхронной.Другим предложением было полностью убрать вызов
WinHttpGetProxyForUrl
и просто передать флаг WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY
в WinHttpOpen
. Это переложит работу по выяснению прокси на функцию WinHttpOpen
, а она сможет выполнить это как часть других своих асинхронных действий. Это кажется хорошей идеей, потому что при этом вы полностью отделяетесь от вопроса выяснения прокси, и вы по-прежнему получаете асинхронное поведение. Ну и вы также получаете удовольствие от исправления ошибки путём удаления кода.Клиент подтвердил, что переключение на флаг
WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY
устранило проблему.
Комментариев нет:
Отправить комментарий
Можно использовать некоторые HTML-теги, например:
<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>
Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку.
Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.
Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.
Примечание. Отправлять комментарии могут только участники этого блога.