diff options
Diffstat (limited to '')
-rw-r--r-- | winpr/libwinpr/synch/barrier.c | 263 |
1 files changed, 263 insertions, 0 deletions
diff --git a/winpr/libwinpr/synch/barrier.c b/winpr/libwinpr/synch/barrier.c new file mode 100644 index 0000000..0021408 --- /dev/null +++ b/winpr/libwinpr/synch/barrier.c @@ -0,0 +1,263 @@ +/** + * WinPR: Windows Portable Runtime + * Synchronization Functions + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2016 Norbert Federa <norbert.federa@thincast.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <winpr/config.h> + +#include <winpr/synch.h> +#include <winpr/assert.h> + +#include "synch.h" + +#include <winpr/crt.h> + +#ifdef WINPR_SYNCHRONIZATION_BARRIER + +#include <winpr/sysinfo.h> +#include <winpr/library.h> +#include <winpr/interlocked.h> +#include <winpr/thread.h> + +/** + * WinPR uses the internal RTL_BARRIER struct members exactly like Windows: + * + * DWORD Reserved1: number of threads that have not yet entered the barrier + * DWORD Reserved2: number of threads required to enter the barrier + * ULONG_PTR Reserved3[2]; two synchronization events (manual reset events) + * DWORD Reserved4; number of processors + * DWORD Reserved5; spincount + */ + +#ifdef _WIN32 + +static HMODULE g_Kernel32 = NULL; +static BOOL g_NativeBarrier = FALSE; +static INIT_ONCE g_InitOnce = INIT_ONCE_STATIC_INIT; + +typedef BOOL(WINAPI* fnInitializeSynchronizationBarrier)(LPSYNCHRONIZATION_BARRIER lpBarrier, + LONG lTotalThreads, LONG lSpinCount); +typedef BOOL(WINAPI* fnEnterSynchronizationBarrier)(LPSYNCHRONIZATION_BARRIER lpBarrier, + DWORD dwFlags); +typedef BOOL(WINAPI* fnDeleteSynchronizationBarrier)(LPSYNCHRONIZATION_BARRIER lpBarrier); + +static fnInitializeSynchronizationBarrier pfnInitializeSynchronizationBarrier = NULL; +static fnEnterSynchronizationBarrier pfnEnterSynchronizationBarrier = NULL; +static fnDeleteSynchronizationBarrier pfnDeleteSynchronizationBarrier = NULL; + +static BOOL CALLBACK InitOnce_Barrier(PINIT_ONCE once, PVOID param, PVOID* context) +{ + g_Kernel32 = LoadLibraryA("kernel32.dll"); + + if (!g_Kernel32) + return TRUE; + + pfnInitializeSynchronizationBarrier = (fnInitializeSynchronizationBarrier)GetProcAddress( + g_Kernel32, "InitializeSynchronizationBarrier"); + + pfnEnterSynchronizationBarrier = + (fnEnterSynchronizationBarrier)GetProcAddress(g_Kernel32, "EnterSynchronizationBarrier"); + + pfnDeleteSynchronizationBarrier = + (fnDeleteSynchronizationBarrier)GetProcAddress(g_Kernel32, "DeleteSynchronizationBarrier"); + + if (pfnInitializeSynchronizationBarrier && pfnEnterSynchronizationBarrier && + pfnDeleteSynchronizationBarrier) + { + g_NativeBarrier = TRUE; + } + + return TRUE; +} + +#endif + +BOOL WINAPI winpr_InitializeSynchronizationBarrier(LPSYNCHRONIZATION_BARRIER lpBarrier, + LONG lTotalThreads, LONG lSpinCount) +{ + SYSTEM_INFO sysinfo; + HANDLE hEvent0 = NULL; + HANDLE hEvent1 = NULL; + +#ifdef _WIN32 + InitOnceExecuteOnce(&g_InitOnce, InitOnce_Barrier, NULL, NULL); + + if (g_NativeBarrier) + return pfnInitializeSynchronizationBarrier(lpBarrier, lTotalThreads, lSpinCount); +#endif + + if (!lpBarrier || lTotalThreads < 1 || lSpinCount < -1) + { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + ZeroMemory(lpBarrier, sizeof(SYNCHRONIZATION_BARRIER)); + + if (lSpinCount == -1) + lSpinCount = 2000; + + if (!(hEvent0 = CreateEvent(NULL, TRUE, FALSE, NULL))) + return FALSE; + + if (!(hEvent1 = CreateEvent(NULL, TRUE, FALSE, NULL))) + { + CloseHandle(hEvent0); + return FALSE; + } + + GetNativeSystemInfo(&sysinfo); + + WINPR_ASSERT(lTotalThreads >= 0); + lpBarrier->Reserved1 = (DWORD)lTotalThreads; + lpBarrier->Reserved2 = (DWORD)lTotalThreads; + lpBarrier->Reserved3[0] = (ULONG_PTR)hEvent0; + lpBarrier->Reserved3[1] = (ULONG_PTR)hEvent1; + lpBarrier->Reserved4 = sysinfo.dwNumberOfProcessors; + WINPR_ASSERT(lSpinCount >= 0); + lpBarrier->Reserved5 = (DWORD)lSpinCount; + + return TRUE; +} + +BOOL WINAPI winpr_EnterSynchronizationBarrier(LPSYNCHRONIZATION_BARRIER lpBarrier, DWORD dwFlags) +{ + LONG remainingThreads = 0; + HANDLE hCurrentEvent = NULL; + HANDLE hDormantEvent = NULL; + +#ifdef _WIN32 + if (g_NativeBarrier) + return pfnEnterSynchronizationBarrier(lpBarrier, dwFlags); +#endif + + if (!lpBarrier) + return FALSE; + + /** + * dwFlags according to + * https://msdn.microsoft.com/en-us/library/windows/desktop/hh706889(v=vs.85).aspx + * + * SYNCHRONIZATION_BARRIER_FLAGS_BLOCK_ONLY (0x01) + * Specifies that the thread entering the barrier should block + * immediately until the last thread enters the barrier. + * + * SYNCHRONIZATION_BARRIER_FLAGS_SPIN_ONLY (0x02) + * Specifies that the thread entering the barrier should spin until the + * last thread enters the barrier, even if the spinning thread exceeds + * the barrier's maximum spin count. + * + * SYNCHRONIZATION_BARRIER_FLAGS_NO_DELETE (0x04) + * Specifies that the function can skip the work required to ensure + * that it is safe to delete the barrier, which can improve + * performance. All threads that enter this barrier must specify the + * flag; otherwise, the flag is ignored. This flag should be used only + * if the barrier will never be deleted. + */ + + hCurrentEvent = (HANDLE)lpBarrier->Reserved3[0]; + hDormantEvent = (HANDLE)lpBarrier->Reserved3[1]; + + remainingThreads = InterlockedDecrement((LONG*)&lpBarrier->Reserved1); + + WINPR_ASSERT(remainingThreads >= 0); + + if (remainingThreads > 0) + { + DWORD dwProcessors = lpBarrier->Reserved4; + BOOL spinOnly = (dwFlags & SYNCHRONIZATION_BARRIER_FLAGS_SPIN_ONLY) ? TRUE : FALSE; + BOOL blockOnly = (dwFlags & SYNCHRONIZATION_BARRIER_FLAGS_BLOCK_ONLY) ? TRUE : FALSE; + BOOL block = TRUE; + + /** + * If SYNCHRONIZATION_BARRIER_FLAGS_SPIN_ONLY is set we will + * always spin and trust that the user knows what he/she/it + * is doing. Otherwise we'll only spin if the flag + * SYNCHRONIZATION_BARRIER_FLAGS_BLOCK_ONLY is not set and + * the number of remaining threads is less than the number + * of processors. + */ + + if (spinOnly || (((ULONG)remainingThreads < dwProcessors) && !blockOnly)) + { + DWORD dwSpinCount = lpBarrier->Reserved5; + DWORD sp = 0; + /** + * nb: we must let the compiler know that our comparand + * can change between the iterations in the loop below + */ + volatile ULONG_PTR* cmp = &lpBarrier->Reserved3[0]; + /* we spin until the last thread _completed_ the event switch */ + while ((block = (*cmp == (ULONG_PTR)hCurrentEvent))) + if (!spinOnly && ++sp > dwSpinCount) + break; + } + + if (block) + WaitForSingleObject(hCurrentEvent, INFINITE); + + return FALSE; + } + + /* reset the dormant event first */ + ResetEvent(hDormantEvent); + + /* reset the remaining counter */ + lpBarrier->Reserved1 = lpBarrier->Reserved2; + + /* switch events - this will also unblock the spinning threads */ + lpBarrier->Reserved3[1] = (ULONG_PTR)hCurrentEvent; + lpBarrier->Reserved3[0] = (ULONG_PTR)hDormantEvent; + + /* signal the blocked threads */ + SetEvent(hCurrentEvent); + + return TRUE; +} + +BOOL WINAPI winpr_DeleteSynchronizationBarrier(LPSYNCHRONIZATION_BARRIER lpBarrier) +{ +#ifdef _WIN32 + if (g_NativeBarrier) + return pfnDeleteSynchronizationBarrier(lpBarrier); +#endif + + /** + * According to https://msdn.microsoft.com/en-us/library/windows/desktop/hh706887(v=vs.85).aspx + * Return value: + * The DeleteSynchronizationBarrier function always returns TRUE. + */ + + if (!lpBarrier) + return TRUE; + + while (lpBarrier->Reserved1 != lpBarrier->Reserved2) + SwitchToThread(); + + if (lpBarrier->Reserved3[0]) + CloseHandle((HANDLE)lpBarrier->Reserved3[0]); + + if (lpBarrier->Reserved3[1]) + CloseHandle((HANDLE)lpBarrier->Reserved3[1]); + + ZeroMemory(lpBarrier, sizeof(SYNCHRONIZATION_BARRIER)); + + return TRUE; +} + +#endif |