diff --git a/README.md b/README.md index a6ba6fca..695a3f4f 100644 --- a/README.md +++ b/README.md @@ -38,12 +38,15 @@ Wine. All those differences are also documented on the Included bug fixes and improvements =================================== -**Bugfixes and features included in the next upcoming release [4]:** +**Bugfixes and features included in the next upcoming release [7]:** +* Add implementation for CreateThreadpool ([Wine Bug #35192](https://bugs.winehq.org/show_bug.cgi?id=35192)) * Call DriverUnload function when unloading a device driver. * Fix check for end_frame in RtlUnwindEx on x86_64. ([Wine Bug #34254](https://bugs.winehq.org/show_bug.cgi?id=34254)) * Fix mouse jittering in Planetside 2 ([Wine Bug #32913](https://bugs.winehq.org/show_bug.cgi?id=32913)) * Implement additional stubs for vcomp dlls ([Wine Bug #31640](https://bugs.winehq.org/show_bug.cgi?id=31640)) +* Implement threadpool timers ([Wine Bug #37306](https://bugs.winehq.org/show_bug.cgi?id=37306)) +* Implement threadpool work items ([Wine Bug #32531](https://bugs.winehq.org/show_bug.cgi?id=32531)) **Bugs fixed in Wine Staging 1.7.35 [146]:** diff --git a/debian/changelog b/debian/changelog index 92522647..8b91888f 100644 --- a/debian/changelog +++ b/debian/changelog @@ -3,6 +3,7 @@ wine-staging (1.7.36) UNRELEASED; urgency=low * Added patch to fix check for end_frame in RtlUnwindEx on x86_64. * Added patch to fix mouse jittering in Planetside 2. * Added patch to implement additional stubs for vcomp dlls. + * Added patchset to implement Vista+ threadpool functions for work / timers. -- Sebastian Lackner Sun, 25 Jan 2015 05:58:36 +0100 wine-staging (1.7.35) unstable; urgency=low diff --git a/patches/ntdll-Vista_Threadpool/0001-ntdll-Add-threadpool-stub-functions-to-specfile.patch b/patches/ntdll-Vista_Threadpool/0001-ntdll-Add-threadpool-stub-functions-to-specfile.patch new file mode 100644 index 00000000..ae598a85 --- /dev/null +++ b/patches/ntdll-Vista_Threadpool/0001-ntdll-Add-threadpool-stub-functions-to-specfile.patch @@ -0,0 +1,81 @@ +From 31df48db7d885ca5679ccc010d954d5df7fba288 Mon Sep 17 00:00:00 2001 +From: Sebastian Lackner +Date: Sat, 31 Jan 2015 02:26:17 +0100 +Subject: ntdll: Add threadpool stub functions to specfile. + +--- + dlls/ntdll/ntdll.spec | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 62 insertions(+) + +diff --git a/dlls/ntdll/ntdll.spec b/dlls/ntdll/ntdll.spec +index 51de6e7..771f669 100644 +--- a/dlls/ntdll/ntdll.spec ++++ b/dlls/ntdll/ntdll.spec +@@ -969,2 +969,64 @@ + @ stdcall RtlxUnicodeStringToOemSize(ptr) RtlUnicodeStringToOemSize ++# @ stub TpAllocAlpcCompletion ++# @ stub TpAllocAlpcCompletionEx ++# @ stub TpAllocCleanupGroup ++# @ stub TpAllocIoCompletion ++# @ stub TpAllocJobNotification ++# @ stub TpAllocPool ++# @ stub TpAllocTimer ++# @ stub TpAllocWait ++# @ stub TpAllocWork ++# @ stub TpAlpcRegisterCompletionList ++# @ stub TpAlpcUnregisterCompletionList ++# @ stub TpCallbackDetectedUnrecoverableError ++# @ stub TpCallbackIndependent ++# @ stub TpCallbackLeaveCriticalSectionOnCompletion ++# @ stub TpCallbackMayRunLong ++# @ stub TpCallbackReleaseMutexOnCompletion ++# @ stub TpCallbackReleaseSemaphoreOnCompletion ++# @ stub TpCallbackSendAlpcMessageOnCompletion ++# @ stub TpCallbackSendPendingAlpcMessage ++# @ stub TpCallbackSetEventOnCompletion ++# @ stub TpCallbackUnloadDllOnCompletion ++# @ stub TpCancelAsyncIoOperation ++# @ stub TpCaptureCaller ++# @ stub TpCheckTerminateWorker ++# @ stub TpDbgDumpHeapUsage ++# @ stub TpDbgSetLogRoutine ++# @ stub TpDisablePoolCallbackChecks ++# @ stub TpDisassociateCallback ++# @ stub TpIsTimerSet ++# @ stub TpPostWork ++# @ stub TpQueryPoolStackInformation ++# @ stub TpReleaseAlpcCompletion ++# @ stub TpReleaseCleanupGroup ++# @ stub TpReleaseCleanupGroupMembers ++# @ stub TpReleaseIoCompletion ++# @ stub TpReleaseJobNotification ++# @ stub TpReleasePool ++# @ stub TpReleaseTimer ++# @ stub TpReleaseWait ++# @ stub TpReleaseWork ++# @ stub TpSetDefaultPoolMaxThreads ++# @ stub TpSetDefaultPoolStackInformation ++# @ stub TpSetPoolMaxThreads ++# @ stub TpSetPoolMaxThreadsSoftLimit ++# @ stub TpSetPoolMinThreads ++# @ stub TpSetPoolStackInformation ++# @ stub TpSetPoolThreadBasePriority ++# @ stub TpSetPoolWorkerThreadIdleTimeout ++# @ stub TpSetTimer ++# @ stub TpSetTimerEx ++# @ stub TpSetWait ++# @ stub TpSetWaitEx ++# @ stub TpSimpleTryPost ++# @ stub TpStartAsyncIoOperation ++# @ stub TpTimerOutstandingCallbackCount ++# @ stub TpTrimPools ++# @ stub TpWaitForAlpcCompletion ++# @ stub TpWaitForIoCompletion ++# @ stub TpWaitForJobNotification ++# @ stub TpWaitForTimer ++# @ stub TpWaitForWait ++# @ stub TpWaitForWork + @ stdcall -ret64 VerSetConditionMask(int64 long long) +-- +2.2.2 + diff --git a/patches/ntdll-Vista_Threadpool/0002-ntdll-Implement-threadpool-cleanup-group-and-callbac.patch b/patches/ntdll-Vista_Threadpool/0002-ntdll-Implement-threadpool-cleanup-group-and-callbac.patch new file mode 100644 index 00000000..96322c59 --- /dev/null +++ b/patches/ntdll-Vista_Threadpool/0002-ntdll-Implement-threadpool-cleanup-group-and-callbac.patch @@ -0,0 +1,1077 @@ +From ceedfdb35e4ee398e3a36aba16778a1fa0d3d58a Mon Sep 17 00:00:00 2001 +From: Sebastian Lackner +Date: Sun, 1 Feb 2015 17:38:16 +0100 +Subject: ntdll: Implement threadpool, cleanup group and callback instance + functions. + +--- + dlls/ntdll/Makefile.in | 1 + + dlls/ntdll/ntdll.spec | 30 +- + dlls/ntdll/threadpool2.c | 966 +++++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 982 insertions(+), 15 deletions(-) + create mode 100644 dlls/ntdll/threadpool2.c + +diff --git a/dlls/ntdll/Makefile.in b/dlls/ntdll/Makefile.in +index ed4bb94..2cecac6 100644 +--- a/dlls/ntdll/Makefile.in ++++ b/dlls/ntdll/Makefile.in +@@ -47,6 +47,7 @@ C_SRCS = \ + tape.c \ + thread.c \ + threadpool.c \ ++ threadpool2.c \ + time.c \ + version.c \ + virtual.c \ +diff --git a/dlls/ntdll/ntdll.spec b/dlls/ntdll/ntdll.spec +index 771f669..256ec6d 100644 +--- a/dlls/ntdll/ntdll.spec ++++ b/dlls/ntdll/ntdll.spec +@@ -969,10 +969,10 @@ + @ stdcall RtlxUnicodeStringToOemSize(ptr) RtlUnicodeStringToOemSize + # @ stub TpAllocAlpcCompletion + # @ stub TpAllocAlpcCompletionEx +-# @ stub TpAllocCleanupGroup ++@ stdcall TpAllocCleanupGroup(ptr) + # @ stub TpAllocIoCompletion + # @ stub TpAllocJobNotification +-# @ stub TpAllocPool ++@ stdcall TpAllocPool(ptr ptr) + # @ stub TpAllocTimer + # @ stub TpAllocWait + # @ stub TpAllocWork +@@ -980,38 +980,38 @@ + # @ stub TpAlpcUnregisterCompletionList + # @ stub TpCallbackDetectedUnrecoverableError + # @ stub TpCallbackIndependent +-# @ stub TpCallbackLeaveCriticalSectionOnCompletion +-# @ stub TpCallbackMayRunLong +-# @ stub TpCallbackReleaseMutexOnCompletion +-# @ stub TpCallbackReleaseSemaphoreOnCompletion ++@ stdcall TpCallbackLeaveCriticalSectionOnCompletion(ptr ptr) ++@ stdcall TpCallbackMayRunLong(ptr) ++@ stdcall TpCallbackReleaseMutexOnCompletion(ptr long) ++@ stdcall TpCallbackReleaseSemaphoreOnCompletion(ptr long long) + # @ stub TpCallbackSendAlpcMessageOnCompletion + # @ stub TpCallbackSendPendingAlpcMessage +-# @ stub TpCallbackSetEventOnCompletion +-# @ stub TpCallbackUnloadDllOnCompletion ++@ stdcall TpCallbackSetEventOnCompletion(ptr long) ++@ stdcall TpCallbackUnloadDllOnCompletion(ptr long) + # @ stub TpCancelAsyncIoOperation + # @ stub TpCaptureCaller + # @ stub TpCheckTerminateWorker + # @ stub TpDbgDumpHeapUsage + # @ stub TpDbgSetLogRoutine + # @ stub TpDisablePoolCallbackChecks +-# @ stub TpDisassociateCallback ++@ stdcall TpDisassociateCallback(ptr) + # @ stub TpIsTimerSet + # @ stub TpPostWork + # @ stub TpQueryPoolStackInformation + # @ stub TpReleaseAlpcCompletion +-# @ stub TpReleaseCleanupGroup +-# @ stub TpReleaseCleanupGroupMembers ++@ stdcall TpReleaseCleanupGroup(ptr) ++@ stdcall TpReleaseCleanupGroupMembers(ptr long ptr) + # @ stub TpReleaseIoCompletion + # @ stub TpReleaseJobNotification +-# @ stub TpReleasePool ++@ stdcall TpReleasePool(ptr) + # @ stub TpReleaseTimer + # @ stub TpReleaseWait + # @ stub TpReleaseWork + # @ stub TpSetDefaultPoolMaxThreads + # @ stub TpSetDefaultPoolStackInformation +-# @ stub TpSetPoolMaxThreads ++@ stdcall TpSetPoolMaxThreads(ptr long) + # @ stub TpSetPoolMaxThreadsSoftLimit +-# @ stub TpSetPoolMinThreads ++@ stdcall TpSetPoolMinThreads(ptr long) + # @ stub TpSetPoolStackInformation + # @ stub TpSetPoolThreadBasePriority + # @ stub TpSetPoolWorkerThreadIdleTimeout +@@ -1019,7 +1019,7 @@ + # @ stub TpSetTimerEx + # @ stub TpSetWait + # @ stub TpSetWaitEx +-# @ stub TpSimpleTryPost ++@ stdcall TpSimpleTryPost(ptr ptr ptr) + # @ stub TpStartAsyncIoOperation + # @ stub TpTimerOutstandingCallbackCount + # @ stub TpTrimPools +diff --git a/dlls/ntdll/threadpool2.c b/dlls/ntdll/threadpool2.c +new file mode 100644 +index 0000000..c4f54af +--- /dev/null ++++ b/dlls/ntdll/threadpool2.c +@@ -0,0 +1,966 @@ ++/* ++ * Vista Threadpool implementation ++ * ++ * Copyright 2014-2015 Sebastian Lackner ++ * ++ * This library is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU Lesser General Public ++ * License as published by the Free Software Foundation; either ++ * version 2.1 of the License, or (at your option) any later version. ++ * ++ * This library is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public ++ * License along with this library; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA ++ */ ++ ++#include "config.h" ++#include "wine/port.h" ++ ++#include ++#include ++#include ++ ++#define NONAMELESSUNION ++#include "ntstatus.h" ++#define WIN32_NO_STATUS ++#include "winternl.h" ++ ++#include "wine/debug.h" ++#include "wine/list.h" ++ ++#include "ntdll_misc.h" ++ ++WINE_DEFAULT_DEBUG_CHANNEL(threadpool); ++ ++/* Besides winetests the following resources were used to implement some ++ * internal details of the threadpool implementation: ++ * ++ * [1] Concurrent Programming on Windows, by Joe Duffy ++ */ ++ ++static inline LONG interlocked_inc( PLONG dest ) ++{ ++ return interlocked_xchg_add( dest, 1 ) + 1; ++} ++ ++static inline LONG interlocked_dec( PLONG dest ) ++{ ++ return interlocked_xchg_add( dest, -1 ) - 1; ++} ++ ++#define THREADPOOL_WORKER_TIMEOUT 5000 ++ ++/* allocated on the stack while a callback is running */ ++struct threadpool_instance ++{ ++ struct threadpool_object *object; ++ DWORD threadid; ++ LONG disassociated; ++ LONG may_run_long; ++ ++ /* cleanup actions */ ++ struct ++ { ++ CRITICAL_SECTION *critical_section; ++ HANDLE mutex; ++ HANDLE semaphore; ++ LONG semaphore_count; ++ HANDLE event; ++ HMODULE library; ++ } cleanup; ++}; ++ ++/* internal threadpool representation */ ++struct threadpool ++{ ++ LONG refcount; ++ BOOL shutdown; ++ CRITICAL_SECTION cs; ++ ++ /* user-defined preferences for number of works */ ++ int max_workers; ++ int min_workers; ++ ++ /* pool of work items, locked via .cs */ ++ struct list pool; ++ RTL_CONDITION_VARIABLE update_event; ++ ++ /* information about worker threads, locked via .cs */ ++ int num_workers; ++ int num_busy_workers; ++}; ++ ++/* internal threadpool object representation */ ++struct threadpool_object ++{ ++ LONG refcount; ++ BOOL shutdown; ++ ++ /* read-only information */ ++ struct threadpool *pool; ++ struct threadpool_group *group; ++ PVOID userdata; ++ PTP_CLEANUP_GROUP_CANCEL_CALLBACK group_cancel_callback; ++ PTP_SIMPLE_CALLBACK finalization_callback; ++ LONG may_run_long; ++ HMODULE race_dll; ++ ++ /* information about the group, locked via .group->cs */ ++ struct list group_entry; /* only used when .group != NULL */ ++ ++ /* information about the pool, locked via .pool->cs */ ++ struct list pool_entry; /* only used when .num_pending_callbacks != 0 */ ++ LONG num_pending_callbacks; ++ LONG num_running_callbacks; ++ RTL_CONDITION_VARIABLE finished_event; ++ ++ /* type of this object */ ++ enum ++ { ++ TP_OBJECT_TYPE_UNDEFINED, ++ TP_OBJECT_TYPE_SIMPLE ++ } type; ++ ++ /* arguments for callback */ ++ union ++ { ++ /* simple callback */ ++ struct ++ { ++ PTP_SIMPLE_CALLBACK callback; ++ } simple; ++ } u; ++}; ++ ++/* internal threadpool group representation */ ++struct threadpool_group ++{ ++ LONG refcount; ++ BOOL shutdown; ++ CRITICAL_SECTION cs; ++ ++ /* locked via .cs */ ++ struct list members; ++}; ++ ++static inline struct threadpool *impl_from_TP_POOL( TP_POOL *pool ) ++{ ++ return (struct threadpool *)pool; ++} ++ ++static inline struct threadpool_group *impl_from_TP_CLEANUP_GROUP( TP_CLEANUP_GROUP *group ) ++{ ++ return (struct threadpool_group *)group; ++} ++ ++static inline struct threadpool_instance *impl_from_TP_CALLBACK_INSTANCE( TP_CALLBACK_INSTANCE *instance ) ++{ ++ return (struct threadpool_instance *)instance; ++} ++ ++static void CALLBACK threadpool_worker_proc( void *param ); ++ ++static NTSTATUS tp_threadpool_alloc( struct threadpool **out ); ++static BOOL tp_threadpool_release( struct threadpool *pool ); ++static void tp_threadpool_shutdown( struct threadpool *pool ); ++ ++static NTSTATUS tp_object_submit( struct threadpool_object *object ); ++static BOOL tp_object_release( struct threadpool_object *object ); ++static void tp_object_shutdown( struct threadpool_object *object ); ++ ++static BOOL tp_group_release( struct threadpool_group *group ); ++ ++/*********************************************************************** ++ * THREADPOOL INSTANCE IMPLEMENTATION ++ ***********************************************************************/ ++ ++static void tp_instance_initialize( struct threadpool_instance *instance, struct threadpool_object *object ) ++{ ++ instance->object = object; ++ instance->threadid = GetCurrentThreadId(); ++ instance->disassociated = FALSE; ++ instance->may_run_long = object->may_run_long; ++ instance->cleanup.critical_section = NULL; ++ instance->cleanup.mutex = NULL; ++ instance->cleanup.semaphore = NULL; ++ instance->cleanup.semaphore_count = 0; ++ instance->cleanup.event = NULL; ++ instance->cleanup.library = NULL; ++} ++ ++static NTSTATUS tp_instance_cleanup( struct threadpool_instance *instance ) ++{ ++ NTSTATUS status; ++ ++ /* According to [1] subsequent functions are not executed if one of the ++ * cleanup steps fails. The order is also based on the description in [1]. */ ++ if (instance->cleanup.critical_section) ++ { ++ RtlLeaveCriticalSection( instance->cleanup.critical_section ); ++ } ++ if (instance->cleanup.mutex) ++ { ++ status = NtReleaseMutant( instance->cleanup.mutex, NULL ); ++ if (status != STATUS_SUCCESS) ++ return status; ++ } ++ if (instance->cleanup.semaphore) ++ { ++ status = NtReleaseSemaphore( instance->cleanup.semaphore, instance->cleanup.semaphore_count, NULL ); ++ if (status != STATUS_SUCCESS) ++ return status; ++ } ++ if (instance->cleanup.event) ++ { ++ status = NtSetEvent( instance->cleanup.event, NULL ); ++ if (status != STATUS_SUCCESS) ++ return status; ++ } ++ if (instance->cleanup.library) ++ { ++ status = LdrUnloadDll( instance->cleanup.library ); ++ if (status != STATUS_SUCCESS) ++ return status; ++ } ++ ++ return STATUS_SUCCESS; ++} ++ ++static void tp_instance_disassociate_thread( struct threadpool_instance *instance ) ++{ ++ struct threadpool_object *object; ++ struct threadpool *pool; ++ ++ if (instance->threadid != GetCurrentThreadId()) ++ { ++ ERR("called from wrong thread, ignoring\n"); ++ return; ++ } ++ if (instance->disassociated) ++ return; ++ ++ object = instance->object; ++ pool = object->pool; ++ RtlEnterCriticalSection( &pool->cs ); ++ ++ object->num_running_callbacks--; ++ if (!object->num_pending_callbacks && !object->num_running_callbacks) ++ RtlWakeAllConditionVariable( &object->finished_event ); ++ ++ RtlLeaveCriticalSection( &pool->cs ); ++ instance->disassociated = TRUE; ++} ++ ++static BOOL tp_instance_may_run_long( struct threadpool_instance *instance ) ++{ ++ struct threadpool_object *object; ++ struct threadpool *pool; ++ NTSTATUS status = STATUS_SUCCESS; ++ ++ if (instance->threadid != GetCurrentThreadId()) ++ { ++ ERR("called from wrong thread, ignoring\n"); ++ return FALSE; ++ } ++ if (instance->may_run_long) ++ return TRUE; ++ ++ object = instance->object; ++ pool = object->pool; ++ RtlEnterCriticalSection( &pool->cs ); ++ ++ if (pool->num_busy_workers >= pool->num_workers && pool->num_workers < pool->max_workers) ++ { ++ HANDLE thread; ++ status = RtlCreateUserThread( GetCurrentProcess(), NULL, FALSE, NULL, 0, 0, ++ threadpool_worker_proc, pool, &thread, NULL ); ++ if (status == STATUS_SUCCESS) ++ { ++ interlocked_inc( &pool->refcount ); ++ pool->num_workers++; ++ NtClose( thread ); ++ } ++ } ++ ++ RtlLeaveCriticalSection( &pool->cs ); ++ instance->may_run_long = TRUE; ++ return !status; ++} ++ ++/*********************************************************************** ++ * THREADPOOL IMPLEMENTATION ++ ***********************************************************************/ ++ ++static struct threadpool *default_threadpool = NULL; ++static struct threadpool *get_default_threadpool( void ) ++{ ++ if (!default_threadpool) ++ { ++ struct threadpool *pool; ++ ++ if (tp_threadpool_alloc( &pool ) != STATUS_SUCCESS) ++ return NULL; ++ ++ if (interlocked_cmpxchg_ptr( (void *)&default_threadpool, pool, NULL ) != NULL) ++ { ++ tp_threadpool_shutdown( pool ); ++ tp_threadpool_release( pool ); ++ } ++ } ++ return default_threadpool; ++} ++ ++static NTSTATUS tp_threadpool_alloc( struct threadpool **out ) ++{ ++ struct threadpool *pool; ++ ++ pool = RtlAllocateHeap( GetProcessHeap(), 0, sizeof(*pool) ); ++ if (!pool) ++ return STATUS_NO_MEMORY; ++ ++ pool->refcount = 1; ++ pool->shutdown = FALSE; ++ ++ RtlInitializeCriticalSection( &pool->cs ); ++ pool->cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": threadpool.cs"); ++ ++ list_init( &pool->pool ); ++ RtlInitializeConditionVariable( &pool->update_event ); ++ ++ pool->max_workers = 500; ++ pool->min_workers = 1; ++ ++ pool->num_workers = 0; ++ pool->num_busy_workers = 0; ++ ++ TRACE("allocated threadpool %p\n", pool); ++ ++ *out = pool; ++ return STATUS_SUCCESS; ++} ++ ++static BOOL tp_threadpool_release( struct threadpool *pool ) ++{ ++ if (interlocked_dec( &pool->refcount )) ++ return FALSE; ++ ++ TRACE("destroying threadpool %p\n", pool); ++ ++ assert( pool != default_threadpool ); ++ assert( pool->shutdown ); ++ assert( list_empty( &pool->pool ) ); ++ ++ pool->cs.DebugInfo->Spare[0] = 0; ++ RtlDeleteCriticalSection( &pool->cs ); ++ ++ RtlFreeHeap( GetProcessHeap(), 0, pool ); ++ return TRUE; ++} ++ ++static void tp_threadpool_shutdown( struct threadpool *pool ) ++{ ++ assert( pool != default_threadpool ); ++ ++ pool->shutdown = TRUE; ++ RtlWakeAllConditionVariable( &pool->update_event ); ++} ++ ++static void CALLBACK threadpool_worker_proc( void *param ) ++{ ++ struct threadpool *pool = param; ++ LARGE_INTEGER timeout; ++ struct list *ptr; ++ ++ RtlEnterCriticalSection( &pool->cs ); ++ for (;;) ++ { ++ while ((ptr = list_head( &pool->pool ))) ++ { ++ struct threadpool_object *object = LIST_ENTRY( ptr, struct threadpool_object, pool_entry ); ++ struct threadpool_instance instance; ++ assert( object->num_pending_callbacks > 0 ); ++ ++ /* If further pending callbacks are queued, move the work item to ++ * the end of the pool list. Otherwise remove it from the pool. */ ++ list_remove( &object->pool_entry ); ++ if (--object->num_pending_callbacks) ++ list_add_tail( &pool->pool, &object->pool_entry ); ++ object->num_running_callbacks++; ++ ++ /* Leave critical section and do the actual callback. */ ++ pool->num_busy_workers++; ++ RtlLeaveCriticalSection( &pool->cs ); ++ tp_instance_initialize( &instance, object ); ++ ++ /* Execute regular worker callback */ ++ switch (object->type) ++ { ++ case TP_OBJECT_TYPE_SIMPLE: ++ { ++ TP_CALLBACK_INSTANCE *cb_instance = (TP_CALLBACK_INSTANCE *)&instance; ++ TRACE( "executing callback %p(%p, %p)\n", ++ object->u.simple.callback, cb_instance, object->userdata ); ++ object->u.simple.callback( cb_instance, object->userdata ); ++ TRACE( "callback %p returned\n", object->u.simple.callback ); ++ break; ++ } ++ ++ default: ++ FIXME( "callback type %u not implemented\n", object->type ); ++ break; ++ } ++ ++ /* Execute finalization callback */ ++ if (object->finalization_callback) ++ { ++ TP_CALLBACK_INSTANCE *cb_instance = (TP_CALLBACK_INSTANCE *)&instance; ++ TRACE( "executing finalization callback %p(%p, %p)\n", ++ object->finalization_callback, cb_instance, object->userdata ); ++ object->finalization_callback( cb_instance, object->userdata ); ++ TRACE( "finalization callback %p returned\n", object->finalization_callback ); ++ } ++ ++ /* Clean up any other resources */ ++ tp_instance_cleanup( &instance ); ++ RtlEnterCriticalSection( &pool->cs ); ++ pool->num_busy_workers--; ++ ++ /* If instance was not disassociated, then wake up waiting objects. */ ++ if (!instance.disassociated) ++ { ++ object->num_running_callbacks--; ++ if (!object->num_pending_callbacks && !object->num_running_callbacks) ++ RtlWakeAllConditionVariable( &object->finished_event ); ++ } ++ ++ tp_object_release( object ); ++ } ++ ++ /* Shutdown worker thread if requested. */ ++ if (pool->shutdown) ++ break; ++ ++ /* Wait for new tasks or until timeout expires. */ ++ timeout.QuadPart = (ULONGLONG)THREADPOOL_WORKER_TIMEOUT * -10000; ++ if (RtlSleepConditionVariableCS( &pool->update_event, &pool->cs, ++ &timeout ) == STATUS_TIMEOUT && !list_head( &pool->pool )) ++ { ++ break; ++ } ++ } ++ pool->num_workers--; ++ RtlLeaveCriticalSection( &pool->cs ); ++ tp_threadpool_release( pool ); ++} ++ ++/*********************************************************************** ++ * THREADPOOL OBJECT IMPLEMENTATION ++ ***********************************************************************/ ++ ++static void tp_object_initialize( struct threadpool_object *object, struct threadpool *pool, ++ PVOID userdata, TP_CALLBACK_ENVIRON *environment ) ++{ ++ object->refcount = 1; ++ object->shutdown = FALSE; ++ ++ /* Read-only information */ ++ object->pool = pool; ++ object->group = NULL; ++ object->userdata = userdata; ++ object->group_cancel_callback = NULL; ++ object->finalization_callback = NULL; ++ object->may_run_long = 0; ++ object->race_dll = NULL; ++ ++ /* Information about the group */ ++ memset( &object->group_entry, 0, sizeof(object->group_entry) ); ++ ++ /* Information about the pool */ ++ memset( &object->pool_entry, 0, sizeof(object->pool_entry) ); ++ object->num_pending_callbacks = 0; ++ object->num_running_callbacks = 0; ++ RtlInitializeConditionVariable( &object->finished_event ); ++ ++ /* Set properties according to environment, if given */ ++ if (environment) ++ { ++ ++ /* Windows doesn't abort when the version field contains garbage */ ++ if (environment->Version != 1) ++ FIXME("unsupported environment version %d\n", environment->Version); ++ ++ /* object->pool was already set */ ++ object->group = impl_from_TP_CLEANUP_GROUP( environment->CleanupGroup ); ++ object->group_cancel_callback = environment->CleanupGroupCancelCallback; ++ object->finalization_callback = environment->FinalizationCallback; ++ object->may_run_long = environment->u.s.LongFunction != 0; ++ object->race_dll = environment->RaceDll; ++ ++ if (environment->ActivationContext) ++ FIXME("activation context not supported yet\n"); ++ ++ if (environment->u.s.Persistent) ++ FIXME("persistent thread support not supported yet\n"); ++ } ++ ++ /* Increase dll refcount */ ++ if (object->race_dll) ++ LdrAddRefDll( 0, object->race_dll ); ++ ++ /* Increase reference-count on the pool */ ++ interlocked_inc( &pool->refcount ); ++ ++ /* Assign this object to a specific group. Please note that this has to be done ++ * as the last step before returning a pointer to the application, otherwise ++ * there is a risk of having race-conditions. */ ++ if (object->group) ++ { ++ struct threadpool_group *group = object->group; ++ interlocked_inc( &group->refcount ); ++ ++ RtlEnterCriticalSection( &group->cs ); ++ list_add_tail( &group->members, &object->group_entry ); ++ RtlLeaveCriticalSection( &group->cs ); ++ } ++} ++ ++static NTSTATUS tp_object_alloc_simple( struct threadpool_object **out, PTP_SIMPLE_CALLBACK callback, ++ PVOID userdata, TP_CALLBACK_ENVIRON *environment ) ++{ ++ struct threadpool_object *object; ++ struct threadpool *pool; ++ ++ /* determine threadpool */ ++ pool = environment ? (struct threadpool *)environment->Pool : NULL; ++ if (!pool) pool = get_default_threadpool(); ++ if (!pool) return STATUS_NO_MEMORY; ++ ++ object = RtlAllocateHeap( GetProcessHeap(), 0, sizeof(*object) ); ++ if (!object) ++ return STATUS_NO_MEMORY; ++ ++ object->type = TP_OBJECT_TYPE_SIMPLE; ++ object->u.simple.callback = callback; ++ tp_object_initialize( object, pool, userdata, environment ); ++ ++ TRACE("allocated object %p of type %u\n", object, object->type); ++ ++ *out = object; ++ return STATUS_SUCCESS; ++} ++ ++static BOOL tp_object_release( struct threadpool_object *object ) ++{ ++ struct threadpool_group *group; ++ ++ if (interlocked_dec( &object->refcount )) ++ return FALSE; ++ ++ TRACE("destroying object %p of type %u\n", object, object->type); ++ ++ assert( object->shutdown ); ++ assert( !object->num_pending_callbacks ); ++ assert( !object->num_running_callbacks ); ++ ++ /* release reference on the group */ ++ if ((group = object->group)) ++ { ++ RtlEnterCriticalSection( &group->cs ); ++ list_remove( &object->group_entry ); ++ RtlLeaveCriticalSection( &group->cs ); ++ tp_group_release( group ); ++ } ++ ++ /* release reference to library */ ++ if (object->race_dll) ++ LdrUnloadDll( object->race_dll ); ++ ++ /* release reference to threadpool */ ++ tp_threadpool_release( object->pool ); ++ ++ RtlFreeHeap( GetProcessHeap(), 0, object ); ++ return TRUE; ++} ++ ++static void tp_object_shutdown( struct threadpool_object *object ) ++{ ++ object->shutdown = TRUE; ++} ++ ++static void tp_object_cancel( struct threadpool_object *object, BOOL group_cancel, PVOID userdata ) ++{ ++ struct threadpool *pool = object->pool; ++ LONG pending_callbacks = 0; ++ ++ /* Remove the pending callbacks from the pool */ ++ RtlEnterCriticalSection( &pool->cs ); ++ if (object->num_pending_callbacks) ++ { ++ pending_callbacks = object->num_pending_callbacks; ++ list_remove( &object->pool_entry ); ++ object->num_pending_callbacks = 0; ++ } ++ RtlLeaveCriticalSection( &pool->cs ); ++ ++ /* Execute group cancellation callback if defined, and if this was actually a group cancel. */ ++ if (pending_callbacks && group_cancel && object->group_cancel_callback) ++ { ++ TRACE( "executing group cancel callback %p(%p, %p)\n", object->group_cancel_callback, object, userdata ); ++ object->group_cancel_callback( object, userdata ); ++ TRACE( "group cancel callback %p returned\n", object->group_cancel_callback ); ++ } ++ ++ /* remove references for removed pending callbacks */ ++ while (pending_callbacks--) ++ tp_object_release( object ); ++} ++ ++static void tp_object_wait( struct threadpool_object *object ) ++{ ++ struct threadpool *pool = object->pool; ++ RtlEnterCriticalSection( &pool->cs ); ++ ++ while (object->num_pending_callbacks || object->num_running_callbacks) ++ RtlSleepConditionVariableCS( &object->finished_event, &pool->cs, NULL ); ++ ++ RtlLeaveCriticalSection( &pool->cs ); ++} ++ ++static NTSTATUS tp_object_submit( struct threadpool_object *object ) ++{ ++ struct threadpool *pool = object->pool; ++ NTSTATUS status = STATUS_SUCCESS; ++ ++ assert( !object->shutdown ); ++ assert( !pool->shutdown ); ++ ++ RtlEnterCriticalSection( &pool->cs ); ++ ++ /* Start new worker threads if required (and allowed) */ ++ if (pool->num_busy_workers >= pool->num_workers && pool->num_workers < pool->max_workers) ++ { ++ HANDLE thread; ++ status = RtlCreateUserThread( GetCurrentProcess(), NULL, FALSE, NULL, 0, 0, ++ threadpool_worker_proc, pool, &thread, NULL ); ++ if (status == STATUS_SUCCESS) ++ { ++ interlocked_inc( &pool->refcount ); ++ pool->num_workers++; ++ NtClose( thread ); ++ } ++ else if (pool->num_workers) ++ { ++ RtlWakeConditionVariable( &pool->update_event ); ++ status = STATUS_SUCCESS; ++ } ++ } ++ else RtlWakeConditionVariable( &pool->update_event ); ++ ++ /* Queue work item into pool and increment refcount */ ++ if (!status) ++ { ++ if (!object->num_pending_callbacks++) ++ list_add_tail( &pool->pool, &object->pool_entry ); ++ ++ interlocked_inc(&object->refcount); ++ } ++ ++ RtlLeaveCriticalSection( &pool->cs ); ++ return status; ++} ++ ++/*********************************************************************** ++ * THREADPOOL GROUP IMPLEMENTATION ++ ***********************************************************************/ ++ ++static NTSTATUS tp_group_alloc( struct threadpool_group **out ) ++{ ++ struct threadpool_group *group; ++ ++ group = RtlAllocateHeap( GetProcessHeap(), 0, sizeof(*group) ); ++ if (!group) ++ return STATUS_NO_MEMORY; ++ ++ group->refcount = 1; ++ group->shutdown = FALSE; ++ ++ RtlInitializeCriticalSection( &group->cs ); ++ group->cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": threadpool_group.cs"); ++ ++ list_init( &group->members ); ++ ++ TRACE("allocated group %p\n", group); ++ ++ *out = group; ++ return STATUS_SUCCESS; ++} ++ ++static BOOL tp_group_release( struct threadpool_group *group ) ++{ ++ if (interlocked_dec( &group->refcount )) ++ return FALSE; ++ ++ TRACE("destroying group %p\n", group); ++ ++ assert( group->shutdown ); ++ assert( list_empty( &group->members ) ); ++ ++ group->cs.DebugInfo->Spare[0] = 0; ++ RtlDeleteCriticalSection( &group->cs ); ++ ++ RtlFreeHeap( GetProcessHeap(), 0, group ); ++ return TRUE; ++} ++ ++static void tp_group_shutdown( struct threadpool_group *group ) ++{ ++ group->shutdown = TRUE; ++} ++ ++static void tp_group_release_members( struct threadpool_group *group, BOOL cancel_pending, PVOID userdata ) ++{ ++ struct threadpool_object *object, *next; ++ struct list members; ++ ++ /* We cannot keep the group locked until all tasks have finished. ++ * Create a temporary list containing all tasks which were member of the group. */ ++ ++ RtlEnterCriticalSection( &group->cs ); ++ LIST_FOR_EACH_ENTRY( object, &group->members, struct threadpool_object, group_entry ) ++ { ++ /* reset the group - objects do no longer to remove manually from the group on destruction. */ ++ assert( object->group == group ); ++ object->group = NULL; ++ ++ /* Simple callbacks are very special. The user doesn't hold any reference, so ++ * they would be released too early. Add one additional temporary reference. */ ++ if (object->type == TP_OBJECT_TYPE_SIMPLE) ++ interlocked_inc(&object->refcount); ++ ++ /* Do not allow to submit new tasks for this object. */ ++ tp_object_shutdown( object ); ++ } ++ ++ list_init( &members ); ++ list_move_tail( &members, &group->members ); ++ RtlLeaveCriticalSection( &group->cs ); ++ ++ /* Cancel pending tasks, execute the group cancel callback. */ ++ if (cancel_pending) ++ { ++ LIST_FOR_EACH_ENTRY( object, &members, struct threadpool_object, group_entry ) ++ tp_object_cancel( object, TRUE, userdata ); ++ } ++ ++ LIST_FOR_EACH_ENTRY_SAFE( object, next, &members, struct threadpool_object, group_entry ) ++ { ++ /* Wait for tasks to finish, afterwards release one reference. This could destroy ++ * the object, so we use LIST_FOR_EACH_ENTRY_SAFE. If the object is not destroyed, ++ * then ->group_entry contains garbage, but that doesn't matter. It will not be ++ * used anymore because ->group == NULL. */ ++ tp_object_wait( object ); ++ tp_object_release( object ); ++ ++ /* Manually release the group reference */ ++ tp_group_release( group ); ++ } ++} ++ ++ ++ ++/*********************************************************************** ++ * TpAllocCleanupGroup (NTDLL.@) ++ */ ++NTSTATUS WINAPI TpAllocCleanupGroup( TP_CLEANUP_GROUP **out ) ++{ ++ TRACE("%p\n", out); ++ if (!out) return STATUS_ACCESS_VIOLATION; ++ return tp_group_alloc( (struct threadpool_group **)out ); ++} ++ ++/*********************************************************************** ++ * TpAllocPool (NTDLL.@) ++ */ ++NTSTATUS WINAPI TpAllocPool( TP_POOL **out, PVOID reserved ) ++{ ++ TRACE("%p %p\n", out, reserved); ++ if (reserved) FIXME("reserved argument is nonzero (%p)", reserved); ++ if (!out) return STATUS_ACCESS_VIOLATION; ++ return tp_threadpool_alloc( (struct threadpool **)out ); ++} ++ ++/*********************************************************************** ++ * TpCallbackLeaveCriticalSectionOnCompletion (NTDLL.@) ++ */ ++VOID WINAPI TpCallbackLeaveCriticalSectionOnCompletion( TP_CALLBACK_INSTANCE *instance, CRITICAL_SECTION *crit ) ++{ ++ struct threadpool_instance *this = impl_from_TP_CALLBACK_INSTANCE( instance ); ++ TRACE("%p %p\n", instance, crit); ++ if (!this) return; ++ if (this->cleanup.critical_section) ++ FIXME("attempt to set multiple cleanup critical sections\n"); ++ else ++ this->cleanup.critical_section = crit; ++} ++ ++/*********************************************************************** ++ * TpCallbackMayRunLong (NTDLL.@) ++ */ ++NTSTATUS WINAPI TpCallbackMayRunLong( TP_CALLBACK_INSTANCE *instance ) ++{ ++ struct threadpool_instance *this = impl_from_TP_CALLBACK_INSTANCE( instance ); ++ TRACE("%p\n", instance); ++ if (!this) return STATUS_ACCESS_VIOLATION; ++ return tp_instance_may_run_long( this ); ++} ++ ++/*********************************************************************** ++ * TpCallbackReleaseMutexOnCompletion (NTDLL.@) ++ */ ++VOID WINAPI TpCallbackReleaseMutexOnCompletion( TP_CALLBACK_INSTANCE *instance, HANDLE mutex ) ++{ ++ struct threadpool_instance *this = impl_from_TP_CALLBACK_INSTANCE( instance ); ++ TRACE("%p %p\n", instance, mutex); ++ if (!this) return; ++ if (this->cleanup.mutex) ++ FIXME("attempt to set multiple cleanup mutexes\n"); ++ else ++ this->cleanup.mutex = mutex; ++} ++ ++/*********************************************************************** ++ * TpCallbackReleaseSemaphoreOnCompletion (NTDLL.@) ++ */ ++VOID WINAPI TpCallbackReleaseSemaphoreOnCompletion( TP_CALLBACK_INSTANCE *instance, HANDLE semaphore, DWORD count ) ++{ ++ struct threadpool_instance *this = impl_from_TP_CALLBACK_INSTANCE( instance ); ++ TRACE("%p %p %u\n", instance, semaphore, count); ++ if (!this) return; ++ if (this->cleanup.semaphore) ++ FIXME("attempt to set multiple cleanup semaphores\n"); ++ else ++ { ++ this->cleanup.semaphore = semaphore; ++ this->cleanup.semaphore_count = count; ++ } ++} ++ ++/*********************************************************************** ++ * TpCallbackSetEventOnCompletion (NTDLL.@) ++ */ ++VOID WINAPI TpCallbackSetEventOnCompletion( TP_CALLBACK_INSTANCE *instance, HANDLE event ) ++{ ++ struct threadpool_instance *this = impl_from_TP_CALLBACK_INSTANCE( instance ); ++ TRACE("%p %p\n", instance, event); ++ if (!this) return; ++ if (this->cleanup.event) ++ FIXME("attempt to set multiple cleanup events\n"); ++ else ++ this->cleanup.event = event; ++} ++ ++/*********************************************************************** ++ * TpCallbackUnloadDllOnCompletion (NTDLL.@) ++ */ ++VOID WINAPI TpCallbackUnloadDllOnCompletion( TP_CALLBACK_INSTANCE *instance, HMODULE module ) ++{ ++ struct threadpool_instance *this = impl_from_TP_CALLBACK_INSTANCE( instance ); ++ TRACE("%p %p\n", instance, module); ++ if (!this) return; ++ if (this->cleanup.library) ++ FIXME("attempt to set multiple cleanup libraries\n"); ++ else ++ this->cleanup.library = module; ++} ++ ++/*********************************************************************** ++ * TpDisassociateCallback (NTDLL.@) ++ */ ++VOID WINAPI TpDisassociateCallback( TP_CALLBACK_INSTANCE *instance ) ++{ ++ struct threadpool_instance *this = impl_from_TP_CALLBACK_INSTANCE( instance ); ++ TRACE("%p\n", instance); ++ if (this) tp_instance_disassociate_thread( this ); ++} ++ ++/*********************************************************************** ++ * TpReleaseCleanupGroup (NTDLL.@) ++ */ ++VOID WINAPI TpReleaseCleanupGroup( TP_CLEANUP_GROUP *group ) ++{ ++ struct threadpool_group *this = impl_from_TP_CLEANUP_GROUP( group ); ++ TRACE("%p\n", group); ++ if (this) ++ { ++ tp_group_shutdown( this ); ++ tp_group_release( this ); ++ } ++} ++ ++/*********************************************************************** ++ * TpReleaseCleanupGroupMembers (NTDLL.@) ++ */ ++VOID WINAPI TpReleaseCleanupGroupMembers( TP_CLEANUP_GROUP *group, BOOL cancel_pending, PVOID userdata ) ++{ ++ struct threadpool_group *this = impl_from_TP_CLEANUP_GROUP( group ); ++ TRACE("%p %d %p\n", group, cancel_pending, userdata); ++ if (this) tp_group_release_members( this, cancel_pending, userdata ); ++} ++ ++/*********************************************************************** ++ * TpReleasePool (NTDLL.@) ++ */ ++VOID WINAPI TpReleasePool( TP_POOL *pool ) ++{ ++ struct threadpool *this = impl_from_TP_POOL( pool ); ++ TRACE("%p\n", pool); ++ if (this) ++ { ++ tp_threadpool_shutdown( this ); ++ tp_threadpool_release( this ); ++ } ++} ++ ++/*********************************************************************** ++ * TpSetPoolMaxThreads (NTDLL.@) ++ */ ++VOID WINAPI TpSetPoolMaxThreads( TP_POOL *pool, DWORD maximum ) ++{ ++ struct threadpool *this = impl_from_TP_POOL( pool ); ++ TRACE("%p %d\n", pool, maximum); ++ if (this) this->max_workers = max(maximum, 1); ++} ++ ++/*********************************************************************** ++ * TpSetPoolMinThreads (NTDLL.@) ++ */ ++BOOL WINAPI TpSetPoolMinThreads( TP_POOL *pool, DWORD minimum ) ++{ ++ struct threadpool *this = impl_from_TP_POOL( pool ); ++ FIXME("%p %d: semi-stub\n", pool, minimum); ++ if (this) this->min_workers = max(minimum, 1); ++ return TRUE; ++} ++ ++/*********************************************************************** ++ * TpSimpleTryPost (NTDLL.@) ++ */ ++NTSTATUS WINAPI TpSimpleTryPost( PTP_SIMPLE_CALLBACK callback, PVOID userdata, TP_CALLBACK_ENVIRON *environment ) ++{ ++ struct threadpool_object *object; ++ NTSTATUS status; ++ TRACE("%p %p %p\n", callback, userdata, environment); ++ status = tp_object_alloc_simple( &object, callback, userdata, environment ); ++ if (!status) ++ { ++ status = tp_object_submit( object ); ++ tp_object_shutdown( object ); ++ tp_object_release( object ); ++ } ++ return status; ++} +-- +2.2.2 + diff --git a/patches/ntdll-Vista_Threadpool/0003-ntdll-Implement-additional-threadpool-work-item-func.patch b/patches/ntdll-Vista_Threadpool/0003-ntdll-Implement-additional-threadpool-work-item-func.patch new file mode 100644 index 00000000..7aa67410 --- /dev/null +++ b/patches/ntdll-Vista_Threadpool/0003-ntdll-Implement-additional-threadpool-work-item-func.patch @@ -0,0 +1,212 @@ +From 1dc1074ad196b6d869028d3dea283dbab8a8a707 Mon Sep 17 00:00:00 2001 +From: Sebastian Lackner +Date: Sun, 1 Feb 2015 18:06:08 +0100 +Subject: ntdll: Implement additional threadpool work item functions. + +--- + dlls/ntdll/ntdll.spec | 8 ++-- + dlls/ntdll/threadpool2.c | 99 +++++++++++++++++++++++++++++++++++++++++++++++- + 2 files changed, 102 insertions(+), 5 deletions(-) + +diff --git a/dlls/ntdll/ntdll.spec b/dlls/ntdll/ntdll.spec +index 256ec6d..bf9e795 100644 +--- a/dlls/ntdll/ntdll.spec ++++ b/dlls/ntdll/ntdll.spec +@@ -975,7 +975,7 @@ + @ stdcall TpAllocPool(ptr ptr) + # @ stub TpAllocTimer + # @ stub TpAllocWait +-# @ stub TpAllocWork ++@ stdcall TpAllocWork(ptr ptr ptr ptr) + # @ stub TpAlpcRegisterCompletionList + # @ stub TpAlpcUnregisterCompletionList + # @ stub TpCallbackDetectedUnrecoverableError +@@ -996,7 +996,7 @@ + # @ stub TpDisablePoolCallbackChecks + @ stdcall TpDisassociateCallback(ptr) + # @ stub TpIsTimerSet +-# @ stub TpPostWork ++@ stdcall TpPostWork(ptr) + # @ stub TpQueryPoolStackInformation + # @ stub TpReleaseAlpcCompletion + @ stdcall TpReleaseCleanupGroup(ptr) +@@ -1006,7 +1006,7 @@ + @ stdcall TpReleasePool(ptr) + # @ stub TpReleaseTimer + # @ stub TpReleaseWait +-# @ stub TpReleaseWork ++@ stdcall TpReleaseWork(ptr) + # @ stub TpSetDefaultPoolMaxThreads + # @ stub TpSetDefaultPoolStackInformation + @ stdcall TpSetPoolMaxThreads(ptr long) +@@ -1030,3 +1030,3 @@ + # @ stub TpWaitForWait +-# @ stub TpWaitForWork ++@ stdcall TpWaitForWork(ptr long) + @ stdcall -ret64 VerSetConditionMask(int64 long long) +diff --git a/dlls/ntdll/threadpool2.c b/dlls/ntdll/threadpool2.c +index c4f54af..acc477f 100644 +--- a/dlls/ntdll/threadpool2.c ++++ b/dlls/ntdll/threadpool2.c +@@ -123,7 +123,8 @@ struct threadpool_object + enum + { + TP_OBJECT_TYPE_UNDEFINED, +- TP_OBJECT_TYPE_SIMPLE ++ TP_OBJECT_TYPE_SIMPLE, ++ TP_OBJECT_TYPE_WORK + } type; + + /* arguments for callback */ +@@ -134,6 +135,11 @@ struct threadpool_object + { + PTP_SIMPLE_CALLBACK callback; + } simple; ++ /* work callback */ ++ struct ++ { ++ PTP_WORK_CALLBACK callback; ++ } work; + } u; + }; + +@@ -153,6 +159,13 @@ static inline struct threadpool *impl_from_TP_POOL( TP_POOL *pool ) + return (struct threadpool *)pool; + } + ++static inline struct threadpool_object *impl_from_TP_WORK( TP_WORK *work ) ++{ ++ struct threadpool_object *object = (struct threadpool_object *)work; ++ assert( !object || object->type == TP_OBJECT_TYPE_WORK ); ++ return object; ++} ++ + static inline struct threadpool_group *impl_from_TP_CLEANUP_GROUP( TP_CLEANUP_GROUP *group ) + { + return (struct threadpool_group *)group; +@@ -410,6 +423,16 @@ static void CALLBACK threadpool_worker_proc( void *param ) + break; + } + ++ case TP_OBJECT_TYPE_WORK: ++ { ++ TP_CALLBACK_INSTANCE *cb_instance = (TP_CALLBACK_INSTANCE *)&instance; ++ TRACE( "executing callback %p(%p, %p)\n", ++ object->u.work.callback, cb_instance, object->userdata ); ++ object->u.work.callback( cb_instance, object->userdata, (TP_WORK *)object ); ++ TRACE( "callback %p returned\n", object->u.work.callback ); ++ break; ++ } ++ + default: + FIXME( "callback type %u not implemented\n", object->type ); + break; +@@ -554,6 +577,31 @@ static NTSTATUS tp_object_alloc_simple( struct threadpool_object **out, PTP_SIMP + return STATUS_SUCCESS; + } + ++static NTSTATUS tp_object_alloc_work( struct threadpool_object **out, PTP_WORK_CALLBACK callback, ++ PVOID userdata, TP_CALLBACK_ENVIRON *environment ) ++{ ++ struct threadpool_object *object; ++ struct threadpool *pool; ++ ++ /* determine threadpool */ ++ pool = environment ? (struct threadpool *)environment->Pool : NULL; ++ if (!pool) pool = get_default_threadpool(); ++ if (!pool) return STATUS_NO_MEMORY; ++ ++ object = RtlAllocateHeap( GetProcessHeap(), 0, sizeof(*object) ); ++ if (!object) ++ return STATUS_NO_MEMORY; ++ ++ object->type = TP_OBJECT_TYPE_WORK; ++ object->u.work.callback = callback; ++ tp_object_initialize( object, pool, userdata, environment ); ++ ++ TRACE("allocated object %p of type %u\n", object, object->type); ++ ++ *out = object; ++ return STATUS_SUCCESS; ++} ++ + static BOOL tp_object_release( struct threadpool_object *object ) + { + struct threadpool_group *group; +@@ -795,6 +843,16 @@ NTSTATUS WINAPI TpAllocPool( TP_POOL **out, PVOID reserved ) + } + + /*********************************************************************** ++ * TpAllocWork (NTDLL.@) ++ */ ++NTSTATUS WINAPI TpAllocWork( TP_WORK **out, PTP_WORK_CALLBACK callback, PVOID userdata, ++ TP_CALLBACK_ENVIRON *environment ) ++{ ++ TRACE("%p %p %p %p\n", out, callback, userdata, environment); ++ return tp_object_alloc_work( (struct threadpool_object **)out, callback, userdata, environment ); ++} ++ ++/*********************************************************************** + * TpCallbackLeaveCriticalSectionOnCompletion (NTDLL.@) + */ + VOID WINAPI TpCallbackLeaveCriticalSectionOnCompletion( TP_CALLBACK_INSTANCE *instance, CRITICAL_SECTION *crit ) +@@ -889,6 +947,16 @@ VOID WINAPI TpDisassociateCallback( TP_CALLBACK_INSTANCE *instance ) + } + + /*********************************************************************** ++ * TpPostWork (NTDLL.@) ++ */ ++VOID WINAPI TpPostWork( TP_WORK *work ) ++{ ++ struct threadpool_object *this = impl_from_TP_WORK( work ); ++ TRACE("%p\n", work); ++ if (this) tp_object_submit( this ); ++} ++ ++/*********************************************************************** + * TpReleaseCleanupGroup (NTDLL.@) + */ + VOID WINAPI TpReleaseCleanupGroup( TP_CLEANUP_GROUP *group ) +@@ -927,6 +995,20 @@ VOID WINAPI TpReleasePool( TP_POOL *pool ) + } + + /*********************************************************************** ++ * TpReleaseWork (NTDLL.@) ++ */ ++VOID WINAPI TpReleaseWork( TP_WORK *work ) ++{ ++ struct threadpool_object *this = impl_from_TP_WORK( work ); ++ TRACE("%p\n", work); ++ if (this) ++ { ++ tp_object_shutdown( this ); ++ tp_object_release( this ); ++ } ++} ++ ++/*********************************************************************** + * TpSetPoolMaxThreads (NTDLL.@) + */ + VOID WINAPI TpSetPoolMaxThreads( TP_POOL *pool, DWORD maximum ) +@@ -964,3 +1046,18 @@ NTSTATUS WINAPI TpSimpleTryPost( PTP_SIMPLE_CALLBACK callback, PVOID userdata, T + } + return status; + } ++ ++/*********************************************************************** ++ * TpWaitForWork (NTDLL.@) ++ */ ++VOID WINAPI TpWaitForWork( TP_WORK *work, BOOL cancel_pending ) ++{ ++ struct threadpool_object *this = impl_from_TP_WORK( work ); ++ TRACE("%p %d\n", work, cancel_pending); ++ if (this) ++ { ++ if (cancel_pending) ++ tp_object_cancel( this, FALSE, NULL ); ++ tp_object_wait( this ); ++ } ++} +-- +2.2.2 + diff --git a/patches/ntdll-Vista_Threadpool/0004-ntdll-Implement-threadpool-timer-functions.patch b/patches/ntdll-Vista_Threadpool/0004-ntdll-Implement-threadpool-timer-functions.patch new file mode 100644 index 00000000..20dd642e --- /dev/null +++ b/patches/ntdll-Vista_Threadpool/0004-ntdll-Implement-threadpool-timer-functions.patch @@ -0,0 +1,551 @@ +From 248c2929e4cef9d57b6ae641cbf428cfb73efe0f Mon Sep 17 00:00:00 2001 +From: Sebastian Lackner +Date: Sun, 1 Feb 2015 18:21:28 +0100 +Subject: ntdll: Implement threadpool timer functions. + +--- + dlls/ntdll/ntdll.spec | 10 +- + dlls/ntdll/threadpool2.c | 387 ++++++++++++++++++++++++++++++++++++++++++++++- + 2 files changed, 391 insertions(+), 6 deletions(-) + +diff --git a/dlls/ntdll/ntdll.spec b/dlls/ntdll/ntdll.spec +index bf9e795..f4328a9 100644 +--- a/dlls/ntdll/ntdll.spec ++++ b/dlls/ntdll/ntdll.spec +@@ -973,7 +973,7 @@ + # @ stub TpAllocIoCompletion + # @ stub TpAllocJobNotification + @ stdcall TpAllocPool(ptr ptr) +-# @ stub TpAllocTimer ++@ stdcall TpAllocTimer(ptr ptr ptr) + # @ stub TpAllocWait + @ stdcall TpAllocWork(ptr ptr ptr ptr) + # @ stub TpAlpcRegisterCompletionList +@@ -995,7 +995,7 @@ + # @ stub TpDbgSetLogRoutine + # @ stub TpDisablePoolCallbackChecks + @ stdcall TpDisassociateCallback(ptr) +-# @ stub TpIsTimerSet ++@ stdcall TpIsTimerSet(ptr) + @ stdcall TpPostWork(ptr) + # @ stub TpQueryPoolStackInformation + # @ stub TpReleaseAlpcCompletion +@@ -1004,7 +1004,7 @@ + # @ stub TpReleaseIoCompletion + # @ stub TpReleaseJobNotification + @ stdcall TpReleasePool(ptr) +-# @ stub TpReleaseTimer ++@ stdcall TpReleaseTimer(ptr) + # @ stub TpReleaseWait + @ stdcall TpReleaseWork(ptr) + # @ stub TpSetDefaultPoolMaxThreads +@@ -1015,7 +1015,7 @@ + # @ stub TpSetPoolStackInformation + # @ stub TpSetPoolThreadBasePriority + # @ stub TpSetPoolWorkerThreadIdleTimeout +-# @ stub TpSetTimer ++@ stdcall TpSetTimer(ptr ptr long long) + # @ stub TpSetTimerEx + # @ stub TpSetWait + # @ stub TpSetWaitEx +@@ -1026,7 +1026,7 @@ + # @ stub TpWaitForAlpcCompletion + # @ stub TpWaitForIoCompletion + # @ stub TpWaitForJobNotification +-# @ stub TpWaitForTimer ++@ stdcall TpWaitForTimer(ptr long) + # @ stub TpWaitForWait + @ stdcall TpWaitForWork(ptr long) + @ stdcall -ret64 VerSetConditionMask(int64 long long) +diff --git a/dlls/ntdll/threadpool2.c b/dlls/ntdll/threadpool2.c +index acc477f..4fa22bb 100644 +--- a/dlls/ntdll/threadpool2.c ++++ b/dlls/ntdll/threadpool2.c +@@ -124,7 +124,8 @@ struct threadpool_object + { + TP_OBJECT_TYPE_UNDEFINED, + TP_OBJECT_TYPE_SIMPLE, +- TP_OBJECT_TYPE_WORK ++ TP_OBJECT_TYPE_WORK, ++ TP_OBJECT_TYPE_TIMER + } type; + + /* arguments for callback */ +@@ -140,6 +141,19 @@ struct threadpool_object + { + PTP_WORK_CALLBACK callback; + } work; ++ /* timer callback */ ++ struct ++ { ++ PTP_TIMER_CALLBACK callback; ++ ++ /* information about the timer, locked via timerqueue.cs */ ++ BOOL is_timer_set; ++ BOOL timer_pending; ++ struct list timer_entry; ++ ULONGLONG timeout; ++ LONG period; ++ LONG window_length; ++ } timer; + } u; + }; + +@@ -154,6 +168,35 @@ struct threadpool_group + struct list members; + }; + ++/* global timerqueue object */ ++static RTL_CRITICAL_SECTION_DEBUG timerqueue_debug; ++static struct ++{ ++ CRITICAL_SECTION cs; ++ BOOL thread_running; ++ ++ /* number of timer objects total */ ++ LONG num_timers; ++ ++ /* list of pending timers */ ++ struct list pending_timers; ++ RTL_CONDITION_VARIABLE update_event; ++} ++timerqueue = ++{ ++ { &timerqueue_debug, -1, 0, 0, 0, 0 }, ++ FALSE, ++ 0, ++ LIST_INIT( timerqueue.pending_timers ), ++ RTL_CONDITION_VARIABLE_INIT ++}; ++static RTL_CRITICAL_SECTION_DEBUG timerqueue_debug = ++{ ++ 0, 0, &timerqueue.cs, ++ { &timerqueue_debug.ProcessLocksList, &timerqueue_debug.ProcessLocksList }, ++ 0, 0, { (DWORD_PTR)(__FILE__ ": timerqueue.cs") } ++}; ++ + static inline struct threadpool *impl_from_TP_POOL( TP_POOL *pool ) + { + return (struct threadpool *)pool; +@@ -166,6 +209,13 @@ static inline struct threadpool_object *impl_from_TP_WORK( TP_WORK *work ) + return object; + } + ++static inline struct threadpool_object *impl_from_TP_TIMER( TP_TIMER *timer ) ++{ ++ struct threadpool_object *object = (struct threadpool_object *)timer; ++ assert( !object || object->type == TP_OBJECT_TYPE_TIMER ); ++ return object; ++} ++ + static inline struct threadpool_group *impl_from_TP_CLEANUP_GROUP( TP_CLEANUP_GROUP *group ) + { + return (struct threadpool_group *)group; +@@ -177,6 +227,7 @@ static inline struct threadpool_instance *impl_from_TP_CALLBACK_INSTANCE( TP_CAL + } + + static void CALLBACK threadpool_worker_proc( void *param ); ++static void CALLBACK timerqueue_thread_proc( void *param ); + + static NTSTATUS tp_threadpool_alloc( struct threadpool **out ); + static BOOL tp_threadpool_release( struct threadpool *pool ); +@@ -189,6 +240,222 @@ static void tp_object_shutdown( struct threadpool_object *object ); + static BOOL tp_group_release( struct threadpool_group *group ); + + /*********************************************************************** ++ * TIMERQUEUE IMPLEMENTATION ++ *********************************************************************** ++ * ++ * Based on [1] there is only one (persistent) thread which handles ++ * timer events. There is a similar implementation in ntdll/ ++ * threadpool.c, but its not directly possible to merge them because of ++ * specific implementation differences, like handling several events at ++ * once using a windowlength parameter. */ ++ ++static NTSTATUS tp_timerqueue_acquire( void ) ++{ ++ NTSTATUS status = STATUS_SUCCESS; ++ RtlEnterCriticalSection( &timerqueue.cs ); ++ ++ if (!timerqueue.thread_running) ++ { ++ HANDLE thread; ++ status = RtlCreateUserThread( GetCurrentProcess(), NULL, FALSE, NULL, 0, 0, ++ timerqueue_thread_proc, NULL, &thread, NULL ); ++ if (status == STATUS_SUCCESS) ++ { ++ NtClose( thread ); ++ timerqueue.thread_running = TRUE; ++ } ++ } ++ ++ if (!status) timerqueue.num_timers++; ++ RtlLeaveCriticalSection( &timerqueue.cs ); ++ return status; ++} ++ ++static void tp_timerqueue_release( void ) ++{ ++ RtlEnterCriticalSection( &timerqueue.cs ); ++ if (!--timerqueue.num_timers) ++ { ++ assert( list_empty( &timerqueue.pending_timers ) ); ++ RtlWakeAllConditionVariable( &timerqueue.update_event ); ++ } ++ RtlLeaveCriticalSection( &timerqueue.cs ); ++} ++ ++static void tp_timerqueue_update_timer( struct threadpool_object *new_timer, LARGE_INTEGER *timeout, ++ LONG period, LONG window_length ) ++{ ++ BOOL queue_timer = FALSE, delete_timer = FALSE; ++ struct threadpool_object *timer; ++ ULONGLONG when; ++ ++ assert( new_timer->type == TP_OBJECT_TYPE_TIMER ); ++ RtlEnterCriticalSection( &timerqueue.cs ); ++ ++ /* Remember if the timer is set or unset */ ++ new_timer->u.timer.is_timer_set = timeout != NULL; ++ ++ if (!timeout) ++ goto update_timer; ++ ++ /* A timeout of zero is a special case, it means that the callback is queued immediately */ ++ if ((when = timeout->QuadPart) == 0) ++ { ++ queue_timer = TRUE; ++ ++ if (!period) ++ { ++ timeout = NULL; ++ goto update_timer; ++ } ++ ++ when = (ULONGLONG)period * -10000; ++ } ++ ++ /* Convert relative timeouts into absolute timeouts */ ++ if ((LONGLONG)when < 0) ++ { ++ LARGE_INTEGER now; ++ NtQuerySystemTime( &now ); ++ when = now.QuadPart - when; ++ } ++ ++update_timer: ++ ++ /* If timer is still pending, then remove the old one */ ++ if (new_timer->u.timer.timer_pending) ++ { ++ list_remove( &new_timer->u.timer.timer_entry ); ++ new_timer->u.timer.timer_pending = FALSE; ++ ++ /* defer calls to tp_object_release until we are done */ ++ delete_timer = TRUE; ++ } ++ ++ /* Timer should be enabled again, add it to the queue */ ++ if (timeout) ++ { ++ interlocked_inc( &new_timer->refcount ); ++ new_timer->u.timer.timeout = when; ++ new_timer->u.timer.period = period; ++ new_timer->u.timer.window_length = window_length; ++ ++ /* insert new_timer into the timer queue */ ++ LIST_FOR_EACH_ENTRY( timer, &timerqueue.pending_timers, struct threadpool_object, u.timer.timer_entry ) ++ { ++ assert( timer->type == TP_OBJECT_TYPE_TIMER ); ++ if (new_timer->u.timer.timeout < timer->u.timer.timeout) ++ break; ++ } ++ list_add_before( &timer->u.timer.timer_entry, &new_timer->u.timer.timer_entry ); ++ ++ /* wake up thread if it should expire earlier than before */ ++ if (list_head( &timerqueue.pending_timers ) == &new_timer->u.timer.timer_entry ) ++ RtlWakeAllConditionVariable( &timerqueue.update_event ); ++ ++ new_timer->u.timer.timer_pending = TRUE; ++ } ++ ++ RtlLeaveCriticalSection( &timerqueue.cs ); ++ if (queue_timer) ++ tp_object_submit( new_timer ); ++ if (delete_timer) ++ tp_object_release( new_timer ); ++} ++ ++static void CALLBACK timerqueue_thread_proc( void *param ) ++{ ++ LARGE_INTEGER now, timeout; ++ ULONGLONG timeout_lower, timeout_upper; ++ struct threadpool_object *other_timer; ++ struct list *ptr; ++ ++ RtlEnterCriticalSection( &timerqueue.cs ); ++ ++ for (;;) ++ { ++ NtQuerySystemTime( &now ); ++ ++ while ((ptr = list_head( &timerqueue.pending_timers ))) ++ { ++ struct threadpool_object *timer = LIST_ENTRY( ptr, struct threadpool_object, u.timer.timer_entry ); ++ assert( timer->type == TP_OBJECT_TYPE_TIMER ); ++ ++ /* Timeout didn't expire yet, nothing to do */ ++ if (timer->u.timer.timeout > now.QuadPart) ++ break; ++ ++ /* Queue a new callback in one of the worker threads */ ++ list_remove( &timer->u.timer.timer_entry ); ++ tp_object_submit( timer ); ++ ++ /* Requeue the timer, except its marked for shutdown */ ++ if (!timer->shutdown && timer->u.timer.period) ++ { ++ /* Update the timeout, make sure its at least the current time (to avoid too many work items) */ ++ timer->u.timer.timeout += (ULONGLONG)timer->u.timer.period * 10000; ++ if (timer->u.timer.timeout <= now.QuadPart) ++ timer->u.timer.timeout = now.QuadPart + 1; ++ ++ /* Insert timer back into the timer queue */ ++ LIST_FOR_EACH_ENTRY( other_timer, &timerqueue.pending_timers, struct threadpool_object, u.timer.timer_entry ) ++ { ++ assert( other_timer->type == TP_OBJECT_TYPE_TIMER ); ++ if (timer->u.timer.timeout < other_timer->u.timer.timeout) ++ break; ++ } ++ list_add_before( &other_timer->u.timer.timer_entry, &timer->u.timer.timer_entry ); ++ } ++ else ++ { ++ /* We no longer need the reference to this timer object */ ++ timer->u.timer.timer_pending = FALSE; ++ tp_object_release( timer ); ++ } ++ } ++ ++ /* Determine next timeout - we use the window_length arguments to optimize wakeup times */ ++ timeout_lower = timeout_upper = TIMEOUT_INFINITE; ++ LIST_FOR_EACH_ENTRY( other_timer, &timerqueue.pending_timers, struct threadpool_object, u.timer.timer_entry ) ++ { ++ ULONGLONG new_timeout_upper; ++ assert( other_timer->type == TP_OBJECT_TYPE_TIMER ); ++ if (other_timer->u.timer.timeout >= timeout_upper) ++ break; ++ ++ timeout_lower = other_timer->u.timer.timeout; ++ new_timeout_upper = timeout_lower + (ULONGLONG)other_timer->u.timer.window_length * 10000; ++ ++ if (timeout_upper > new_timeout_upper) ++ timeout_upper = new_timeout_upper; ++ } ++ ++ ++ if (!timerqueue.num_timers) ++ { ++ /* All timers have been destroyed, if no new timers are created within some amount of ++ * time, then we can shutdown this thread. */ ++ timeout.QuadPart = (ULONGLONG)THREADPOOL_WORKER_TIMEOUT * -10000; ++ if (RtlSleepConditionVariableCS( &timerqueue.update_event, ++ &timerqueue.cs, &timeout ) == STATUS_TIMEOUT && !timerqueue.num_timers) ++ { ++ break; ++ } ++ } ++ else ++ { ++ /* Wait for timer update events or until the next timer expires. */ ++ timeout.QuadPart = timeout_lower; ++ RtlSleepConditionVariableCS( &timerqueue.update_event, &timerqueue.cs, &timeout ); ++ } ++ } ++ ++ timerqueue.thread_running = FALSE; ++ RtlLeaveCriticalSection( &timerqueue.cs ); ++} ++ ++ ++/*********************************************************************** + * THREADPOOL INSTANCE IMPLEMENTATION + ***********************************************************************/ + +@@ -433,6 +700,16 @@ static void CALLBACK threadpool_worker_proc( void *param ) + break; + } + ++ case TP_OBJECT_TYPE_TIMER: ++ { ++ TP_CALLBACK_INSTANCE *cb_instance = (TP_CALLBACK_INSTANCE *)&instance; ++ TRACE( "executing callback %p(%p, %p)\n", ++ object->u.timer.callback, cb_instance, object->userdata ); ++ object->u.timer.callback( cb_instance, object->userdata, (TP_TIMER *)object ); ++ TRACE( "callback %p returned\n", object->u.timer.callback ); ++ break; ++ } ++ + default: + FIXME( "callback type %u not implemented\n", object->type ); + break; +@@ -602,6 +879,48 @@ static NTSTATUS tp_object_alloc_work( struct threadpool_object **out, PTP_WORK_C + return STATUS_SUCCESS; + } + ++static NTSTATUS tp_object_alloc_timer( struct threadpool_object **out, PTP_TIMER_CALLBACK callback, ++ PVOID userdata, TP_CALLBACK_ENVIRON *environment ) ++{ ++ struct threadpool_object *object; ++ struct threadpool *pool; ++ NTSTATUS status; ++ ++ /* determine threadpool */ ++ pool = environment ? (struct threadpool *)environment->Pool : NULL; ++ if (!pool) pool = get_default_threadpool(); ++ if (!pool) return STATUS_NO_MEMORY; ++ ++ object = RtlAllocateHeap( GetProcessHeap(), 0, sizeof(*object) ); ++ if (!object) ++ return STATUS_NO_MEMORY; ++ ++ status = tp_timerqueue_acquire(); ++ if (status) ++ { ++ RtlFreeHeap( GetProcessHeap(), 0, object ); ++ return status; ++ } ++ ++ object->type = TP_OBJECT_TYPE_TIMER; ++ object->u.timer.callback = callback; ++ ++ object->u.timer.timer_pending = FALSE; ++ memset( &object->u.timer.timer_entry, 0, sizeof(object->u.timer.timer_entry)); ++ ++ object->u.timer.is_timer_set = FALSE; ++ object->u.timer.timeout = 0; ++ object->u.timer.period = 0; ++ object->u.timer.window_length = 0; ++ ++ tp_object_initialize( object, pool, userdata, environment ); ++ ++ TRACE("allocated object %p of type %u\n", object, object->type); ++ ++ *out = object; ++ return STATUS_SUCCESS; ++} ++ + static BOOL tp_object_release( struct threadpool_object *object ) + { + struct threadpool_group *group; +@@ -624,6 +943,13 @@ static BOOL tp_object_release( struct threadpool_object *object ) + tp_group_release( group ); + } + ++ /* release reference on the timerqueue */ ++ if (object->type == TP_OBJECT_TYPE_TIMER) ++ { ++ assert( !object->u.timer.timer_pending ); ++ tp_timerqueue_release(); ++ } ++ + /* release reference to library */ + if (object->race_dll) + LdrUnloadDll( object->race_dll ); +@@ -843,6 +1169,16 @@ NTSTATUS WINAPI TpAllocPool( TP_POOL **out, PVOID reserved ) + } + + /*********************************************************************** ++ * TpAllocTimer (NTDLL.@) ++ */ ++NTSTATUS WINAPI TpAllocTimer( TP_TIMER **out, PTP_TIMER_CALLBACK callback, PVOID userdata, ++ TP_CALLBACK_ENVIRON *environment ) ++{ ++ TRACE("%p %p %p %p\n", out, callback, userdata, environment); ++ return tp_object_alloc_timer( (struct threadpool_object **)out, callback, userdata, environment ); ++} ++ ++/*********************************************************************** + * TpAllocWork (NTDLL.@) + */ + NTSTATUS WINAPI TpAllocWork( TP_WORK **out, PTP_WORK_CALLBACK callback, PVOID userdata, +@@ -947,6 +1283,16 @@ VOID WINAPI TpDisassociateCallback( TP_CALLBACK_INSTANCE *instance ) + } + + /*********************************************************************** ++ * TpIsTimerSet (NTDLL.@) ++ */ ++BOOL WINAPI TpIsTimerSet( TP_TIMER *timer ) ++{ ++ struct threadpool_object *this = impl_from_TP_TIMER( timer ); ++ TRACE("%p\n", timer); ++ return this ? this->u.timer.is_timer_set : FALSE; ++} ++ ++/*********************************************************************** + * TpPostWork (NTDLL.@) + */ + VOID WINAPI TpPostWork( TP_WORK *work ) +@@ -995,6 +1341,20 @@ VOID WINAPI TpReleasePool( TP_POOL *pool ) + } + + /*********************************************************************** ++ * TpReleaseTimer (NTDLL.@) ++ */ ++VOID WINAPI TpReleaseTimer( TP_TIMER *timer ) ++{ ++ struct threadpool_object *this = impl_from_TP_TIMER( timer ); ++ TRACE("%p\n", timer); ++ if (this) ++ { ++ tp_object_shutdown( this ); ++ tp_object_release( this ); ++ } ++} ++ ++/*********************************************************************** + * TpReleaseWork (NTDLL.@) + */ + VOID WINAPI TpReleaseWork( TP_WORK *work ) +@@ -1030,6 +1390,16 @@ BOOL WINAPI TpSetPoolMinThreads( TP_POOL *pool, DWORD minimum ) + } + + /*********************************************************************** ++ * TpSetTimer (NTDLL.@) ++ */ ++VOID WINAPI TpSetTimer( TP_TIMER *timer, LARGE_INTEGER *timeout, LONG period, LONG window_length ) ++{ ++ struct threadpool_object *this = impl_from_TP_TIMER( timer ); ++ TRACE("%p %p %u %u\n", timer, timeout, period, window_length); ++ if (this) tp_timerqueue_update_timer( this, timeout, period, window_length ); ++} ++ ++/*********************************************************************** + * TpSimpleTryPost (NTDLL.@) + */ + NTSTATUS WINAPI TpSimpleTryPost( PTP_SIMPLE_CALLBACK callback, PVOID userdata, TP_CALLBACK_ENVIRON *environment ) +@@ -1048,6 +1418,21 @@ NTSTATUS WINAPI TpSimpleTryPost( PTP_SIMPLE_CALLBACK callback, PVOID userdata, T + } + + /*********************************************************************** ++ * TpWaitForTimer (NTDLL.@) ++ */ ++VOID WINAPI TpWaitForTimer( TP_TIMER *timer, BOOL cancel_pending ) ++{ ++ struct threadpool_object *this = impl_from_TP_TIMER( timer ); ++ TRACE("%p %d\n", timer, cancel_pending); ++ if (this) ++ { ++ if (cancel_pending) ++ tp_object_cancel( this, FALSE, NULL ); ++ tp_object_wait( this ); ++ } ++} ++ ++/*********************************************************************** + * TpWaitForWork (NTDLL.@) + */ + VOID WINAPI TpWaitForWork( TP_WORK *work, BOOL cancel_pending ) +-- +2.2.2 + diff --git a/patches/ntdll-Vista_Threadpool/0005-ntdll-tests-Add-tests-for-Tp-threadpool-functions.patch b/patches/ntdll-Vista_Threadpool/0005-ntdll-tests-Add-tests-for-Tp-threadpool-functions.patch new file mode 100644 index 00000000..3c2658ec --- /dev/null +++ b/patches/ntdll-Vista_Threadpool/0005-ntdll-tests-Add-tests-for-Tp-threadpool-functions.patch @@ -0,0 +1,809 @@ +From 19b57d882236efe0708f7d495ca7301a6c4955e4 Mon Sep 17 00:00:00 2001 +From: Sebastian Lackner +Date: Sun, 1 Feb 2015 18:14:09 +0100 +Subject: ntdll/tests: Add tests for Tp* threadpool functions. + +--- + dlls/ntdll/tests/Makefile.in | 1 + + dlls/ntdll/tests/threadpool.c | 779 ++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 780 insertions(+) + create mode 100644 dlls/ntdll/tests/threadpool.c + +diff --git a/dlls/ntdll/tests/Makefile.in b/dlls/ntdll/tests/Makefile.in +index 81b4466..fc352dd 100644 +--- a/dlls/ntdll/tests/Makefile.in ++++ b/dlls/ntdll/tests/Makefile.in +@@ -21,4 +21,5 @@ C_SRCS = \ + rtlbitmap.c \ + rtlstr.c \ + string.c \ ++ threadpool.c \ + time.c +diff --git a/dlls/ntdll/tests/threadpool.c b/dlls/ntdll/tests/threadpool.c +new file mode 100644 +index 0000000..434bd9a +--- /dev/null ++++ b/dlls/ntdll/tests/threadpool.c +@@ -0,0 +1,779 @@ ++/* Unit test suite for Threadpool functions ++ * ++ * Copyright 2015 Sebastian Lackner ++ * ++ * This library is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU Lesser General Public ++ * License as published by the Free Software Foundation; either ++ * version 2.1 of the License, or (at your option) any later version. ++ * ++ * This library is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public ++ * License along with this library; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA ++ */ ++ ++#include "ntdll_test.h" ++ ++static HMODULE hntdll = 0; ++static NTSTATUS (WINAPI *pTpAllocCleanupGroup)(TP_CLEANUP_GROUP **); ++static NTSTATUS (WINAPI *pTpAllocIoCompletion)(TP_IO **,HANDLE,PTP_WIN32_IO_CALLBACK,PVOID,TP_CALLBACK_ENVIRON *); ++static NTSTATUS (WINAPI *pTpAllocPool)(TP_POOL **,PVOID); ++static NTSTATUS (WINAPI *pTpAllocTimer)(TP_TIMER **,PTP_TIMER_CALLBACK,PVOID,TP_CALLBACK_ENVIRON *); ++static NTSTATUS (WINAPI *pTpAllocWait)(TP_WAIT **,PTP_WAIT_CALLBACK,PVOID,TP_CALLBACK_ENVIRON *); ++static NTSTATUS (WINAPI *pTpAllocWork)(TP_WORK **,PTP_WORK_CALLBACK,PVOID,TP_CALLBACK_ENVIRON *); ++static VOID (WINAPI *pTpCallbackLeaveCriticalSectionOnCompletion)(TP_CALLBACK_INSTANCE *,CRITICAL_SECTION *); ++static NTSTATUS (WINAPI *pTpCallbackMayRunLong)(TP_CALLBACK_INSTANCE *); ++static VOID (WINAPI *pTpCallbackReleaseMutexOnCompletion)(TP_CALLBACK_INSTANCE *,HANDLE); ++static VOID (WINAPI *pTpCallbackReleaseSemaphoreOnCompletion)(TP_CALLBACK_INSTANCE *,HANDLE,DWORD); ++static VOID (WINAPI *pTpCallbackSetEventOnCompletion)(TP_CALLBACK_INSTANCE *,HANDLE); ++static VOID (WINAPI *pTpCallbackUnloadDllOnCompletion)(TP_CALLBACK_INSTANCE *,HMODULE); ++static VOID (WINAPI *pTpCancelAsyncIoOperation)(TP_IO *); ++static VOID (WINAPI *pTpDisassociateCallback)(TP_CALLBACK_INSTANCE *); ++static BOOL (WINAPI *pTpIsTimerSet)(TP_TIMER *); ++static VOID (WINAPI *pTpPostWork)(TP_WORK *); ++static VOID (WINAPI *pTpReleaseCleanupGroup)(TP_CLEANUP_GROUP *); ++static VOID (WINAPI *pTpReleaseCleanupGroupMembers)(TP_CLEANUP_GROUP *,BOOL,PVOID); ++static VOID (WINAPI *pTpReleaseIoCompletion)(TP_IO *); ++static VOID (WINAPI *pTpReleasePool)(TP_POOL *); ++static VOID (WINAPI *pTpReleaseTimer)(TP_TIMER *); ++static VOID (WINAPI *pTpReleaseWait)(TP_WAIT *); ++static VOID (WINAPI *pTpReleaseWork)(TP_WORK *); ++static VOID (WINAPI *pTpSetPoolMaxThreads)(TP_POOL *,DWORD); ++static BOOL (WINAPI *pTpSetPoolMinThreads)(TP_POOL *,DWORD); ++static VOID (WINAPI *pTpSetTimer)(TP_TIMER *,LARGE_INTEGER *,LONG,LONG); ++static VOID (WINAPI *pTpSetWait)(TP_WAIT *,HANDLE,LARGE_INTEGER *); ++static NTSTATUS (WINAPI *pTpSimpleTryPost)(PTP_SIMPLE_CALLBACK,PVOID,TP_CALLBACK_ENVIRON *); ++static VOID (WINAPI *pTpStartAsyncIoOperation)(TP_IO *); ++static VOID (WINAPI *pTpWaitForIoCompletion)(TP_IO *,BOOL); ++static VOID (WINAPI *pTpWaitForTimer)(TP_TIMER *,BOOL); ++static VOID (WINAPI *pTpWaitForWait)(TP_WAIT *,BOOL); ++static VOID (WINAPI *pTpWaitForWork)(TP_WORK *,BOOL); ++ ++#define NTDLL_GET_PROC(func) \ ++ do \ ++ { \ ++ p ## func = (void *)GetProcAddress(hntdll, #func); \ ++ if (!p ## func) trace("Failed to get address for %s\n", #func); \ ++ } \ ++ while (0) ++ ++static BOOL init_threadpool(void) ++{ ++ hntdll = GetModuleHandleA("ntdll"); ++ if (!hntdll) ++ { ++ win_skip("Could not load ntdll\n"); ++ return FALSE; ++ } ++ ++ NTDLL_GET_PROC(TpAllocCleanupGroup); ++ NTDLL_GET_PROC(TpAllocIoCompletion); ++ NTDLL_GET_PROC(TpAllocPool); ++ NTDLL_GET_PROC(TpAllocTimer); ++ NTDLL_GET_PROC(TpAllocWait); ++ NTDLL_GET_PROC(TpAllocWork); ++ NTDLL_GET_PROC(TpCallbackLeaveCriticalSectionOnCompletion); ++ NTDLL_GET_PROC(TpCallbackMayRunLong); ++ NTDLL_GET_PROC(TpCallbackReleaseMutexOnCompletion); ++ NTDLL_GET_PROC(TpCallbackReleaseSemaphoreOnCompletion); ++ NTDLL_GET_PROC(TpCallbackSetEventOnCompletion); ++ NTDLL_GET_PROC(TpCallbackUnloadDllOnCompletion); ++ NTDLL_GET_PROC(TpCancelAsyncIoOperation); ++ NTDLL_GET_PROC(TpDisassociateCallback); ++ NTDLL_GET_PROC(TpIsTimerSet); ++ NTDLL_GET_PROC(TpPostWork); ++ NTDLL_GET_PROC(TpReleaseCleanupGroup); ++ NTDLL_GET_PROC(TpReleaseCleanupGroupMembers); ++ NTDLL_GET_PROC(TpReleaseIoCompletion); ++ NTDLL_GET_PROC(TpReleasePool); ++ NTDLL_GET_PROC(TpReleaseTimer); ++ NTDLL_GET_PROC(TpReleaseWait); ++ NTDLL_GET_PROC(TpReleaseWork); ++ NTDLL_GET_PROC(TpSetPoolMaxThreads); ++ NTDLL_GET_PROC(TpSetPoolMinThreads); ++ NTDLL_GET_PROC(TpSetTimer); ++ NTDLL_GET_PROC(TpSetWait); ++ NTDLL_GET_PROC(TpSimpleTryPost); ++ NTDLL_GET_PROC(TpStartAsyncIoOperation); ++ NTDLL_GET_PROC(TpWaitForIoCompletion); ++ NTDLL_GET_PROC(TpWaitForTimer); ++ NTDLL_GET_PROC(TpWaitForWait); ++ NTDLL_GET_PROC(TpWaitForWork); ++ ++ if (!pTpAllocPool) ++ { ++ win_skip("Threadpool functions not supported, skipping tests\n"); ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ ++#undef NTDLL_GET_PROC ++ ++ ++static void CALLBACK simple_cb(TP_CALLBACK_INSTANCE *instance, void *userdata) ++{ ++ trace("Running simple callback\n"); ++ Sleep(100); ++ InterlockedIncrement( (LONG *)userdata ); ++} ++ ++static void test_default_threadpool(void) ++{ ++ TP_CALLBACK_ENVIRON environment; ++ NTSTATUS status; ++ LONG userdata; ++ ++ /* Post the callback and manually wait until it has finished */ ++ userdata = 0; ++ memset(&environment, 0, sizeof(environment)); ++ environment.Version = 1; ++ status = pTpSimpleTryPost(simple_cb, &userdata, &environment); ++ ok(!status, "TpSimpleTryPost failed with status %x\n", status); ++ while (userdata == 0) Sleep(10); ++ ok(userdata == 1, "expected userdata = 1, got %u\n", userdata); ++} ++ ++static void test_tp_simple(void) ++{ ++ TP_CALLBACK_ENVIRON environment; ++ TP_CLEANUP_GROUP *group; ++ TP_POOL *pool; ++ NTSTATUS status; ++ LONG userdata; ++ int i; ++ ++ /* Allocate new threadpool */ ++ pool = NULL; ++ status = pTpAllocPool(&pool, NULL); ++ ok(!status, "TpAllocPool failed with status %x\n", status); ++ ok(pool != NULL, "expected pool != NULL\n"); ++ ++ /* Post the callback and manually wait until it has finished */ ++ userdata = 0; ++ memset(&environment, 0, sizeof(environment)); ++ environment.Version = 1; ++ environment.Pool = pool; ++ status = pTpSimpleTryPost(simple_cb, &userdata, &environment); ++ ok(!status, "TpSimpleTryPost failed with status %x\n", status); ++ while (userdata == 0) Sleep(10); ++ ok(userdata == 1, "expected userdata = 1, got %u\n", userdata); ++ ++ /* Test with invalid version number */ ++ userdata = 0; ++ memset(&environment, 0, sizeof(environment)); ++ environment.Version = 9999; ++ environment.Pool = pool; ++ status = pTpSimpleTryPost(simple_cb, &userdata, &environment); ++ ok(!status, "TpSimpleTryPost failed with status %x\n", status); ++ while (userdata == 0) Sleep(10); ++ ok(userdata == 1, "expected userdata = 1, got %u\n", userdata); ++ ++ /* Allocate a cleanup group for synchronization */ ++ group = NULL; ++ status = pTpAllocCleanupGroup(&group); ++ ok(!status, "TpAllocCleanupGroup failed with status %x\n", status); ++ ok(group != NULL, "expected pool != NULL\n"); ++ ++ userdata = 0; ++ memset(&environment, 0, sizeof(environment)); ++ environment.Version = 1; ++ environment.Pool = pool; ++ environment.CleanupGroup = group; ++ status = pTpSimpleTryPost(simple_cb, &userdata, &environment); ++ ok(!status, "TpSimpleTryPost failed with status %x\n", status); ++ ++ pTpReleaseCleanupGroupMembers(group, FALSE, NULL); ++ ok(userdata == 1, "expected userdata = 1, got %u\n", userdata); ++ ++ /* Test cancellation of pending simple callbacks. */ ++ userdata = 0; ++ memset(&environment, 0, sizeof(environment)); ++ environment.Version = 1; ++ environment.Pool = pool; ++ environment.CleanupGroup = group; ++ for (i = 0; i < 100; i++) ++ { ++ status = pTpSimpleTryPost(simple_cb, &userdata, &environment); ++ ok(!status, "TpSimpleTryPost failed with status %x\n", status); ++ } ++ ++ pTpReleaseCleanupGroupMembers(group, TRUE, NULL); ++ ok(userdata < 100, "expected userdata < 100, got %u\n", userdata); ++ ++ /* Cleanup */ ++ pTpReleaseCleanupGroup(group); ++ pTpReleasePool(pool); ++} ++ ++static void CALLBACK work_cb(TP_CALLBACK_INSTANCE *instance, void *userdata, TP_WORK *work) ++{ ++ trace("Running work callback\n"); ++ Sleep(10); ++ InterlockedIncrement( (LONG *)userdata ); ++} ++ ++static void CALLBACK work2_cb(TP_CALLBACK_INSTANCE *instance, void *userdata, TP_WORK *work) ++{ ++ trace("Running work2 callback\n"); ++ Sleep(10); ++ InterlockedExchangeAdd( (LONG *)userdata, 0x10000 ); ++} ++ ++static void test_tp_work(void) ++{ ++ TP_CALLBACK_ENVIRON environment; ++ TP_WORK *work; ++ TP_POOL *pool; ++ NTSTATUS status; ++ LONG userdata; ++ int i; ++ ++ /* Allocate new threadpool */ ++ pool = NULL; ++ status = pTpAllocPool(&pool, NULL); ++ ok(!status, "TpAllocPool failed with status %x\n", status); ++ ok(pool != NULL, "expected pool != NULL\n"); ++ ++ /* Allocate new work item */ ++ work = NULL; ++ memset(&environment, 0, sizeof(environment)); ++ environment.Version = 1; ++ environment.Pool = pool; ++ status = pTpAllocWork(&work, work_cb, &userdata, &environment); ++ ok(!status, "TpAllocWork failed with status %x\n", status); ++ ok(work != NULL, "expected work != NULL\n"); ++ ++ /* Post 10 identical work items at once */ ++ userdata = 0; ++ for (i = 0; i < 10; i++) ++ pTpPostWork(work); ++ pTpWaitForWork(work, FALSE); ++ ok(userdata == 10, "expected userdata = 10, got %u\n", userdata); ++ ++ /* Add more tasks and cancel them immediately */ ++ userdata = 0; ++ for (i = 0; i < 10; i++) ++ pTpPostWork(work); ++ pTpWaitForWork(work, TRUE); ++ ok(userdata < 10, "expected userdata < 10, got %u\n", userdata); ++ ++ /* Cleanup */ ++ pTpReleaseWork(work); ++ pTpReleasePool(pool); ++} ++ ++static void test_tp_work_scheduler(void) ++{ ++ TP_CALLBACK_ENVIRON environment; ++ TP_CLEANUP_GROUP *group; ++ TP_WORK *work, *work2; ++ TP_POOL *pool; ++ NTSTATUS status; ++ LONG userdata; ++ int i; ++ ++ /* Allocate new threadpool */ ++ pool = NULL; ++ status = pTpAllocPool(&pool, NULL); ++ ok(!status, "TpAllocPool failed with status %x\n", status); ++ ok(pool != NULL, "expected pool != NULL\n"); ++ ++ /* We limit the pool to a single thread */ ++ pTpSetPoolMaxThreads(pool, 1); ++ ++ /* Create a cleanup group */ ++ group = NULL; ++ status = pTpAllocCleanupGroup(&group); ++ ok(!status, "TpAllocCleanupGroup failed with status %x\n", status); ++ ok(group != NULL, "expected pool != NULL\n"); ++ ++ /* The first work item has no cleanup group associated */ ++ work = NULL; ++ memset(&environment, 0, sizeof(environment)); ++ environment.Version = 1; ++ environment.Pool = pool; ++ status = pTpAllocWork(&work, work_cb, &userdata, &environment); ++ ok(!status, "TpAllocWork failed with status %x\n", status); ++ ok(work != NULL, "expected work != NULL\n"); ++ ++ /* Allocate a second work item with a cleanup group */ ++ work2 = NULL; ++ memset(&environment, 0, sizeof(environment)); ++ environment.Version = 1; ++ environment.Pool = pool; ++ environment.CleanupGroup = group; ++ status = pTpAllocWork(&work2, work2_cb, &userdata, &environment); ++ ok(!status, "TpAllocWork failed with status %x\n", status); ++ ok(work2 != NULL, "expected work2 != NULL\n"); ++ ++ /* The 'work' callbacks are not blocking execution of work2 callbacks */ ++ userdata = 0; ++ for (i = 0; i < 10; i++) ++ pTpPostWork(work); ++ for (i = 0; i < 10; i++) ++ pTpPostWork(work2); ++ Sleep(30); ++ pTpWaitForWork(work, TRUE); ++ pTpWaitForWork(work2, TRUE); ++ ok(userdata & 0xffff, "expected userdata & 0xffff != 0, got %u\n", userdata & 0xffff); ++ ok(userdata >> 16, "expected userdata >> 16 != 0, got %u\n", userdata >> 16); ++ ++ /* Test ReleaseCleanupGroupMembers on a work item */ ++ userdata = 0; ++ for (i = 0; i < 100; i++) ++ pTpPostWork(work); ++ for (i = 0; i < 10; i++) ++ pTpPostWork(work2); ++ pTpReleaseCleanupGroupMembers(group, FALSE, NULL); ++ pTpWaitForWork(work, TRUE); ++ ok((userdata & 0xffff) < 100, "expected userdata & 0xffff < 100, got %u\n", userdata & 0xffff); ++ ok(userdata >> 16 == 10, "expected userdata >> 16 == 10, got %u\n", userdata >> 16); ++ ++ /* Cleanup */ ++ pTpReleaseWork(work); ++ pTpReleaseCleanupGroup(group); ++ pTpReleasePool(pool); ++} ++ ++static void CALLBACK instance_cb(TP_CALLBACK_INSTANCE *instance, void *userdata) ++{ ++ trace("Running instance callback\n"); ++ pTpCallbackMayRunLong(instance); ++ Sleep(100); ++ InterlockedIncrement( (LONG *)userdata ); ++} ++ ++static void CALLBACK instance2_cb(TP_CALLBACK_INSTANCE *instance, void *userdata) ++{ ++ trace("Running instance2 callback\n"); ++ ok(*(LONG *)userdata == 1, "expected *userdata = 1, got %u\n", *(LONG *)userdata); ++ InterlockedIncrement( (LONG *)userdata ); ++} ++ ++static void CALLBACK instance3_cb(TP_CALLBACK_INSTANCE *instance, void *userdata) ++{ ++ trace("Running instance3 callback\n"); ++ pTpDisassociateCallback(instance); ++ Sleep(100); ++ InterlockedIncrement( (LONG *)userdata ); ++} ++ ++static void CALLBACK instance4_cb(TP_CALLBACK_INSTANCE *instance, void *userdata) ++{ ++ trace("Running instance4 callback\n"); ++ pTpCallbackReleaseSemaphoreOnCompletion(instance, userdata, 1); ++} ++ ++static HANDLE instance_finalization_semaphore; ++ ++static void CALLBACK instance_finalization_cb(TP_CALLBACK_INSTANCE *instance, void *userdata) ++{ ++ DWORD ret; ++ trace("Running instance finalization callback\n"); ++ ok(*(LONG *)userdata == 1, "expected *userdata = 1, got %u\n", *(LONG *)userdata); ++ ++ /* Make sure that this callback is called before the regular instance cleanup tasks */ ++ ret = WaitForSingleObject(instance_finalization_semaphore, 100); ++ ok(ret == WAIT_TIMEOUT, "expected ret = WAIT_TIMEOUT, got %u\n", ret); ++ ++ InterlockedIncrement( (LONG *)userdata ); ++} ++ ++static void CALLBACK instance5_cb(TP_CALLBACK_INSTANCE *instance, void *userdata) ++{ ++ trace("Running instance5 callback\n"); ++ pTpCallbackReleaseSemaphoreOnCompletion(instance, instance_finalization_semaphore, 1); ++ InterlockedIncrement( (LONG *)userdata ); ++} ++ ++ ++static void test_tp_instance(void) ++{ ++ TP_CALLBACK_ENVIRON environment; ++ TP_CLEANUP_GROUP *group; ++ TP_POOL *pool; ++ NTSTATUS status; ++ LONG userdata; ++ HANDLE semaphore; ++ DWORD ret; ++ ++ /* Allocate new threadpool */ ++ pool = NULL; ++ status = pTpAllocPool(&pool, NULL); ++ ok(!status, "TpAllocPool failed with status %x\n", status); ++ ok(pool != NULL, "expected pool != NULL\n"); ++ ++ /* We limit the pool to a single thread */ ++ pTpSetPoolMaxThreads(pool, 1); ++ ++ /* Test behaviour of TpCallbackMayRunLong when the max number of threads is reached */ ++ userdata = 0; ++ memset(&environment, 0, sizeof(environment)); ++ environment.Version = 1; ++ environment.Pool = pool; ++ status = pTpSimpleTryPost(instance_cb, &userdata, &environment); ++ ok(!status, "TpSimpleTryPost failed with status %x\n", status); ++ status = pTpSimpleTryPost(instance2_cb, &userdata, &environment); ++ ok(!status, "TpSimpleTryPost failed with status %x\n", status); ++ while (userdata != 2) Sleep(10); ++ ++ /* Test behaviour of TpDisassociateCallback on wait functions */ ++ group = NULL; ++ status = pTpAllocCleanupGroup(&group); ++ ok(!status, "TpAllocCleanupGroup failed with status %x\n", status); ++ ok(group != NULL, "expected pool != NULL\n"); ++ ++ userdata = 0; ++ memset(&environment, 0, sizeof(environment)); ++ environment.Version = 1; ++ environment.Pool = pool; ++ environment.CleanupGroup = group; ++ status = pTpSimpleTryPost(instance3_cb, &userdata, &environment); ++ ok(!status, "TpSimpleTryPost failed with status %x\n", status); ++ ++ pTpReleaseCleanupGroupMembers(group, FALSE, NULL); ++ todo_wine /* behaviour contradicts the MSDN description? */ ++ ok(userdata == 1, "expected userdata = 1, got %u\n", userdata); ++ while (userdata == 0) Sleep(10); ++ ++ pTpReleaseCleanupGroup(group); ++ ++ /* Test for TpCallbackReleaseSemaphoreOnCompletion */ ++ semaphore = CreateSemaphoreW(NULL, 0, 1, NULL); ++ ok(semaphore != NULL, "failed to create semaphore\n"); ++ memset(&environment, 0, sizeof(environment)); ++ environment.Version = 1; ++ environment.Pool = pool; ++ status = pTpSimpleTryPost(instance4_cb, semaphore, &environment); ++ ok(!status, "TpSimpleTryPost failed with status %x\n", status); ++ ret = WaitForSingleObject(semaphore, 1000); ++ ok(ret == WAIT_OBJECT_0, "expected ret = WAIT_OBJECT_0, got %u\n", ret); ++ ++ /* Test for finalization callback */ ++ userdata = 0; ++ instance_finalization_semaphore = semaphore; ++ memset(&environment, 0, sizeof(environment)); ++ environment.Version = 1; ++ environment.Pool = pool; ++ environment.FinalizationCallback = instance_finalization_cb; ++ status = pTpSimpleTryPost(instance5_cb, &userdata, &environment); ++ ok(!status, "TpSimpleTryPost failed with status %x\n", status); ++ ret = WaitForSingleObject(semaphore, 1000); ++ ok(ret == WAIT_OBJECT_0, "expected ret = WAIT_OBJECT_0, got %u\n", ret); ++ while (userdata != 2) Sleep(10); ++ ++ CloseHandle(semaphore); ++ ++ /* Cleanup */ ++ pTpReleasePool(pool); ++} ++ ++static DWORD group_cancel_tid; ++ ++static void CALLBACK group_cancel_cleanup_cb(void *object, void *userdata) ++{ ++ trace("Running group cancel cleanup callback\n"); ++ InterlockedIncrement( (LONG *)userdata ); ++ group_cancel_tid = GetCurrentThreadId(); ++} ++ ++static void CALLBACK group_cancel_cb(TP_CALLBACK_INSTANCE *instance, void *userdata) ++{ ++ trace("Running group cancel callback\n"); ++ pTpCallbackMayRunLong(instance); ++ Sleep(100); ++ ok(*(LONG *)userdata == 1, "expected *userdata = 1, got %u\n", *(LONG *)userdata); ++ InterlockedIncrement( (LONG *)userdata ); ++} ++ ++static void CALLBACK dummy_cb(TP_CALLBACK_INSTANCE *instance, void *userdata) ++{ ++ ok(0, "Unexpected call to dummy_cb function\n"); ++} ++ ++static void test_tp_group_cancel(void) ++{ ++ TP_CALLBACK_ENVIRON environment; ++ TP_CLEANUP_GROUP *group; ++ TP_WORK *work; ++ TP_POOL *pool; ++ NTSTATUS status; ++ LONG userdata, userdata2; ++ int i; ++ ++ /* Allocate new threadpool */ ++ pool = NULL; ++ status = pTpAllocPool(&pool, NULL); ++ ok(!status, "TpAllocPool failed with status %x\n", status); ++ ok(pool != NULL, "expected pool != NULL\n"); ++ ++ /* We limit the pool to a single thread */ ++ pTpSetPoolMaxThreads(pool, 1); ++ ++ /* Allocate a cleanup group */ ++ group = NULL; ++ status = pTpAllocCleanupGroup(&group); ++ ok(!status, "TpAllocCleanupGroup failed with status %x\n", status); ++ ok(group != NULL, "expected pool != NULL\n"); ++ ++ /* Test execution of cancellation callback */ ++ userdata = 0; ++ memset(&environment, 0, sizeof(environment)); ++ environment.Version = 1; ++ environment.Pool = pool; ++ status = pTpSimpleTryPost(group_cancel_cb, &userdata, &environment); ++ ok(!status, "TpSimpleTryPost failed with status %x\n", status); ++ ++ memset(&environment, 0, sizeof(environment)); ++ environment.Version = 1; ++ environment.Pool = pool; ++ environment.CleanupGroup = group; ++ environment.CleanupGroupCancelCallback = group_cancel_cleanup_cb; ++ status = pTpSimpleTryPost(dummy_cb, NULL, &environment); ++ ok(!status, "TpSimpleTryPost failed with status %x\n", status); ++ ++ group_cancel_tid = 0; ++ pTpReleaseCleanupGroupMembers(group, TRUE, &userdata); ++ ok(userdata == 1, "expected userdata = 1, got %u\n", userdata); ++ ok(group_cancel_tid == GetCurrentThreadId(), "expected tid %x, got %x\n", ++ GetCurrentThreadId(), group_cancel_tid); ++ while (userdata != 2) Sleep(10); ++ ++ /* Test cancellation callback for elements with multiple instances */ ++ /* Allocate new work item */ ++ work = NULL; ++ memset(&environment, 0, sizeof(environment)); ++ environment.Version = 1; ++ environment.Pool = pool; ++ environment.CleanupGroup = group; ++ environment.CleanupGroupCancelCallback = group_cancel_cleanup_cb; ++ status = pTpAllocWork(&work, work_cb, &userdata, &environment); ++ ok(!status, "TpAllocWork failed with status %x\n", status); ++ ok(work != NULL, "expected work != NULL\n"); ++ ++ /* Post 10 identical work items at once */ ++ userdata = userdata2 = 0; ++ for (i = 0; i < 10; i++) ++ pTpPostWork(work); ++ ++ /* Check if we get multiple cancellation callbacks */ ++ group_cancel_tid = 0; ++ pTpReleaseCleanupGroupMembers(group, TRUE, &userdata2); ++ ok(userdata < 10, "expected userdata < 10, got %u\n", userdata); ++ ok(userdata2 == 1, "expected only one cancellation callback, got %u\n", userdata2); ++ ok(group_cancel_tid == GetCurrentThreadId(), "expected tid %x, got %x\n", ++ GetCurrentThreadId(), group_cancel_tid); ++ ++ /* Cleanup */ ++ pTpReleaseCleanupGroup(group); ++ pTpReleasePool(pool); ++} ++ ++static void CALLBACK timer_cb(TP_CALLBACK_INSTANCE *instance, void *userdata, TP_TIMER *timer) ++{ ++ trace("Running timer callback\n"); ++ InterlockedIncrement( (LONG *)userdata ); ++} ++ ++static void test_tp_timer(void) ++{ ++ TP_CALLBACK_ENVIRON environment; ++ TP_TIMER *timer; ++ TP_POOL *pool; ++ NTSTATUS status; ++ LONG userdata; ++ BOOL success; ++ LARGE_INTEGER when; ++ DWORD ticks; ++ ++ /* Allocate new threadpool */ ++ pool = NULL; ++ status = pTpAllocPool(&pool, NULL); ++ ok(!status, "TpAllocPool failed with status %x\n", status); ++ ok(pool != NULL, "expected pool != NULL\n"); ++ ++ /* Allocate new timer item */ ++ timer = NULL; ++ memset(&environment, 0, sizeof(environment)); ++ environment.Version = 1; ++ environment.Pool = pool; ++ status = pTpAllocTimer(&timer, timer_cb, &userdata, &environment); ++ ok(!status, "TpAllocTimer failed with status %x\n", status); ++ ok(timer != NULL, "expected timer != NULL\n"); ++ ++ success = pTpIsTimerSet(timer); ++ ok(!success, "expected TpIsTimerSet(...) = FALSE\n"); ++ ++ /* Set a relative timeout */ ++ userdata = 0; ++ when.QuadPart = (ULONGLONG)500 * -10000; ++ pTpSetTimer(timer, &when, 0, 0); ++ success = pTpIsTimerSet(timer); ++ ok(success, "expected TpIsTimerSet(...) = TRUE\n"); ++ ++ /* Wait until timer has triggered */ ++ pTpWaitForTimer(timer, FALSE); ++ Sleep(250); ++ ok(userdata == 0, "expected userdata = 0, got %u\n", userdata); ++ while (userdata == 0) Sleep(10); ++ ok(userdata == 1, "expected userdata = 1, got %u\n", userdata); ++ success = pTpIsTimerSet(timer); ++ ok(success, "expected TpIsTimerSet(...) = TRUE\n"); ++ ++ /* Set an absolute timeout */ ++ userdata = 0; ++ NtQuerySystemTime( &when ); ++ when.QuadPart += (ULONGLONG)500 * 10000; ++ pTpSetTimer(timer, &when, 0, 0); ++ success = pTpIsTimerSet(timer); ++ ok(success, "expected TpIsTimerSet(...) = TRUE\n"); ++ ++ /* Wait until timer has triggered */ ++ pTpWaitForTimer(timer, FALSE); ++ Sleep(250); ++ ok(userdata == 0, "expected userdata = 0, got %u\n", userdata); ++ while (userdata == 0) Sleep(10); ++ ok(userdata == 1, "expected userdata = 1, got %u\n", userdata); ++ success = pTpIsTimerSet(timer); ++ ok(success, "expected TpIsTimerSet(...) = TRUE\n"); ++ ++ /* Test a relative timeout repeated periodically */ ++ userdata = 0; ++ when.QuadPart = (ULONGLONG)50 * -10000; ++ pTpSetTimer(timer, &when, 50, 0); ++ success = pTpIsTimerSet(timer); ++ ok(success, "expected TpIsTimerSet(...) = TRUE\n"); ++ ++ /* Wait until the timer was triggered a couple of times */ ++ ticks = GetTickCount(); ++ while (userdata < 100) Sleep(10); ++ ticks = GetTickCount() - ticks; ++ pTpSetTimer(timer, NULL, 0, 0); ++ pTpWaitForTimer(timer, TRUE); ++ ok(ticks >= 4500 && ticks <= 5500, "expected approximately 5000 ticks, got %u\n", ticks); ++ success = pTpIsTimerSet(timer); ++ ok(!success, "expected TpIsTimerSet(...) = FALSE\n"); ++ ++ /* Test with zero timeout, we expect a call immediately */ ++ userdata = 0; ++ when.QuadPart = 0; ++ pTpSetTimer(timer, &when, 0, 0); ++ success = pTpIsTimerSet(timer); ++ ok(success, "expected TpIsTimerSet(...) = TRUE\n"); ++ ++ /* Wait until timer has triggered */ ++ pTpWaitForTimer(timer, FALSE); ++ ok(userdata == 1 || broken(userdata == 0) /* Win 8 */, ++ "expected userdata = 1, got %u\n", userdata); ++ while (userdata == 0) Sleep(10); ++ success = pTpIsTimerSet(timer); ++ ok(success, "expected TpIsTimerSet(...) = TRUE\n"); ++ ++ /* Unset the timer again */ ++ pTpSetTimer(timer, NULL, 0, 0); ++ success = pTpIsTimerSet(timer); ++ ok(!success, "expected TpIsTimerSet(...) = FALSE\n"); ++ ++ /* Cleanup */ ++ pTpReleaseTimer(timer); ++ pTpReleasePool(pool); ++} ++ ++static void CALLBACK window_length_cb(TP_CALLBACK_INSTANCE *instance, void *userdata, TP_TIMER *timer) ++{ ++ trace("Running window length callback\n"); ++ (*(DWORD *)userdata) = GetTickCount(); ++} ++ ++static void test_tp_window_length(void) ++{ ++ TP_CALLBACK_ENVIRON environment; ++ TP_TIMER *timer, *timer2; ++ DWORD ticks, ticks2; ++ TP_POOL *pool; ++ NTSTATUS status; ++ LARGE_INTEGER when; ++ ++ /* Allocate new threadpool */ ++ pool = NULL; ++ status = pTpAllocPool(&pool, NULL); ++ ok(!status, "TpAllocPool failed with status %x\n", status); ++ ok(pool != NULL, "expected pool != NULL\n"); ++ ++ /* Allocate two identical timers */ ++ timer = NULL; ++ memset(&environment, 0, sizeof(environment)); ++ environment.Version = 1; ++ environment.Pool = pool; ++ status = pTpAllocTimer(&timer, window_length_cb, &ticks, &environment); ++ ok(!status, "TpAllocTimer failed with status %x\n", status); ++ ok(timer != NULL, "expected timer != NULL\n"); ++ ++ status = pTpAllocTimer(&timer2, window_length_cb, &ticks2, &environment); ++ ok(!status, "TpAllocTimer failed with status %x\n", status); ++ ok(timer2 != NULL, "expected timer2 != NULL\n"); ++ ++ /* Choose parameters so that timers are not merged */ ++ ticks = ticks2 = 0; ++ NtQuerySystemTime( &when ); ++ when.QuadPart += (ULONGLONG)500 * 10000; ++ pTpSetTimer(timer2, &when, 0, 0); ++ Sleep(50); ++ when.QuadPart -= (ULONGLONG)400 * 10000; ++ pTpSetTimer(timer, &when, 0, 200); ++ while (!ticks || !ticks2) Sleep(10); ++ ok(ticks2 >= ticks + 150, "expected that timers are not merged\n"); ++ ++ /* On Windows the timers also get merged in this case */ ++ ticks = ticks2 = 0; ++ NtQuerySystemTime( &when ); ++ when.QuadPart += (ULONGLONG)100 * 10000; ++ pTpSetTimer(timer, &when, 0, 450); ++ Sleep(50); ++ when.QuadPart += (ULONGLONG)400 * 10000; ++ pTpSetTimer(timer2, &when, 0, 0); ++ while (!ticks || !ticks2) Sleep(10); ++ todo_wine ++ ok(ticks2 >= ticks - 50 && ticks2 <= ticks + 50, "expected that timers are merged\n"); ++ ++ /* Timers will be merged */ ++ ticks = ticks2 = 0; ++ NtQuerySystemTime( &when ); ++ when.QuadPart += (ULONGLONG)500 * 10000; ++ pTpSetTimer(timer2, &when, 0, 0); ++ Sleep(50); ++ when.QuadPart -= (ULONGLONG)400 * 10000; ++ pTpSetTimer(timer, &when, 0, 450); ++ while (!ticks || !ticks2) Sleep(10); ++ ok(ticks2 >= ticks - 50 && ticks2 <= ticks + 50, "expected that timers are merged\n"); ++ ++ /* Cleanup */ ++ pTpReleaseTimer(timer); ++ pTpReleaseTimer(timer2); ++ pTpReleasePool(pool); ++} ++ ++START_TEST(threadpool) ++{ ++ if(!init_threadpool()) ++ return; ++ ++ test_default_threadpool(); ++ test_tp_simple(); ++ test_tp_work(); ++ test_tp_work_scheduler(); ++ test_tp_instance(); ++ test_tp_group_cancel(); ++ test_tp_timer(); ++ test_tp_window_length(); ++ ++ /* FIXME: Make sure worker threads have terminated before. */ ++ Sleep(100); ++} +-- +2.2.2 + diff --git a/patches/ntdll-Vista_Threadpool/0006-kernel32-Forward-various-threadpool-functions-to-ntd.patch b/patches/ntdll-Vista_Threadpool/0006-kernel32-Forward-various-threadpool-functions-to-ntd.patch new file mode 100644 index 00000000..5b11c1a3 --- /dev/null +++ b/patches/ntdll-Vista_Threadpool/0006-kernel32-Forward-various-threadpool-functions-to-ntd.patch @@ -0,0 +1,342 @@ +From 3477bdc9e48312f6188f9c9beb8d062291f8b81f Mon Sep 17 00:00:00 2001 +From: Sebastian Lackner +Date: Sun, 1 Feb 2015 19:41:13 +0100 +Subject: kernel32: Forward various threadpool functions to ntdll. + +--- + dlls/kernel32/kernel32.spec | 48 ++++++++++---------- + dlls/kernel32/tests/thread.c | 6 +-- + dlls/kernel32/thread.c | 101 +++++++++++++++++++++++++++++++++++++++++++ + include/winternl.h | 27 ++++++++++++ + 4 files changed, 155 insertions(+), 27 deletions(-) + +diff --git a/dlls/kernel32/kernel32.spec b/dlls/kernel32/kernel32.spec +index 3719505..4d170d5 100644 +--- a/dlls/kernel32/kernel32.spec ++++ b/dlls/kernel32/kernel32.spec +@@ -204,7 +204,7 @@ + @ stdcall BuildCommDCBAndTimeoutsA(str ptr ptr) + @ stdcall BuildCommDCBAndTimeoutsW(wstr ptr ptr) + @ stdcall BuildCommDCBW(wstr ptr) +-# @ stub CallbackMayRunLong ++@ stdcall CallbackMayRunLong(ptr) ntdll.TpCallbackMayRunLong + @ stdcall CallNamedPipeA(str ptr long ptr long ptr long) + @ stdcall CallNamedPipeW(wstr ptr long ptr long ptr long) + @ stub CancelDeviceWakeupRequest +@@ -228,13 +228,13 @@ + # @ stub ClosePrivateNamespace + @ stdcall CloseProfileUserMapping() + @ stub CloseSystemHandle +-# @ stub CloseThreadpool +-# @ stub CloseThreadpoolCleanupGroup +-# @ stub CloseThreadpoolCleanupGroupMembers ++@ stdcall CloseThreadpool(ptr) ntdll.TpReleasePool ++@ stdcall CloseThreadpoolCleanupGroup(ptr) ntdll.TpReleaseCleanupGroup ++@ stdcall CloseThreadpoolCleanupGroupMembers(ptr long ptr) ntdll.TpReleaseCleanupGroupMembers + # @ stub CloseThreadpoolIo +-# @ stub CloseThreadpoolTimer ++@ stdcall CloseThreadpoolTimer(ptr) ntdll.TpReleaseTimer + # @ stub CloseThreadpoolWait +-# @ stub CloseThreadpoolWork ++@ stdcall CloseThreadpoolWork(ptr) ntdll.TpReleaseWork + @ stdcall CmdBatNotification(long) + @ stdcall CommConfigDialogA(str long ptr) + @ stdcall CommConfigDialogW(wstr long ptr) +@@ -331,12 +331,12 @@ + @ stdcall CreateSymbolicLinkW(wstr wstr long) + @ stdcall CreateTapePartition(long long long long) + @ stdcall CreateThread(ptr long ptr long long ptr) +-# @ stub CreateThreadpool +-# @ stub CreateThreadpoolCleanupGroup ++@ stdcall CreateThreadpool(ptr) ++@ stdcall CreateThreadpoolCleanupGroup() + # @ stub CreateThreadpoolIo +-# @ stub CreateThreadpoolTimer ++@ stdcall CreateThreadpoolTimer(ptr ptr ptr) + # @ stub CreateThreadpoolWait +-# @ stub CreateThreadpoolWork ++@ stdcall CreateThreadpoolWork(ptr ptr ptr) + @ stdcall CreateTimerQueue () + @ stdcall CreateTimerQueueTimer(ptr long ptr ptr long long long) + @ stdcall CreateToolhelp32Snapshot(long long) +@@ -369,7 +369,7 @@ + @ stdcall DeleteFileW(wstr) + # @ stub DeleteProcThreadAttributeList + # @ stub DisableThreadProfiling +-# @ stub DisassociateCurrentThreadFromCallback ++@ stdcall DisassociateCurrentThreadFromCallback(ptr) ntdll.TpDisassociateCallback + @ stdcall DeleteTimerQueue(long) + @ stdcall DeleteTimerQueueEx (long long) + @ stdcall DeleteTimerQueueTimer(long long long) +@@ -495,7 +495,6 @@ + @ stdcall FindFirstVolumeMountPointA(str ptr long) + @ stdcall FindFirstVolumeMountPointW(wstr ptr long) + @ stdcall FindFirstVolumeW(ptr long) +-# @ stub FreeLibraryWhenCallbackReturns + @ stdcall FindNextChangeNotification(long) + @ stdcall FindNextFileA(long ptr) + # @ stub FindNextFileNameW +@@ -533,6 +532,7 @@ + @ stub -i386 FreeLSCallback + @ stdcall FreeLibrary(long) + @ stdcall FreeLibraryAndExitThread(long long) ++@ stdcall FreeLibraryWhenCallbackReturns(ptr ptr) ntdll.TpCallbackUnloadDllOnCompletion + @ stdcall FreeResource(long) + @ stdcall -i386 -private FreeSLCallback(long) krnl386.exe16.FreeSLCallback + @ stub FreeUserPhysicalPages +@@ -980,7 +980,7 @@ + @ stub -i386 IsSLCallback + @ stdcall IsSystemResumeAutomatic() + @ stdcall IsThreadAFiber() +-# @ stub IsThreadpoolTimerSet ++@ stdcall IsThreadpoolTimerSet(ptr) ntdll.TpIsTimerSet + # @ stub IsTimeZoneRedirectionEnabled + # @ stub IsValidCalDateTime + @ stdcall IsValidCodePage(long) +@@ -1034,7 +1034,7 @@ + @ stdcall LZSeek(long long long) + @ stdcall LZStart() + @ stdcall LeaveCriticalSection(ptr) ntdll.RtlLeaveCriticalSection +-# @ stub LeaveCriticalSectionWhenCallbackReturns ++@ stdcall LeaveCriticalSectionWhenCallbackReturns(ptr ptr) ntdll.TpCallbackLeaveCriticalSectionOnCompletion + # @ stub LoadAppInitDlls + @ stdcall LoadLibraryA(str) + @ stdcall LoadLibraryExA( str long long) +@@ -1251,9 +1251,9 @@ + @ stdcall ReinitializeCriticalSection(ptr) + @ stdcall ReleaseActCtx(ptr) + @ stdcall ReleaseMutex(long) +-# @ stub ReleaseMutexWhenCallbackReturns ++@ stdcall ReleaseMutexWhenCallbackReturns(ptr long) ntdll.TpCallbackReleaseMutexOnCompletion + @ stdcall ReleaseSemaphore(long long ptr) +-# @ stub ReleaseSemaphoreWhenCallbackReturns ++@ stdcall ReleaseSemaphoreWhenCallbackReturns(ptr long long) ntdll.TpCallbackReleaseSemaphoreOnCompletion + @ stdcall ReleaseSRWLockExclusive(ptr) ntdll.RtlReleaseSRWLockExclusive + @ stdcall ReleaseSRWLockShared(ptr) ntdll.RtlReleaseSRWLockShared + @ stdcall RemoveDirectoryA(str) +@@ -1384,7 +1384,7 @@ + @ stdcall SetEnvironmentVariableW(wstr wstr) + @ stdcall SetErrorMode(long) + @ stdcall SetEvent(long) +-# @ stub SetEventWhenCallbackReturns ++@ stdcall SetEventWhenCallbackReturns(ptr long) ntdll.TpCallbackSetEventOnCompletion + @ stdcall SetFileApisToANSI() + @ stdcall SetFileApisToOEM() + @ stdcall SetFileAttributesA(str long) +@@ -1453,9 +1453,9 @@ + # @ stub SetThreadToken + @ stdcall SetThreadUILanguage(long) + # @ stub SetThreadpoolStackInformation +-# @ stub SetThreadpoolThreadMaximum +-# @ stub SetThreadpoolThreadMinimum +-# @ stub SetThreadpoolTimer ++@ stdcall SetThreadpoolThreadMaximum(ptr long) ntdll.TpSetPoolMaxThreads ++@ stdcall SetThreadpoolThreadMinimum(ptr long) ntdll.TpSetPoolMinThreads ++@ stdcall SetThreadpoolTimer(ptr ptr long long) + # @ stub SetThreadpoolWait + @ stdcall SetTimeZoneInformation(ptr) + @ stub SetTimerQueueTimer +@@ -1481,7 +1481,7 @@ + # @ stub SortCloseHandle + # @ stub SortGetHandle + # @ stub StartThreadpoolIo +-# @ stub SubmitThreadpoolWork ++@ stdcall SubmitThreadpoolWork(ptr) ntdll.TpPostWork + @ stdcall SuspendThread(long) + @ stdcall SwitchToFiber(ptr) + @ stdcall SwitchToThread() +@@ -1508,7 +1508,7 @@ + @ stdcall TryAcquireSRWLockExclusive(ptr) ntdll.RtlTryAcquireSRWLockExclusive + @ stdcall TryAcquireSRWLockShared(ptr) ntdll.RtlTryAcquireSRWLockShared + @ stdcall TryEnterCriticalSection(ptr) ntdll.RtlTryEnterCriticalSection +-# @ stub TrySubmitThreadpoolCallback ++@ stdcall TrySubmitThreadpoolCallback(ptr ptr ptr) ntdll.TpSimpleTryPost + @ stdcall TzSpecificLocalTimeToSystemTime(ptr ptr ptr) + # @ stub TzSpecificLocalTimeToSystemTimeEx + # @ stub -arch=x86_64 uaw_lstrcmpW +@@ -1570,9 +1570,9 @@ + @ stdcall WaitForSingleObject(long long) + @ stdcall WaitForSingleObjectEx(long long long) + # @ stub WaitForThreadpoolIoCallbacks +-# @ stub WaitForThreadpoolTimerCallbacks ++@ stdcall WaitForThreadpoolTimerCallbacks(ptr long) ntdll.TpWaitForTimer + # @ stub WaitForThreadpoolWaitCallbacks +-# @ stub WaitForThreadpoolWorkCallbacks ++@ stdcall WaitForThreadpoolWorkCallbacks(ptr long) ntdll.TpWaitForWork + @ stdcall WaitNamedPipeA (str long) + @ stdcall WaitNamedPipeW (wstr long) + @ stdcall WakeAllConditionVariable(ptr) ntdll.RtlWakeAllConditionVariable +diff --git a/dlls/kernel32/tests/thread.c b/dlls/kernel32/tests/thread.c +index d3ecd2a..25801b2 100644 +--- a/dlls/kernel32/tests/thread.c ++++ b/dlls/kernel32/tests/thread.c +@@ -1627,8 +1627,8 @@ static void test_threadpool(void) + int workcalled = 0; + + if (!pCreateThreadpool) { +- todo_wine win_skip("thread pool apis not supported.\n"); +- return; ++ win_skip("thread pool apis not supported.\n"); ++ return; + } + + work = pCreateThreadpoolWork(threadpool_workcallback, &workcalled, NULL); +@@ -1640,7 +1640,7 @@ static void test_threadpool(void) + ok (workcalled == 1, "expected work to be called once, got %d\n", workcalled); + + pool = pCreateThreadpool(NULL); +- todo_wine ok (pool != NULL, "CreateThreadpool failed\n"); ++ ok (pool != NULL, "CreateThreadpool failed\n"); + } + + static void test_reserved_tls(void) +diff --git a/dlls/kernel32/thread.c b/dlls/kernel32/thread.c +index 0abfdf1..085b011 100644 +--- a/dlls/kernel32/thread.c ++++ b/dlls/kernel32/thread.c +@@ -831,3 +831,104 @@ BOOL WINAPI GetThreadPreferredUILanguages( DWORD flags, PULONG count, PCZZWSTR b + *buffersize = 0; + return TRUE; + } ++ ++/*********************************************************************** ++ * CreateThreadpool (KERNEL32.@) ++ */ ++PTP_POOL WINAPI CreateThreadpool( PVOID reserved ) ++{ ++ TP_POOL *pool; ++ NTSTATUS status; ++ ++ TRACE( "%p\n", reserved ); ++ ++ status = TpAllocPool( &pool, reserved ); ++ if (status) ++ { ++ SetLastError( RtlNtStatusToDosError(status) ); ++ return NULL; ++ } ++ ++ return pool; ++} ++ ++/*********************************************************************** ++ * CreateThreadpoolCleanupGroup (KERNEL32.@) ++ */ ++PTP_CLEANUP_GROUP WINAPI CreateThreadpoolCleanupGroup( void ) ++{ ++ TP_CLEANUP_GROUP *group; ++ NTSTATUS status; ++ ++ TRACE( "\n" ); ++ ++ status = TpAllocCleanupGroup( &group ); ++ if (status) ++ { ++ SetLastError( RtlNtStatusToDosError(status) ); ++ return NULL; ++ } ++ ++ return group; ++} ++ ++/*********************************************************************** ++ * CreateThreadpoolTimer (KERNEL32.@) ++ */ ++PTP_TIMER WINAPI CreateThreadpoolTimer( PTP_TIMER_CALLBACK callback, PVOID userdata, ++ TP_CALLBACK_ENVIRON *environment ) ++{ ++ TP_TIMER *timer; ++ NTSTATUS status; ++ ++ TRACE( "%p, %p, %p\n", callback, userdata, environment ); ++ ++ status = TpAllocTimer( &timer, callback, userdata, environment ); ++ if (status) ++ { ++ SetLastError( RtlNtStatusToDosError(status) ); ++ return NULL; ++ } ++ ++ return timer; ++} ++ ++/*********************************************************************** ++ * CreateThreadpoolWork (KERNEL32.@) ++ */ ++PTP_WORK WINAPI CreateThreadpoolWork( PTP_WORK_CALLBACK callback, PVOID userdata, ++ TP_CALLBACK_ENVIRON *environment ) ++{ ++ TP_WORK *work; ++ NTSTATUS status; ++ ++ TRACE( "%p, %p, %p\n", callback, userdata, environment ); ++ ++ status = TpAllocWork( &work, callback, userdata, environment ); ++ if (status) ++ { ++ SetLastError( RtlNtStatusToDosError(status) ); ++ return NULL; ++ } ++ ++ return work; ++} ++ ++/*********************************************************************** ++ * SetThreadpoolTimer (KERNEL32.@) ++ */ ++VOID WINAPI SetThreadpoolTimer( TP_TIMER *timer, FILETIME *due_time, ++ DWORD period, DWORD window_length ) ++{ ++ LARGE_INTEGER timeout; ++ ++ TRACE( "%p, %p, %u, %u\n", timer, due_time, period, window_length ); ++ ++ if (due_time) ++ { ++ timeout.u.LowPart = due_time->dwLowDateTime; ++ timeout.u.HighPart = due_time->dwHighDateTime; ++ } ++ ++ TpSetTimer( timer, due_time ? &timeout : NULL, period, window_length ); ++} +diff --git a/include/winternl.h b/include/winternl.h +index 1a694da..a534bd1 100644 +--- a/include/winternl.h ++++ b/include/winternl.h +@@ -2599,6 +2599,33 @@ NTSYSAPI LONGLONG WINAPI RtlLargeIntegerSubtract(LONGLONG,LONGLONG); + NTSYSAPI NTSTATUS WINAPI RtlLargeIntegerToChar(const ULONGLONG *,ULONG,ULONG,PCHAR); + #endif + ++/* Threadpool functions */ ++ ++NTSYSAPI NTSTATUS WINAPI TpAllocCleanupGroup(TP_CLEANUP_GROUP **); ++NTSYSAPI NTSTATUS WINAPI TpAllocPool(TP_POOL **,PVOID); ++NTSYSAPI NTSTATUS WINAPI TpAllocTimer(TP_TIMER **,PTP_TIMER_CALLBACK,PVOID,TP_CALLBACK_ENVIRON *); ++NTSYSAPI NTSTATUS WINAPI TpAllocWork(TP_WORK **,PTP_WORK_CALLBACK,PVOID,TP_CALLBACK_ENVIRON *); ++NTSYSAPI void WINAPI TpCallbackLeaveCriticalSectionOnCompletion(TP_CALLBACK_INSTANCE *,RTL_CRITICAL_SECTION *); ++NTSYSAPI NTSTATUS WINAPI TpCallbackMayRunLong(TP_CALLBACK_INSTANCE *); ++NTSYSAPI void WINAPI TpCallbackReleaseMutexOnCompletion(TP_CALLBACK_INSTANCE *,HANDLE); ++NTSYSAPI void WINAPI TpCallbackReleaseSemaphoreOnCompletion(TP_CALLBACK_INSTANCE *,HANDLE,DWORD); ++NTSYSAPI void WINAPI TpCallbackSetEventOnCompletion(TP_CALLBACK_INSTANCE *,HANDLE); ++NTSYSAPI void WINAPI TpCallbackUnloadDllOnCompletion(TP_CALLBACK_INSTANCE *,HMODULE); ++NTSYSAPI void WINAPI TpDisassociateCallback(TP_CALLBACK_INSTANCE *); ++NTSYSAPI BOOL WINAPI TpIsTimerSet(TP_TIMER *); ++NTSYSAPI void WINAPI TpPostWork(TP_WORK *); ++NTSYSAPI void WINAPI TpReleaseCleanupGroup(TP_CLEANUP_GROUP *); ++NTSYSAPI void WINAPI TpReleaseCleanupGroupMembers(TP_CLEANUP_GROUP *,BOOL,PVOID); ++NTSYSAPI void WINAPI TpReleasePool(TP_POOL *); ++NTSYSAPI void WINAPI TpReleaseTimer(TP_TIMER *); ++NTSYSAPI void WINAPI TpReleaseWork(TP_WORK *); ++NTSYSAPI void WINAPI TpSetPoolMaxThreads(TP_POOL *,DWORD); ++NTSYSAPI BOOL WINAPI TpSetPoolMinThreads(TP_POOL *,DWORD); ++NTSYSAPI void WINAPI TpSetTimer(TP_TIMER *, LARGE_INTEGER *,LONG,LONG); ++NTSYSAPI NTSTATUS WINAPI TpSimpleTryPost(PTP_SIMPLE_CALLBACK,PVOID,TP_CALLBACK_ENVIRON *); ++NTSYSAPI void WINAPI TpWaitForTimer(TP_TIMER *,BOOL); ++NTSYSAPI void WINAPI TpWaitForWork(TP_WORK *,BOOL); ++ + /* Wine internal functions */ + + NTSYSAPI NTSTATUS CDECL wine_nt_to_unix_file_name( const UNICODE_STRING *nameW, ANSI_STRING *unix_name_ret, +-- +2.2.2 + diff --git a/patches/ntdll-Vista_Threadpool/definition b/patches/ntdll-Vista_Threadpool/definition new file mode 100644 index 00000000..57ef07b1 --- /dev/null +++ b/patches/ntdll-Vista_Threadpool/definition @@ -0,0 +1,3 @@ +Fixes: [35192] Add implementation for CreateThreadpool +Fixes: [32531] Implement threadpool work items +Fixes: [37306] Implement threadpool timers diff --git a/patches/ntdll-WinSqm/0001-ntdll-Add-stubs-for-WinSqmStartSession-WinSqmEndSess.patch b/patches/ntdll-WinSqm/0001-ntdll-Add-stubs-for-WinSqmStartSession-WinSqmEndSess.patch index a6bf03fb..366f1d3b 100644 --- a/patches/ntdll-WinSqm/0001-ntdll-Add-stubs-for-WinSqmStartSession-WinSqmEndSess.patch +++ b/patches/ntdll-WinSqm/0001-ntdll-Add-stubs-for-WinSqmStartSession-WinSqmEndSess.patch @@ -14,15 +14,11 @@ diff --git a/dlls/ntdll/ntdll.spec b/dlls/ntdll/ntdll.spec index 51de6e7..44dfc22 100644 --- a/dlls/ntdll/ntdll.spec +++ b/dlls/ntdll/ntdll.spec -@@ -968,6 +968,8 @@ - @ stdcall RtlxUnicodeStringToAnsiSize(ptr) RtlUnicodeStringToAnsiSize - @ stdcall RtlxUnicodeStringToOemSize(ptr) RtlUnicodeStringToOemSize +@@ -970,2 +970,4 @@ @ stdcall -ret64 VerSetConditionMask(int64 long long) +@ stdcall WinSqmEndSession(ptr) +@ stdcall WinSqmStartSession(ptr long long) @ stdcall ZwAcceptConnectPort(ptr long ptr long long ptr) NtAcceptConnectPort - @ stdcall ZwAccessCheck(ptr long long ptr ptr ptr ptr ptr) NtAccessCheck - @ stdcall ZwAccessCheckAndAuditAlarm(ptr long ptr ptr ptr long ptr long ptr ptr ptr) NtAccessCheckAndAuditAlarm diff --git a/dlls/ntdll/rtl.c b/dlls/ntdll/rtl.c index 8f6f386..2e87beb 100644 --- a/dlls/ntdll/rtl.c diff --git a/patches/patchinstall.sh b/patches/patchinstall.sh index 80ee6d53..83ac46e4 100755 --- a/patches/patchinstall.sh +++ b/patches/patchinstall.sh @@ -132,6 +132,7 @@ patch_enable_all () enable_ntdll_RtlUnwindEx="$1" enable_ntdll_ThreadTime="$1" enable_ntdll_User_Shared_Data="$1" + enable_ntdll_Vista_Threadpool="$1" enable_ntdll_WRITECOPY="$1" enable_ntdll_WinSqm="$1" enable_ntoskrnl_DriverTest="$1" @@ -417,6 +418,9 @@ patch_enable () ntdll-User_Shared_Data) enable_ntdll_User_Shared_Data="$2" ;; + ntdll-Vista_Threadpool) + enable_ntdll_Vista_Threadpool="$2" + ;; ntdll-WRITECOPY) enable_ntdll_WRITECOPY="$2" ;; @@ -2212,6 +2216,35 @@ if test "$enable_ntdll_User_Shared_Data" -eq 1; then ) >> "$patchlist" fi +# Patchset ntdll-Vista_Threadpool +# | +# | This patchset fixes the following Wine bugs: +# | * [#35192] Add implementation for CreateThreadpool +# | * [#32531] Implement threadpool work items +# | * [#37306] Implement threadpool timers +# | +# | Modified files: +# | * dlls/kernel32/kernel32.spec, dlls/kernel32/tests/thread.c, dlls/kernel32/thread.c, dlls/ntdll/Makefile.in, +# | dlls/ntdll/ntdll.spec, dlls/ntdll/tests/Makefile.in, dlls/ntdll/tests/threadpool.c, dlls/ntdll/threadpool2.c, +# | include/winternl.h +# | +if test "$enable_ntdll_Vista_Threadpool" -eq 1; then + patch_apply ntdll-Vista_Threadpool/0001-ntdll-Add-threadpool-stub-functions-to-specfile.patch + patch_apply ntdll-Vista_Threadpool/0002-ntdll-Implement-threadpool-cleanup-group-and-callbac.patch + patch_apply ntdll-Vista_Threadpool/0003-ntdll-Implement-additional-threadpool-work-item-func.patch + patch_apply ntdll-Vista_Threadpool/0004-ntdll-Implement-threadpool-timer-functions.patch + patch_apply ntdll-Vista_Threadpool/0005-ntdll-tests-Add-tests-for-Tp-threadpool-functions.patch + patch_apply ntdll-Vista_Threadpool/0006-kernel32-Forward-various-threadpool-functions-to-ntd.patch + ( + echo '+ { "Sebastian Lackner", "ntdll: Add threadpool stub functions to specfile.", 1 },'; + echo '+ { "Sebastian Lackner", "ntdll: Implement threadpool, cleanup group and callback instance functions.", 1 },'; + echo '+ { "Sebastian Lackner", "ntdll: Implement additional threadpool work item functions.", 1 },'; + echo '+ { "Sebastian Lackner", "ntdll: Implement threadpool timer functions.", 1 },'; + echo '+ { "Sebastian Lackner", "ntdll/tests: Add tests for Tp* threadpool functions.", 1 },'; + echo '+ { "Sebastian Lackner", "kernel32: Forward various threadpool functions to ntdll.", 1 },'; + ) >> "$patchlist" +fi + # Patchset ntdll-WinSqm # | # | This patchset fixes the following Wine bugs: