diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f08444..d5d7338 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -210,6 +210,39 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/test/test_threading.c ${CMAKE_CURRENT_SOURCE_DIR}/test/test_assert.h) + add_custom_command( + OUTPUT ${WIBO_TEST_BIN_DIR}/test_handleapi.exe + COMMAND ${WIBO_MINGW_CC} -Wall -Wextra -O2 + -I${CMAKE_CURRENT_SOURCE_DIR}/test + -o test_handleapi.exe + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_handleapi.c + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_handleapi.c + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_assert.h) + + add_custom_command( + OUTPUT ${WIBO_TEST_BIN_DIR}/test_synchapi.exe + COMMAND ${WIBO_MINGW_CC} -Wall -Wextra -O2 + -I${CMAKE_CURRENT_SOURCE_DIR}/test + -o test_synchapi.exe + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_synchapi.c + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_synchapi.c + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_assert.h) + + add_custom_command( + OUTPUT ${WIBO_TEST_BIN_DIR}/test_processes.exe + COMMAND ${WIBO_MINGW_CC} -Wall -Wextra -O2 + -I${CMAKE_CURRENT_SOURCE_DIR}/test + -o test_processes.exe + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_processes.c + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_processes.c + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_assert.h) + add_custom_command( OUTPUT ${WIBO_TEST_BIN_DIR}/test_heap.exe COMMAND ${WIBO_MINGW_CC} -Wall -Wextra -O2 @@ -342,6 +375,9 @@ if(BUILD_TESTING) ${WIBO_TEST_BIN_DIR}/test_bcrypt.exe ${WIBO_TEST_BIN_DIR}/test_resources.exe ${WIBO_TEST_BIN_DIR}/test_threading.exe + ${WIBO_TEST_BIN_DIR}/test_handleapi.exe + ${WIBO_TEST_BIN_DIR}/test_synchapi.exe + ${WIBO_TEST_BIN_DIR}/test_processes.exe ${WIBO_TEST_BIN_DIR}/test_heap.exe ${WIBO_TEST_BIN_DIR}/test_actctx.exe ${WIBO_TEST_BIN_DIR}/test_overlapped_io.exe @@ -388,6 +424,24 @@ if(BUILD_TESTING) WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} DEPENDS wibo.build_fixtures) + add_test(NAME wibo.test_handleapi + COMMAND $ ${WIBO_TEST_BIN_DIR}/test_handleapi.exe) + set_tests_properties(wibo.test_handleapi PROPERTIES + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS wibo.build_fixtures) + + add_test(NAME wibo.test_synchapi + COMMAND $ ${WIBO_TEST_BIN_DIR}/test_synchapi.exe) + set_tests_properties(wibo.test_synchapi PROPERTIES + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS wibo.build_fixtures) + + add_test(NAME wibo.test_processes + COMMAND $ ${WIBO_TEST_BIN_DIR}/test_processes.exe) + set_tests_properties(wibo.test_processes PROPERTIES + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS wibo.build_fixtures) + add_test(NAME wibo.test_heap COMMAND $ ${WIBO_TEST_BIN_DIR}/test_heap.exe) set_tests_properties(wibo.test_heap PROPERTIES diff --git a/dll/kernel32/synchapi.cpp b/dll/kernel32/synchapi.cpp index 58dd013..d831f76 100644 --- a/dll/kernel32/synchapi.cpp +++ b/dll/kernel32/synchapi.cpp @@ -189,7 +189,7 @@ HANDLE WIN_FUNC CreateSemaphoreA(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LO BOOL WIN_FUNC ReleaseSemaphore(HANDLE hSemaphore, LONG lReleaseCount, PLONG lpPreviousCount) { HOST_CONTEXT_GUARD(); DEBUG_LOG("ReleaseSemaphore(%p, %ld, %p)\n", hSemaphore, lReleaseCount, lpPreviousCount); - if (lReleaseCount <= 0) { + if (lReleaseCount < 0) { wibo::lastError = ERROR_INVALID_PARAMETER; return FALSE; } @@ -330,9 +330,8 @@ DWORD WIN_FUNC WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds) { pthread_t self = pthread_self(); std::unique_lock lk(th->m); if (pthread_equal(th->thread, self)) { - // Cannot wait on self - wibo::lastError = ERROR_INVALID_HANDLE; - return WAIT_FAILED; + // Windows actually allows you to wait on your own thread, but why bother? + return WAIT_TIMEOUT; } bool ok = doWait(lk, th->cv, [&] { return th->signaled.load(std::memory_order_acquire); }); return ok ? WAIT_OBJECT_0 : WAIT_TIMEOUT; @@ -341,9 +340,8 @@ DWORD WIN_FUNC WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds) { auto po = std::move(obj).downcast(); std::unique_lock lk(po->m); if (po->pidfd == -1) { - // Cannot wait on self - wibo::lastError = ERROR_INVALID_HANDLE; - return WAIT_FAILED; + // Windows actually allows you to wait on your own process, but why bother? + return WAIT_TIMEOUT; } bool ok = doWait(lk, po->cv, [&] { return po->signaled.load(std::memory_order_acquire); }); return ok ? WAIT_OBJECT_0 : WAIT_TIMEOUT; diff --git a/src/handles.cpp b/src/handles.cpp index 1753434..ab953aa 100644 --- a/src/handles.cpp +++ b/src/handles.cpp @@ -186,6 +186,13 @@ bool Handles::duplicateTo(HANDLE src, Handles &dst, HANDLE &out, uint32_t desire uint32_t effAccess = (options & DUPLICATE_SAME_ACCESS) ? meta.grantedAccess : (desiredAccess & meta.grantedAccess); const uint32_t flags = (inherit ? HANDLE_FLAG_INHERIT : 0); + + // Reuse the same handle if duplicating within the same table and no changes + if (&dst == this && closeSource && effAccess == meta.grantedAccess && flags == meta.flags) { + out = src; + return true; + } + out = dst.alloc(std::move(obj), effAccess, flags); if (closeSource) { diff --git a/test/test_actctx.c b/test/test_actctx.c index 3c01bc1..62956d9 100644 --- a/test/test_actctx.c +++ b/test/test_actctx.c @@ -51,22 +51,21 @@ static void check_invalid_parameters(void) { data.cbSize = sizeof(data); GUID fakeGuid = {0}; SetLastError(0); - BOOL ok = FindActCtxSectionStringW(0, &fakeGuid, ACTIVATION_CONTEXT_SECTION_DLL_REDIRECTION, L"msvcr80.dll", &data); - TEST_CHECK(!ok); + TEST_CHECK( + !FindActCtxSectionStringW(0, &fakeGuid, ACTIVATION_CONTEXT_SECTION_DLL_REDIRECTION, L"msvcr80.dll", &data)); TEST_CHECK_EQ(ERROR_INVALID_PARAMETER, GetLastError()); ACTCTX_SECTION_KEYED_DATA sized = {0}; sized.cbSize = sizeof(data) - 4; SetLastError(0); - ok = FindActCtxSectionStringW(0, NULL, ACTIVATION_CONTEXT_SECTION_DLL_REDIRECTION, L"msvcr80.dll", &sized); - TEST_CHECK(!ok); + TEST_CHECK(!FindActCtxSectionStringW(0, NULL, ACTIVATION_CONTEXT_SECTION_DLL_REDIRECTION, L"msvcr80.dll", &sized)); TEST_CHECK_EQ(ERROR_INSUFFICIENT_BUFFER, GetLastError()); ACTCTX_SECTION_KEYED_DATA flags = {0}; flags.cbSize = sizeof(flags); SetLastError(0); - ok = FindActCtxSectionStringW(0x2, NULL, ACTIVATION_CONTEXT_SECTION_DLL_REDIRECTION, L"msvcr80.dll", &flags); - TEST_CHECK(!ok); + TEST_CHECK( + !FindActCtxSectionStringW(0x2, NULL, ACTIVATION_CONTEXT_SECTION_DLL_REDIRECTION, L"msvcr80.dll", &flags)); TEST_CHECK_EQ(ERROR_INVALID_PARAMETER, GetLastError()); } @@ -74,17 +73,15 @@ static void check_missing_entries(void) { ACTCTX_SECTION_KEYED_DATA data = {0}; data.cbSize = sizeof(data); SetLastError(0); - BOOL ok = - FindActCtxSectionStringW(0, NULL, ACTIVATION_CONTEXT_SECTION_DLL_REDIRECTION, L"totally_missing.dll", &data); - TEST_CHECK(!ok); + TEST_CHECK( + !FindActCtxSectionStringW(0, NULL, ACTIVATION_CONTEXT_SECTION_DLL_REDIRECTION, L"totally_missing.dll", &data)); TEST_CHECK_EQ(ERROR_SXS_KEY_NOT_FOUND, GetLastError()); ACTCTX_SECTION_KEYED_DATA wrongSection = {0}; wrongSection.cbSize = sizeof(wrongSection); SetLastError(0); - ok = FindActCtxSectionStringW(0, NULL, ACTIVATION_CONTEXT_SECTION_ASSEMBLY_INFORMATION, L"msvcr80.dll", - &wrongSection); - TEST_CHECK(!ok); + TEST_CHECK(!FindActCtxSectionStringW(0, NULL, ACTIVATION_CONTEXT_SECTION_ASSEMBLY_INFORMATION, L"msvcr80.dll", + &wrongSection)); TEST_CHECK_EQ(ERROR_SXS_KEY_NOT_FOUND, GetLastError()); } diff --git a/test/test_handleapi.c b/test/test_handleapi.c new file mode 100644 index 0000000..19209ea --- /dev/null +++ b/test/test_handleapi.c @@ -0,0 +1,101 @@ +#include "test_assert.h" +#include +#include +#include + +static void test_duplicate_handle_basic(void) { + HANDLE evt = CreateEventA(NULL, TRUE, FALSE, NULL); + TEST_CHECK(evt != NULL); + + HANDLE dup = NULL; + BOOL ok = DuplicateHandle(GetCurrentProcess(), evt, GetCurrentProcess(), &dup, 0, FALSE, DUPLICATE_SAME_ACCESS); + TEST_CHECK(ok); + TEST_CHECK(dup != NULL); + TEST_CHECK(dup != evt); + + TEST_CHECK(SetEvent(evt)); + DWORD waitResult = WaitForSingleObject(dup, 0); + TEST_CHECK_EQ(WAIT_OBJECT_0, waitResult); + TEST_CHECK(ResetEvent(evt)); + + TEST_CHECK(CloseHandle(dup)); + TEST_CHECK(CloseHandle(evt)); +} + +static void test_duplicate_handle_close_source(void) { + HANDLE evt = CreateEventA(NULL, FALSE, FALSE, NULL); + TEST_CHECK(evt != NULL); + + HANDLE dup = NULL; + BOOL ok = DuplicateHandle(GetCurrentProcess(), evt, GetCurrentProcess(), &dup, 0, FALSE, + DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE); + TEST_CHECK(ok); + TEST_CHECK(dup != NULL); + + // Since we're duplicating within the same process with DUPLICATE_CLOSE_SOURCE, + // we should get back the same handle value + TEST_CHECK(dup == evt); + + TEST_CHECK(SetEvent(dup)); + TEST_CHECK_EQ(WAIT_OBJECT_0, WaitForSingleObject(dup, 0)); + TEST_CHECK(CloseHandle(dup)); +} + +static void test_duplicate_handle_invalid_source(void) { + HANDLE bogus = (HANDLE)(uintptr_t)0x1234; + HANDLE out = NULL; + SetLastError(0); + TEST_CHECK( + !DuplicateHandle(GetCurrentProcess(), bogus, GetCurrentProcess(), &out, 0, FALSE, DUPLICATE_SAME_ACCESS)); + TEST_CHECK_EQ(ERROR_INVALID_HANDLE, GetLastError()); + TEST_CHECK(out == NULL); +} + +static void test_duplicate_handle_invalid_target_process(void) { + HANDLE evt = CreateEventA(NULL, TRUE, FALSE, NULL); + TEST_CHECK(evt != NULL); + + HANDLE dup = NULL; + SetLastError(0xDEADBEEF); + TEST_CHECK(!DuplicateHandle(GetCurrentProcess(), evt, NULL, &dup, 0, FALSE, DUPLICATE_SAME_ACCESS)); + TEST_CHECK_EQ(ERROR_INVALID_HANDLE, GetLastError()); + TEST_CHECK(dup == NULL); + + TEST_CHECK(CloseHandle(evt)); +} + +static void test_duplicate_pseudo_process_handle(void) { + HANDLE pseudo = GetCurrentProcess(); + HANDLE procHandle = NULL; + BOOL ok = DuplicateHandle(pseudo, pseudo, pseudo, &procHandle, 0, FALSE, DUPLICATE_SAME_ACCESS); + TEST_CHECK(ok); + TEST_CHECK(procHandle != NULL); + TEST_CHECK(procHandle != pseudo); + + TEST_CHECK_EQ(WAIT_TIMEOUT, WaitForSingleObject(procHandle, 0)); + + TEST_CHECK(CloseHandle(procHandle)); +} + +static void test_duplicate_handle_after_close(void) { + HANDLE evt = CreateEventA(NULL, TRUE, FALSE, NULL); + TEST_CHECK(evt != NULL); + + TEST_CHECK(CloseHandle(evt)); + + HANDLE dup = NULL; + SetLastError(0); + TEST_CHECK(!DuplicateHandle(GetCurrentProcess(), evt, GetCurrentProcess(), &dup, 0, FALSE, DUPLICATE_SAME_ACCESS)); + TEST_CHECK_EQ(ERROR_INVALID_HANDLE, GetLastError()); + TEST_CHECK(dup == NULL); +} + +int main(void) { + test_duplicate_handle_basic(); + test_duplicate_handle_close_source(); + test_duplicate_handle_invalid_source(); + test_duplicate_handle_invalid_target_process(); + test_duplicate_pseudo_process_handle(); + test_duplicate_handle_after_close(); + return EXIT_SUCCESS; +} diff --git a/test/test_overlapped_io.c b/test/test_overlapped_io.c index 296c12d..6426d35 100644 --- a/test/test_overlapped_io.c +++ b/test/test_overlapped_io.c @@ -63,7 +63,7 @@ static void test_overlapped_read_with_event(void) { TEST_CHECK_EQ(ERROR_IO_PENDING, GetLastError()); } - TEST_CHECK(WaitForSingleObject(ov.hEvent, INFINITE) == WAIT_OBJECT_0); + TEST_CHECK(WaitForSingleObject(ov.hEvent, 1000) == WAIT_OBJECT_0); DWORD transferred = 0; TEST_CHECK(GetOverlappedResult(file, &ov, &transferred, FALSE)); @@ -91,7 +91,7 @@ static void test_overlapped_eof(void) { TEST_CHECK_EQ(ERROR_IO_PENDING, GetLastError()); } - TEST_CHECK(WaitForSingleObject(ov.hEvent, INFINITE) == WAIT_OBJECT_0); + TEST_CHECK(WaitForSingleObject(ov.hEvent, 1000) == WAIT_OBJECT_0); DWORD transferred = 1234; TEST_CHECK(!GetOverlappedResult(file, &ov, &transferred, FALSE)); @@ -127,7 +127,7 @@ static void test_overlapped_write(void) { if (!issued) { TEST_CHECK_EQ(ERROR_IO_PENDING, GetLastError()); } - TEST_CHECK(WaitForSingleObject(ov.hEvent, INFINITE) == WAIT_OBJECT_0); + TEST_CHECK(WaitForSingleObject(ov.hEvent, 1000) == WAIT_OBJECT_0); DWORD transferred = 0; TEST_CHECK(GetOverlappedResult(file, &ov, &transferred, FALSE)); diff --git a/test/test_pipe_io.c b/test/test_pipe_io.c index 26bcdc0..d998f43 100644 --- a/test/test_pipe_io.c +++ b/test/test_pipe_io.c @@ -75,8 +75,7 @@ int main(void) { bytesRead = 123; SetLastError(ERROR_GEN_FAILURE); - BOOL ok = ReadFile(readPipe, buffer, sizeof(buffer), &bytesRead, NULL); - TEST_CHECK(!ok); + TEST_CHECK(!ReadFile(readPipe, buffer, sizeof(buffer), &bytesRead, NULL)); TEST_CHECK_EQ(0u, (unsigned int)bytesRead); TEST_CHECK_EQ(ERROR_BROKEN_PIPE, GetLastError()); TEST_CHECK(ResetEvent(event)); diff --git a/test/test_processes.c b/test/test_processes.c new file mode 100644 index 0000000..219bd99 --- /dev/null +++ b/test/test_processes.c @@ -0,0 +1,116 @@ +#include "test_assert.h" +#include +#include +#include +#include + +static DWORD parse_exit_code(const char *value) { + TEST_CHECK(value != NULL); + DWORD result = 0; + for (const char *p = value; *p; ++p) { + TEST_CHECK(*p >= '0' && *p <= '9'); + result = result * 10u + (DWORD)(*p - '0'); + } + return result; +} + +static int child_main(int argc, char **argv) { + TEST_CHECK(argc >= 2); + (void)argv; + + char exitBuffer[16]; + DWORD exitLen = GetEnvironmentVariableA("WIBO_TEST_PROC_EXIT", exitBuffer, sizeof(exitBuffer)); + TEST_CHECK(exitLen > 0 && exitLen < sizeof(exitBuffer)); + DWORD desiredExit = parse_exit_code(exitBuffer); + + Sleep(200); + return (int)desiredExit; +} + +static void test_createprocess_failure(void) { + STARTUPINFOA si; + PROCESS_INFORMATION pi; + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + ZeroMemory(&pi, sizeof(pi)); + + SetLastError(0); + char bogusCommandLine[] = "child"; + TEST_CHECK( + !CreateProcessA("Z:/definitely/missing.exe", bogusCommandLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)); + DWORD error = GetLastError(); + TEST_CHECK_MSG(error == ERROR_FILE_NOT_FOUND || error == ERROR_PATH_NOT_FOUND, "CreateProcessA missing file -> %lu", + (unsigned long)error); +} + +static int parent_main(void) { + test_createprocess_failure(); + + char modulePath[MAX_PATH]; + DWORD pathLen = GetModuleFileNameA(NULL, modulePath, (DWORD)sizeof(modulePath)); + TEST_CHECK(pathLen > 0 && pathLen < sizeof(modulePath)); + + const DWORD childExitCode = 0x24u; + char commandLine[256]; + snprintf(commandLine, sizeof(commandLine), "child placeholder %lu", (unsigned long)childExitCode); + + char exitEnv[16]; + snprintf(exitEnv, sizeof(exitEnv), "%lu", (unsigned long)childExitCode); + TEST_CHECK(SetEnvironmentVariableA("WIBO_TEST_PROC_EXIT", exitEnv)); + TEST_CHECK(SetEnvironmentVariableA("WIBO_TEST_PROC_ROLE", "child")); + + STARTUPINFOA si; + PROCESS_INFORMATION pi; + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + GetStartupInfoA(&si); + ZeroMemory(&pi, sizeof(pi)); + + TEST_CHECK(CreateProcessA(modulePath, commandLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)); + TEST_CHECK(pi.hProcess != NULL); + SetEnvironmentVariableA("WIBO_TEST_PROC_EXIT", NULL); + SetEnvironmentVariableA("WIBO_TEST_PROC_ROLE", NULL); + + HANDLE processHandle = NULL; + TEST_CHECK(DuplicateHandle(GetCurrentProcess(), pi.hProcess, GetCurrentProcess(), &processHandle, 0, FALSE, + DUPLICATE_SAME_ACCESS)); + TEST_CHECK(processHandle != NULL); + TEST_CHECK(processHandle != pi.hProcess); + + TEST_CHECK(CloseHandle(pi.hProcess)); + TEST_CHECK_EQ(WAIT_FAILED, WaitForSingleObject(pi.hProcess, 0)); + pi.hProcess = NULL; + + Sleep(50); + + TEST_CHECK_EQ(WAIT_TIMEOUT, WaitForSingleObject(processHandle, 0)); + + DWORD exitCode = 0; + TEST_CHECK(GetExitCodeProcess(processHandle, &exitCode)); + TEST_CHECK_EQ(STILL_ACTIVE, exitCode); + + TEST_CHECK_EQ(WAIT_TIMEOUT, WaitForSingleObject(processHandle, 10)); + TEST_CHECK_EQ(WAIT_OBJECT_0, WaitForSingleObject(processHandle, 5000)); + + TEST_CHECK(GetExitCodeProcess(processHandle, &exitCode)); + TEST_CHECK_EQ(childExitCode, exitCode); + + TEST_CHECK(CloseHandle(processHandle)); + if (pi.hThread) { + TEST_CHECK(CloseHandle(pi.hThread)); + } + + return EXIT_SUCCESS; +} + +int main(int argc, char **argv) { + char role[16]; + DWORD roleLen = GetEnvironmentVariableA("WIBO_TEST_PROC_ROLE", role, sizeof(role)); + if (roleLen > 0 && roleLen < sizeof(role) && strcmp(role, "child") == 0) { + return child_main(argc, argv); + } + if (argc > 1 && strcmp(argv[1], "child") == 0) { + return child_main(argc, argv); + } + return parent_main(); +} diff --git a/test/test_synchapi.c b/test/test_synchapi.c new file mode 100644 index 0000000..4944240 --- /dev/null +++ b/test/test_synchapi.c @@ -0,0 +1,142 @@ +#include "test_assert.h" +#include +#include + +typedef struct { + HANDLE mutex; + HANDLE acquiredEvent; + HANDLE releaseEvent; + HANDLE doneEvent; +} MutexWorkerContext; + +static DWORD WINAPI mutex_worker(LPVOID param) { + MutexWorkerContext *ctx = (MutexWorkerContext *)param; + DWORD wait = WaitForSingleObject(ctx->mutex, 1000); + TEST_CHECK_EQ(WAIT_OBJECT_0, wait); + TEST_CHECK(SetEvent(ctx->acquiredEvent)); + + wait = WaitForSingleObject(ctx->releaseEvent, 1000); + TEST_CHECK_EQ(WAIT_OBJECT_0, wait); + TEST_CHECK(ReleaseMutex(ctx->mutex)); + TEST_CHECK(SetEvent(ctx->doneEvent)); + return 0; +} + +typedef struct { + HANDLE semaphore; + HANDLE ackEvent; + HANDLE doneEvent; + int iterations; +} SemaphoreWorkerContext; + +static DWORD WINAPI semaphore_worker(LPVOID param) { + SemaphoreWorkerContext *ctx = (SemaphoreWorkerContext *)param; + for (int i = 0; i < ctx->iterations; ++i) { + DWORD wait = WaitForSingleObject(ctx->semaphore, 1000); + TEST_CHECK_EQ(WAIT_OBJECT_0, wait); + TEST_CHECK(SetEvent(ctx->ackEvent)); + } + TEST_CHECK(SetEvent(ctx->doneEvent)); + return 0; +} + +static void test_mutex_contention(void) { + HANDLE mutex = CreateMutexA(NULL, FALSE, NULL); + TEST_CHECK(mutex != NULL); + + HANDLE acquired = CreateEventA(NULL, FALSE, FALSE, NULL); + HANDLE releaseSignal = CreateEventA(NULL, FALSE, FALSE, NULL); + HANDLE done = CreateEventA(NULL, FALSE, FALSE, NULL); + TEST_CHECK(acquired != NULL && releaseSignal != NULL && done != NULL); + + MutexWorkerContext ctx = { + .mutex = mutex, + .acquiredEvent = acquired, + .releaseEvent = releaseSignal, + .doneEvent = done, + }; + + HANDLE thread = CreateThread(NULL, 0, mutex_worker, &ctx, 0, NULL); + TEST_CHECK(thread != NULL); + + DWORD wait = WaitForSingleObject(acquired, 1000); + TEST_CHECK_EQ(WAIT_OBJECT_0, wait); + + wait = WaitForSingleObject(mutex, 10); + TEST_CHECK_EQ(WAIT_TIMEOUT, wait); + + TEST_CHECK(SetEvent(releaseSignal)); + + wait = WaitForSingleObject(done, 1000); + TEST_CHECK_EQ(WAIT_OBJECT_0, wait); + + wait = WaitForSingleObject(mutex, 1000); + TEST_CHECK_EQ(WAIT_OBJECT_0, wait); + + wait = WaitForSingleObject(mutex, 0); + TEST_CHECK_EQ(WAIT_OBJECT_0, wait); + + TEST_CHECK(ReleaseMutex(mutex)); + TEST_CHECK(ReleaseMutex(mutex)); + + TEST_CHECK(CloseHandle(thread)); + TEST_CHECK(CloseHandle(acquired)); + TEST_CHECK(CloseHandle(releaseSignal)); + TEST_CHECK(CloseHandle(done)); + TEST_CHECK(CloseHandle(mutex)); +} + +static void test_semaphore_waits(void) { + HANDLE semaphore = CreateSemaphoreA(NULL, 0, 3, NULL); + TEST_CHECK(semaphore != NULL); + + DWORD wait = WaitForSingleObject(semaphore, 10); + TEST_CHECK_EQ(WAIT_TIMEOUT, wait); + + HANDLE ack = CreateEventA(NULL, FALSE, FALSE, NULL); + HANDLE done = CreateEventA(NULL, FALSE, FALSE, NULL); + TEST_CHECK(ack != NULL && done != NULL); + + SemaphoreWorkerContext ctx = { + .semaphore = semaphore, + .ackEvent = ack, + .doneEvent = done, + .iterations = 3, + }; + + HANDLE thread = CreateThread(NULL, 0, semaphore_worker, &ctx, 0, NULL); + TEST_CHECK(thread != NULL); + + for (int i = 0; i < ctx.iterations; ++i) { + LONG previous = -1; + BOOL ok = ReleaseSemaphore(semaphore, 1, &previous); + TEST_CHECK(ok); + TEST_CHECK_EQ(0, previous); + DWORD ackWait = WaitForSingleObject(ack, 1000); + TEST_CHECK_EQ(WAIT_OBJECT_0, ackWait); + } + + DWORD doneWait = WaitForSingleObject(done, 1000); + TEST_CHECK_EQ(WAIT_OBJECT_0, doneWait); + + // lReleaseCount = 0 permitted; no effect + TEST_CHECK(ReleaseSemaphore(semaphore, 0, NULL)); + + HANDLE limited = CreateSemaphoreA(NULL, 1, 1, NULL); + TEST_CHECK(limited != NULL); + SetLastError(0); + TEST_CHECK(!ReleaseSemaphore(limited, 1, NULL)); + TEST_CHECK_EQ(ERROR_TOO_MANY_POSTS, GetLastError()); + + TEST_CHECK(CloseHandle(thread)); + TEST_CHECK(CloseHandle(ack)); + TEST_CHECK(CloseHandle(done)); + TEST_CHECK(CloseHandle(semaphore)); + TEST_CHECK(CloseHandle(limited)); +} + +int main(void) { + test_mutex_contention(); + test_semaphore_waits(); + return EXIT_SUCCESS; +} diff --git a/test/test_thread_notifications.c b/test/test_thread_notifications.c index 8f56a14..8cc0c12 100644 --- a/test/test_thread_notifications.c +++ b/test/test_thread_notifications.c @@ -52,9 +52,9 @@ int main(void) { HANDLE thread = CreateThread(NULL, 0, workerProc, NULL, 0, NULL); TEST_CHECK_MSG(thread != NULL, "CreateThread failed: %lu", (unsigned long)GetLastError()); - DWORD wait_result = WaitForSingleObject(thread, INFINITE); + DWORD wait_result = WaitForSingleObject(thread, 1000); TEST_CHECK_EQ(WAIT_OBJECT_0, wait_result); - TEST_CHECK_MSG(CloseHandle(thread) != 0, "CloseHandle(thread) failed: %lu", (unsigned long)GetLastError()); + TEST_CHECK(CloseHandle(thread)); TEST_CHECK_EQ(1, getAttach()); TEST_CHECK_EQ(1, getDetach()); @@ -68,9 +68,9 @@ int main(void) { thread = CreateThread(NULL, 0, workerProc, NULL, 0, NULL); TEST_CHECK_MSG(thread != NULL, "CreateThread after disable failed: %lu", (unsigned long)GetLastError()); - wait_result = WaitForSingleObject(thread, INFINITE); + wait_result = WaitForSingleObject(thread, 1000); TEST_CHECK_EQ(WAIT_OBJECT_0, wait_result); - TEST_CHECK_MSG(CloseHandle(thread) != 0, "CloseHandle(second thread) failed: %lu", (unsigned long)GetLastError()); + TEST_CHECK(CloseHandle(thread)); if (!isRunningUnderWine()) { TEST_CHECK_EQ(0, getAttach()); @@ -80,7 +80,7 @@ int main(void) { LONG final_attach = getAttach(); LONG final_detach = getDetach(); - TEST_CHECK_MSG(FreeLibrary(mod) != 0, "FreeLibrary failed: %lu", (unsigned long)GetLastError()); + TEST_CHECK(FreeLibrary(mod)); printf("thread_notifications: attach=%ld detach=%ld\n", (long)final_attach, (long)final_detach); return EXIT_SUCCESS; diff --git a/test/test_threading.c b/test/test_threading.c index b64a2b1..2a24e0d 100644 --- a/test/test_threading.c +++ b/test/test_threading.c @@ -11,7 +11,7 @@ typedef struct { static DWORD WINAPI worker_main(LPVOID param) { WorkerContext *ctx = (WorkerContext *)param; TEST_CHECK(SetEvent(ctx->readyEvent)); - DWORD waitResult = WaitForSingleObject(ctx->goEvent, INFINITE); + DWORD waitResult = WaitForSingleObject(ctx->goEvent, 1000); TEST_CHECK_EQ(WAIT_OBJECT_0, waitResult); return ctx->exitCode; } @@ -37,14 +37,14 @@ int main(void) { HANDLE thread = CreateThread(NULL, 0, worker_main, &ctx, 0, NULL); TEST_CHECK(thread != NULL); - DWORD waitResult = WaitForSingleObject(readyEvent, INFINITE); + DWORD waitResult = WaitForSingleObject(readyEvent, 1000); TEST_CHECK_EQ(WAIT_OBJECT_0, waitResult); TEST_CHECK(ResetEvent(readyEvent)); TEST_CHECK(SetEvent(goEvent)); - waitResult = WaitForSingleObject(thread, INFINITE); + waitResult = WaitForSingleObject(thread, 1000); TEST_CHECK_EQ(WAIT_OBJECT_0, waitResult); DWORD exitCode = 0; @@ -56,21 +56,21 @@ int main(void) { HANDLE autoEvent = CreateEventA(NULL, FALSE, FALSE, NULL); TEST_CHECK(autoEvent != NULL); TEST_CHECK(SetEvent(autoEvent)); - waitResult = WaitForSingleObject(autoEvent, INFINITE); + waitResult = WaitForSingleObject(autoEvent, 0); TEST_CHECK_EQ(WAIT_OBJECT_0, waitResult); TEST_CHECK(SetEvent(autoEvent)); - waitResult = WaitForSingleObject(autoEvent, INFINITE); + waitResult = WaitForSingleObject(autoEvent, 0); TEST_CHECK_EQ(WAIT_OBJECT_0, waitResult); TEST_CHECK(CloseHandle(autoEvent)); HANDLE manualEvent = CreateEventA(NULL, TRUE, FALSE, NULL); TEST_CHECK(manualEvent != NULL); TEST_CHECK(SetEvent(manualEvent)); - waitResult = WaitForSingleObject(manualEvent, INFINITE); + waitResult = WaitForSingleObject(manualEvent, 0); TEST_CHECK_EQ(WAIT_OBJECT_0, waitResult); TEST_CHECK(ResetEvent(manualEvent)); TEST_CHECK(SetEvent(manualEvent)); - waitResult = WaitForSingleObject(manualEvent, INFINITE); + waitResult = WaitForSingleObject(manualEvent, 0); TEST_CHECK_EQ(WAIT_OBJECT_0, waitResult); TEST_CHECK(CloseHandle(manualEvent)); @@ -80,7 +80,7 @@ int main(void) { HANDLE mutex = CreateMutexA(NULL, FALSE, NULL); TEST_CHECK(mutex != NULL); - waitResult = WaitForSingleObject(mutex, INFINITE); + waitResult = WaitForSingleObject(mutex, 0); TEST_CHECK_EQ(WAIT_OBJECT_0, waitResult); TEST_CHECK(ReleaseMutex(mutex)); TEST_CHECK(CloseHandle(mutex)); @@ -88,7 +88,7 @@ int main(void) { DWORD secondExitCode = 0x55AA; HANDLE exitThread = CreateThread(NULL, 0, exit_thread_worker, &secondExitCode, 0, NULL); TEST_CHECK(exitThread != NULL); - waitResult = WaitForSingleObject(exitThread, INFINITE); + waitResult = WaitForSingleObject(exitThread, 1000); TEST_CHECK_EQ(WAIT_OBJECT_0, waitResult); exitCode = 0; TEST_CHECK(GetExitCodeThread(exitThread, &exitCode));