mirror of
https://gitlab.winehq.org/wine/wine-staging.git
synced 2024-11-21 16:46:54 -08:00
ntdll-Vista_Threadpool: Clean up and split remaining patches.
This commit is contained in:
parent
cde323b772
commit
fc0847724e
@ -0,0 +1,146 @@
|
||||
From f4397c4e7c279c41b12b03dfa1b368044de436f7 Mon Sep 17 00:00:00 2001
|
||||
From: Sebastian Lackner <sebastian@fds-team.de>
|
||||
Date: Sat, 4 Jul 2015 04:11:31 +0200
|
||||
Subject: ntdll: Implement TpAllocWait and TpReleaseWait.
|
||||
|
||||
---
|
||||
dlls/ntdll/ntdll.spec | 2 ++
|
||||
dlls/ntdll/threadpool.c | 67 ++++++++++++++++++++++++++++++++++++++++++++++++-
|
||||
2 files changed, 68 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/dlls/ntdll/ntdll.spec b/dlls/ntdll/ntdll.spec
|
||||
index ce9d1bb..78814cd 100644
|
||||
--- a/dlls/ntdll/ntdll.spec
|
||||
+++ b/dlls/ntdll/ntdll.spec
|
||||
@@ -973,6 +973,7 @@
|
||||
@ stdcall TpAllocCleanupGroup(ptr)
|
||||
@ stdcall TpAllocPool(ptr ptr)
|
||||
@ stdcall TpAllocTimer(ptr ptr ptr ptr)
|
||||
+@ stdcall TpAllocWait(ptr ptr ptr ptr)
|
||||
@ stdcall TpAllocWork(ptr ptr ptr ptr)
|
||||
@ stdcall TpCallbackLeaveCriticalSectionOnCompletion(ptr ptr)
|
||||
@ stdcall TpCallbackMayRunLong(ptr)
|
||||
@@ -987,6 +988,7 @@
|
||||
@ stdcall TpReleaseCleanupGroupMembers(ptr long ptr)
|
||||
@ stdcall TpReleasePool(ptr)
|
||||
@ stdcall TpReleaseTimer(ptr)
|
||||
+@ stdcall TpReleaseWait(ptr)
|
||||
@ stdcall TpReleaseWork(ptr)
|
||||
@ stdcall TpSetPoolMaxThreads(ptr long)
|
||||
@ stdcall TpSetPoolMinThreads(ptr long)
|
||||
diff --git a/dlls/ntdll/threadpool.c b/dlls/ntdll/threadpool.c
|
||||
index ef502ba..b9aece4 100644
|
||||
--- a/dlls/ntdll/threadpool.c
|
||||
+++ b/dlls/ntdll/threadpool.c
|
||||
@@ -159,7 +159,8 @@ enum threadpool_objtype
|
||||
{
|
||||
TP_OBJECT_TYPE_SIMPLE,
|
||||
TP_OBJECT_TYPE_WORK,
|
||||
- TP_OBJECT_TYPE_TIMER
|
||||
+ TP_OBJECT_TYPE_TIMER,
|
||||
+ TP_OBJECT_TYPE_WAIT
|
||||
};
|
||||
|
||||
/* internal threadpool object representation */
|
||||
@@ -209,6 +210,10 @@ struct threadpool_object
|
||||
LONG period;
|
||||
LONG window_length;
|
||||
} timer;
|
||||
+ struct
|
||||
+ {
|
||||
+ PTP_WAIT_CALLBACK callback;
|
||||
+ } wait;
|
||||
} u;
|
||||
};
|
||||
|
||||
@@ -286,6 +291,13 @@ static inline struct threadpool_object *impl_from_TP_TIMER( TP_TIMER *timer )
|
||||
return object;
|
||||
}
|
||||
|
||||
+static inline struct threadpool_object *impl_from_TP_WAIT( TP_WAIT *wait )
|
||||
+{
|
||||
+ struct threadpool_object *object = (struct threadpool_object *)wait;
|
||||
+ assert( object->type == TP_OBJECT_TYPE_WAIT );
|
||||
+ return object;
|
||||
+}
|
||||
+
|
||||
static inline struct threadpool_group *impl_from_TP_CLEANUP_GROUP( TP_CLEANUP_GROUP *group )
|
||||
{
|
||||
return (struct threadpool_group *)group;
|
||||
@@ -1907,6 +1919,15 @@ static void CALLBACK threadpool_worker_proc( void *param )
|
||||
break;
|
||||
}
|
||||
|
||||
+ case TP_OBJECT_TYPE_WAIT:
|
||||
+ {
|
||||
+ TRACE( "executing wait callback %p(%p, %p, %p, %u)\n",
|
||||
+ object->u.wait.callback, callback_instance, object->userdata, object, WAIT_OBJECT_0 );
|
||||
+ object->u.wait.callback( callback_instance, object->userdata, (TP_WAIT *)object, WAIT_OBJECT_0 );
|
||||
+ TRACE( "callback %p returned\n", object->u.wait.callback );
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
default:
|
||||
assert(0);
|
||||
break;
|
||||
@@ -2052,6 +2073,37 @@ NTSTATUS WINAPI TpAllocTimer( TP_TIMER **out, PTP_TIMER_CALLBACK callback, PVOID
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
+ * TpAllocWait (NTDLL.@)
|
||||
+ */
|
||||
+NTSTATUS WINAPI TpAllocWait( TP_WAIT **out, PTP_WAIT_CALLBACK callback, PVOID userdata,
|
||||
+ TP_CALLBACK_ENVIRON *environment )
|
||||
+{
|
||||
+ struct threadpool_object *object;
|
||||
+ struct threadpool *pool;
|
||||
+ NTSTATUS status;
|
||||
+
|
||||
+ TRACE( "%p %p %p %p\n", out, callback, userdata, environment );
|
||||
+
|
||||
+ object = RtlAllocateHeap( GetProcessHeap(), 0, sizeof(*object) );
|
||||
+ if (!object)
|
||||
+ return STATUS_NO_MEMORY;
|
||||
+
|
||||
+ status = tp_threadpool_lock( &pool, environment );
|
||||
+ if (status)
|
||||
+ {
|
||||
+ RtlFreeHeap( GetProcessHeap(), 0, object );
|
||||
+ return status;
|
||||
+ }
|
||||
+
|
||||
+ object->type = TP_OBJECT_TYPE_WAIT;
|
||||
+ object->u.wait.callback = callback;
|
||||
+ tp_object_initialize( object, pool, userdata, environment );
|
||||
+
|
||||
+ *out = (TP_WAIT *)object;
|
||||
+ return STATUS_SUCCESS;
|
||||
+}
|
||||
+
|
||||
+/***********************************************************************
|
||||
* TpAllocWork (NTDLL.@)
|
||||
*/
|
||||
NTSTATUS WINAPI TpAllocWork( TP_WORK **out, PTP_WORK_CALLBACK callback, PVOID userdata,
|
||||
@@ -2356,6 +2408,19 @@ VOID WINAPI TpReleaseTimer( TP_TIMER *timer )
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
+ * TpReleaseWait (NTDLL.@)
|
||||
+ */
|
||||
+VOID WINAPI TpReleaseWait( TP_WAIT *wait )
|
||||
+{
|
||||
+ struct threadpool_object *this = impl_from_TP_WAIT( wait );
|
||||
+
|
||||
+ TRACE( "%p\n", wait );
|
||||
+
|
||||
+ tp_object_shutdown( this );
|
||||
+ tp_object_release( this );
|
||||
+}
|
||||
+
|
||||
+/***********************************************************************
|
||||
* TpReleaseWork (NTDLL.@)
|
||||
*/
|
||||
VOID WINAPI TpReleaseWork( TP_WORK *work )
|
||||
--
|
||||
2.4.4
|
||||
|
@ -1,31 +1,18 @@
|
||||
From 102fb5ddc55779ac9c95937faef0cc063774612f Mon Sep 17 00:00:00 2001
|
||||
From 4371c3cd5674720b230ec584199b7d5cbb67e2fc Mon Sep 17 00:00:00 2001
|
||||
From: Sebastian Lackner <sebastian@fds-team.de>
|
||||
Date: Wed, 4 Mar 2015 13:33:25 +0100
|
||||
Subject: ntdll: Implement threadpool wait objects.
|
||||
Date: Sat, 4 Jul 2015 04:32:31 +0200
|
||||
Subject: ntdll: Implement threadpool wait queues.
|
||||
|
||||
---
|
||||
dlls/ntdll/ntdll.spec | 4 +
|
||||
dlls/ntdll/threadpool.c | 474 +++++++++++++++++++++++++++++++++++++++++++++++-
|
||||
2 files changed, 471 insertions(+), 7 deletions(-)
|
||||
dlls/ntdll/ntdll.spec | 2 +
|
||||
dlls/ntdll/threadpool.c | 367 ++++++++++++++++++++++++++++++++++++++++++++++--
|
||||
2 files changed, 361 insertions(+), 8 deletions(-)
|
||||
|
||||
diff --git a/dlls/ntdll/ntdll.spec b/dlls/ntdll/ntdll.spec
|
||||
index ce9d1bb..75dc647 100644
|
||||
index 78814cd..75dc647 100644
|
||||
--- a/dlls/ntdll/ntdll.spec
|
||||
+++ b/dlls/ntdll/ntdll.spec
|
||||
@@ -973,6 +973,7 @@
|
||||
@ stdcall TpAllocCleanupGroup(ptr)
|
||||
@ stdcall TpAllocPool(ptr ptr)
|
||||
@ stdcall TpAllocTimer(ptr ptr ptr ptr)
|
||||
+@ stdcall TpAllocWait(ptr ptr ptr ptr)
|
||||
@ stdcall TpAllocWork(ptr ptr ptr ptr)
|
||||
@ stdcall TpCallbackLeaveCriticalSectionOnCompletion(ptr ptr)
|
||||
@ stdcall TpCallbackMayRunLong(ptr)
|
||||
@@ -987,12 +988,15 @@
|
||||
@ stdcall TpReleaseCleanupGroupMembers(ptr long ptr)
|
||||
@ stdcall TpReleasePool(ptr)
|
||||
@ stdcall TpReleaseTimer(ptr)
|
||||
+@ stdcall TpReleaseWait(ptr)
|
||||
@ stdcall TpReleaseWork(ptr)
|
||||
@@ -993,8 +993,10 @@
|
||||
@ stdcall TpSetPoolMaxThreads(ptr long)
|
||||
@ stdcall TpSetPoolMinThreads(ptr long)
|
||||
@ stdcall TpSetTimer(ptr ptr long long)
|
||||
@ -37,7 +24,7 @@ index ce9d1bb..75dc647 100644
|
||||
@ stdcall -ret64 VerSetConditionMask(int64 long long)
|
||||
@ stdcall WinSqmIsOptedIn()
|
||||
diff --git a/dlls/ntdll/threadpool.c b/dlls/ntdll/threadpool.c
|
||||
index ef502ba..773c3d6 100644
|
||||
index b9aece4..f86e965 100644
|
||||
--- a/dlls/ntdll/threadpool.c
|
||||
+++ b/dlls/ntdll/threadpool.c
|
||||
@@ -137,6 +137,7 @@ struct timer_queue
|
||||
@ -48,23 +35,10 @@ index ef502ba..773c3d6 100644
|
||||
|
||||
/* internal threadpool representation */
|
||||
struct threadpool
|
||||
@@ -159,7 +160,8 @@ enum threadpool_objtype
|
||||
{
|
||||
TP_OBJECT_TYPE_SIMPLE,
|
||||
TP_OBJECT_TYPE_WORK,
|
||||
- TP_OBJECT_TYPE_TIMER
|
||||
+ TP_OBJECT_TYPE_TIMER,
|
||||
+ TP_OBJECT_TYPE_WAIT
|
||||
};
|
||||
|
||||
/* internal threadpool object representation */
|
||||
@@ -209,6 +211,17 @@ struct threadpool_object
|
||||
LONG period;
|
||||
LONG window_length;
|
||||
} timer;
|
||||
+ struct
|
||||
+ {
|
||||
+ PTP_WAIT_CALLBACK callback;
|
||||
@@ -213,6 +214,13 @@ struct threadpool_object
|
||||
struct
|
||||
{
|
||||
PTP_WAIT_CALLBACK callback;
|
||||
+ LONG signaled;
|
||||
+ /* information about the wait, locked via waitqueue.cs */
|
||||
+ struct waitqueue_bucket *bucket;
|
||||
@ -72,11 +46,10 @@ index ef502ba..773c3d6 100644
|
||||
+ struct list wait_entry;
|
||||
+ ULONGLONG timeout;
|
||||
+ HANDLE handle;
|
||||
+ } wait;
|
||||
} wait;
|
||||
} u;
|
||||
};
|
||||
|
||||
@@ -267,6 +280,38 @@ static RTL_CRITICAL_SECTION_DEBUG timerqueue_debug =
|
||||
@@ -272,6 +280,38 @@ static RTL_CRITICAL_SECTION_DEBUG timerqueue_debug =
|
||||
0, 0, { (DWORD_PTR)(__FILE__ ": timerqueue.cs") }
|
||||
};
|
||||
|
||||
@ -108,28 +81,14 @@ index ef502ba..773c3d6 100644
|
||||
+ struct list bucket_entry;
|
||||
+ LONG objcount;
|
||||
+ struct list reserved;
|
||||
+ struct list waits;
|
||||
+ struct list waiting;
|
||||
+ HANDLE update_event;
|
||||
+};
|
||||
+
|
||||
static inline struct threadpool *impl_from_TP_POOL( TP_POOL *pool )
|
||||
{
|
||||
return (struct threadpool *)pool;
|
||||
@@ -286,6 +331,13 @@ static inline struct threadpool_object *impl_from_TP_TIMER( TP_TIMER *timer )
|
||||
return object;
|
||||
}
|
||||
|
||||
+static inline struct threadpool_object *impl_from_TP_WAIT( TP_WAIT *wait )
|
||||
+{
|
||||
+ struct threadpool_object *object = (struct threadpool_object *)wait;
|
||||
+ assert( object->type == TP_OBJECT_TYPE_WAIT );
|
||||
+ return object;
|
||||
+}
|
||||
+
|
||||
static inline struct threadpool_group *impl_from_TP_CLEANUP_GROUP( TP_CLEANUP_GROUP *group )
|
||||
{
|
||||
return (struct threadpool_group *)group;
|
||||
@@ -297,7 +349,7 @@ static inline struct threadpool_instance *impl_from_TP_CALLBACK_INSTANCE( TP_CAL
|
||||
@@ -309,7 +349,7 @@ static inline struct threadpool_instance *impl_from_TP_CALLBACK_INSTANCE( TP_CAL
|
||||
}
|
||||
|
||||
static void CALLBACK threadpool_worker_proc( void *param );
|
||||
@ -138,7 +97,7 @@ index ef502ba..773c3d6 100644
|
||||
static void tp_object_shutdown( struct threadpool_object *object );
|
||||
static BOOL tp_object_release( struct threadpool_object *object );
|
||||
static struct threadpool *default_threadpool = NULL;
|
||||
@@ -1249,7 +1301,7 @@ static void CALLBACK timerqueue_thread_proc( void *param )
|
||||
@@ -1261,7 +1301,7 @@ static void CALLBACK timerqueue_thread_proc( void *param )
|
||||
/* Queue a new callback in one of the worker threads. */
|
||||
list_remove( &timer->u.timer.timer_entry );
|
||||
timer->u.timer.timer_pending = FALSE;
|
||||
@ -147,7 +106,7 @@ index ef502ba..773c3d6 100644
|
||||
|
||||
/* Insert the timer back into the queue, except its marked for shutdown. */
|
||||
if (timer->u.timer.period && !timer->shutdown)
|
||||
@@ -1386,6 +1438,255 @@ static void tp_timerqueue_unlock( struct threadpool_object *timer )
|
||||
@@ -1398,6 +1438,216 @@ static void tp_timerqueue_unlock( struct threadpool_object *timer )
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
@ -174,7 +133,7 @@ index ef502ba..773c3d6 100644
|
||||
+ num_handles = 0;
|
||||
+
|
||||
+ /* Check for expired waits. */
|
||||
+ LIST_FOR_EACH_ENTRY_SAFE( wait, next, &bucket->waits, struct threadpool_object, u.wait.wait_entry )
|
||||
+ LIST_FOR_EACH_ENTRY_SAFE( wait, next, &bucket->waiting, struct threadpool_object, u.wait.wait_entry )
|
||||
+ {
|
||||
+ assert( wait->type == TP_OBJECT_TYPE_WAIT );
|
||||
+ if (wait->u.wait.timeout <= now.QuadPart)
|
||||
@ -207,12 +166,13 @@ index ef502ba..773c3d6 100644
|
||||
+ timeout.QuadPart = (ULONGLONG)THREADPOOL_WORKER_TIMEOUT * -10000;
|
||||
+ status = NtWaitForMultipleObjects( 1, &bucket->update_event, TRUE, FALSE, &timeout );
|
||||
+ RtlEnterCriticalSection( &waitqueue.cs );
|
||||
+
|
||||
+ if (status == STATUS_TIMEOUT && !bucket->objcount)
|
||||
+ break;
|
||||
+ }
|
||||
+ else
|
||||
+ {
|
||||
+ /* Wait for a wait queue update event or until an event is triggered */
|
||||
+ /* Wait for the next event. */
|
||||
+ handles[num_handles] = bucket->update_event;
|
||||
+ RtlLeaveCriticalSection( &waitqueue.cs );
|
||||
+ status = NtWaitForMultipleObjects( num_handles + 1, handles, TRUE, FALSE, &timeout );
|
||||
@ -224,7 +184,7 @@ index ef502ba..773c3d6 100644
|
||||
+ assert( wait->type == TP_OBJECT_TYPE_WAIT );
|
||||
+ if (wait->u.wait.bucket)
|
||||
+ {
|
||||
+ /* Wait fulfilled. */
|
||||
+ /* Wait object signaled. */
|
||||
+ assert( wait->u.wait.bucket == bucket );
|
||||
+ list_remove( &wait->u.wait.wait_entry );
|
||||
+ list_add_tail( &bucket->reserved, &wait->u.wait.wait_entry );
|
||||
@ -242,46 +202,6 @@ index ef502ba..773c3d6 100644
|
||||
+ assert( wait->type == TP_OBJECT_TYPE_WAIT );
|
||||
+ tp_object_release( wait );
|
||||
+ }
|
||||
+
|
||||
+ /* Try to merge with other threads. */
|
||||
+ if (waitqueue.num_buckets > 1 && bucket->objcount &&
|
||||
+ bucket->objcount < MAXIMUM_WAITQUEUE_OBJECTS / 2)
|
||||
+ {
|
||||
+ struct waitqueue_bucket *other_bucket;
|
||||
+ LIST_FOR_EACH_ENTRY( other_bucket, &waitqueue.buckets, struct waitqueue_bucket, bucket_entry )
|
||||
+ {
|
||||
+ if (other_bucket != bucket && other_bucket->objcount &&
|
||||
+ other_bucket->objcount + bucket->objcount <= MAXIMUM_WAITQUEUE_OBJECTS)
|
||||
+ {
|
||||
+ other_bucket->objcount += bucket->objcount;
|
||||
+ bucket->objcount = 0;
|
||||
+
|
||||
+ /* Update reserved list. */
|
||||
+ LIST_FOR_EACH_ENTRY( wait, &bucket->reserved, struct threadpool_object, u.wait.wait_entry )
|
||||
+ {
|
||||
+ assert( wait->type == TP_OBJECT_TYPE_WAIT );
|
||||
+ wait->u.wait.bucket = other_bucket;
|
||||
+ }
|
||||
+ list_move_tail( &other_bucket->reserved, &bucket->reserved );
|
||||
+
|
||||
+ /* Update wait list. */
|
||||
+ LIST_FOR_EACH_ENTRY( wait, &bucket->waits, struct threadpool_object, u.wait.wait_entry )
|
||||
+ {
|
||||
+ assert( wait->type == TP_OBJECT_TYPE_WAIT );
|
||||
+ wait->u.wait.bucket = other_bucket;
|
||||
+ }
|
||||
+ list_move_tail( &other_bucket->waits, &bucket->waits );
|
||||
+
|
||||
+ /* Move bucket to the end to keep probability of
|
||||
+ * newly added wait objects as small as possible. */
|
||||
+ list_remove( &bucket->bucket_entry );
|
||||
+ list_add_tail( &waitqueue.buckets, &bucket->bucket_entry );
|
||||
+
|
||||
+ NtSetEvent( other_bucket->update_event, NULL );
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ /* Remove this bucket from the list. */
|
||||
@ -295,9 +215,9 @@ index ef502ba..773c3d6 100644
|
||||
+
|
||||
+ assert( bucket->objcount == 0 );
|
||||
+ assert( list_empty( &bucket->reserved ) );
|
||||
+ assert( list_empty( &bucket->waits ) );
|
||||
+
|
||||
+ assert( list_empty( &bucket->waiting ) );
|
||||
+ NtClose( bucket->update_event );
|
||||
+
|
||||
+ RtlFreeHeap( GetProcessHeap(), 0, bucket );
|
||||
+}
|
||||
+
|
||||
@ -343,7 +263,7 @@ index ef502ba..773c3d6 100644
|
||||
+
|
||||
+ bucket->objcount = 0;
|
||||
+ list_init( &bucket->reserved );
|
||||
+ list_init( &bucket->waits );
|
||||
+ list_init( &bucket->waiting );
|
||||
+
|
||||
+ status = NtCreateEvent( &bucket->update_event, EVENT_ALL_ACCESS,
|
||||
+ NULL, SynchronizationEvent, FALSE );
|
||||
@ -403,7 +323,7 @@ index ef502ba..773c3d6 100644
|
||||
* tp_threadpool_alloc (internal)
|
||||
*
|
||||
* Allocates a new threadpool object.
|
||||
@@ -1654,7 +1955,7 @@ static void tp_object_initialize( struct threadpool_object *object, struct threa
|
||||
@@ -1666,7 +1916,7 @@ static void tp_object_initialize( struct threadpool_object *object, struct threa
|
||||
* will be set, and tp_object_submit would fail with an assertion. */
|
||||
|
||||
if (is_simple_callback)
|
||||
@ -412,7 +332,7 @@ index ef502ba..773c3d6 100644
|
||||
|
||||
if (object->group)
|
||||
{
|
||||
@@ -1680,7 +1981,7 @@ static void tp_object_initialize( struct threadpool_object *object, struct threa
|
||||
@@ -1692,7 +1942,7 @@ static void tp_object_initialize( struct threadpool_object *object, struct threa
|
||||
* Submits a threadpool object to the associcated threadpool. This
|
||||
* function has to be VOID because TpPostWork can never fail on Windows.
|
||||
*/
|
||||
@ -421,18 +341,18 @@ index ef502ba..773c3d6 100644
|
||||
{
|
||||
struct threadpool *pool = object->pool;
|
||||
NTSTATUS status = STATUS_UNSUCCESSFUL;
|
||||
@@ -1710,6 +2011,10 @@ static void tp_object_submit( struct threadpool_object *object )
|
||||
@@ -1722,6 +1972,10 @@ static void tp_object_submit( struct threadpool_object *object )
|
||||
if (!object->num_pending_callbacks++)
|
||||
list_add_tail( &pool->pool, &object->pool_entry );
|
||||
|
||||
+ /* Remember how often the wait was fulfilled. */
|
||||
+ /* Count how often the object was signaled. */
|
||||
+ if (object->type == TP_OBJECT_TYPE_WAIT && success)
|
||||
+ object->u.wait.signaled++;
|
||||
+
|
||||
/* No new thread started - wake up one existing thread. */
|
||||
if (status != STATUS_SUCCESS)
|
||||
{
|
||||
@@ -1736,6 +2041,9 @@ static void tp_object_cancel( struct threadpool_object *object, BOOL group_cance
|
||||
@@ -1748,6 +2002,9 @@ static void tp_object_cancel( struct threadpool_object *object, BOOL group_cance
|
||||
pending_callbacks = object->num_pending_callbacks;
|
||||
object->num_pending_callbacks = 0;
|
||||
list_remove( &object->pool_entry );
|
||||
@ -442,7 +362,7 @@ index ef502ba..773c3d6 100644
|
||||
}
|
||||
RtlLeaveCriticalSection( &pool->cs );
|
||||
|
||||
@@ -1785,6 +2093,8 @@ static void tp_object_shutdown( struct threadpool_object *object )
|
||||
@@ -1797,6 +2054,8 @@ static void tp_object_shutdown( struct threadpool_object *object )
|
||||
{
|
||||
if (object->type == TP_OBJECT_TYPE_TIMER)
|
||||
tp_timerqueue_unlock( object );
|
||||
@ -451,7 +371,7 @@ index ef502ba..773c3d6 100644
|
||||
|
||||
object->shutdown = TRUE;
|
||||
}
|
||||
@@ -1839,6 +2149,7 @@ static void CALLBACK threadpool_worker_proc( void *param )
|
||||
@@ -1851,6 +2110,7 @@ static void CALLBACK threadpool_worker_proc( void *param )
|
||||
TP_CALLBACK_INSTANCE *callback_instance;
|
||||
struct threadpool_instance instance;
|
||||
struct threadpool *pool = param;
|
||||
@ -459,7 +379,7 @@ index ef502ba..773c3d6 100644
|
||||
LARGE_INTEGER timeout;
|
||||
struct list *ptr;
|
||||
NTSTATUS status;
|
||||
@@ -1859,6 +2170,13 @@ static void CALLBACK threadpool_worker_proc( void *param )
|
||||
@@ -1871,6 +2131,13 @@ static void CALLBACK threadpool_worker_proc( void *param )
|
||||
if (--object->num_pending_callbacks)
|
||||
list_add_tail( &pool->pool, &object->pool_entry );
|
||||
|
||||
@ -473,50 +393,21 @@ index ef502ba..773c3d6 100644
|
||||
/* Leave critical section and do the actual callback. */
|
||||
object->num_associated_callbacks++;
|
||||
object->num_running_callbacks++;
|
||||
@@ -1907,6 +2225,15 @@ static void CALLBACK threadpool_worker_proc( void *param )
|
||||
break;
|
||||
}
|
||||
|
||||
+ case TP_OBJECT_TYPE_WAIT:
|
||||
+ {
|
||||
+ TRACE( "executing wait callback %p(%p, %p, %p, %u)\n",
|
||||
@@ -1922,8 +2189,8 @@ static void CALLBACK threadpool_worker_proc( void *param )
|
||||
case TP_OBJECT_TYPE_WAIT:
|
||||
{
|
||||
TRACE( "executing wait callback %p(%p, %p, %p, %u)\n",
|
||||
- object->u.wait.callback, callback_instance, object->userdata, object, WAIT_OBJECT_0 );
|
||||
- object->u.wait.callback( callback_instance, object->userdata, (TP_WAIT *)object, WAIT_OBJECT_0 );
|
||||
+ object->u.wait.callback, callback_instance, object->userdata, object, wait_result );
|
||||
+ object->u.wait.callback( callback_instance, object->userdata, (TP_WAIT *)object, wait_result );
|
||||
+ TRACE( "callback %p returned\n", object->u.wait.callback );
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
default:
|
||||
assert(0);
|
||||
TRACE( "callback %p returned\n", object->u.wait.callback );
|
||||
break;
|
||||
@@ -2052,6 +2379,46 @@ NTSTATUS WINAPI TpAllocTimer( TP_TIMER **out, PTP_TIMER_CALLBACK callback, PVOID
|
||||
}
|
||||
}
|
||||
@@ -2097,6 +2364,15 @@ NTSTATUS WINAPI TpAllocWait( TP_WAIT **out, PTP_WAIT_CALLBACK callback, PVOID us
|
||||
|
||||
/***********************************************************************
|
||||
+ * TpAllocWait (NTDLL.@)
|
||||
+ */
|
||||
+NTSTATUS WINAPI TpAllocWait( TP_WAIT **out, PTP_WAIT_CALLBACK callback, PVOID userdata,
|
||||
+ TP_CALLBACK_ENVIRON *environment )
|
||||
+{
|
||||
+ struct threadpool_object *object;
|
||||
+ struct threadpool *pool;
|
||||
+ NTSTATUS status;
|
||||
+
|
||||
+ TRACE( "%p %p %p %p\n", out, callback, userdata, environment );
|
||||
+
|
||||
+ object = RtlAllocateHeap( GetProcessHeap(), 0, sizeof(*object) );
|
||||
+ if (!object)
|
||||
+ return STATUS_NO_MEMORY;
|
||||
+
|
||||
+ status = tp_threadpool_lock( &pool, environment );
|
||||
+ if (status)
|
||||
+ {
|
||||
+ RtlFreeHeap( GetProcessHeap(), 0, object );
|
||||
+ return status;
|
||||
+ }
|
||||
+
|
||||
+ object->type = TP_OBJECT_TYPE_WAIT;
|
||||
+ object->u.wait.callback = callback;
|
||||
object->type = TP_OBJECT_TYPE_WAIT;
|
||||
object->u.wait.callback = callback;
|
||||
+
|
||||
+ status = tp_waitqueue_lock( object );
|
||||
+ if (status)
|
||||
@ -526,17 +417,10 @@ index ef502ba..773c3d6 100644
|
||||
+ return status;
|
||||
+ }
|
||||
+
|
||||
+ tp_object_initialize( object, pool, userdata, environment );
|
||||
+
|
||||
+ *out = (TP_WAIT *)object;
|
||||
+ return STATUS_SUCCESS;
|
||||
+}
|
||||
+
|
||||
+/***********************************************************************
|
||||
* TpAllocWork (NTDLL.@)
|
||||
*/
|
||||
NTSTATUS WINAPI TpAllocWork( TP_WORK **out, PTP_WORK_CALLBACK callback, PVOID userdata,
|
||||
@@ -2252,7 +2619,7 @@ VOID WINAPI TpPostWork( TP_WORK *work )
|
||||
tp_object_initialize( object, pool, userdata, environment );
|
||||
|
||||
*out = (TP_WAIT *)object;
|
||||
@@ -2304,7 +2580,7 @@ VOID WINAPI TpPostWork( TP_WORK *work )
|
||||
|
||||
TRACE( "%p\n", work );
|
||||
|
||||
@ -545,27 +429,7 @@ index ef502ba..773c3d6 100644
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
@@ -2356,6 +2723,19 @@ VOID WINAPI TpReleaseTimer( TP_TIMER *timer )
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
+ * TpReleaseWait (NTDLL.@)
|
||||
+ */
|
||||
+VOID WINAPI TpReleaseWait( TP_WAIT *wait )
|
||||
+{
|
||||
+ struct threadpool_object *this = impl_from_TP_WAIT( wait );
|
||||
+
|
||||
+ TRACE( "%p\n", wait );
|
||||
+
|
||||
+ tp_object_shutdown( this );
|
||||
+ tp_object_release( this );
|
||||
+}
|
||||
+
|
||||
+/***********************************************************************
|
||||
* TpReleaseWork (NTDLL.@)
|
||||
*/
|
||||
VOID WINAPI TpReleaseWork( TP_WORK *work )
|
||||
@@ -2493,7 +2873,73 @@ VOID WINAPI TpSetTimer( TP_TIMER *timer, LARGE_INTEGER *timeout, LONG period, LO
|
||||
@@ -2558,7 +2834,68 @@ VOID WINAPI TpSetTimer( TP_TIMER *timer, LARGE_INTEGER *timeout, LONG period, LO
|
||||
RtlLeaveCriticalSection( &timerqueue.cs );
|
||||
|
||||
if (submit_timer)
|
||||
@ -579,57 +443,52 @@ index ef502ba..773c3d6 100644
|
||||
+VOID WINAPI TpSetWait( TP_WAIT *wait, HANDLE handle, LARGE_INTEGER *timeout )
|
||||
+{
|
||||
+ struct threadpool_object *this = impl_from_TP_WAIT( wait );
|
||||
+ ULONGLONG timestamp = TIMEOUT_INFINITE;
|
||||
+ BOOL submit_wait = FALSE;
|
||||
+
|
||||
+ TRACE( "%p %p %p\n", wait, handle, timeout );
|
||||
+
|
||||
+ RtlEnterCriticalSection( &waitqueue.cs );
|
||||
+ assert( this->u.wait.bucket );
|
||||
+
|
||||
+ /* update wait handle */
|
||||
+ assert( this->u.wait.bucket );
|
||||
+ this->u.wait.handle = handle;
|
||||
+
|
||||
+ /* for performance reasons we only wake up when something has changed */
|
||||
+ if (handle || this->u.wait.wait_pending)
|
||||
+ {
|
||||
+ struct waitqueue_bucket *bucket = this->u.wait.bucket;
|
||||
+ list_remove( &this->u.wait.wait_entry );
|
||||
+
|
||||
+ /* Convert relative timeout to absolute timestamp. */
|
||||
+ if (handle && timeout)
|
||||
+ {
|
||||
+ timestamp = timeout->QuadPart;
|
||||
+ if ((LONGLONG)timestamp < 0)
|
||||
+ {
|
||||
+ LARGE_INTEGER now;
|
||||
+ NtQuerySystemTime( &now );
|
||||
+ timestamp = now.QuadPart - timestamp;
|
||||
+ }
|
||||
+ else if (!timestamp)
|
||||
+ {
|
||||
+ submit_wait = TRUE;
|
||||
+ handle = NULL;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ /* Add wait object back into one of the queues. */
|
||||
+ if (handle)
|
||||
+ {
|
||||
+ ULONGLONG when = TIMEOUT_INFINITE;
|
||||
+
|
||||
+ if (timeout)
|
||||
+ {
|
||||
+ when = timeout->QuadPart;
|
||||
+
|
||||
+ /* A timeout of zero means that the wait should be submitted immediately */
|
||||
+ if (when == 0)
|
||||
+ {
|
||||
+ submit_wait = TRUE;
|
||||
+ goto remove_wait;
|
||||
+ }
|
||||
+
|
||||
+ /* Convert relative timeout to absolute */
|
||||
+ if ((LONGLONG)when < 0)
|
||||
+ {
|
||||
+ LARGE_INTEGER now;
|
||||
+ NtQuerySystemTime( &now );
|
||||
+ when = now.QuadPart - when;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ list_add_tail( &bucket->waits, &this->u.wait.wait_entry );
|
||||
+ list_add_tail( &bucket->waiting, &this->u.wait.wait_entry );
|
||||
+ this->u.wait.wait_pending = TRUE;
|
||||
+ this->u.wait.timeout = when;
|
||||
+ this->u.wait.timeout = timestamp;
|
||||
+ }
|
||||
+ else
|
||||
+ {
|
||||
+remove_wait:
|
||||
+ list_add_tail( &bucket->reserved, &this->u.wait.wait_entry );
|
||||
+ this->u.wait.wait_pending = FALSE;
|
||||
+ }
|
||||
+
|
||||
+ /* Wake up the wait queue thread. */
|
||||
+ NtSetEvent( bucket->update_event, NULL );
|
||||
+ }
|
||||
+
|
||||
@ -640,7 +499,7 @@ index ef502ba..773c3d6 100644
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
@@ -2541,6 +2987,20 @@ VOID WINAPI TpWaitForTimer( TP_TIMER *timer, BOOL cancel_pending )
|
||||
@@ -2606,6 +2943,20 @@ VOID WINAPI TpWaitForTimer( TP_TIMER *timer, BOOL cancel_pending )
|
||||
}
|
||||
|
||||
/***********************************************************************
|
@ -1,363 +0,0 @@
|
||||
From c8a3a30a9d4f4a3609d59298d486b691f682390e Mon Sep 17 00:00:00 2001
|
||||
From: Sebastian Lackner <sebastian@fds-team.de>
|
||||
Date: Fri, 6 Feb 2015 20:09:41 +0100
|
||||
Subject: ntdll/tests: Add tests for threadpool wait objects.
|
||||
|
||||
---
|
||||
dlls/ntdll/tests/threadpool.c | 295 ++++++++++++++++++++++++++++++++++++++++++
|
||||
1 file changed, 295 insertions(+)
|
||||
|
||||
diff --git a/dlls/ntdll/tests/threadpool.c b/dlls/ntdll/tests/threadpool.c
|
||||
index 0671202..9904d46 100644
|
||||
--- a/dlls/ntdll/tests/threadpool.c
|
||||
+++ b/dlls/ntdll/tests/threadpool.c
|
||||
@@ -24,11 +24,13 @@ static HMODULE hntdll = 0;
|
||||
static NTSTATUS (WINAPI *pTpAllocCleanupGroup)(TP_CLEANUP_GROUP **);
|
||||
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 NTSTATUS (WINAPI *pTpCallbackMayRunLong)(TP_CALLBACK_INSTANCE *);
|
||||
static VOID (WINAPI *pTpCallbackReleaseSemaphoreOnCompletion)(TP_CALLBACK_INSTANCE *,HANDLE,DWORD);
|
||||
static VOID (WINAPI *pTpDisassociateCallback)(TP_CALLBACK_INSTANCE *);
|
||||
static BOOL (WINAPI *pTpIsTimerSet)(TP_TIMER *);
|
||||
+static VOID (WINAPI *pTpReleaseWait)(TP_WAIT *);
|
||||
static VOID (WINAPI *pTpPostWork)(TP_WORK *);
|
||||
static VOID (WINAPI *pTpReleaseCleanupGroup)(TP_CLEANUP_GROUP *);
|
||||
static VOID (WINAPI *pTpReleaseCleanupGroupMembers)(TP_CLEANUP_GROUP *,BOOL,PVOID);
|
||||
@@ -37,8 +39,10 @@ static VOID (WINAPI *pTpReleaseTimer)(TP_TIMER *);
|
||||
static VOID (WINAPI *pTpReleaseWork)(TP_WORK *);
|
||||
static VOID (WINAPI *pTpSetPoolMaxThreads)(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 *pTpWaitForTimer)(TP_TIMER *,BOOL);
|
||||
+static VOID (WINAPI *pTpWaitForWait)(TP_WAIT *,BOOL);
|
||||
static VOID (WINAPI *pTpWaitForWork)(TP_WORK *,BOOL);
|
||||
|
||||
#define NTDLL_GET_PROC(func) \
|
||||
@@ -61,6 +65,7 @@ static BOOL init_threadpool(void)
|
||||
NTDLL_GET_PROC(TpAllocCleanupGroup);
|
||||
NTDLL_GET_PROC(TpAllocPool);
|
||||
NTDLL_GET_PROC(TpAllocTimer);
|
||||
+ NTDLL_GET_PROC(TpAllocWait);
|
||||
NTDLL_GET_PROC(TpAllocWork);
|
||||
NTDLL_GET_PROC(TpCallbackMayRunLong);
|
||||
NTDLL_GET_PROC(TpCallbackReleaseSemaphoreOnCompletion);
|
||||
@@ -71,11 +76,14 @@ static BOOL init_threadpool(void)
|
||||
NTDLL_GET_PROC(TpReleaseCleanupGroupMembers);
|
||||
NTDLL_GET_PROC(TpReleasePool);
|
||||
NTDLL_GET_PROC(TpReleaseTimer);
|
||||
+ NTDLL_GET_PROC(TpReleaseWait);
|
||||
NTDLL_GET_PROC(TpReleaseWork);
|
||||
NTDLL_GET_PROC(TpSetPoolMaxThreads);
|
||||
NTDLL_GET_PROC(TpSetTimer);
|
||||
+ NTDLL_GET_PROC(TpSetWait);
|
||||
NTDLL_GET_PROC(TpSimpleTryPost);
|
||||
NTDLL_GET_PROC(TpWaitForTimer);
|
||||
+ NTDLL_GET_PROC(TpWaitForWait);
|
||||
NTDLL_GET_PROC(TpWaitForWork);
|
||||
|
||||
if (!pTpAllocPool)
|
||||
@@ -906,6 +914,291 @@ static void test_tp_window_length(void)
|
||||
CloseHandle(semaphore);
|
||||
}
|
||||
|
||||
+static void CALLBACK wait_cb(TP_CALLBACK_INSTANCE *instance, void *userdata, TP_WAIT *wait, TP_WAIT_RESULT result)
|
||||
+{
|
||||
+ trace("Running wait callback\n");
|
||||
+
|
||||
+ if (result == WAIT_OBJECT_0)
|
||||
+ InterlockedIncrement((LONG *)userdata);
|
||||
+ else if (result == WAIT_TIMEOUT)
|
||||
+ InterlockedExchangeAdd((LONG *)userdata, 0x10000);
|
||||
+ else
|
||||
+ ok(0, "unexpected result %u\n", result);
|
||||
+}
|
||||
+
|
||||
+static void test_tp_wait(void)
|
||||
+{
|
||||
+ TP_CALLBACK_ENVIRON environment;
|
||||
+ HANDLE semaphore;
|
||||
+ TP_WAIT *wait, *wait2;
|
||||
+ TP_POOL *pool;
|
||||
+ NTSTATUS status;
|
||||
+ LONG userdata;
|
||||
+ LARGE_INTEGER when;
|
||||
+ 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");
|
||||
+
|
||||
+ /* Allocate new wait items */
|
||||
+ wait = NULL;
|
||||
+ memset(&environment, 0, sizeof(environment));
|
||||
+ environment.Version = 1;
|
||||
+ environment.Pool = pool;
|
||||
+ status = pTpAllocWait(&wait, wait_cb, &userdata, &environment);
|
||||
+ ok(!status, "TpAllocWait failed with status %x\n", status);
|
||||
+ ok(wait != NULL, "expected wait != NULL\n");
|
||||
+
|
||||
+ wait2 = NULL;
|
||||
+ status = pTpAllocWait(&wait2, wait_cb, &userdata, &environment);
|
||||
+ ok(!status, "TpAllocWait failed with status %x\n", status);
|
||||
+ ok(wait != NULL, "expected wait != NULL\n");
|
||||
+
|
||||
+ semaphore = CreateSemaphoreW(NULL, 0, 1, NULL);
|
||||
+ ok(semaphore != NULL, "failed to create semaphore\n");
|
||||
+
|
||||
+ /* Infinite timeout, signal the semaphore immediately */
|
||||
+ userdata = 0;
|
||||
+ pTpSetWait(wait, semaphore, NULL);
|
||||
+ ReleaseSemaphore(semaphore, 1, NULL);
|
||||
+ Sleep(50);
|
||||
+ ok(userdata == 1, "expected userdata = 1, got %u\n", userdata);
|
||||
+
|
||||
+ /* Relative timeout, no event */
|
||||
+ userdata = 0;
|
||||
+ when.QuadPart = (ULONGLONG)50 * -10000;
|
||||
+ pTpSetWait(wait, semaphore, &when);
|
||||
+ Sleep(100);
|
||||
+ pTpWaitForWait(wait, FALSE);
|
||||
+ ok(userdata == 0x10000, "expected userdata = 0x10000, got %u\n", userdata);
|
||||
+ ret = WaitForSingleObject(semaphore, 50);
|
||||
+ ok(ret == WAIT_TIMEOUT, "expected ret = WAIT_TIMEOUT, got %u\n", ret);
|
||||
+
|
||||
+ /* Relative timeout, with event */
|
||||
+ userdata = 0;
|
||||
+ when.QuadPart = (ULONGLONG)500 * -10000;
|
||||
+ pTpSetWait(wait, semaphore, &when);
|
||||
+ pTpWaitForWait(wait, TRUE);
|
||||
+ Sleep(250);
|
||||
+ ReleaseSemaphore(semaphore, 1, NULL);
|
||||
+ Sleep(50);
|
||||
+ pTpWaitForWait(wait, FALSE);
|
||||
+ ok(userdata == 1, "expected userdata = 1, got %u\n", userdata);
|
||||
+ ret = WaitForSingleObject(semaphore, 50);
|
||||
+ ok(ret == WAIT_TIMEOUT, "expected ret = WAIT_TIMEOUT, got %u\n", ret);
|
||||
+
|
||||
+ /* Absolute timeout, no event */
|
||||
+ userdata = 0;
|
||||
+ NtQuerySystemTime( &when );
|
||||
+ when.QuadPart += (ULONGLONG)50 * 10000;
|
||||
+ pTpSetWait(wait, semaphore, &when);
|
||||
+ Sleep(100);
|
||||
+ pTpWaitForWait(wait, FALSE);
|
||||
+ ok(userdata == 0x10000, "expected userdata = 0x10000, got %u\n", userdata);
|
||||
+ ret = WaitForSingleObject(semaphore, 50);
|
||||
+ ok(ret == WAIT_TIMEOUT, "expected ret = WAIT_TIMEOUT, got %u\n", ret);
|
||||
+
|
||||
+ /* Absolute timeout, with event */
|
||||
+ userdata = 0;
|
||||
+ NtQuerySystemTime( &when );
|
||||
+ when.QuadPart += (ULONGLONG)500 * 10000;
|
||||
+ pTpSetWait(wait, semaphore, &when);
|
||||
+ pTpWaitForWait(wait, TRUE);
|
||||
+ Sleep(250);
|
||||
+ ReleaseSemaphore(semaphore, 1, NULL);
|
||||
+ Sleep(50);
|
||||
+ pTpWaitForWait(wait, FALSE);
|
||||
+ ok(userdata == 1, "expected userdata = 1, got %u\n", userdata);
|
||||
+ ret = WaitForSingleObject(semaphore, 50);
|
||||
+ ok(ret == WAIT_TIMEOUT, "expected ret = WAIT_TIMEOUT, got %u\n", ret);
|
||||
+
|
||||
+ /* Trigger event immediately */
|
||||
+ userdata = 0;
|
||||
+ when.QuadPart = 0;
|
||||
+ pTpSetWait(wait, semaphore, &when);
|
||||
+ pTpWaitForWait(wait, FALSE);
|
||||
+ ok(userdata == 0x10000, "expected userdata = 0x10000, got %u\n", userdata);
|
||||
+ ret = WaitForSingleObject(semaphore, 50);
|
||||
+ ok(ret == WAIT_TIMEOUT, "expected ret = WAIT_TIMEOUT, got %u\n", ret);
|
||||
+
|
||||
+ /* Cancel a pending wait */
|
||||
+ userdata = 0;
|
||||
+ when.QuadPart = (ULONGLONG)500 * -10000;
|
||||
+ pTpSetWait(wait, semaphore, &when);
|
||||
+ pTpWaitForWait(wait, TRUE);
|
||||
+ Sleep(250);
|
||||
+ pTpSetWait(wait, NULL, (void *)0xdeadbeef);
|
||||
+ Sleep(50);
|
||||
+ ReleaseSemaphore(semaphore, 1, NULL);
|
||||
+ Sleep(50);
|
||||
+ ok(userdata == 0, "expected userdata = 0, got %u\n", userdata);
|
||||
+ ret = WaitForSingleObject(semaphore, 1000);
|
||||
+ ok(ret == WAIT_OBJECT_0, "expected ret = WAIT_OBJECT_0, got %u\n", ret);
|
||||
+
|
||||
+ /* Test with INVALID_HANDLE_VALUE */
|
||||
+ userdata = 0;
|
||||
+ when.QuadPart = 0;
|
||||
+ pTpSetWait(wait, INVALID_HANDLE_VALUE, &when);
|
||||
+ Sleep(50);
|
||||
+ ok(userdata == 0x10000, "expected userdata = 0x10000, got %u\n", userdata);
|
||||
+
|
||||
+ /* Cancel a pending wait with INVALID_HANDLE_VALUE */
|
||||
+ userdata = 0;
|
||||
+ when.QuadPart = (ULONGLONG)500 * -10000;
|
||||
+ pTpSetWait(wait, semaphore, &when);
|
||||
+ pTpWaitForWait(wait, TRUE);
|
||||
+ Sleep(250);
|
||||
+ when.QuadPart = (ULONGLONG)100 * -10000;
|
||||
+ pTpSetWait(wait, INVALID_HANDLE_VALUE, &when);
|
||||
+ Sleep(250);
|
||||
+ ok(userdata == 0x10000, "expected userdata = 0x10000, got %u\n", userdata);
|
||||
+
|
||||
+ /* Add two objects with the same semaphore */
|
||||
+ userdata = 0;
|
||||
+ pTpSetWait(wait, semaphore, NULL);
|
||||
+ pTpSetWait(wait2, semaphore, NULL);
|
||||
+ ok(userdata == 0, "expected userdata = 0, got %u\n", userdata);
|
||||
+ ReleaseSemaphore(semaphore, 1, NULL);
|
||||
+ Sleep(10);
|
||||
+ pTpWaitForWait(wait, FALSE);
|
||||
+ pTpWaitForWait(wait2, FALSE);
|
||||
+ ok(userdata == 1, "expected userdata = 1, got %u\n", userdata);
|
||||
+ ret = WaitForSingleObject(semaphore, 50);
|
||||
+ ok(ret == WAIT_TIMEOUT, "expected ret = WAIT_TIMEOUT, got %u\n", ret);
|
||||
+
|
||||
+ CloseHandle(semaphore);
|
||||
+ semaphore = CreateSemaphoreW(NULL, 0, 2, NULL);
|
||||
+ ok(semaphore != NULL, "failed to create semaphore\n");
|
||||
+
|
||||
+ /* Repeat test above, but with a semaphore of count 2 */
|
||||
+ userdata = 0;
|
||||
+ pTpSetWait(wait, semaphore, NULL);
|
||||
+ pTpSetWait(wait2, semaphore, NULL);
|
||||
+ ok(userdata == 0, "expected userdata = 0, got %u\n", userdata);
|
||||
+ ReleaseSemaphore(semaphore, 2, NULL);
|
||||
+ Sleep(10);
|
||||
+ pTpWaitForWait(wait, FALSE);
|
||||
+ pTpWaitForWait(wait2, FALSE);
|
||||
+ ok(userdata == 2, "expected userdata = 2, got %u\n", userdata);
|
||||
+ ret = WaitForSingleObject(semaphore, 50);
|
||||
+ ok(ret == WAIT_TIMEOUT, "expected ret = WAIT_TIMEOUT, got %u\n", ret);
|
||||
+
|
||||
+ CloseHandle(semaphore);
|
||||
+
|
||||
+ /* Cleanup */
|
||||
+ pTpReleaseWait(wait2);
|
||||
+ pTpReleaseWait(wait);
|
||||
+ pTpReleasePool(pool);
|
||||
+}
|
||||
+
|
||||
+static LONG multi_wait_callbacks;
|
||||
+static DWORD multi_wait_result;
|
||||
+
|
||||
+static void CALLBACK multi_wait_cb(TP_CALLBACK_INSTANCE *instance, void *userdata, TP_WAIT *wait, TP_WAIT_RESULT result)
|
||||
+{
|
||||
+ DWORD index = (DWORD)(DWORD_PTR)userdata;
|
||||
+ InterlockedIncrement(&multi_wait_callbacks);
|
||||
+
|
||||
+ if (result == WAIT_OBJECT_0)
|
||||
+ multi_wait_result = index;
|
||||
+ else if (result == WAIT_TIMEOUT)
|
||||
+ multi_wait_result = 0x10000 | index;
|
||||
+ else
|
||||
+ ok(0, "unexpected result %u\n", result);
|
||||
+}
|
||||
+
|
||||
+static void test_tp_multi_wait(void)
|
||||
+{
|
||||
+ TP_CALLBACK_ENVIRON environment;
|
||||
+ HANDLE semaphores[512];
|
||||
+ TP_WAIT *waits[512];
|
||||
+ TP_POOL *pool;
|
||||
+ NTSTATUS status;
|
||||
+ LARGE_INTEGER when;
|
||||
+ 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");
|
||||
+
|
||||
+ memset(&environment, 0, sizeof(environment));
|
||||
+ environment.Version = 1;
|
||||
+ environment.Pool = pool;
|
||||
+
|
||||
+ /* Create semaphores, wait objects and enable them */
|
||||
+ for (i = 0; i < sizeof(semaphores)/sizeof(semaphores[0]); i++)
|
||||
+ {
|
||||
+ semaphores[i] = CreateSemaphoreW(NULL, 0, 1, NULL);
|
||||
+ ok(semaphores[i] != NULL, "failed to create semaphores[%d]\n", i);
|
||||
+
|
||||
+ waits[i] = NULL;
|
||||
+ status = pTpAllocWait(&waits[i], multi_wait_cb, (void *)(DWORD_PTR)i, &environment);
|
||||
+ ok(!status, "TpAllocWait failed with status %x\n", status);
|
||||
+ ok(waits[i] != NULL, "expected waits[%d] != NULL\n", i);
|
||||
+
|
||||
+ pTpSetWait(waits[i], semaphores[i], NULL);
|
||||
+ }
|
||||
+
|
||||
+ /* Now test releasing the semaphores */
|
||||
+ for (i = 0; i < sizeof(semaphores)/sizeof(semaphores[0]); i++)
|
||||
+ {
|
||||
+ multi_wait_callbacks = 0;
|
||||
+ multi_wait_result = 0;
|
||||
+
|
||||
+ ReleaseSemaphore(semaphores[i], 1, NULL);
|
||||
+ while (multi_wait_callbacks == 0) Sleep(10);
|
||||
+ ok(multi_wait_callbacks == 1, "expected multi_wait_callbacks = 1, got %u\n", multi_wait_callbacks);
|
||||
+ ok(multi_wait_result == i, "expected multi_wait_result = %u, got %u\n", multi_wait_result, i);
|
||||
+
|
||||
+ pTpSetWait(waits[i], semaphores[i], NULL);
|
||||
+ }
|
||||
+
|
||||
+ /* Now again in the reversed order */
|
||||
+ for (i = sizeof(semaphores)/sizeof(semaphores[0]) - 1; i >= 0; i--)
|
||||
+ {
|
||||
+ multi_wait_callbacks = 0;
|
||||
+ multi_wait_result = 0;
|
||||
+
|
||||
+ ReleaseSemaphore(semaphores[i], 1, NULL);
|
||||
+ while (multi_wait_callbacks == 0) Sleep(10);
|
||||
+ ok(multi_wait_callbacks == 1, "expected multi_wait_callbacks = 1, got %u\n", multi_wait_callbacks);
|
||||
+ ok(multi_wait_result == i, "expected multi_wait_result = %u, got %u\n", multi_wait_result, i);
|
||||
+
|
||||
+ pTpSetWait(waits[i], semaphores[i], NULL);
|
||||
+ }
|
||||
+
|
||||
+ /* Now test with a timeout */
|
||||
+ multi_wait_callbacks = 0;
|
||||
+ multi_wait_result = 0;
|
||||
+ for (i = 0; i < sizeof(semaphores)/sizeof(semaphores[0]); i++)
|
||||
+ {
|
||||
+ when.QuadPart = 0;
|
||||
+ pTpSetWait(waits[i], semaphores[i], &when);
|
||||
+ }
|
||||
+ Sleep(50);
|
||||
+ ok(multi_wait_callbacks == sizeof(semaphores)/sizeof(semaphores[0]),
|
||||
+ "got wrong multi_wait_callbacks %u\n", multi_wait_callbacks);
|
||||
+ ok(multi_wait_result >> 16, "expected multi_wait_result >> 16 != 0\n");
|
||||
+
|
||||
+ /* Add them all again, we want that the wait is pending while destroying it */
|
||||
+ for (i = 0; i < sizeof(semaphores)/sizeof(semaphores[0]); i++)
|
||||
+ pTpSetWait(waits[i], semaphores[i], NULL);
|
||||
+
|
||||
+ /* Destroy the objects and semaphores */
|
||||
+ for (i = 0; i < sizeof(semaphores)/sizeof(semaphores[0]); i++)
|
||||
+ {
|
||||
+ pTpReleaseWait(waits[i]);
|
||||
+ NtClose(semaphores[i]);
|
||||
+ }
|
||||
+
|
||||
+ pTpReleasePool(pool);
|
||||
+}
|
||||
+
|
||||
START_TEST(threadpool)
|
||||
{
|
||||
if (!init_threadpool())
|
||||
@@ -919,4 +1212,6 @@ START_TEST(threadpool)
|
||||
test_tp_disassociate();
|
||||
test_tp_timer();
|
||||
test_tp_window_length();
|
||||
+ test_tp_wait();
|
||||
+ test_tp_multi_wait();
|
||||
}
|
||||
--
|
||||
2.4.4
|
||||
|
@ -0,0 +1,334 @@
|
||||
From ef697e2435d35ad1a53fd60209da7a7d4100dcf2 Mon Sep 17 00:00:00 2001
|
||||
From: Sebastian Lackner <sebastian@fds-team.de>
|
||||
Date: Sat, 4 Jul 2015 04:36:23 +0200
|
||||
Subject: ntdll/tests: Add basic tests for threadpool wait objects.
|
||||
|
||||
---
|
||||
dlls/ntdll/tests/threadpool.c | 266 ++++++++++++++++++++++++++++++++++++++++++
|
||||
1 file changed, 266 insertions(+)
|
||||
|
||||
diff --git a/dlls/ntdll/tests/threadpool.c b/dlls/ntdll/tests/threadpool.c
|
||||
index 0671202..c808400 100644
|
||||
--- a/dlls/ntdll/tests/threadpool.c
|
||||
+++ b/dlls/ntdll/tests/threadpool.c
|
||||
@@ -24,11 +24,13 @@ static HMODULE hntdll = 0;
|
||||
static NTSTATUS (WINAPI *pTpAllocCleanupGroup)(TP_CLEANUP_GROUP **);
|
||||
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 NTSTATUS (WINAPI *pTpCallbackMayRunLong)(TP_CALLBACK_INSTANCE *);
|
||||
static VOID (WINAPI *pTpCallbackReleaseSemaphoreOnCompletion)(TP_CALLBACK_INSTANCE *,HANDLE,DWORD);
|
||||
static VOID (WINAPI *pTpDisassociateCallback)(TP_CALLBACK_INSTANCE *);
|
||||
static BOOL (WINAPI *pTpIsTimerSet)(TP_TIMER *);
|
||||
+static VOID (WINAPI *pTpReleaseWait)(TP_WAIT *);
|
||||
static VOID (WINAPI *pTpPostWork)(TP_WORK *);
|
||||
static VOID (WINAPI *pTpReleaseCleanupGroup)(TP_CLEANUP_GROUP *);
|
||||
static VOID (WINAPI *pTpReleaseCleanupGroupMembers)(TP_CLEANUP_GROUP *,BOOL,PVOID);
|
||||
@@ -37,8 +39,10 @@ static VOID (WINAPI *pTpReleaseTimer)(TP_TIMER *);
|
||||
static VOID (WINAPI *pTpReleaseWork)(TP_WORK *);
|
||||
static VOID (WINAPI *pTpSetPoolMaxThreads)(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 *pTpWaitForTimer)(TP_TIMER *,BOOL);
|
||||
+static VOID (WINAPI *pTpWaitForWait)(TP_WAIT *,BOOL);
|
||||
static VOID (WINAPI *pTpWaitForWork)(TP_WORK *,BOOL);
|
||||
|
||||
#define NTDLL_GET_PROC(func) \
|
||||
@@ -61,6 +65,7 @@ static BOOL init_threadpool(void)
|
||||
NTDLL_GET_PROC(TpAllocCleanupGroup);
|
||||
NTDLL_GET_PROC(TpAllocPool);
|
||||
NTDLL_GET_PROC(TpAllocTimer);
|
||||
+ NTDLL_GET_PROC(TpAllocWait);
|
||||
NTDLL_GET_PROC(TpAllocWork);
|
||||
NTDLL_GET_PROC(TpCallbackMayRunLong);
|
||||
NTDLL_GET_PROC(TpCallbackReleaseSemaphoreOnCompletion);
|
||||
@@ -71,11 +76,14 @@ static BOOL init_threadpool(void)
|
||||
NTDLL_GET_PROC(TpReleaseCleanupGroupMembers);
|
||||
NTDLL_GET_PROC(TpReleasePool);
|
||||
NTDLL_GET_PROC(TpReleaseTimer);
|
||||
+ NTDLL_GET_PROC(TpReleaseWait);
|
||||
NTDLL_GET_PROC(TpReleaseWork);
|
||||
NTDLL_GET_PROC(TpSetPoolMaxThreads);
|
||||
NTDLL_GET_PROC(TpSetTimer);
|
||||
+ NTDLL_GET_PROC(TpSetWait);
|
||||
NTDLL_GET_PROC(TpSimpleTryPost);
|
||||
NTDLL_GET_PROC(TpWaitForTimer);
|
||||
+ NTDLL_GET_PROC(TpWaitForWait);
|
||||
NTDLL_GET_PROC(TpWaitForWork);
|
||||
|
||||
if (!pTpAllocPool)
|
||||
@@ -906,6 +914,263 @@ static void test_tp_window_length(void)
|
||||
CloseHandle(semaphore);
|
||||
}
|
||||
|
||||
+struct wait_info
|
||||
+{
|
||||
+ HANDLE semaphore;
|
||||
+ LONG userdata;
|
||||
+};
|
||||
+
|
||||
+static void CALLBACK wait_cb(TP_CALLBACK_INSTANCE *instance, void *userdata,
|
||||
+ TP_WAIT *wait, TP_WAIT_RESULT result)
|
||||
+{
|
||||
+ struct wait_info *info = userdata;
|
||||
+ trace("Running wait callback\n");
|
||||
+
|
||||
+ if (result == WAIT_OBJECT_0)
|
||||
+ InterlockedIncrement(&info->userdata);
|
||||
+ else if (result == WAIT_TIMEOUT)
|
||||
+ InterlockedExchangeAdd(&info->userdata, 0x10000);
|
||||
+ else
|
||||
+ ok(0, "unexpected result %u\n", result);
|
||||
+ ReleaseSemaphore(info->semaphore, 1, NULL);
|
||||
+}
|
||||
+
|
||||
+static void test_tp_wait(void)
|
||||
+{
|
||||
+ TP_CALLBACK_ENVIRON environment;
|
||||
+ TP_WAIT *wait1, *wait2;
|
||||
+ struct wait_info info;
|
||||
+ HANDLE semaphores[2];
|
||||
+ LARGE_INTEGER when;
|
||||
+ NTSTATUS status;
|
||||
+ TP_POOL *pool;
|
||||
+ DWORD result;
|
||||
+
|
||||
+ semaphores[0] = CreateSemaphoreW(NULL, 0, 2, NULL);
|
||||
+ ok(semaphores[0] != NULL, "failed to create semaphore\n");
|
||||
+ semaphores[1] = CreateSemaphoreW(NULL, 0, 1, NULL);
|
||||
+ ok(semaphores[1] != NULL, "failed to create semaphore\n");
|
||||
+ info.semaphore = semaphores[0];
|
||||
+
|
||||
+ /* 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 wait items */
|
||||
+ memset(&environment, 0, sizeof(environment));
|
||||
+ environment.Version = 1;
|
||||
+ environment.Pool = pool;
|
||||
+
|
||||
+ wait1 = NULL;
|
||||
+ status = pTpAllocWait(&wait1, wait_cb, &info, &environment);
|
||||
+ ok(!status, "TpAllocWait failed with status %x\n", status);
|
||||
+ ok(wait1 != NULL, "expected wait1 != NULL\n");
|
||||
+
|
||||
+ wait2 = NULL;
|
||||
+ status = pTpAllocWait(&wait2, wait_cb, &info, &environment);
|
||||
+ ok(!status, "TpAllocWait failed with status %x\n", status);
|
||||
+ ok(wait2 != NULL, "expected wait2 != NULL\n");
|
||||
+
|
||||
+ /* infinite timeout, signal the semaphore immediately */
|
||||
+ info.userdata = 0;
|
||||
+ pTpSetWait(wait1, semaphores[1], NULL);
|
||||
+ ReleaseSemaphore(semaphores[1], 1, NULL);
|
||||
+ result = WaitForSingleObject(semaphores[0], 100);
|
||||
+ ok(result == WAIT_OBJECT_0, "WaitForSingleObject returned %u\n", result);
|
||||
+ ok(info.userdata == 1, "expected info.userdata = 1, got %u\n", info.userdata);
|
||||
+ result = WaitForSingleObject(semaphores[1], 0);
|
||||
+ ok(result == WAIT_TIMEOUT, "WaitForSingleObject returned %u\n", result);
|
||||
+
|
||||
+ /* relative timeout, no event */
|
||||
+ info.userdata = 0;
|
||||
+ when.QuadPart = (ULONGLONG)200 * -10000;
|
||||
+ pTpSetWait(wait1, semaphores[1], &when);
|
||||
+ result = WaitForSingleObject(semaphores[0], 100);
|
||||
+ ok(result == WAIT_TIMEOUT, "WaitForSingleObject returned %u\n", result);
|
||||
+ ok(info.userdata == 0, "expected info.userdata = 0, got %u\n", info.userdata);
|
||||
+ result = WaitForSingleObject(semaphores[0], 200);
|
||||
+ ok(result == WAIT_OBJECT_0, "WaitForSingleObject returned %u\n", result);
|
||||
+ ok(info.userdata == 0x10000, "expected info.userdata = 0x10000, got %u\n", info.userdata);
|
||||
+ result = WaitForSingleObject(semaphores[1], 0);
|
||||
+ ok(result == WAIT_TIMEOUT, "WaitForSingleObject returned %u\n", result);
|
||||
+
|
||||
+ /* repeat test with call to TpWaitForWait(..., TRUE) */
|
||||
+ info.userdata = 0;
|
||||
+ when.QuadPart = (ULONGLONG)200 * -10000;
|
||||
+ pTpSetWait(wait1, semaphores[1], &when);
|
||||
+ result = WaitForSingleObject(semaphores[0], 100);
|
||||
+ ok(result == WAIT_TIMEOUT, "WaitForSingleObject returned %u\n", result);
|
||||
+ pTpWaitForWait(wait1, TRUE);
|
||||
+ ok(info.userdata == 0, "expected info.userdata = 0, got %u\n", info.userdata);
|
||||
+ result = WaitForSingleObject(semaphores[0], 200);
|
||||
+ ok(result == WAIT_OBJECT_0 || broken(result == WAIT_TIMEOUT) /* Win 8 */,
|
||||
+ "WaitForSingleObject returned %u\n", result);
|
||||
+ if (result == WAIT_OBJECT_0)
|
||||
+ ok(info.userdata == 0x10000, "expected info.userdata = 0x10000, got %u\n", info.userdata);
|
||||
+ else
|
||||
+ ok(info.userdata == 0, "expected info.userdata = 0, got %u\n", info.userdata);
|
||||
+ result = WaitForSingleObject(semaphores[1], 0);
|
||||
+ ok(result == WAIT_TIMEOUT, "WaitForSingleObject returned %u\n", result);
|
||||
+
|
||||
+ /* relative timeout, with event */
|
||||
+ info.userdata = 0;
|
||||
+ when.QuadPart = (ULONGLONG)200 * -10000;
|
||||
+ pTpSetWait(wait1, semaphores[1], &when);
|
||||
+ result = WaitForSingleObject(semaphores[0], 100);
|
||||
+ ok(result == WAIT_TIMEOUT, "WaitForSingleObject returned %u\n", result);
|
||||
+ ok(info.userdata == 0, "expected info.userdata = 0, got %u\n", info.userdata);
|
||||
+ ReleaseSemaphore(semaphores[1], 1, NULL);
|
||||
+ result = WaitForSingleObject(semaphores[0], 100);
|
||||
+ ok(result == WAIT_OBJECT_0, "WaitForSingleObject returned %u\n", result);
|
||||
+ ok(info.userdata == 1, "expected info.userdata = 1, got %u\n", info.userdata);
|
||||
+ result = WaitForSingleObject(semaphores[1], 0);
|
||||
+ ok(result == WAIT_TIMEOUT, "WaitForSingleObject returned %u\n", result);
|
||||
+
|
||||
+ /* repeat test with call to TpWaitForWait(..., TRUE) */
|
||||
+ info.userdata = 0;
|
||||
+ when.QuadPart = (ULONGLONG)200 * -10000;
|
||||
+ pTpSetWait(wait1, semaphores[1], &when);
|
||||
+ result = WaitForSingleObject(semaphores[0], 100);
|
||||
+ ok(result == WAIT_TIMEOUT, "WaitForSingleObject returned %u\n", result);
|
||||
+ pTpWaitForWait(wait1, TRUE);
|
||||
+ ok(info.userdata == 0, "expected info.userdata = 0, got %u\n", info.userdata);
|
||||
+ ReleaseSemaphore(semaphores[1], 1, NULL);
|
||||
+ result = WaitForSingleObject(semaphores[0], 100);
|
||||
+ ok(result == WAIT_OBJECT_0 || broken(result == WAIT_TIMEOUT) /* Win 8 */,
|
||||
+ "WaitForSingleObject returned %u\n", result);
|
||||
+ if (result == WAIT_OBJECT_0)
|
||||
+ {
|
||||
+ ok(info.userdata == 1, "expected info.userdata = 1, got %u\n", info.userdata);
|
||||
+ result = WaitForSingleObject(semaphores[1], 0);
|
||||
+ ok(result == WAIT_TIMEOUT, "WaitForSingleObject returned %u\n", result);
|
||||
+ }
|
||||
+ else
|
||||
+ {
|
||||
+ ok(info.userdata == 0, "expected info.userdata = 0, got %u\n", info.userdata);
|
||||
+ result = WaitForSingleObject(semaphores[1], 0);
|
||||
+ ok(result == WAIT_OBJECT_0, "WaitForSingleObject returned %u\n", result);
|
||||
+ }
|
||||
+
|
||||
+ /* absolute timeout, no event */
|
||||
+ info.userdata = 0;
|
||||
+ NtQuerySystemTime( &when );
|
||||
+ when.QuadPart += (ULONGLONG)200 * 10000;
|
||||
+ pTpSetWait(wait1, semaphores[1], &when);
|
||||
+ result = WaitForSingleObject(semaphores[0], 100);
|
||||
+ ok(result == WAIT_TIMEOUT, "WaitForSingleObject returned %u\n", result);
|
||||
+ ok(info.userdata == 0, "expected info.userdata = 0, got %u\n", info.userdata);
|
||||
+ result = WaitForSingleObject(semaphores[0], 200);
|
||||
+ ok(result == WAIT_OBJECT_0, "WaitForSingleObject returned %u\n", result);
|
||||
+ ok(info.userdata == 0x10000, "expected info.userdata = 0x10000, got %u\n", info.userdata);
|
||||
+ result = WaitForSingleObject(semaphores[1], 0);
|
||||
+ ok(result == WAIT_TIMEOUT, "WaitForSingleObject returned %u\n", result);
|
||||
+
|
||||
+ /* absolute timeout, with event */
|
||||
+ info.userdata = 0;
|
||||
+ NtQuerySystemTime( &when );
|
||||
+ when.QuadPart += (ULONGLONG)200 * 10000;
|
||||
+ pTpSetWait(wait1, semaphores[1], &when);
|
||||
+ result = WaitForSingleObject(semaphores[0], 100);
|
||||
+ ok(result == WAIT_TIMEOUT, "WaitForSingleObject returned %u\n", result);
|
||||
+ ok(info.userdata == 0, "expected info.userdata = 0, got %u\n", info.userdata);
|
||||
+ ReleaseSemaphore(semaphores[1], 1, NULL);
|
||||
+ result = WaitForSingleObject(semaphores[0], 100);
|
||||
+ ok(result == WAIT_OBJECT_0, "WaitForSingleObject returned %u\n", result);
|
||||
+ ok(info.userdata == 1, "expected info.userdata = 1, got %u\n", info.userdata);
|
||||
+ result = WaitForSingleObject(semaphores[1], 0);
|
||||
+ ok(result == WAIT_TIMEOUT, "WaitForSingleObject returned %u\n", result);
|
||||
+
|
||||
+ /* test timeout of zero */
|
||||
+ info.userdata = 0;
|
||||
+ when.QuadPart = 0;
|
||||
+ pTpSetWait(wait1, semaphores[1], &when);
|
||||
+ result = WaitForSingleObject(semaphores[0], 100);
|
||||
+ ok(result == WAIT_OBJECT_0, "WaitForSingleObject returned %u\n", result);
|
||||
+ ok(info.userdata == 0x10000, "expected info.userdata = 0x10000, got %u\n", info.userdata);
|
||||
+ result = WaitForSingleObject(semaphores[1], 0);
|
||||
+ ok(result == WAIT_TIMEOUT, "WaitForSingleObject returned %u\n", result);
|
||||
+
|
||||
+ /* cancel a pending wait */
|
||||
+ info.userdata = 0;
|
||||
+ when.QuadPart = (ULONGLONG)250 * -10000;
|
||||
+ pTpSetWait(wait1, semaphores[1], &when);
|
||||
+ result = WaitForSingleObject(semaphores[0], 100);
|
||||
+ ok(result == WAIT_TIMEOUT, "WaitForSingleObject returned %u\n", result);
|
||||
+ pTpSetWait(wait1, NULL, (void *)0xdeadbeef);
|
||||
+ Sleep(50);
|
||||
+ ReleaseSemaphore(semaphores[1], 1, NULL);
|
||||
+ result = WaitForSingleObject(semaphores[0], 100);
|
||||
+ ok(result == WAIT_TIMEOUT, "WaitForSingleObject returned %u\n", result);
|
||||
+ ok(info.userdata == 0, "expected info.userdata = 0, got %u\n", info.userdata);
|
||||
+ result = WaitForSingleObject(semaphores[1], 0);
|
||||
+ ok(result == WAIT_OBJECT_0, "WaitForSingleObject returned %u\n", result);
|
||||
+
|
||||
+ /* test with INVALID_HANDLE_VALUE */
|
||||
+ info.userdata = 0;
|
||||
+ when.QuadPart = 0;
|
||||
+ pTpSetWait(wait1, INVALID_HANDLE_VALUE, &when);
|
||||
+ result = WaitForSingleObject(semaphores[0], 100);
|
||||
+ ok(result == WAIT_OBJECT_0, "WaitForSingleObject returned %u\n", result);
|
||||
+ ok(info.userdata == 0x10000, "expected info.userdata = 0x10000, got %u\n", info.userdata);
|
||||
+
|
||||
+ /* cancel a pending wait with INVALID_HANDLE_VALUE */
|
||||
+ info.userdata = 0;
|
||||
+ when.QuadPart = (ULONGLONG)250 * -10000;
|
||||
+ pTpSetWait(wait1, semaphores[1], &when);
|
||||
+ result = WaitForSingleObject(semaphores[0], 100);
|
||||
+ ok(result == WAIT_TIMEOUT, "WaitForSingleObject returned %u\n", result);
|
||||
+ when.QuadPart = 0;
|
||||
+ pTpSetWait(wait1, INVALID_HANDLE_VALUE, &when);
|
||||
+ Sleep(50);
|
||||
+ ReleaseSemaphore(semaphores[1], 1, NULL);
|
||||
+ result = WaitForSingleObject(semaphores[0], 100);
|
||||
+ ok(result == WAIT_OBJECT_0, "WaitForSingleObject returned %u\n", result);
|
||||
+ ok(info.userdata == 0x10000, "expected info.userdata = 0x10000, got %u\n", info.userdata);
|
||||
+ result = WaitForSingleObject(semaphores[1], 0);
|
||||
+ ok(result == WAIT_OBJECT_0, "WaitForSingleObject returned %u\n", result);
|
||||
+
|
||||
+ CloseHandle(semaphores[1]);
|
||||
+ semaphores[1] = CreateSemaphoreW(NULL, 0, 2, NULL);
|
||||
+ ok(semaphores[1] != NULL, "failed to create semaphore\n");
|
||||
+
|
||||
+ /* add two wait objects with the same semaphore */
|
||||
+ info.userdata = 0;
|
||||
+ pTpSetWait(wait1, semaphores[1], NULL);
|
||||
+ pTpSetWait(wait2, semaphores[1], NULL);
|
||||
+ Sleep(50);
|
||||
+ ReleaseSemaphore(semaphores[1], 1, NULL);
|
||||
+ result = WaitForSingleObject(semaphores[0], 100);
|
||||
+ ok(result == WAIT_OBJECT_0, "WaitForSingleObject returned %u\n", result);
|
||||
+ result = WaitForSingleObject(semaphores[0], 100);
|
||||
+ ok(result == WAIT_TIMEOUT, "WaitForSingleObject returned %u\n", result);
|
||||
+ ok(info.userdata == 1, "expected info.userdata = 1, got %u\n", info.userdata);
|
||||
+ result = WaitForSingleObject(semaphores[1], 0);
|
||||
+ ok(result == WAIT_TIMEOUT, "WaitForSingleObject returned %u\n", result);
|
||||
+
|
||||
+ /* repeat test above, but with a semaphore of count 2 */
|
||||
+ info.userdata = 0;
|
||||
+ pTpSetWait(wait1, semaphores[1], NULL);
|
||||
+ pTpSetWait(wait2, semaphores[1], NULL);
|
||||
+ Sleep(50);
|
||||
+ result = ReleaseSemaphore(semaphores[1], 2, NULL);
|
||||
+ result = WaitForSingleObject(semaphores[0], 100);
|
||||
+ ok(result == WAIT_OBJECT_0, "WaitForSingleObject returned %u\n", result);
|
||||
+ result = WaitForSingleObject(semaphores[0], 100);
|
||||
+ ok(result == WAIT_OBJECT_0, "WaitForSingleObject returned %u\n", result);
|
||||
+ ok(info.userdata == 2, "expected info.userdata = 2, got %u\n", info.userdata);
|
||||
+ result = WaitForSingleObject(semaphores[1], 0);
|
||||
+ ok(result == WAIT_TIMEOUT, "WaitForSingleObject returned %u\n", result);
|
||||
+
|
||||
+ /* cleanup */
|
||||
+ pTpReleaseWait(wait1);
|
||||
+ pTpReleaseWait(wait2);
|
||||
+ pTpReleasePool(pool);
|
||||
+ CloseHandle(semaphores[0]);
|
||||
+ CloseHandle(semaphores[1]);
|
||||
+}
|
||||
+
|
||||
START_TEST(threadpool)
|
||||
{
|
||||
if (!init_threadpool())
|
||||
@@ -919,4 +1184,5 @@ START_TEST(threadpool)
|
||||
test_tp_disassociate();
|
||||
test_tp_timer();
|
||||
test_tp_window_length();
|
||||
+ test_tp_wait();
|
||||
}
|
||||
--
|
||||
2.4.4
|
||||
|
@ -0,0 +1,147 @@
|
||||
From fa0a0443778b2c4730a66ee4909681e0379673f4 Mon Sep 17 00:00:00 2001
|
||||
From: Sebastian Lackner <sebastian@fds-team.de>
|
||||
Date: Sat, 4 Jul 2015 13:29:38 +0200
|
||||
Subject: ntdll/tests: Add highly multithreaded wait tests.
|
||||
|
||||
---
|
||||
dlls/ntdll/tests/threadpool.c | 119 ++++++++++++++++++++++++++++++++++++++++++
|
||||
1 file changed, 119 insertions(+)
|
||||
|
||||
diff --git a/dlls/ntdll/tests/threadpool.c b/dlls/ntdll/tests/threadpool.c
|
||||
index c808400..7235dfd 100644
|
||||
--- a/dlls/ntdll/tests/threadpool.c
|
||||
+++ b/dlls/ntdll/tests/threadpool.c
|
||||
@@ -1171,6 +1171,124 @@ static void test_tp_wait(void)
|
||||
CloseHandle(semaphores[1]);
|
||||
}
|
||||
|
||||
+static struct
|
||||
+{
|
||||
+ HANDLE semaphore;
|
||||
+ DWORD result;
|
||||
+} multi_wait_info;
|
||||
+
|
||||
+static void CALLBACK multi_wait_cb(TP_CALLBACK_INSTANCE *instance, void *userdata, TP_WAIT *wait, TP_WAIT_RESULT result)
|
||||
+{
|
||||
+ DWORD index = (DWORD)(DWORD_PTR)userdata;
|
||||
+
|
||||
+ if (result == WAIT_OBJECT_0)
|
||||
+ multi_wait_info.result = index;
|
||||
+ else if (result == WAIT_TIMEOUT)
|
||||
+ multi_wait_info.result = 0x10000 | index;
|
||||
+ else
|
||||
+ ok(0, "unexpected result %u\n", result);
|
||||
+ ReleaseSemaphore(multi_wait_info.semaphore, 1, NULL);
|
||||
+}
|
||||
+
|
||||
+static void test_tp_multi_wait(void)
|
||||
+{
|
||||
+ TP_CALLBACK_ENVIRON environment;
|
||||
+ HANDLE semaphores[512];
|
||||
+ TP_WAIT *waits[512];
|
||||
+ LARGE_INTEGER when;
|
||||
+ HANDLE semaphore;
|
||||
+ NTSTATUS status;
|
||||
+ TP_POOL *pool;
|
||||
+ DWORD result;
|
||||
+ int i;
|
||||
+
|
||||
+ semaphore = CreateSemaphoreW(NULL, 0, 512, NULL);
|
||||
+ ok(semaphore != NULL, "failed to create semaphore\n");
|
||||
+ multi_wait_info.semaphore = semaphore;
|
||||
+
|
||||
+ /* 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");
|
||||
+
|
||||
+ memset(&environment, 0, sizeof(environment));
|
||||
+ environment.Version = 1;
|
||||
+ environment.Pool = pool;
|
||||
+
|
||||
+ /* create semaphores, wait objects and enable them */
|
||||
+ for (i = 0; i < sizeof(semaphores)/sizeof(semaphores[0]); i++)
|
||||
+ {
|
||||
+ semaphores[i] = CreateSemaphoreW(NULL, 0, 1, NULL);
|
||||
+ ok(semaphores[i] != NULL, "failed to create semaphore %i\n", i);
|
||||
+
|
||||
+ waits[i] = NULL;
|
||||
+ status = pTpAllocWait(&waits[i], multi_wait_cb, (void *)(DWORD_PTR)i, &environment);
|
||||
+ ok(!status, "TpAllocWait failed with status %x\n", status);
|
||||
+ ok(waits[i] != NULL, "expected waits[%d] != NULL\n", i);
|
||||
+
|
||||
+ pTpSetWait(waits[i], semaphores[i], NULL);
|
||||
+ }
|
||||
+
|
||||
+ /* test releasing each semaphore */
|
||||
+ for (i = 0; i < sizeof(semaphores)/sizeof(semaphores[0]); i++)
|
||||
+ {
|
||||
+ multi_wait_info.result = 0;
|
||||
+ ReleaseSemaphore(semaphores[i], 1, NULL);
|
||||
+
|
||||
+ result = WaitForSingleObject(semaphore, 100);
|
||||
+ ok(result == WAIT_OBJECT_0, "WaitForSingleObject returned %u\n", result);
|
||||
+ ok(multi_wait_info.result == i, "expected result %d, got %u\n", i, multi_wait_info.result);
|
||||
+
|
||||
+ pTpSetWait(waits[i], semaphores[i], NULL);
|
||||
+ }
|
||||
+
|
||||
+ /* repeat the same test in reverse order */
|
||||
+ for (i = sizeof(semaphores)/sizeof(semaphores[0]) - 1; i >= 0; i--)
|
||||
+ {
|
||||
+ multi_wait_info.result = 0;
|
||||
+ ReleaseSemaphore(semaphores[i], 1, NULL);
|
||||
+
|
||||
+ result = WaitForSingleObject(semaphore, 100);
|
||||
+ ok(result == WAIT_OBJECT_0, "WaitForSingleObject returned %u\n", result);
|
||||
+ ok(multi_wait_info.result == i, "expected result %d, got %u\n", i, multi_wait_info.result);
|
||||
+
|
||||
+ pTpSetWait(waits[i], semaphores[i], NULL);
|
||||
+ }
|
||||
+
|
||||
+ /* now test with a timeout */
|
||||
+ multi_wait_info.result = 0;
|
||||
+ for (i = 0; i < sizeof(semaphores)/sizeof(semaphores[0]); i++)
|
||||
+ {
|
||||
+ when.QuadPart = (ULONGLONG)50 * -10000;
|
||||
+ pTpSetWait(waits[i], semaphores[i], &when);
|
||||
+ }
|
||||
+ for (i = 0; i < sizeof(semaphores)/sizeof(semaphores[0]); i++)
|
||||
+ {
|
||||
+ result = WaitForSingleObject(semaphore, 100);
|
||||
+ ok(result == WAIT_OBJECT_0, "WaitForSingleObject returned %u\n", result);
|
||||
+ }
|
||||
+ ok(multi_wait_info.result >> 16, "expected multi_wait_info.result >> 16 != 0\n");
|
||||
+
|
||||
+ /* destroy the wait objects and semaphores while waiting -
|
||||
+ * broken applications might do that too. */
|
||||
+ for (i = 0; i < sizeof(semaphores)/sizeof(semaphores[0]); i++)
|
||||
+ {
|
||||
+ pTpSetWait(waits[i], semaphores[i], NULL);
|
||||
+ }
|
||||
+
|
||||
+ Sleep(50);
|
||||
+
|
||||
+ for (i = 0; i < sizeof(semaphores)/sizeof(semaphores[0]); i++)
|
||||
+ {
|
||||
+ pTpReleaseWait(waits[i]);
|
||||
+ NtClose(semaphores[i]);
|
||||
+ }
|
||||
+
|
||||
+ pTpReleasePool(pool);
|
||||
+ CloseHandle(semaphore);
|
||||
+}
|
||||
+
|
||||
START_TEST(threadpool)
|
||||
{
|
||||
if (!init_threadpool())
|
||||
@@ -1185,4 +1303,5 @@ START_TEST(threadpool)
|
||||
test_tp_timer();
|
||||
test_tp_window_length();
|
||||
test_tp_wait();
|
||||
+ test_tp_multi_wait();
|
||||
}
|
||||
--
|
||||
2.4.4
|
||||
|
@ -0,0 +1,63 @@
|
||||
From 6cdcda50c44c46c50feccc486f96a2e4e4818bfb Mon Sep 17 00:00:00 2001
|
||||
From: Sebastian Lackner <sebastian@fds-team.de>
|
||||
Date: Sat, 4 Jul 2015 15:54:45 +0200
|
||||
Subject: ntdll: Try to merge threadpool wait queue buckets if possible.
|
||||
|
||||
---
|
||||
dlls/ntdll/threadpool.c | 40 ++++++++++++++++++++++++++++++++++++++++
|
||||
1 file changed, 40 insertions(+)
|
||||
|
||||
diff --git a/dlls/ntdll/threadpool.c b/dlls/ntdll/threadpool.c
|
||||
index f86e965..d6a57c3 100644
|
||||
--- a/dlls/ntdll/threadpool.c
|
||||
+++ b/dlls/ntdll/threadpool.c
|
||||
@@ -1530,6 +1530,46 @@ static void CALLBACK waitqueue_thread_proc( void *param )
|
||||
assert( wait->type == TP_OBJECT_TYPE_WAIT );
|
||||
tp_object_release( wait );
|
||||
}
|
||||
+
|
||||
+ /* Try to merge bucket with other threads. */
|
||||
+ if (waitqueue.num_buckets > 1 && bucket->objcount &&
|
||||
+ bucket->objcount <= MAXIMUM_WAITQUEUE_OBJECTS * 1 / 3)
|
||||
+ {
|
||||
+ struct waitqueue_bucket *other_bucket;
|
||||
+ LIST_FOR_EACH_ENTRY( other_bucket, &waitqueue.buckets, struct waitqueue_bucket, bucket_entry )
|
||||
+ {
|
||||
+ if (other_bucket != bucket && other_bucket->objcount &&
|
||||
+ other_bucket->objcount + bucket->objcount <= MAXIMUM_WAITQUEUE_OBJECTS * 2 / 3)
|
||||
+ {
|
||||
+ other_bucket->objcount += bucket->objcount;
|
||||
+ bucket->objcount = 0;
|
||||
+
|
||||
+ /* Update reserved list. */
|
||||
+ LIST_FOR_EACH_ENTRY( wait, &bucket->reserved, struct threadpool_object, u.wait.wait_entry )
|
||||
+ {
|
||||
+ assert( wait->type == TP_OBJECT_TYPE_WAIT );
|
||||
+ wait->u.wait.bucket = other_bucket;
|
||||
+ }
|
||||
+ list_move_tail( &other_bucket->reserved, &bucket->reserved );
|
||||
+
|
||||
+ /* Update waiting list. */
|
||||
+ LIST_FOR_EACH_ENTRY( wait, &bucket->waiting, struct threadpool_object, u.wait.wait_entry )
|
||||
+ {
|
||||
+ assert( wait->type == TP_OBJECT_TYPE_WAIT );
|
||||
+ wait->u.wait.bucket = other_bucket;
|
||||
+ }
|
||||
+ list_move_tail( &other_bucket->waiting, &bucket->waiting );
|
||||
+
|
||||
+ /* Move bucket to the end, to keep the probability of
|
||||
+ * newly added wait objects as small as possible. */
|
||||
+ list_remove( &bucket->bucket_entry );
|
||||
+ list_add_tail( &waitqueue.buckets, &bucket->bucket_entry );
|
||||
+
|
||||
+ NtSetEvent( other_bucket->update_event, NULL );
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
|
||||
/* Remove this bucket from the list. */
|
||||
--
|
||||
2.4.4
|
||||
|
@ -1,4 +1,4 @@
|
||||
From 898045c05b21aa7cbdfb168f4b4ce8ffb06b1ae0 Mon Sep 17 00:00:00 2001
|
||||
From 25310b36af718466fc095b6dd9b86ce15d42e0ee Mon Sep 17 00:00:00 2001
|
||||
From: Sebastian Lackner <sebastian@fds-team.de>
|
||||
Date: Fri, 6 Feb 2015 20:24:27 +0100
|
||||
Subject: kernel32: Forward threadpool wait functions to ntdll.
|
||||
@ -50,7 +50,7 @@ index a14d03b..77e55e1 100644
|
||||
@ stdcall WaitNamedPipeA (str long)
|
||||
@ stdcall WaitNamedPipeW (wstr long)
|
||||
diff --git a/dlls/kernel32/thread.c b/dlls/kernel32/thread.c
|
||||
index 21ec276..d592692 100644
|
||||
index 21ec276..c992e0d 100644
|
||||
--- a/dlls/kernel32/thread.c
|
||||
+++ b/dlls/kernel32/thread.c
|
||||
@@ -942,6 +942,27 @@ PTP_TIMER WINAPI CreateThreadpoolTimer( PTP_TIMER_CALLBACK callback, PVOID userd
|
||||
@ -81,12 +81,10 @@ index 21ec276..d592692 100644
|
||||
* CreateThreadpoolWork (KERNEL32.@)
|
||||
*/
|
||||
PTP_WORK WINAPI CreateThreadpoolWork( PTP_WORK_CALLBACK callback, PVOID userdata,
|
||||
@@ -1000,3 +1021,25 @@ BOOL WINAPI TrySubmitThreadpoolCallback( PTP_SIMPLE_CALLBACK callback, PVOID use
|
||||
|
||||
return TRUE;
|
||||
@@ -982,6 +1003,28 @@ VOID WINAPI SetThreadpoolTimer( TP_TIMER *timer, FILETIME *due_time,
|
||||
}
|
||||
+
|
||||
+/***********************************************************************
|
||||
|
||||
/***********************************************************************
|
||||
+ * SetThreadpoolWait (KERNEL32.@)
|
||||
+ */
|
||||
+VOID WINAPI SetThreadpoolWait( TP_WAIT *wait, HANDLE handle, FILETIME *due_time )
|
||||
@ -107,6 +105,11 @@ index 21ec276..d592692 100644
|
||||
+
|
||||
+ TpSetWait( wait, handle, due_time ? &timeout : NULL );
|
||||
+}
|
||||
+
|
||||
+/***********************************************************************
|
||||
* TrySubmitThreadpoolCallback (KERNEL32.@)
|
||||
*/
|
||||
BOOL WINAPI TrySubmitThreadpoolCallback( PTP_SIMPLE_CALLBACK callback, PVOID userdata,
|
||||
diff --git a/include/winternl.h b/include/winternl.h
|
||||
index e1707fd..7ab1bd8 100644
|
||||
--- a/include/winternl.h
|
@ -3696,12 +3696,18 @@ fi
|
||||
# | dlls/ntdll/threadpool.c, include/winternl.h
|
||||
# |
|
||||
if test "$enable_ntdll_Vista_Threadpool" -eq 1; then
|
||||
patch_apply ntdll-Vista_Threadpool/0001-ntdll-Implement-threadpool-wait-objects.patch
|
||||
patch_apply ntdll-Vista_Threadpool/0002-ntdll-tests-Add-tests-for-threadpool-wait-objects.patch
|
||||
patch_apply ntdll-Vista_Threadpool/0003-kernel32-Forward-threadpool-wait-functions-to-ntdll.patch
|
||||
patch_apply ntdll-Vista_Threadpool/0001-ntdll-Implement-TpAllocWait-and-TpReleaseWait.patch
|
||||
patch_apply ntdll-Vista_Threadpool/0002-ntdll-Implement-threadpool-wait-queues.patch
|
||||
patch_apply ntdll-Vista_Threadpool/0003-ntdll-tests-Add-basic-tests-for-threadpool-wait-obje.patch
|
||||
patch_apply ntdll-Vista_Threadpool/0004-ntdll-tests-Add-highly-multithreaded-wait-tests.patch
|
||||
patch_apply ntdll-Vista_Threadpool/0005-ntdll-Try-to-merge-threadpool-wait-queue-buckets-if-.patch
|
||||
patch_apply ntdll-Vista_Threadpool/0006-kernel32-Forward-threadpool-wait-functions-to-ntdll.patch
|
||||
(
|
||||
echo '+ { "Sebastian Lackner", "ntdll: Implement threadpool wait objects.", 1 },';
|
||||
echo '+ { "Sebastian Lackner", "ntdll/tests: Add tests for threadpool wait objects.", 1 },';
|
||||
echo '+ { "Sebastian Lackner", "ntdll: Implement TpAllocWait and TpReleaseWait.", 1 },';
|
||||
echo '+ { "Sebastian Lackner", "ntdll: Implement threadpool wait queues.", 1 },';
|
||||
echo '+ { "Sebastian Lackner", "ntdll/tests: Add basic tests for threadpool wait objects.", 1 },';
|
||||
echo '+ { "Sebastian Lackner", "ntdll/tests: Add highly multithreaded wait tests.", 1 },';
|
||||
echo '+ { "Sebastian Lackner", "ntdll: Try to merge threadpool wait queue buckets if possible.", 1 },';
|
||||
echo '+ { "Sebastian Lackner", "kernel32: Forward threadpool wait functions to ntdll.", 1 },';
|
||||
) >> "$patchlist"
|
||||
fi
|
||||
|
Loading…
Reference in New Issue
Block a user