wine/dlls/rtworkq/tests/rtworkq.c
Conor McCarthy acf72ed171 ntdll: Duplicate handles for thread pool waits.
Supports waitable timer closure while pending, and we must not wait on
invalid handles generally. Details are in code comments.
2025-06-19 10:30:42 +02:00

679 lines
25 KiB
C

/*
* Copyright 2020 Nikolay Sivov for CodeWeavers
*
* 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 <stdarg.h>
#include <string.h>
#define COBJMACROS
#include "windef.h"
#include "winbase.h"
#include "initguid.h"
#include "rtworkq.h"
#include "wine/test.h"
/* Should be kept in sync with corresponding MFASYNC_CALLBACK_ constants. */
enum rtwq_callback_queue_id
{
RTWQ_CALLBACK_QUEUE_UNDEFINED = 0x00000000,
RTWQ_CALLBACK_QUEUE_STANDARD = 0x00000001,
RTWQ_CALLBACK_QUEUE_RT = 0x00000002,
RTWQ_CALLBACK_QUEUE_IO = 0x00000003,
RTWQ_CALLBACK_QUEUE_TIMER = 0x00000004,
RTWQ_CALLBACK_QUEUE_MULTITHREADED = 0x00000005,
RTWQ_CALLBACK_QUEUE_LONG_FUNCTION = 0x00000007,
RTWQ_CALLBACK_QUEUE_PRIVATE_MASK = 0xffff0000,
RTWQ_CALLBACK_QUEUE_ALL = 0xffffffff,
};
#define check_platform_lock_count(a) check_platform_lock_count_(__LINE__, a)
static void check_platform_lock_count_(unsigned int line, unsigned int expected)
{
int i, count = 0;
BOOL unexpected;
DWORD queue;
HRESULT hr;
for (;;)
{
if (FAILED(hr = RtwqAllocateWorkQueue(RTWQ_STANDARD_WORKQUEUE, &queue)))
{
unexpected = hr != RTWQ_E_SHUTDOWN;
break;
}
RtwqUnlockWorkQueue(queue);
hr = RtwqUnlockPlatform();
if ((unexpected = FAILED(hr)))
break;
++count;
}
for (i = 0; i < count; ++i)
RtwqLockPlatform();
if (unexpected)
count = -1;
ok_(__FILE__, line)(count == expected, "Unexpected lock count %d.\n", count);
}
struct test_callback
{
IRtwqAsyncCallback IRtwqAsyncCallback_iface;
LONG refcount;
HANDLE event;
UINT sleep_ms;
IRtwqAsyncResult *result;
};
static struct test_callback *impl_from_IRtwqAsyncCallback(IRtwqAsyncCallback *iface)
{
return CONTAINING_RECORD(iface, struct test_callback, IRtwqAsyncCallback_iface);
}
static HRESULT WINAPI testcallback_QueryInterface(IRtwqAsyncCallback *iface, REFIID riid, void **obj)
{
if (IsEqualIID(riid, &IID_IRtwqAsyncCallback) ||
IsEqualIID(riid, &IID_IUnknown))
{
*obj = iface;
IRtwqAsyncCallback_AddRef(iface);
return S_OK;
}
*obj = NULL;
return E_NOINTERFACE;
}
static ULONG WINAPI testcallback_AddRef(IRtwqAsyncCallback *iface)
{
struct test_callback *callback = impl_from_IRtwqAsyncCallback(iface);
return InterlockedIncrement(&callback->refcount);
}
static ULONG WINAPI testcallback_Release(IRtwqAsyncCallback *iface)
{
struct test_callback *callback = impl_from_IRtwqAsyncCallback(iface);
ULONG refcount = InterlockedDecrement(&callback->refcount);
if (!refcount)
{
CloseHandle(callback->event);
free(callback);
}
return refcount;
}
static HRESULT WINAPI testcallback_GetParameters(IRtwqAsyncCallback *iface, DWORD *flags, DWORD *queue)
{
ok(flags != NULL && queue != NULL, "Unexpected arguments.\n");
return E_NOTIMPL;
}
static HRESULT WINAPI testcallback_Invoke(IRtwqAsyncCallback *iface, IRtwqAsyncResult *result)
{
struct test_callback *callback = impl_from_IRtwqAsyncCallback(iface);
if (callback->sleep_ms)
Sleep(callback->sleep_ms);
callback->result = result;
SetEvent(callback->event);
return S_OK;
}
static const IRtwqAsyncCallbackVtbl testcallbackvtbl =
{
testcallback_QueryInterface,
testcallback_AddRef,
testcallback_Release,
testcallback_GetParameters,
testcallback_Invoke,
};
static struct test_callback * create_test_callback(void)
{
struct test_callback *callback = calloc(1, sizeof(*callback));
callback->IRtwqAsyncCallback_iface.lpVtbl = &testcallbackvtbl;
callback->refcount = 1;
callback->event = CreateEventA(NULL, FALSE, FALSE, NULL);
return callback;
}
static DWORD wait_async_callback_result(IRtwqAsyncCallback *iface, DWORD timeout, IRtwqAsyncResult **result)
{
struct test_callback *callback = impl_from_IRtwqAsyncCallback(iface);
DWORD res = WaitForSingleObject(callback->event, timeout);
*result = callback->result;
callback->result = NULL;
return res;
}
static void test_platform_init(void)
{
APTTYPEQUALIFIER qualifier;
APTTYPE apttype;
HRESULT hr;
/* Startup initializes MTA. */
hr = CoGetApartmentType(&apttype, &qualifier);
ok(hr == CO_E_NOTINITIALIZED, "Unexpected hr %#lx.\n", hr);
hr = RtwqStartup();
ok(hr == S_OK, "Failed to start up, hr %#lx.\n", hr);
hr = CoGetApartmentType(&apttype, &qualifier);
ok(hr == S_OK || broken(FAILED(hr)) /* Win8 */, "Unexpected hr %#lx.\n", hr);
if (SUCCEEDED(hr))
ok(apttype == APTTYPE_MTA && qualifier == APTTYPEQUALIFIER_IMPLICIT_MTA,
"Unexpected apartment type %d, qualifier %d.\n", apttype, qualifier);
hr = RtwqShutdown();
ok(hr == S_OK, "Failed to shut down, hr %#lx.\n", hr);
hr = CoGetApartmentType(&apttype, &qualifier);
ok(hr == CO_E_NOTINITIALIZED, "Unexpected hr %#lx.\n", hr);
/* Try with STA initialized before startup. */
hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
ok(hr == S_OK, "Failed to initialize, hr %#lx.\n", hr);
hr = CoGetApartmentType(&apttype, &qualifier);
ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
ok(apttype == APTTYPE_MAINSTA && qualifier == APTTYPEQUALIFIER_NONE,
"Unexpected apartment type %d, qualifier %d.\n", apttype, qualifier);
hr = RtwqStartup();
ok(hr == S_OK, "Failed to start up, hr %#lx.\n", hr);
hr = CoGetApartmentType(&apttype, &qualifier);
ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
ok(apttype == APTTYPE_MAINSTA && qualifier == APTTYPEQUALIFIER_NONE,
"Unexpected apartment type %d, qualifier %d.\n", apttype, qualifier);
hr = RtwqShutdown();
ok(hr == S_OK, "Failed to shut down, hr %#lx.\n", hr);
CoUninitialize();
/* Startup -> init main STA -> uninitialize -> shutdown */
hr = RtwqStartup();
ok(hr == S_OK, "Failed to start up, hr %#lx.\n", hr);
hr = CoGetApartmentType(&apttype, &qualifier);
ok(hr == S_OK || broken(FAILED(hr)) /* Win8 */, "Unexpected hr %#lx.\n", hr);
if (SUCCEEDED(hr))
ok(apttype == APTTYPE_MTA && qualifier == APTTYPEQUALIFIER_IMPLICIT_MTA,
"Unexpected apartment type %d, qualifier %d.\n", apttype, qualifier);
hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
ok(hr == S_OK, "Failed to initialize, hr %#lx.\n", hr);
hr = CoGetApartmentType(&apttype, &qualifier);
ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
ok(apttype == APTTYPE_MAINSTA && qualifier == APTTYPEQUALIFIER_NONE,
"Unexpected apartment type %d, qualifier %d.\n", apttype, qualifier);
CoUninitialize();
hr = CoGetApartmentType(&apttype, &qualifier);
ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
ok(apttype == APTTYPE_MTA && qualifier == APTTYPEQUALIFIER_IMPLICIT_MTA,
"Unexpected apartment type %d, qualifier %d.\n", apttype, qualifier);
hr = RtwqShutdown();
ok(hr == S_OK, "Failed to shut down, hr %#lx.\n", hr);
}
static void test_undefined_queue_id(void)
{
IRtwqAsyncResult *result, *callback_result;
struct test_callback *test_callback;
HRESULT hr;
DWORD res;
hr = RtwqStartup();
ok(hr == S_OK, "Failed to start up, hr %#lx.\n", hr);
test_callback = create_test_callback();
hr = RtwqCreateAsyncResult(NULL, &test_callback->IRtwqAsyncCallback_iface, NULL, &result);
ok(hr == S_OK, "got %#lx\n", hr);
hr = RtwqPutWorkItem(RTWQ_CALLBACK_QUEUE_UNDEFINED, 0, result);
ok(hr == S_OK, "got %#lx\n", hr);
res = wait_async_callback_result(&test_callback->IRtwqAsyncCallback_iface, 100, &callback_result);
ok(res == 0, "got %#lx\n", res);
ok(callback_result == result, "Expected result %p, got %p.\n", result, callback_result);
hr = RtwqPutWorkItem(0xffff, 0, result);
ok(hr == S_OK, "got %#lx\n", hr);
res = wait_async_callback_result(&test_callback->IRtwqAsyncCallback_iface, 100, &callback_result);
ok(res == 0, "got %#lx\n", res);
ok(callback_result == result, "Expected result %p, got %p.\n", result, callback_result);
hr = RtwqPutWorkItem(0x4000, 0, result);
ok(hr == S_OK, "got %#lx\n", hr);
res = wait_async_callback_result(&test_callback->IRtwqAsyncCallback_iface, 100, &callback_result);
ok(res == 0, "got %#lx\n", res);
ok(callback_result == result, "Expected result %p, got %p.\n", result, callback_result);
hr = RtwqPutWorkItem(0x10000, 0, result);
ok(hr == RTWQ_E_INVALID_WORKQUEUE, "got %#lx\n", hr);
IRtwqAsyncResult_Release(result);
IRtwqAsyncCallback_Release(&test_callback->IRtwqAsyncCallback_iface);
hr = RtwqShutdown();
ok(hr == S_OK, "Failed to shut down, hr %#lx.\n", hr);
}
static void test_work_queue(void)
{
IRtwqAsyncResult *result, *result2, *callback_result;
struct test_callback *test_callback;
RTWQWORKITEM_KEY key, key2;
DWORD res, queue;
LONG refcount;
HRESULT hr;
hr = RtwqLockPlatform();
ok(hr == S_OK, "Failed to lock, %#lx.\n", hr);
hr = RtwqAllocateWorkQueue(RTWQ_STANDARD_WORKQUEUE, &queue);
ok(hr == RTWQ_E_SHUTDOWN, "Unexpected hr %#lx.\n", hr);
hr = RtwqUnlockPlatform();
ok(hr == S_OK, "Failed to unlock, %#lx.\n", hr);
test_callback = create_test_callback();
/* Create async results without startup. */
hr = RtwqCreateAsyncResult(NULL, &test_callback->IRtwqAsyncCallback_iface, NULL, &result);
ok(hr == S_OK, "Failed to create result, hr %#lx.\n", hr);
hr = RtwqCreateAsyncResult(NULL, &test_callback->IRtwqAsyncCallback_iface, NULL, &result2);
ok(hr == S_OK, "Failed to create result, hr %#lx.\n", hr);
IRtwqAsyncResult_Release(result);
IRtwqAsyncResult_Release(result2);
hr = RtwqStartup();
ok(hr == S_OK, "Failed to start up, hr %#lx.\n", hr);
/* Before startup the platform lock count does not track the maximum AsyncResult count. */
check_platform_lock_count(1);
hr = RtwqStartup();
ok(hr == S_OK, "Failed to start up, hr %#lx.\n", hr);
/* Startup only locks once. */
check_platform_lock_count(1);
hr = RtwqShutdown();
ok(hr == S_OK, "Failed to shut down, hr %#lx.\n", hr);
hr = RtwqCreateAsyncResult(NULL, &test_callback->IRtwqAsyncCallback_iface, NULL, &result);
ok(hr == S_OK, "Failed to create result, hr %#lx.\n", hr);
/* Platform locked by the AsyncResult object. */
check_platform_lock_count(2);
hr = RtwqCreateAsyncResult(NULL, &test_callback->IRtwqAsyncCallback_iface, NULL, &result2);
ok(hr == S_OK, "Failed to create result, hr %#lx.\n", hr);
check_platform_lock_count(3);
IRtwqAsyncResult_Release(result);
IRtwqAsyncResult_Release(result2);
/* Platform lock count for AsyncResult objects does not decrease
* unless the platform is in shutdown state. */
check_platform_lock_count(3);
hr = RtwqCreateAsyncResult(NULL, &test_callback->IRtwqAsyncCallback_iface, NULL, &result);
ok(hr == S_OK, "Failed to create result, hr %#lx.\n", hr);
/* Platform lock count tracks the maximum AsyncResult count plus one for startup. */
check_platform_lock_count(3);
hr = RtwqPutWorkItem(RTWQ_CALLBACK_QUEUE_STANDARD, 0, result);
ok(hr == S_OK, "got %#lx\n", hr);
res = wait_async_callback_result(&test_callback->IRtwqAsyncCallback_iface, 100, &callback_result);
ok(res == 0, "got %#lx\n", res);
/* TODO: Wine often has a release call pending in another thread at this point. */
refcount = IRtwqAsyncResult_Release(result);
flaky_wine
ok(!refcount, "Unexpected refcount %ld.\n", refcount);
check_platform_lock_count(3);
hr = RtwqLockPlatform();
ok(hr == S_OK, "Failed to lock, %#lx.\n", hr);
hr = RtwqLockPlatform();
ok(hr == S_OK, "Failed to lock, %#lx.\n", hr);
check_platform_lock_count(5);
hr = RtwqShutdown();
ok(hr == S_OK, "Failed to shut down, hr %#lx.\n", hr);
/* Platform is in shutdown state if either the lock count or the startup count is <= 0. */
hr = RtwqAllocateWorkQueue(RTWQ_STANDARD_WORKQUEUE, &queue);
ok(hr == RTWQ_E_SHUTDOWN, "Unexpected hr %#lx.\n", hr);
/* Platform can be unlocked after shutdown. */
hr = RtwqUnlockPlatform();
ok(hr == S_OK, "Failed to unlock, %#lx.\n", hr);
hr = RtwqStartup();
ok(hr == S_OK, "Failed to start up, hr %#lx.\n", hr);
/* Platform locks for AsyncResult objects were released on shutdown, but the explicit lock was not. */
check_platform_lock_count(2);
hr = RtwqUnlockPlatform();
ok(hr == S_OK, "Failed to unlock, %#lx.\n", hr);
check_platform_lock_count(1);
hr = RtwqUnlockPlatform();
ok(hr == S_OK, "Failed to unlock, %#lx.\n", hr);
/* Zero lock count. */
hr = RtwqAllocateWorkQueue(RTWQ_STANDARD_WORKQUEUE, &queue);
ok(hr == RTWQ_E_SHUTDOWN, "Unexpected hr %#lx.\n", hr);
hr = RtwqUnlockPlatform();
ok(hr == S_OK, "Failed to unlock, %#lx.\n", hr);
/* Negative lock count. */
hr = RtwqAllocateWorkQueue(RTWQ_STANDARD_WORKQUEUE, &queue);
ok(hr == RTWQ_E_SHUTDOWN, "Unexpected hr %#lx.\n", hr);
hr = RtwqLockPlatform();
ok(hr == S_OK, "Failed to lock, %#lx.\n", hr);
hr = RtwqLockPlatform();
ok(hr == S_OK, "Failed to lock, %#lx.\n", hr);
check_platform_lock_count(1);
hr = RtwqCreateAsyncResult(NULL, &test_callback->IRtwqAsyncCallback_iface, NULL, &result);
ok(hr == S_OK, "Failed to create result, hr %#lx.\n", hr);
check_platform_lock_count(2);
hr = RtwqShutdown();
ok(hr == S_OK, "Failed to shut down, hr %#lx.\n", hr);
/* Release an AsyncResult object after shutdown. Platform lock count tracks the AsyncResult
* count. It's not possible to show if unlock occurs immedately or on the next startup. */
IRtwqAsyncResult_Release(result);
hr = RtwqStartup();
ok(hr == S_OK, "Failed to start up, hr %#lx.\n", hr);
check_platform_lock_count(1);
hr = RtwqCreateAsyncResult(NULL, &test_callback->IRtwqAsyncCallback_iface, NULL, &result);
ok(hr == S_OK, "Failed to create result, hr %#lx.\n", hr);
hr = RtwqShutdown();
ok(hr == S_OK, "Failed to shut down, hr %#lx.\n", hr);
hr = RtwqStartup();
ok(hr == S_OK, "Failed to start up, hr %#lx.\n", hr);
check_platform_lock_count(2);
/* Release an AsyncResult object after shutdown and startup. */
IRtwqAsyncResult_Release(result);
check_platform_lock_count(2);
hr = RtwqCreateAsyncResult(NULL, &test_callback->IRtwqAsyncCallback_iface, NULL, &result);
ok(hr == S_OK, "Failed to create result, hr %#lx.\n", hr);
hr = RtwqScheduleWorkItem(result, -5000, &key);
ok(hr == S_OK, "Failed to schedule item, hr %#lx.\n", hr);
check_platform_lock_count(2);
hr = RtwqCreateAsyncResult(NULL, &test_callback->IRtwqAsyncCallback_iface, NULL, &result2);
ok(hr == S_OK, "Failed to create result, hr %#lx.\n", hr);
hr = RtwqScheduleWorkItem(result2, -5000, &key2);
ok(hr == S_OK, "Failed to schedule item, hr %#lx.\n", hr);
check_platform_lock_count(3);
hr = RtwqCancelWorkItem(key);
ok(hr == S_OK, "Failed to cancel item, hr %#lx.\n", hr);
hr = RtwqCancelWorkItem(key2);
ok(hr == S_OK, "Failed to cancel item, hr %#lx.\n", hr);
IRtwqAsyncResult_Release(result);
IRtwqAsyncResult_Release(result2);
check_platform_lock_count(3);
hr = RtwqCreateAsyncResult(NULL, &test_callback->IRtwqAsyncCallback_iface, NULL, &result);
ok(hr == S_OK, "Failed to create result, hr %#lx.\n", hr);
hr = RtwqScheduleWorkItem(result, -5000, &key);
ok(hr == S_OK, "Failed to schedule item, hr %#lx.\n", hr);
check_platform_lock_count(3);
hr = RtwqShutdown();
ok(hr == S_OK, "Failed to shut down, hr %#lx.\n", hr);
/* Shutdown while a scheduled item is pending leaks the AsyncResult. */
refcount = IRtwqAsyncResult_Release(result);
ok(refcount == 1, "Unexpected refcount %ld.\n", refcount);
IRtwqAsyncResult_Release(result);
hr = RtwqAllocateWorkQueue(RTWQ_STANDARD_WORKQUEUE, &queue);
ok(hr == RTWQ_E_SHUTDOWN, "Unexpected hr %#lx.\n", hr);
hr = RtwqCancelWorkItem(key);
ok(hr == RTWQ_E_SHUTDOWN, "Unexpected hr %#lx.\n", hr);
res = wait_async_callback_result(&test_callback->IRtwqAsyncCallback_iface, 0, &callback_result);
ok(res == WAIT_TIMEOUT, "got res %#lx\n", res);
IRtwqAsyncCallback_Release(&test_callback->IRtwqAsyncCallback_iface);
}
static void test_scheduled_items(void)
{
struct test_callback *test_callback, *test_callback2;
RTWQWORKITEM_KEY key, key2;
IRtwqAsyncResult *result, *result2, *callback_result;
HANDLE timer, event, event2;
LARGE_INTEGER time;
ULONG refcount;
HRESULT hr;
DWORD res;
test_callback = create_test_callback();
hr = RtwqStartup();
ok(hr == S_OK, "Failed to start up, hr %#lx.\n", hr);
hr = RtwqCreateAsyncResult(NULL, &test_callback->IRtwqAsyncCallback_iface, NULL, &result);
ok(hr == S_OK, "Failed to create result, hr %#lx.\n", hr);
hr = RtwqScheduleWorkItem(result, -5000, &key);
ok(hr == S_OK, "Failed to schedule item, hr %#lx.\n", hr);
IRtwqAsyncResult_Release(result);
hr = RtwqCancelWorkItem(key);
ok(hr == S_OK, "Failed to cancel item, hr %#lx.\n", hr);
refcount = IRtwqAsyncCallback_Release(&test_callback->IRtwqAsyncCallback_iface);
ok(refcount == 0, "Unexpected refcount %lu.\n", refcount);
hr = RtwqCancelWorkItem(key);
ok(hr == RTWQ_E_NOT_FOUND || broken(hr == S_OK) /* < win10 */, "Unexpected hr %#lx.\n", hr);
test_callback = create_test_callback();
hr = RtwqCreateAsyncResult(NULL, &test_callback->IRtwqAsyncCallback_iface, NULL, &result);
ok(hr == S_OK, "Failed to create result, hr %#lx.\n", hr);
hr = RtwqPutWaitingWorkItem(NULL, 0, result, &key);
ok(hr == S_OK, "Failed to add waiting item, hr %#lx.\n", hr);
hr = RtwqPutWaitingWorkItem(NULL, 0, result, &key2);
ok(hr == S_OK, "Failed to add waiting item, hr %#lx.\n", hr);
hr = RtwqCancelWorkItem(key);
ok(hr == S_OK, "Failed to cancel item, hr %#lx.\n", hr);
hr = RtwqCancelWorkItem(key2);
ok(hr == S_OK, "Failed to cancel item, hr %#lx.\n", hr);
hr = RtwqShutdown();
ok(hr == S_OK, "Failed to shut down, hr %#lx.\n", hr);
Sleep(20);
/* One or two callback invocations with RTWQ_E_OPERATION_CANCELLED may have been
* pending when RtwqShutdown() was called. Release depends upon their execution. */
refcount = IRtwqAsyncResult_Release(result);
ok(refcount == 0, "Unexpected refcount %lu.\n", refcount);
hr = RtwqStartup();
ok(hr == S_OK, "Failed to start up, hr %#lx.\n", hr);
test_callback2 = create_test_callback();
timer = CreateWaitableTimerW(NULL, TRUE, NULL);
event = CreateEventA(NULL, FALSE, FALSE, NULL);
hr = RtwqCreateAsyncResult(NULL, &test_callback->IRtwqAsyncCallback_iface, NULL, &result);
ok(hr == S_OK, "Failed to create result, hr %#lx.\n", hr);
hr = RtwqCreateAsyncResult(NULL, &test_callback2->IRtwqAsyncCallback_iface, NULL, &result2);
ok(hr == S_OK, "Failed to create result, hr %#lx.\n", hr);
time.QuadPart = -1000000LL;
SetWaitableTimer(timer, &time, 0, NULL, NULL, 0);
hr = RtwqPutWaitingWorkItem(timer, 0, result, NULL);
ok(hr == S_OK, "got %#lx\n", hr);
/* Close the timer handle while the item is pending. This should work and
* not cause failure to execute other waiting items.*/
CloseHandle(timer);
IRtwqAsyncResult_Release(result);
hr = RtwqPutWaitingWorkItem(event, 0, result2, NULL);
ok(hr == S_OK, "got %#lx\n", hr);
IRtwqAsyncResult_Release(result2);
res = wait_async_callback_result(&test_callback->IRtwqAsyncCallback_iface, 200, &callback_result);
ok(res == 0, "got %#lx\n", res);
SetEvent(event);
res = wait_async_callback_result(&test_callback2->IRtwqAsyncCallback_iface, 100, &callback_result);
ok(res == 0, "got %#lx\n", res);
CloseHandle(event);
event = CreateEventA(NULL, FALSE, FALSE, NULL);
event2 = CreateEventA(NULL, FALSE, FALSE, NULL);
hr = RtwqCreateAsyncResult(NULL, &test_callback->IRtwqAsyncCallback_iface, NULL, &result);
ok(hr == S_OK, "Failed to create result, hr %#lx.\n", hr);
hr = RtwqCreateAsyncResult(NULL, &test_callback2->IRtwqAsyncCallback_iface, NULL, &result2);
ok(hr == S_OK, "Failed to create result, hr %#lx.\n", hr);
hr = RtwqPutWaitingWorkItem(event, 0, result, &key);
ok(hr == S_OK, "got %#lx\n", hr);
/* Abandon the waiting item. This should not cause failure to execute other waiting items. */
CloseHandle(event);
IRtwqAsyncResult_Release(result);
hr = RtwqPutWaitingWorkItem(event2, 0, result2, NULL);
ok(hr == S_OK, "got %#lx\n", hr);
IRtwqAsyncResult_Release(result2);
SetEvent(event2);
res = wait_async_callback_result(&test_callback2->IRtwqAsyncCallback_iface, 100, &callback_result);
ok(res == 0, "got %#lx\n", res);
hr = RtwqCancelWorkItem(key);
ok(hr == S_OK, "Failed to cancel item, hr %#lx.\n", hr);
CloseHandle(event2);
hr = RtwqShutdown();
ok(hr == S_OK, "Failed to shut down, hr %#lx.\n", hr);
IRtwqAsyncCallback_Release(&test_callback->IRtwqAsyncCallback_iface);
IRtwqAsyncCallback_Release(&test_callback2->IRtwqAsyncCallback_iface);
}
static void test_queue_shutdown(void)
{
IRtwqAsyncResult *result, *result2, *callback_result;
struct test_callback *test_callback, *test_callback2;
DWORD res, queue;
LONG refcount;
HRESULT hr;
hr = RtwqStartup();
ok(hr == S_OK, "Failed to start up, hr %#lx.\n", hr);
test_callback = create_test_callback();
test_callback2 = create_test_callback();
test_callback->sleep_ms = 10;
test_callback2->sleep_ms = 10;
hr = RtwqCreateAsyncResult(NULL, &test_callback->IRtwqAsyncCallback_iface, NULL, &result);
ok(hr == S_OK, "Failed to create result, hr %#lx.\n", hr);
hr = RtwqCreateAsyncResult(NULL, &test_callback2->IRtwqAsyncCallback_iface, NULL, &result2);
ok(hr == S_OK, "Failed to create result, hr %#lx.\n", hr);
hr = RtwqPutWorkItem(RTWQ_CALLBACK_QUEUE_STANDARD, 0, result);
ok(hr == S_OK, "got %#lx\n", hr);
hr = RtwqPutWorkItem(RTWQ_CALLBACK_QUEUE_STANDARD, 0, result2);
ok(hr == S_OK, "got %#lx\n", hr);
/* At least the second item will be delayed until after shutdown. Execution still occurs after shutdown. */
hr = RtwqShutdown();
ok(hr == S_OK, "Failed to shut down, hr %#lx.\n", hr);
res = wait_async_callback_result(&test_callback->IRtwqAsyncCallback_iface, 100, &callback_result);
ok(res == 0, "got %#lx\n", res);
res = wait_async_callback_result(&test_callback2->IRtwqAsyncCallback_iface, 100, &callback_result);
ok(res == 0, "got %#lx\n", res);
refcount = IRtwqAsyncResult_Release(result);
ok(!refcount, "Unexpected refcount %ld.\n", refcount);
refcount = IRtwqAsyncResult_Release(result2);
ok(!refcount, "Unexpected refcount %ld.\n", refcount);
hr = RtwqStartup();
ok(hr == S_OK, "Failed to start up, hr %#lx.\n", hr);
hr = RtwqAllocateWorkQueue(RTWQ_STANDARD_WORKQUEUE, &queue);
ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
hr = RtwqCreateAsyncResult(NULL, &test_callback->IRtwqAsyncCallback_iface, NULL, &result);
ok(hr == S_OK, "Failed to create result, hr %#lx.\n", hr);
hr = RtwqCreateAsyncResult(NULL, &test_callback2->IRtwqAsyncCallback_iface, NULL, &result2);
ok(hr == S_OK, "Failed to create result, hr %#lx.\n", hr);
hr = RtwqPutWorkItem(queue, 0, result);
ok(hr == S_OK, "got %#lx\n", hr);
hr = RtwqPutWorkItem(queue, 0, result2);
ok(hr == S_OK, "got %#lx\n", hr);
/* Execution still occurs after unlock. */
RtwqUnlockWorkQueue(queue);
res = wait_async_callback_result(&test_callback->IRtwqAsyncCallback_iface, 100, &callback_result);
ok(res == 0, "got %#lx\n", res);
res = wait_async_callback_result(&test_callback2->IRtwqAsyncCallback_iface, 100, &callback_result);
ok(res == 0, "got %#lx\n", res);
refcount = IRtwqAsyncResult_Release(result);
ok(!refcount, "Unexpected refcount %ld.\n", refcount);
/* An internal reference to result2 may still be held here even in Windows. */
IRtwqAsyncResult_Release(result2);
hr = RtwqShutdown();
ok(hr == S_OK, "Failed to shut down, hr %#lx.\n", hr);
IRtwqAsyncCallback_Release(&test_callback->IRtwqAsyncCallback_iface);
IRtwqAsyncCallback_Release(&test_callback2->IRtwqAsyncCallback_iface);
}
START_TEST(rtworkq)
{
test_platform_init();
test_undefined_queue_id();
test_work_queue();
test_scheduled_items();
test_queue_shutdown();
}