Имеется исходник драйвера минифильтра, который ведет лог разных IRP-операций с файлами.
Архитектура довольно прикольная — своей простотой...
В PRE_OPERATION_CALLBACK ничего нет.
Есть POST_OPERATION_CALLBACK — он из операций и всяких собранных данных (вроде PID процесса) формирует готовые строки и добавляет их в "очередь" на логирование.
И есть отдельный поток, запущен он таким образом:
PsCreateSystemThread(hThread, (ACCESS_MASK)0,
NULL,
(HANDLE) 0,
NULL,
loggerThread,
NULL);
В потоке каждые 10 минут проверяется, не наполнилась ли "очередь", и если да, то она грузится в лог (лог — это файл, но там еще всякая обработка буфера, который в этом файл запишется — есть сжатие и еще что-то — все прямо здесь, в драйвере) и опустошается.
А "очередь" представляет собой не что иное, как простой PWCHAR (даже не UNICODE_STRING). Прямо так и объявлено, в глобальной области:
PWCHAR szLogQueue;
Резюмируя: каллбек аппендит данные в глобальный PWCHAR, а совершенно не зависящий от него поток каждые свои 10 минут проверяет PWCHAR, открывает-пишет-закрывает файл лога и заново allocatит PWCHAR (в NonPagedPool).
И вроде бы все это работает, да только код течёт. За несколько часов забивает все ОЗУ. Написал ускоренный тест — все подтвердилось.
И мне, еще очень начинающему в NT, нужно исправить это — проставив ExFreePoolWithTag везде, где нужно.
Делать что-либо радикальное, типа вынести логирование в юзермод или хотя бы переписать на UNICODE_STRING, я пока не могу. Код лапша, да и я не потяну.
Казалось бы, все просто. В потоке перед alloc ставим ExFreePoolWithTag. А чтобы не было такого, когда поток уже очистил, но не выделил заново, а тут к указателю обращается каллбак — для этого используем мьютексы, то есть и обращения из потока, и обращения из каллбака заворачиваем в lockи.
Только вот беда — согласно MSDN, POST_OPERATION_CALLBACK не должен сам по себе создавать мьютексы. Причем про ждать не сказано, а вот создавать — да.
Правда, тот же MSDN разрешает делать это через FltDoCompletionProcessingWhenSafe и FltQueueDeferredIoWorkItem.
Но уже в этих функциях написано, что в случае IRP_MJ_WRITE их опять же применять нельзя — а этому драйверу нужна и эта операция в том числе — он пишет число записанных байт и др.
В итоге имеем, что никаких lockов сюда вообще нельзя. И как тогда поступить?
Здравствуйте, LimyKurn, Вы писали:
LK>Есть POST_OPERATION_CALLBACK — он из операций и всяких собранных данных (вроде PID процесса) формирует готовые строки и добавляет их в "очередь" на логирование. LK>... LK>В итоге имеем, что никаких lockов сюда вообще нельзя. И как тогда поступить?
POST_OPERATION_CALLBACK в общем случае может вызываться на DISPATCH_LEVEL. Поэтому никаких мьютексов и ожиданий,
здесь для синхронизации можно использовать только спинлок (потому что спинлок допустимо использовать на этом IRQL, а
другие объекты — нет). Очередь и память под ее объекты, соответственно, тоже должна использовать исключительно
NonPagedPool(Nx), иначе нарвемся на IRQL_NOT_LESS_OR_EQUAL. Здесь, кстати, нужна повышенная бдительность.
Например, имя файла, получаемое через FltGetFileNameInformation, выделяется из paged-пула, поэтому тупо взять его и
запихнуть в такую очередь нельзя, нужно сначала где-нибудь в безопасном месте, например, на pre-create или на
post-create (там всегда PASSIVE_LEVEL) сделать копию имени в nonpaged-памяти.
Использовать FltDoCompletionProcessingWhenSafe или FLT_PREOP_SYNCHRONIZE здесь тоже не стоит, это только ухудшит
производительность системы.
Re[2]: Как синхронизировать PFLT_POST_OPERATION_CALLBACK и п
Здравствуйте, okman, Вы писали:
O>Здравствуйте, LimyKurn, Вы писали:
LK>>Есть POST_OPERATION_CALLBACK — он из операций и всяких собранных данных (вроде PID процесса) формирует готовые строки и добавляет их в "очередь" на логирование. LK>>... LK>>В итоге имеем, что никаких lockов сюда вообще нельзя. И как тогда поступить?
O>POST_OPERATION_CALLBACK в общем случае может вызываться на DISPATCH_LEVEL. Поэтому никаких мьютексов и ожиданий, O>здесь для синхронизации можно использовать только спинлок (потому что спинлок допустимо использовать на этом IRQL, а O>другие объекты — нет). Очередь и память под ее объекты, соответственно, тоже должна использовать исключительно O>NonPagedPool(Nx), иначе нарвемся на IRQL_NOT_LESS_OR_EQUAL. Здесь, кстати, нужна повышенная бдительность. O>Например, имя файла, получаемое через FltGetFileNameInformation, выделяется из paged-пула, поэтому тупо взять его и O>запихнуть в такую очередь нельзя, нужно сначала где-нибудь в безопасном месте, например, на pre-create или на O>post-create (там всегда PASSIVE_LEVEL) сделать копию имени в nonpaged-памяти.
O>Использовать FltDoCompletionProcessingWhenSafe или FLT_PREOP_SYNCHRONIZE здесь тоже не стоит, это только ухудшит O>производительность системы.
А, кстати, что такое pre-create и post-create, у которых PASSIVE_LEVEL? FltGetFileNameInformation тут тоже используется. Проблем вроде BSODов не наблюдается никак. Однако меня больше интересует другое — раз там PASSIVE_LEVEL, то нельзя ли там синхронизировать? Но важно, чтобы решение поддерживало и IRP_MJ_WRITE.
> POST_OPERATION_CALLBACK в общем случае может вызываться на DISPATCH_LEVEL
А если просто отсечь случаи, когда он на нем вызывается? То можно ли будет использовать мьютексы и сможет ли система логировать все операции с файлами, в том числе IRP_MJ_WRITE и операции не только от usermode-приложений, но желательно и от других драйверов?
> здесь для синхронизации можно использовать только спинлок
Про них читал, что нельзя ждать дольше 24 микросекунд. Меня это привело в некоторое замешательство. А если процессор перегружен, и код, который укладывался в эти 24, больше не укладывается? Это требование связано только с быстродействием (в данном случае — чтобы в системе не тормозили операции создания файлов, записи в них и т.д.) или его нарушение может привести к ошибкам?
И что все-таки можно сделать? Я могу согласиться с тем, что решение делать такое жирное логирование в отдельном потоке драйвера — это плохое решение. Но само по себе использование отдельного потока, в который каждые N секунд\минут попадают данные из POST_OPERATION_CALLBACK, вроде бы не относится к такому — это может в некоторых случаях быть нужно. Поэтому хотя бы для общего развития хотелось бы найти решение.
(Ну а вообще я сам сторонник того, что у каждого драйвера, если это не драйвер устройства, должен быть свой сервис или приложение в usermode.)
Стоит ли заменить на UNICODE_STRING? В идеале, конечно, хотелось бы нормальный контейнер, как std::vector в C++. На C++ можно писать целый месяц и ни разу ничего не очищать, т.к. ничего и не выделяешь. Сюда бы тоже такое. Кстати, там еще и список один самодельный заменить надо, а то тоже течет. Или быстродействие и другие проблемы помешают реализовать подобное?
> что такое pre-create и post-create, у которых PASSIVE_LEVEL?
pre- и post-калбэки для IRP_MJ_CREATE. Они всегда вызываются на PASSIVE_LEVEL (это есть в документации).
> А если просто отсечь случаи, когда он на нем [DISPATCH_LEVEL] вызывается? То можно ли будет использовать > мьютексы и сможет ли система логировать все операции с файлами, в том числе IRP_MJ_WRITE и операции > не только от usermode-приложений, но желательно и от других драйверов?
Отсечь — т.е. так вот:
if (KeGetCurrentIrql() < DISPATCH_LEVEL)
{
addLogRecord(...);
}
Ну тогда надо быть готовым к тому, что какие-то операции в лог не попадут.
Вряд ли это то, что требуется. А так да, на IRQL < DISPATCH_LEVEL можно использовать
любые стандартные объекты синхронизации — fast_mutex, pushlock, eresource, etc.
> Про них [spinlocks] читал, что нельзя ждать дольше 24 микросекунд. Меня это привело в некоторое > замешательство. А если процессор перегружен, и код, который укладывался в эти 24, больше не укладывается?
Захват спинлока — это, фактически, эксклюзивный захват процессора/ядра.
Пока захвачен спинлок, процессор выполняет код только одного потока, остальные потоки,
запланированные для выполнения на этом процессоре, выполняться не будут.
Потому наилучший сценарий использования спинлока — быстро захватить блокировку, выполнить
несколько коротких операций (в идеале — максимум несколько процессорных инструкций,
например, добавить элемент в linked list) и выйти.
> Это требование связано только с быстродействием (в данном случае — чтобы в системе не тормозили > операции создания файлов, записи в них и т.д.) или его нарушение может привести к ошибкам?
Насколько я знаю, это может привести к BSOD DPC_WATCHDOG_VIOLATION, если спинлок захвачен слишком долго.
> И что все-таки можно сделать? Я могу согласиться с тем, что решение делать такое жирное > логирование в отдельном потоке драйвера — это плохое решение.
Я не вижу здесь ничего ужасного. Минифильтр перехватывает некоторый файловый I/O и добавляет
информацию в очередь. Отдельный поток разбирает очередь и пишет, не торопять, на диск.
Вполне избитая схема. Максимум, над чем тут стоит подумать — это чтобы не выделять слишком
много nonpaged-памяти, так как это дорогой (и не масштабируемый) ресурс.
> Стоит ли заменить на UNICODE_STRING? В идеале, конечно, хотелось бы нормальный контейнер, > как std::vector в C++. На C++ можно писать целый месяц и ни разу ничего не очищать, т.к. ничего и > не выделяешь. Сюда бы тоже такое. Кстати, там еще и список один самодельный заменить надо, а > то тоже течет. Или быстродействие и другие проблемы помешают реализовать подобное?
IMHO, сначала нужно добиться нормальной ровной работы драйвера, чтобы не текла память, чтобы он
не ронял систему в BSOD и работал штатно, без отклонений, а уже потом "вылизывать" и оптимизировать.
Еще советую сразу подключать Driver Verifier, так сразу наиболее грубые ошибки можно будет выловить
(например, обращение к paged-памяти на dispatch level).
Re: Как синхронизировать PFLT_POST_OPERATION_CALLBACK и поток -
Здравствуйте, LimyKurn, Вы писали:
LK>А "очередь" представляет собой не что иное, как простой PWCHAR (даже не UNICODE_STRING). Прямо так и объявлено, в глобальной области: LK>PWCHAR szLogQueue;
..
LK>Только вот беда — согласно MSDN, POST_OPERATION_CALLBACK не должен сам по себе создавать мьютексы. Причем про ждать не сказано, а вот создавать — да. LK>Правда, тот же MSDN разрешает делать это через FltDoCompletionProcessingWhenSafe и FltQueueDeferredIoWorkItem. LK>Но уже в этих функциях написано, что в случае IRP_MJ_WRITE их опять же применять нельзя — а этому драйверу нужна и эта операция в том числе — он пишет число записанных байт и др. LK>В итоге имеем, что никаких lockов сюда вообще нельзя. И как тогда поступить?
очередь — глобальный указатель, ок. Не надо никаких мьютексов, надо в потоке делать InterlockedExchange этого указателя, подсовывая ему новый пустой буфер. А тот, что получили после интерлока спокойно писать на диск и освобождать. все.
Re[4]: Как синхронизировать PFLT_POST_OPERATION_CALLBACK и п
Здравствуйте, okman, Вы писали:
>> что такое pre-create и post-create, у которых PASSIVE_LEVEL?
O>pre- и post-калбэки для IRP_MJ_CREATE. Они всегда вызываются на PASSIVE_LEVEL (это есть в документации).
>> А если просто отсечь случаи, когда он на нем [DISPATCH_LEVEL] вызывается? То можно ли будет использовать >> мьютексы и сможет ли система логировать все операции с файлами, в том числе IRP_MJ_WRITE и операции >> не только от usermode-приложений, но желательно и от других драйверов?
O>Отсечь — т.е. так вот: O>
O>Ну тогда надо быть готовым к тому, что какие-то операции в лог не попадут. O>Вряд ли это то, что требуется. А так да, на IRQL < DISPATCH_LEVEL можно использовать O>любые стандартные объекты синхронизации — fast_mutex, pushlock, eresource, etc.
>> Про них [spinlocks] читал, что нельзя ждать дольше 24 микросекунд. Меня это привело в некоторое >> замешательство. А если процессор перегружен, и код, который укладывался в эти 24, больше не укладывается?
O>Захват спинлока — это, фактически, эксклюзивный захват процессора/ядра. O>Пока захвачен спинлок, процессор выполняет код только одного потока, остальные потоки, O>запланированные для выполнения на этом процессоре, выполняться не будут. O>Потому наилучший сценарий использования спинлока — быстро захватить блокировку, выполнить O>несколько коротких операций (в идеале — максимум несколько процессорных инструкций, O>например, добавить элемент в linked list) и выйти.
>> Это требование связано только с быстродействием (в данном случае — чтобы в системе не тормозили >> операции создания файлов, записи в них и т.д.) или его нарушение может привести к ошибкам?
O>Насколько я знаю, это может привести к BSOD DPC_WATCHDOG_VIOLATION, если спинлок захвачен слишком долго.
>> И что все-таки можно сделать? Я могу согласиться с тем, что решение делать такое жирное >> логирование в отдельном потоке драйвера — это плохое решение.
O>Я не вижу здесь ничего ужасного. Минифильтр перехватывает некоторый файловый I/O и добавляет O>информацию в очередь. Отдельный поток разбирает очередь и пишет, не торопять, на диск. O>Вполне избитая схема. Максимум, над чем тут стоит подумать — это чтобы не выделять слишком O>много nonpaged-памяти, так как это дорогой (и не масштабируемый) ресурс.
>> Стоит ли заменить на UNICODE_STRING? В идеале, конечно, хотелось бы нормальный контейнер, >> как std::vector в C++. На C++ можно писать целый месяц и ни разу ничего не очищать, т.к. ничего и >> не выделяешь. Сюда бы тоже такое. Кстати, там еще и список один самодельный заменить надо, а >> то тоже течет. Или быстродействие и другие проблемы помешают реализовать подобное?
O>IMHO, сначала нужно добиться нормальной ровной работы драйвера, чтобы не текла память, чтобы он O>не ронял систему в BSOD и работал штатно, без отклонений, а уже потом "вылизывать" и оптимизировать. O>Еще советую сразу подключать Driver Verifier, так сразу наиболее грубые ошибки можно будет выловить O>(например, обращение к paged-памяти на dispatch level).
Так UNICODE_STRING я не просто так предлагаю, а для решения то самой проблемы.
В BSOD он вроде не роняет, но это пока. Однако нету очистки памяти. Поток после того как все запишет — просто заново выделяет этот глобальный PWCHAR через ExAllocatePoolWithTag.
Я пытаюсь это исправить, вызвав перед этим ExFreePoolWithTag. (есть и другие места с утечками, но это самое большое по размеру буфера, да и фиксить по-любому надо все места) Но это вот приводит к BSOD. Я полагаю, что именно из-за рассинхронизации: поток уже сделал ExFreePoolWithTag, но еще не сделал ExAllocatePoolWithTag, а в это время фильтр успевает обратиться к PWCHAR.
Вот другой чел советует InterlockedExchange.
Насчет UNICODE_STRING — я ее рассматривал по той причине, что она не хранится как указатель (указатель внутри нее, а не она сама).
Рассмотрим код: 2 одинаковых потока, один в бесконечном цикле создает глобально UNICODE_STRING (в т.ч. выделяя ее Buffer), другой в бесконечном цикле освобождает этот буфер. Синхронизации никакой нет. Однако BSOD не происходит
Теперь вместо UNICODE_STRING используем PWCHAR и переписываем код аналогично. BSOD — сразу.
Ну, а "что-то типа std::vector" вообще позволило бы не чистить память, т.к. оно само ее выделяет и чистит внутри себя, когда надо.
Про verifier — спасибо, гляну. А что именно он дает, в 2 словах?
Здравствуйте, LimyKurn, Вы писали:
LK>Так UNICODE_STRING я не просто так предлагаю, а для решения то самой проблемы.
UNICODE_STRING, vector или просто wchar_t — это не важно. Пока у тебя не будет синхронизации доступа к
этой переменной, работать твой драйвер корректно не будет.
LK>Про verifier — спасибо, гляну. А что именно он дает, в 2 словах?
Verifier позволяет быстрее обнаруживать различные скрытые ошибки, которые в нормальных условиях обычно
не проявляются сразу или проявляются спустя долгое время, когда программа уже в релизе.
Ну например, ты обращаешься к paged-памяти на DISPATCH_LEVEL. В нормальных условиях это иногда
может работать месяцами и годами, не выпадая в BSOD, а затем вдруг начинает падать (как правило, в
самый неожиданный и неудачный момент).
Когда ты включаешь соответствующую проверку в Verifier, он принудительно сбрасывает paged-память на
диск при повышении IRQL, так что данная ошибка всплывает практически сразу.
Это лишь один из примеров.
Re[2]: Как синхронизировать PFLT_POST_OPERATION_CALLBACK и поток -
Здравствуйте, mike_rs, Вы писали:
_>очередь — глобальный указатель, ок. Не надо никаких мьютексов, надо в потоке делать InterlockedExchange этого указателя, подсовывая ему новый пустой буфер. А тот, что получили после интерлока спокойно писать на диск и освобождать. все.
А добавлять данные в такую очередь как? Можно пример?
(учитываем, что minifilter callbacks могут вызываться одновременно в разных потоках и,
соответственно, конкурировать за доступ к очереди).
Re[6]: Как синхронизировать PFLT_POST_OPERATION_CALLBACK и п
Здравствуйте, okman, Вы писали:
O>Здравствуйте, LimyKurn, Вы писали:
LK>>Так UNICODE_STRING я не просто так предлагаю, а для решения то самой проблемы.
O>UNICODE_STRING, vector или просто wchar_t — это не важно. Пока у тебя не будет синхронизации доступа к O>этой переменной, работать твой драйвер корректно не будет.
LK>>Про verifier — спасибо, гляну. А что именно он дает, в 2 словах?
O>Verifier позволяет быстрее обнаруживать различные скрытые ошибки, которые в нормальных условиях обычно O>не проявляются сразу или проявляются спустя долгое время, когда программа уже в релизе.
O>Ну например, ты обращаешься к paged-памяти на DISPATCH_LEVEL. В нормальных условиях это иногда O>может работать месяцами и годами, не выпадая в BSOD, а затем вдруг начинает падать (как правило, в O>самый неожиданный и неудачный момент).
O>Когда ты включаешь соответствующую проверку в Verifier, он принудительно сбрасывает paged-память на O>диск при повышении IRQL, так что данная ошибка всплывает практически сразу.
O>Это лишь один из примеров.
Спасибо за verifier, а вот по синхронизации надо либо что-то делать, либо все-таки счесть данную архитектуру чем-то ужасным (пусть она и избитая в юзермоде, но здесь не та обстановка. Но правда если переделывать архитектуру, то мы получим гемор — делать службу не вариант, т.к. ее нет, совсем — гемор по ее созданию слишком большой. А если перенести логирование в минифильтры, то это гемор поменьше, но придется его оптимизировать по быстродействию, что ли, к тому же логирование создает файл — от рекурсии защищаться придется). Но и спинлоки как вариант "что-то делать" — не нравятся.
Здравствуйте, LimyKurn, Вы писали:
LK>Спасибо за verifier, а вот по синхронизации надо либо что-то делать, либо все-таки счесть данную архитектуру чем-то ужасным
Если задача заключается в том, чтобы собирать какие-то данные на post operation callback и записывать в файл,
то вряд ли есть что-то лучше, чем отдельный поток, который это делает. Не вижу резона гонять эти данные в user mode и
писать в файл оттуда, это никаких выгод не даст, получится только лишнее усложнение на ровном месте.
Очередь можно сделать на базе linked list и засинхронизировать доступ к ней, как я уже писал, через спинлоки.
Рабочий поток будет ждать (KeWaitForXxx) на событии и при его сигналинге выгребать данные из очереди и сбрасывать их на диск.
То же самое делать можно и по таймауту (например, когда KeWaitForXxx вернула STATUS_TIMEOUT).
LK>А если перенести логирование в минифильтры, то это гемор поменьше, но придется его оптимизировать по быстродействию, что ли, к тому же логирование создает файл — от рекурсии защищаться придется).
Там рекурсии неоткуда возникнуть.
LK>Но и спинлоки как вариант "что-то делать" — не нравятся.
Почему?
Re[8]: Как синхронизировать PFLT_POST_OPERATION_CALLBACK и п
Здравствуйте, okman, Вы писали:
O>Здравствуйте, LimyKurn, Вы писали:
LK>>Спасибо за verifier, а вот по синхронизации надо либо что-то делать, либо все-таки счесть данную архитектуру чем-то ужасным
O>Если задача заключается в том, чтобы собирать какие-то данные на post operation callback и записывать в файл, O>то вряд ли есть что-то лучше, чем отдельный поток, который это делает. Не вижу резона гонять эти данные в user mode и O>писать в файл оттуда, это никаких выгод не даст, получится только лишнее усложнение на ровном месте.
O>Очередь можно сделать на базе linked list и засинхронизировать доступ к ней, как я уже писал, через спинлоки. O>Рабочий поток будет ждать (KeWaitForXxx) на событии и при его сигналинге выгребать данные из очереди и сбрасывать их на диск. O>То же самое делать можно и по таймауту (например, когда KeWaitForXxx вернула STATUS_TIMEOUT).
LK>>А если перенести логирование в минифильтры, то это гемор поменьше, но придется его оптимизировать по быстродействию, что ли, к тому же логирование создает файл — от рекурсии защищаться придется).
O>Там рекурсии неоткуда возникнуть.
LK>>Но и спинлоки как вариант "что-то делать" — не нравятся.
O>Почему?
Ты только сейчас появился, а у меня дедлайн послезавтра) Хотя посплю всего 2 часа, но все равно похоже придется временно оставить вопрос с этой проблемой и заняться другими моментами драйвера собственно которые меня просили сделать. Ну или применить InterlockedExchange (хочу применить в рабочем потоке призамене указателя, а в каллбаках просто ничего не менять — там же нет ни очистки, ни выделения памяти. Какие проблемы?)
А спинлоки не нравятся тем, что в 24 мксек можно не уложиться из-за тормозов процессора тех же — и будет БСОД.
Здравствуйте, LimyKurn, Вы писали:
LK>А спинлоки не нравятся тем, что в 24 мксек можно не уложиться из-за тормозов процессора тех же — и будет БСОД.
Если под спинлоком выполнять только простейшие операции, например вставку или удаление элемента из списка,
то про ограничение в 25 мкс можно смело забыть. Инфа 100%.
Проблемы возникают тогда, когда захватив спинлок, начинают вызывать другие функции в большом количестве.
Но и даже в таком варианте надо очень постараться, чтобы выйти за лимит.
Re[10]: Как синхронизировать PFLT_POST_OPERATION_CALLBACK и п
Здравствуйте, okman, Вы писали:
O>Здравствуйте, LimyKurn, Вы писали:
LK>>А спинлоки не нравятся тем, что в 24 мксек можно не уложиться из-за тормозов процессора тех же — и будет БСОД.
O>Если под спинлоком выполнять только простейшие операции, например вставку или удаление элемента из списка, O>то про ограничение в 25 мкс можно смело забыть. Инфа 100%.
O>Проблемы возникают тогда, когда захватив спинлок, начинают вызывать другие функции в большом количестве. O>Но и даже в таком варианте надо очень постараться, чтобы выйти за лимит.
Спасибо. Но и сам понимаешь — решение довольно сложное со спинлоком. Учитывая, что я вообще впервые с ним знакомлюсь.
Re[3]: Как синхронизировать PFLT_POST_OPERATION_CALLBACK и поток -
O>А добавлять данные в такую очередь как? Можно пример? O>(учитываем, что minifilter callbacks могут вызываться одновременно в разных потоках и, O>соответственно, конкурировать за доступ к очереди).
Здравствуйте, okman, Вы писали:
O>Я не вижу здесь ничего ужасного. Минифильтр перехватывает некоторый файловый I/O и добавляет O>информацию в очередь. Отдельный поток разбирает очередь и пишет, не торопять, на диск. O>Вполне избитая схема. Максимум, над чем тут стоит подумать — это чтобы не выделять слишком O>много nonpaged-памяти, так как это дорогой (и не масштабируемый) ресурс.
Есть еще одно решение, которое никто не предложил.
В каллбеке создавать каждый раз новый поток и передавать все параметры. А в нем уже делать что хочешь. Если нет цели прерывать операцию (но разве POST-каллбеки умеют прерывать?), то в целом сойдет, хотя громоздко и непонятно зачем нужно, когда таки есть нормальное решение.