Milestone 5: deliver embedded RDP sessions and lifecycle hardening

This commit is contained in:
Keith Smith
2026-03-03 18:59:26 -07:00
parent 230a401386
commit 36006bd4aa
2941 changed files with 724359 additions and 77 deletions

View File

@@ -0,0 +1,38 @@
# WinPR: Windows Portable Runtime
# libwinpr-pool cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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.
set(WINPR_THREADPOOL_DEFAULT_MAX_COUNT "16" CACHE STRING "The maximum (default) number of threads in a pool")
set(WINPR_THREADPOOL_DEFAULT_MIN_COUNT "4" CACHE STRING "The minimum (default) number of threads in a pool")
winpr_definition_add(WINPR_THREADPOOL_DEFAULT_MAX_COUNT=${WINPR_THREADPOOL_DEFAULT_MAX_COUNT})
winpr_definition_add(WINPR_THREADPOOL_DEFAULT_MIN_COUNT=${WINPR_THREADPOOL_DEFAULT_MIN_COUNT})
winpr_module_add(
synch.c
work.c
timer.c
io.c
cleanup_group.c
pool.c
pool.h
callback.c
callback_cleanup.c
)
winpr_library_add_private(${CMAKE_THREAD_LIBS_INIT} ${CMAKE_DL_LIBS})
if(BUILD_TESTING_INTERNAL OR BUILD_TESTING)
add_subdirectory(test)
endif()

View File

@@ -0,0 +1,9 @@
set(MINWIN_LAYER "1")
set(MINWIN_GROUP "core")
set(MINWIN_MAJOR_VERSION "2")
set(MINWIN_MINOR_VERSION "1")
set(MINWIN_SHORT_NAME "threadpool")
set(MINWIN_LONG_NAME "Thread Pool API")
set(MODULE_LIBRARY_NAME
"api-ms-win-${MINWIN_GROUP}-${MINWIN_SHORT_NAME}-l${MINWIN_LAYER}-${MINWIN_MAJOR_VERSION}-${MINWIN_MINOR_VERSION}"
)

View File

@@ -0,0 +1,57 @@
/**
* WinPR: Windows Portable Runtime
* Thread Pool API (Callback)
*
* Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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/crt.h>
#include <winpr/pool.h>
#include <winpr/wlog.h>
#include <winpr/library.h>
#ifdef WINPR_THREAD_POOL
#ifdef _WIN32
typedef BOOL(WINAPI* pCallbackMayRunLong_t)(PTP_CALLBACK_INSTANCE pci);
static INIT_ONCE init_once_module = INIT_ONCE_STATIC_INIT;
static pCallbackMayRunLong_t pCallbackMayRunLong = nullptr;
static BOOL CALLBACK init_module(PINIT_ONCE once, PVOID param, PVOID* context)
{
HMODULE kernel32 = LoadLibraryA("kernel32.dll");
if (kernel32)
pCallbackMayRunLong =
GetProcAddressAs(kernel32, "CallbackMayRunLong", pCallbackMayRunLong_t);
return TRUE;
}
#endif
BOOL winpr_CallbackMayRunLong(WINPR_ATTR_UNUSED PTP_CALLBACK_INSTANCE pci)
{
#ifdef _WIN32
if (!InitOnceExecuteOnce(&init_once_module, init_module, nullptr, nullptr))
return FALSE;
if (pCallbackMayRunLong)
return pCallbackMayRunLong(pci);
#endif
WLog_ERR("TODO", "TODO: implement");
/* No default implementation */
return FALSE;
}
#endif /* WINPR_THREAD_POOL defined */

View File

@@ -0,0 +1,157 @@
/**
* WinPR: Windows Portable Runtime
* Thread Pool API (Callback Clean-up)
*
* Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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/crt.h>
#include <winpr/pool.h>
#include <winpr/library.h>
#include "pool.h"
#ifdef WINPR_THREAD_POOL
#ifdef _WIN32
static INIT_ONCE init_once_module = INIT_ONCE_STATIC_INIT;
static VOID(WINAPI* pSetEventWhenCallbackReturns)(PTP_CALLBACK_INSTANCE pci, HANDLE evt);
static VOID(WINAPI* pReleaseSemaphoreWhenCallbackReturns)(PTP_CALLBACK_INSTANCE pci, HANDLE sem,
DWORD crel);
static VOID(WINAPI* pReleaseMutexWhenCallbackReturns)(PTP_CALLBACK_INSTANCE pci, HANDLE mut);
static VOID(WINAPI* pLeaveCriticalSectionWhenCallbackReturns)(PTP_CALLBACK_INSTANCE pci,
PCRITICAL_SECTION pcs);
static VOID(WINAPI* pFreeLibraryWhenCallbackReturns)(PTP_CALLBACK_INSTANCE pci, HMODULE mod);
static VOID(WINAPI* pDisassociateCurrentThreadFromCallback)(PTP_CALLBACK_INSTANCE pci);
static BOOL CALLBACK init_module(PINIT_ONCE once, PVOID param, PVOID* context)
{
HMODULE kernel32 = LoadLibraryA("kernel32.dll");
if (kernel32)
{
pSetEventWhenCallbackReturns =
GetProcAddressAs(kernel32, "SetEventWhenCallbackReturns"), void*);
pReleaseSemaphoreWhenCallbackReturns =
GetProcAddressAs(kernel32, "ReleaseSemaphoreWhenCallbackReturns", void*);
pReleaseMutexWhenCallbackReturns =
GetProcAddressAs(kernel32, "ReleaseMutexWhenCallbackReturns", void*);
pLeaveCriticalSectionWhenCallbackReturns =
GetProcAddressAs(kernel32, "LeaveCriticalSectionWhenCallbackReturns", void*);
pFreeLibraryWhenCallbackReturns =
GetProcAddressAs(kernel32, "FreeLibraryWhenCallbackReturns", void*);
pDisassociateCurrentThreadFromCallback =
GetProcAddressAs(kernel32, "DisassociateCurrentThreadFromCallback", void*);
}
return TRUE;
}
#endif
VOID SetEventWhenCallbackReturns(WINPR_ATTR_UNUSED PTP_CALLBACK_INSTANCE pci,
WINPR_ATTR_UNUSED HANDLE evt)
{
#ifdef _WIN32
if (!InitOnceExecuteOnce(&init_once_module, init_module, nullptr, nullptr))
return;
if (pSetEventWhenCallbackReturns)
{
pSetEventWhenCallbackReturns(pci, evt);
return;
}
#endif
WLog_ERR("TODO", "TODO: implement");
/* No default implementation */
}
VOID ReleaseSemaphoreWhenCallbackReturns(WINPR_ATTR_UNUSED PTP_CALLBACK_INSTANCE pci,
WINPR_ATTR_UNUSED HANDLE sem, WINPR_ATTR_UNUSED DWORD crel)
{
#ifdef _WIN32
if (!InitOnceExecuteOnce(&init_once_module, init_module, nullptr, nullptr))
return;
if (pReleaseSemaphoreWhenCallbackReturns)
{
pReleaseSemaphoreWhenCallbackReturns(pci, sem, crel);
return;
}
#endif
/* No default implementation */
WLog_ERR("TODO", "TODO: implement");
}
VOID ReleaseMutexWhenCallbackReturns(WINPR_ATTR_UNUSED PTP_CALLBACK_INSTANCE pci,
WINPR_ATTR_UNUSED HANDLE mut)
{
#ifdef _WIN32
if (!InitOnceExecuteOnce(&init_once_module, init_module, nullptr, nullptr))
return;
if (pReleaseMutexWhenCallbackReturns)
{
pReleaseMutexWhenCallbackReturns(pci, mut);
return;
}
#endif
/* No default implementation */
WLog_ERR("TODO", "TODO: implement");
}
VOID LeaveCriticalSectionWhenCallbackReturns(WINPR_ATTR_UNUSED PTP_CALLBACK_INSTANCE pci,
WINPR_ATTR_UNUSED PCRITICAL_SECTION pcs)
{
#ifdef _WIN32
if (!InitOnceExecuteOnce(&init_once_module, init_module, nullptr, nullptr))
return;
if (pLeaveCriticalSectionWhenCallbackReturns)
{
pLeaveCriticalSectionWhenCallbackReturns(pci, pcs);
}
#endif
/* No default implementation */
WLog_ERR("TODO", "TODO: implement");
}
VOID FreeLibraryWhenCallbackReturns(WINPR_ATTR_UNUSED PTP_CALLBACK_INSTANCE pci,
WINPR_ATTR_UNUSED HMODULE mod)
{
#ifdef _WIN32
if (!InitOnceExecuteOnce(&init_once_module, init_module, nullptr, nullptr))
return;
if (pFreeLibraryWhenCallbackReturns)
{
pFreeLibraryWhenCallbackReturns(pci, mod);
return;
}
#endif
/* No default implementation */
WLog_ERR("TODO", "TODO: implement");
}
VOID DisassociateCurrentThreadFromCallback(WINPR_ATTR_UNUSED PTP_CALLBACK_INSTANCE pci)
{
#ifdef _WIN32
if (!InitOnceExecuteOnce(&init_once_module, init_module, nullptr, nullptr))
return;
if (pDisassociateCurrentThreadFromCallback)
{
pDisassociateCurrentThreadFromCallback(pci);
return;
}
#endif
WLog_ERR("TODO", "TODO: implement");
/* No default implementation */
}
#endif /* WINPR_THREAD_POOL defined */

View File

@@ -0,0 +1,145 @@
/**
* WinPR: Windows Portable Runtime
* Thread Pool API (Clean-up Group)
*
* Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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/winpr.h>
#include <winpr/crt.h>
#include <winpr/pool.h>
#include <winpr/library.h>
#include "pool.h"
#ifdef WINPR_THREAD_POOL
#ifdef _WIN32
static INIT_ONCE init_once_module = INIT_ONCE_STATIC_INIT;
static PTP_CLEANUP_GROUP(WINAPI* pCreateThreadpoolCleanupGroup)();
static VOID(WINAPI* pCloseThreadpoolCleanupGroupMembers)(PTP_CLEANUP_GROUP ptpcg,
BOOL fCancelPendingCallbacks,
PVOID pvCleanupContext);
static VOID(WINAPI* pCloseThreadpoolCleanupGroup)(PTP_CLEANUP_GROUP ptpcg);
static BOOL CALLBACK init_module(PINIT_ONCE once, PVOID param, PVOID* context)
{
HMODULE kernel32 = LoadLibraryA("kernel32.dll");
if (kernel32)
{
pCreateThreadpoolCleanupGroup =
GetProcAddressAs(kernel32, "CreateThreadpoolCleanupGroup", void*);
pCloseThreadpoolCleanupGroupMembers =
GetProcAddressAs(kernel32, "CloseThreadpoolCleanupGroupMembers", void*);
pCloseThreadpoolCleanupGroup =
GetProcAddressAs(kernel32, "CloseThreadpoolCleanupGroup", void*);
}
return TRUE;
}
#endif
PTP_CLEANUP_GROUP winpr_CreateThreadpoolCleanupGroup(void)
{
PTP_CLEANUP_GROUP cleanupGroup = nullptr;
#ifdef _WIN32
if (!InitOnceExecuteOnce(&init_once_module, init_module, nullptr, nullptr))
return nullptr;
if (pCreateThreadpoolCleanupGroup)
return pCreateThreadpoolCleanupGroup();
return cleanupGroup;
#else
cleanupGroup = (PTP_CLEANUP_GROUP)calloc(1, sizeof(TP_CLEANUP_GROUP));
if (!cleanupGroup)
return nullptr;
cleanupGroup->groups = ArrayList_New(FALSE);
if (!cleanupGroup->groups)
{
free(cleanupGroup);
return nullptr;
}
return cleanupGroup;
#endif
}
VOID winpr_SetThreadpoolCallbackCleanupGroup(PTP_CALLBACK_ENVIRON pcbe, PTP_CLEANUP_GROUP ptpcg,
PTP_CLEANUP_GROUP_CANCEL_CALLBACK pfng)
{
pcbe->CleanupGroup = ptpcg;
pcbe->CleanupGroupCancelCallback = pfng;
#ifndef _WIN32
pcbe->CleanupGroup->env = pcbe;
#endif
}
VOID winpr_CloseThreadpoolCleanupGroupMembers(WINPR_ATTR_UNUSED PTP_CLEANUP_GROUP ptpcg,
WINPR_ATTR_UNUSED BOOL fCancelPendingCallbacks,
WINPR_ATTR_UNUSED PVOID pvCleanupContext)
{
#ifdef _WIN32
if (!InitOnceExecuteOnce(&init_once_module, init_module, nullptr, nullptr))
return;
if (pCloseThreadpoolCleanupGroupMembers)
{
pCloseThreadpoolCleanupGroupMembers(ptpcg, fCancelPendingCallbacks, pvCleanupContext);
return;
}
#else
while (ArrayList_Count(ptpcg->groups) > 0)
{
PTP_WORK work = ArrayList_GetItem(ptpcg->groups, 0);
winpr_CloseThreadpoolWork(work);
}
#endif
}
VOID winpr_CloseThreadpoolCleanupGroup(PTP_CLEANUP_GROUP ptpcg)
{
#ifdef _WIN32
if (!InitOnceExecuteOnce(&init_once_module, init_module, nullptr, nullptr))
return;
if (pCloseThreadpoolCleanupGroup)
{
pCloseThreadpoolCleanupGroup(ptpcg);
return;
}
#else
if (ptpcg && ptpcg->groups)
ArrayList_Free(ptpcg->groups);
if (ptpcg && ptpcg->env)
ptpcg->env->CleanupGroup = nullptr;
free(ptpcg);
#endif
}
#endif /* WINPR_THREAD_POOL defined */

View File

@@ -0,0 +1,58 @@
/**
* WinPR: Windows Portable Runtime
* Thread Pool API (I/O)
*
* Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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/crt.h>
#include <winpr/pool.h>
#include <winpr/wlog.h>
#ifdef WINPR_THREAD_POOL
PTP_IO winpr_CreateThreadpoolIo(WINPR_ATTR_UNUSED HANDLE fl,
WINPR_ATTR_UNUSED PTP_WIN32_IO_CALLBACK pfnio,
WINPR_ATTR_UNUSED PVOID pv,
WINPR_ATTR_UNUSED PTP_CALLBACK_ENVIRON pcbe)
{
WLog_ERR("TODO", "TODO: Implement");
return nullptr;
}
VOID winpr_CloseThreadpoolIo(WINPR_ATTR_UNUSED PTP_IO pio)
{
WLog_ERR("TODO", "TODO: Implement");
}
VOID winpr_StartThreadpoolIo(WINPR_ATTR_UNUSED PTP_IO pio)
{
WLog_ERR("TODO", "TODO: Implement");
}
VOID winpr_CancelThreadpoolIo(WINPR_ATTR_UNUSED PTP_IO pio)
{
WLog_ERR("TODO", "TODO: Implement");
}
VOID winpr_WaitForThreadpoolIoCallbacks(WINPR_ATTR_UNUSED PTP_IO pio,
WINPR_ATTR_UNUSED BOOL fCancelPendingCallbacks)
{
WLog_ERR("TODO", "TODO: Implement");
}
#endif

View File

@@ -0,0 +1,278 @@
/**
* WinPR: Windows Portable Runtime
* Thread Pool API (Pool)
*
* Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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/crt.h>
#include <winpr/sysinfo.h>
#include <winpr/pool.h>
#include <winpr/library.h>
#include "pool.h"
#ifdef WINPR_THREAD_POOL
#ifdef _WIN32
static INIT_ONCE init_once_module = INIT_ONCE_STATIC_INIT;
static PTP_POOL(WINAPI* pCreateThreadpool)(PVOID reserved);
static VOID(WINAPI* pCloseThreadpool)(PTP_POOL ptpp);
static BOOL(WINAPI* pSetThreadpoolThreadMinimum)(PTP_POOL ptpp, DWORD cthrdMic);
static VOID(WINAPI* pSetThreadpoolThreadMaximum)(PTP_POOL ptpp, DWORD cthrdMost);
static BOOL CALLBACK init_module(PINIT_ONCE once, PVOID param, PVOID* context)
{
HMODULE kernel32 = LoadLibraryA("kernel32.dll");
if (kernel32)
{
pCreateThreadpool = GetProcAddressAs(kernel32, "CreateThreadpool", void*);
pCloseThreadpool = GetProcAddressAs(kernel32, "CloseThreadpool", void*);
pSetThreadpoolThreadMinimum =
GetProcAddressAs(kernel32, "SetThreadpoolThreadMinimum", void*);
pSetThreadpoolThreadMaximum =
GetProcAddressAs(kernel32, "SetThreadpoolThreadMaximum", void*);
}
return TRUE;
}
#endif
static TP_POOL DEFAULT_POOL = {
0, /* DWORD Minimum */
500, /* DWORD Maximum */
nullptr, /* wArrayList* Threads */
nullptr, /* wQueue* PendingQueue */
nullptr, /* HANDLE TerminateEvent */
nullptr, /* wCountdownEvent* WorkComplete */
};
static DWORD WINAPI thread_pool_work_func(LPVOID arg)
{
DWORD status = 0;
PTP_POOL pool = nullptr;
PTP_WORK work = nullptr;
HANDLE events[2];
PTP_CALLBACK_INSTANCE callbackInstance = nullptr;
pool = (PTP_POOL)arg;
events[0] = pool->TerminateEvent;
events[1] = Queue_Event(pool->PendingQueue);
while (1)
{
status = WaitForMultipleObjects(2, events, FALSE, INFINITE);
if (status == WAIT_OBJECT_0)
break;
if (status != (WAIT_OBJECT_0 + 1))
break;
callbackInstance = (PTP_CALLBACK_INSTANCE)Queue_Dequeue(pool->PendingQueue);
if (callbackInstance)
{
work = callbackInstance->Work;
work->WorkCallback(callbackInstance, work->CallbackParameter, work);
CountdownEvent_Signal(pool->WorkComplete, 1);
free(callbackInstance);
}
}
ExitThread(0);
return 0;
}
static void threads_close(void* thread)
{
(void)WaitForSingleObject(thread, INFINITE);
(void)CloseHandle(thread);
}
static BOOL InitializeThreadpool(PTP_POOL pool)
{
BOOL rc = FALSE;
wObject* obj = nullptr;
if (pool->Threads)
return TRUE;
if (!(pool->PendingQueue = Queue_New(TRUE, -1, -1)))
goto fail;
if (!(pool->WorkComplete = CountdownEvent_New(0)))
goto fail;
if (!(pool->TerminateEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr)))
goto fail;
if (!(pool->Threads = ArrayList_New(TRUE)))
goto fail;
obj = ArrayList_Object(pool->Threads);
obj->fnObjectFree = threads_close;
#if !defined(WINPR_THREADPOOL_DEFAULT_MIN_COUNT)
#error "WINPR_THREADPOOL_DEFAULT_MIN_COUNT must be defined"
#endif
#if !defined(WINPR_THREADPOOL_DEFAULT_MAX_COUNT)
#error "WINPR_THREADPOOL_DEFAULT_MAX_COUNT must be defined"
#endif
{
SYSTEM_INFO info = WINPR_C_ARRAY_INIT;
GetSystemInfo(&info);
DWORD min = info.dwNumberOfProcessors;
DWORD max = info.dwNumberOfProcessors;
if (info.dwNumberOfProcessors < WINPR_THREADPOOL_DEFAULT_MIN_COUNT)
min = WINPR_THREADPOOL_DEFAULT_MIN_COUNT;
if (info.dwNumberOfProcessors > WINPR_THREADPOOL_DEFAULT_MAX_COUNT)
max = WINPR_THREADPOOL_DEFAULT_MAX_COUNT;
if (min > max)
min = max;
if (!SetThreadpoolThreadMinimum(pool, min))
goto fail;
SetThreadpoolThreadMaximum(pool, max);
}
rc = TRUE;
fail:
return rc;
}
PTP_POOL GetDefaultThreadpool(void)
{
PTP_POOL pool = &DEFAULT_POOL;
if (!InitializeThreadpool(pool))
return nullptr;
return pool;
}
PTP_POOL winpr_CreateThreadpool(PVOID reserved)
{
PTP_POOL pool = nullptr;
#ifdef _WIN32
if (!InitOnceExecuteOnce(&init_once_module, init_module, nullptr, nullptr))
return nullptr;
if (pCreateThreadpool)
return pCreateThreadpool(reserved);
#else
WINPR_UNUSED(reserved);
#endif
if (!(pool = (PTP_POOL)calloc(1, sizeof(TP_POOL))))
return nullptr;
if (!InitializeThreadpool(pool))
{
winpr_CloseThreadpool(pool);
return nullptr;
}
return pool;
}
VOID winpr_CloseThreadpool(PTP_POOL ptpp)
{
#ifdef _WIN32
if (!InitOnceExecuteOnce(&init_once_module, init_module, nullptr, nullptr))
return;
if (pCloseThreadpool)
{
pCloseThreadpool(ptpp);
return;
}
#endif
(void)SetEvent(ptpp->TerminateEvent);
ArrayList_Free(ptpp->Threads);
Queue_Free(ptpp->PendingQueue);
CountdownEvent_Free(ptpp->WorkComplete);
(void)CloseHandle(ptpp->TerminateEvent);
{
TP_POOL empty = WINPR_C_ARRAY_INIT;
*ptpp = empty;
}
if (ptpp != &DEFAULT_POOL)
free(ptpp);
}
BOOL winpr_SetThreadpoolThreadMinimum(PTP_POOL ptpp, DWORD cthrdMic)
{
BOOL rc = FALSE;
#ifdef _WIN32
if (!InitOnceExecuteOnce(&init_once_module, init_module, nullptr, nullptr))
return FALSE;
if (pSetThreadpoolThreadMinimum)
return pSetThreadpoolThreadMinimum(ptpp, cthrdMic);
#endif
ptpp->Minimum = cthrdMic;
ArrayList_Lock(ptpp->Threads);
while (ArrayList_Count(ptpp->Threads) < ptpp->Minimum)
{
HANDLE thread = CreateThread(nullptr, 0, thread_pool_work_func, (void*)ptpp, 0, nullptr);
if (!thread)
goto fail;
if (!ArrayList_Append(ptpp->Threads, thread))
{
(void)CloseHandle(thread);
goto fail;
}
}
rc = TRUE;
fail:
ArrayList_Unlock(ptpp->Threads);
return rc;
}
VOID winpr_SetThreadpoolThreadMaximum(PTP_POOL ptpp, DWORD cthrdMost)
{
#ifdef _WIN32
if (!InitOnceExecuteOnce(&init_once_module, init_module, nullptr, nullptr))
return;
if (pSetThreadpoolThreadMaximum)
{
pSetThreadpoolThreadMaximum(ptpp, cthrdMost);
return;
}
#endif
ptpp->Maximum = cthrdMost;
ArrayList_Lock(ptpp->Threads);
if (ArrayList_Count(ptpp->Threads) > ptpp->Maximum)
{
(void)SetEvent(ptpp->TerminateEvent);
ArrayList_Clear(ptpp->Threads);
(void)ResetEvent(ptpp->TerminateEvent);
}
ArrayList_Unlock(ptpp->Threads);
winpr_SetThreadpoolThreadMinimum(ptpp, ptpp->Minimum);
}
#endif /* WINPR_THREAD_POOL defined */

View File

@@ -0,0 +1,122 @@
/**
* WinPR: Windows Portable Runtime
* Thread Pool API (Pool)
*
* Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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.
*/
#ifndef WINPR_POOL_PRIVATE_H
#define WINPR_POOL_PRIVATE_H
#include <winpr/windows.h>
#include <winpr/pool.h>
#include <winpr/synch.h>
#include <winpr/thread.h>
#include <winpr/collections.h>
#if defined(_WIN32)
#if (_WIN32_WINNT < _WIN32_WINNT_WIN6) || defined(__MINGW32__)
struct S_TP_CALLBACK_INSTANCE
{
PTP_WORK Work;
};
struct S_TP_POOL
{
DWORD Minimum;
DWORD Maximum;
wArrayList* Threads;
wQueue* PendingQueue;
HANDLE TerminateEvent;
wCountdownEvent* WorkComplete;
};
struct S_TP_WORK
{
PVOID CallbackParameter;
PTP_WORK_CALLBACK WorkCallback;
PTP_CALLBACK_ENVIRON CallbackEnvironment;
};
struct S_TP_TIMER
{
void* dummy;
};
struct S_TP_WAIT
{
void* dummy;
};
struct S_TP_IO
{
void* dummy;
};
struct S_TP_CLEANUP_GROUP
{
void* dummy;
};
#endif
#else
struct S_TP_CALLBACK_INSTANCE
{
PTP_WORK Work;
};
struct S_TP_POOL
{
DWORD Minimum;
DWORD Maximum;
wArrayList* Threads;
wQueue* PendingQueue;
HANDLE TerminateEvent;
wCountdownEvent* WorkComplete;
};
struct S_TP_WORK
{
PVOID CallbackParameter;
PTP_WORK_CALLBACK WorkCallback;
PTP_CALLBACK_ENVIRON CallbackEnvironment;
};
struct S_TP_TIMER
{
void* dummy;
};
struct S_TP_WAIT
{
void* dummy;
};
struct S_TP_IO
{
void* dummy;
};
struct S_TP_CLEANUP_GROUP
{
wArrayList* groups;
PTP_CALLBACK_ENVIRON env;
};
#endif
PTP_POOL GetDefaultThreadpool(void);
#endif /* WINPR_POOL_PRIVATE_H */

View File

@@ -0,0 +1,53 @@
/**
* WinPR: Windows Portable Runtime
* Thread Pool API (Synch)
*
* Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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/crt.h>
#include <winpr/pool.h>
#include <winpr/wlog.h>
#ifdef WINPR_THREAD_POOL
PTP_WAIT winpr_CreateThreadpoolWait(WINPR_ATTR_UNUSED PTP_WAIT_CALLBACK pfnwa,
WINPR_ATTR_UNUSED PVOID pv,
WINPR_ATTR_UNUSED PTP_CALLBACK_ENVIRON pcbe)
{
WLog_ERR("TODO", "TODO: Implement");
return nullptr;
}
VOID winpr_CloseThreadpoolWait(WINPR_ATTR_UNUSED PTP_WAIT pwa)
{
WLog_ERR("TODO", "TODO: Implement");
}
VOID winpr_SetThreadpoolWait(WINPR_ATTR_UNUSED PTP_WAIT pwa, WINPR_ATTR_UNUSED HANDLE h,
WINPR_ATTR_UNUSED PFILETIME pftTimeout)
{
WLog_ERR("TODO", "TODO: Implement");
}
VOID winpr_WaitForThreadpoolWaitCallbacks(WINPR_ATTR_UNUSED PTP_WAIT pwa,
WINPR_ATTR_UNUSED BOOL fCancelPendingCallbacks)
{
WLog_ERR("TODO", "TODO: Implement");
}
#endif

View File

@@ -0,0 +1,23 @@
set(MODULE_NAME "TestPool")
set(MODULE_PREFIX "TEST_POOL")
disable_warnings_for_directory(${CMAKE_CURRENT_BINARY_DIR})
set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
set(${MODULE_PREFIX}_TESTS TestPoolIO.c TestPoolSynch.c TestPoolThread.c TestPoolTimer.c TestPoolWork.c)
create_test_sourcelist(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_DRIVER} ${${MODULE_PREFIX}_TESTS})
add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
target_link_libraries(${MODULE_NAME} winpr)
set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
foreach(test ${${MODULE_PREFIX}_TESTS})
get_filename_component(TestName ${test} NAME_WE)
add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
endforeach()
set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test")

View File

@@ -0,0 +1,8 @@
#include <winpr/crt.h>
#include <winpr/pool.h>
int TestPoolIO(int argc, char* argv[])
{
return 0;
}

View File

@@ -0,0 +1,8 @@
#include <winpr/crt.h>
#include <winpr/pool.h>
int TestPoolSynch(int argc, char* argv[])
{
return 0;
}

View File

@@ -0,0 +1,41 @@
#include <winpr/crt.h>
#include <winpr/pool.h>
/**
* Improve Scalability With New Thread Pool APIs:
* http://msdn.microsoft.com/en-us/magazine/cc16332.aspx
*
* Developing with Thread Pool Enhancements:
* http://msdn.microsoft.com/en-us/library/cc308561.aspx
*
* Introduction to the Windows Threadpool:
* http://blogs.msdn.com/b/harip/archive/2010/10/11/introduction-to-the-windows-threadpool-part-1.aspx
* http://blogs.msdn.com/b/harip/archive/2010/10/12/introduction-to-the-windows-threadpool-part-2.aspx
*/
int TestPoolThread(int argc, char* argv[])
{
TP_POOL* pool = nullptr;
WINPR_UNUSED(argc);
WINPR_UNUSED(argv);
if (!(pool = CreateThreadpool(nullptr)))
{
printf("CreateThreadpool failed\n");
return -1;
}
if (!SetThreadpoolThreadMinimum(pool, 8)) /* default is 0 */
{
printf("SetThreadpoolThreadMinimum failed\n");
return -1;
}
SetThreadpoolThreadMaximum(pool, 64); /* default is 500 */
CloseThreadpool(pool);
return 0;
}

View File

@@ -0,0 +1,8 @@
#include <winpr/crt.h>
#include <winpr/pool.h>
int TestPoolTimer(int argc, char* argv[])
{
return 0;
}

View File

@@ -0,0 +1,134 @@
#include <winpr/wtypes.h>
#include <winpr/crt.h>
#include <winpr/pool.h>
#include <winpr/interlocked.h>
static LONG count = 0;
static void CALLBACK test_WorkCallback(PTP_CALLBACK_INSTANCE instance, void* context, PTP_WORK work)
{
printf("Hello %s: %03" PRId32 " (thread: 0x%08" PRIX32 ")\n", (char*)context,
InterlockedIncrement(&count), GetCurrentThreadId());
for (int index = 0; index < 100; index++)
{
BYTE a[1024];
BYTE b[1024];
BYTE c[1024] = WINPR_C_ARRAY_INIT;
FillMemory(a, ARRAYSIZE(a), 0xAA);
FillMemory(b, ARRAYSIZE(b), 0xBB);
CopyMemory(c, a, ARRAYSIZE(a));
CopyMemory(c, b, ARRAYSIZE(b));
}
}
static BOOL test1(void)
{
PTP_WORK work = nullptr;
printf("Global Thread Pool\n");
work = CreateThreadpoolWork(test_WorkCallback, "world", nullptr);
if (!work)
{
printf("CreateThreadpoolWork failure\n");
return FALSE;
}
/**
* You can post a work object one or more times (up to MAXULONG) without waiting for prior
* callbacks to complete. The callbacks will execute in parallel. To improve efficiency, the
* thread pool may throttle the threads.
*/
for (int index = 0; index < 10; index++)
SubmitThreadpoolWork(work);
WaitForThreadpoolWorkCallbacks(work, FALSE);
CloseThreadpoolWork(work);
return TRUE;
}
static BOOL test2(void)
{
BOOL rc = FALSE;
PTP_POOL pool = nullptr;
PTP_WORK work = nullptr;
PTP_CLEANUP_GROUP cleanupGroup = nullptr;
TP_CALLBACK_ENVIRON environment;
printf("Private Thread Pool\n");
if (!(pool = CreateThreadpool(nullptr)))
{
printf("CreateThreadpool failure\n");
return FALSE;
}
if (!SetThreadpoolThreadMinimum(pool, 4))
{
printf("SetThreadpoolThreadMinimum failure\n");
goto fail;
}
SetThreadpoolThreadMaximum(pool, 8);
InitializeThreadpoolEnvironment(&environment);
SetThreadpoolCallbackPool(&environment, pool);
cleanupGroup = CreateThreadpoolCleanupGroup();
if (!cleanupGroup)
{
printf("CreateThreadpoolCleanupGroup failure\n");
goto fail;
}
SetThreadpoolCallbackCleanupGroup(&environment, cleanupGroup, nullptr);
work = CreateThreadpoolWork(test_WorkCallback, "world", &environment);
if (!work)
{
printf("CreateThreadpoolWork failure\n");
goto fail;
}
for (int index = 0; index < 10; index++)
SubmitThreadpoolWork(work);
WaitForThreadpoolWorkCallbacks(work, FALSE);
rc = TRUE;
fail:
if (cleanupGroup)
{
CloseThreadpoolCleanupGroupMembers(cleanupGroup, TRUE, nullptr);
CloseThreadpoolCleanupGroup(cleanupGroup);
DestroyThreadpoolEnvironment(&environment);
/**
* See Remarks at
* https://msdn.microsoft.com/en-us/library/windows/desktop/ms682043(v=vs.85).aspx If there
* is a cleanup group associated with the work object, it is not necessary to call
* CloseThreadpoolWork ! calling the CloseThreadpoolCleanupGroupMembers function releases
* the work, wait, and timer objects associated with the cleanup group.
*/
// CloseThreadpoolWork(work); // this would segfault, see comment above. */
}
CloseThreadpool(pool);
return rc;
}
int TestPoolWork(int argc, char* argv[])
{
WINPR_UNUSED(argc);
WINPR_UNUSED(argv);
if (!test1())
return -1;
if (!test2())
return -1;
return 0;
}

View File

@@ -0,0 +1,61 @@
/**
* WinPR: Windows Portable Runtime
* Thread Pool API (Timer)
*
* Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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/crt.h>
#include <winpr/pool.h>
#include <winpr/wlog.h>
#ifdef WINPR_THREAD_POOL
PTP_TIMER winpr_CreateThreadpoolTimer(WINPR_ATTR_UNUSED PTP_TIMER_CALLBACK pfnti,
WINPR_ATTR_UNUSED PVOID pv,
WINPR_ATTR_UNUSED PTP_CALLBACK_ENVIRON pcbe)
{
WLog_ERR("TODO", "TODO: Implement");
return nullptr;
}
VOID winpr_CloseThreadpoolTimer(WINPR_ATTR_UNUSED PTP_TIMER pti)
{
WLog_ERR("TODO", "TODO: Implement");
}
BOOL winpr_IsThreadpoolTimerSet(WINPR_ATTR_UNUSED PTP_TIMER pti)
{
WLog_ERR("TODO", "TODO: Implement");
return FALSE;
}
VOID winpr_SetThreadpoolTimer(WINPR_ATTR_UNUSED PTP_TIMER pti,
WINPR_ATTR_UNUSED PFILETIME pftDueTime,
WINPR_ATTR_UNUSED DWORD msPeriod,
WINPR_ATTR_UNUSED DWORD msWindowLength)
{
WLog_ERR("TODO", "TODO: Implement");
}
VOID winpr_WaitForThreadpoolTimerCallbacks(WINPR_ATTR_UNUSED PTP_TIMER pti,
WINPR_ATTR_UNUSED BOOL fCancelPendingCallbacks)
{
WLog_ERR("TODO", "TODO: Implement");
}
#endif

View File

@@ -0,0 +1,206 @@
/**
* WinPR: Windows Portable Runtime
* Thread Pool API (Work)
*
* Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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/assert.h>
#include <winpr/crt.h>
#include <winpr/pool.h>
#include <winpr/library.h>
#include "pool.h"
#include "../log.h"
#define TAG WINPR_TAG("pool")
#ifdef WINPR_THREAD_POOL
#ifdef _WIN32
static INIT_ONCE init_once_module = INIT_ONCE_STATIC_INIT;
static PTP_WORK(WINAPI* pCreateThreadpoolWork)(PTP_WORK_CALLBACK pfnwk, PVOID pv,
PTP_CALLBACK_ENVIRON pcbe);
static VOID(WINAPI* pCloseThreadpoolWork)(PTP_WORK pwk);
static VOID(WINAPI* pSubmitThreadpoolWork)(PTP_WORK pwk);
static BOOL(WINAPI* pTrySubmitThreadpoolCallback)(PTP_SIMPLE_CALLBACK pfns, PVOID pv,
PTP_CALLBACK_ENVIRON pcbe);
static VOID(WINAPI* pWaitForThreadpoolWorkCallbacks)(PTP_WORK pwk, BOOL fCancelPendingCallbacks);
static BOOL CALLBACK init_module(PINIT_ONCE once, PVOID param, PVOID* context)
{
HMODULE kernel32 = LoadLibraryA("kernel32.dll");
if (kernel32)
{
pCreateThreadpoolWork = GetProcAddressAs(kernel32, "CreateThreadpoolWork", void*);
pCloseThreadpoolWork = GetProcAddressAs(kernel32, "CloseThreadpoolWork", void*);
pSubmitThreadpoolWork = GetProcAddressAs(kernel32, "SubmitThreadpoolWork", void*);
pTrySubmitThreadpoolCallback =
GetProcAddressAs(kernel32, "TrySubmitThreadpoolCallback", void*);
pWaitForThreadpoolWorkCallbacks =
GetProcAddressAs(kernel32, "WaitForThreadpoolWorkCallbacks", void*);
}
return TRUE;
}
#endif
static TP_CALLBACK_ENVIRON DEFAULT_CALLBACK_ENVIRONMENT = {
1, /* Version */
nullptr, /* Pool */
nullptr, /* CleanupGroup */
nullptr, /* CleanupGroupCancelCallback */
nullptr, /* RaceDll */
nullptr, /* FinalizationCallback */
{ 0 } /* Flags */
};
PTP_WORK winpr_CreateThreadpoolWork(PTP_WORK_CALLBACK pfnwk, PVOID pv, PTP_CALLBACK_ENVIRON pcbe)
{
PTP_WORK work = nullptr;
#ifdef _WIN32
if (!InitOnceExecuteOnce(&init_once_module, init_module, nullptr, nullptr))
return nullptr;
if (pCreateThreadpoolWork)
return pCreateThreadpoolWork(pfnwk, pv, pcbe);
#endif
work = (PTP_WORK)calloc(1, sizeof(TP_WORK));
if (work)
{
if (!pcbe)
{
pcbe = &DEFAULT_CALLBACK_ENVIRONMENT;
pcbe->Pool = GetDefaultThreadpool();
}
work->CallbackEnvironment = pcbe;
work->WorkCallback = pfnwk;
work->CallbackParameter = pv;
#ifndef _WIN32
if (pcbe->CleanupGroup)
ArrayList_Append(pcbe->CleanupGroup->groups, work);
#endif
}
return work;
}
VOID winpr_CloseThreadpoolWork(PTP_WORK pwk)
{
#ifdef _WIN32
if (!InitOnceExecuteOnce(&init_once_module, init_module, nullptr, nullptr))
return;
if (pCloseThreadpoolWork)
{
pCloseThreadpoolWork(pwk);
return;
}
#else
WINPR_ASSERT(pwk);
WINPR_ASSERT(pwk->CallbackEnvironment);
if (pwk->CallbackEnvironment->CleanupGroup)
ArrayList_Remove(pwk->CallbackEnvironment->CleanupGroup->groups, pwk);
#endif
free(pwk);
}
VOID winpr_SubmitThreadpoolWork(PTP_WORK pwk)
{
PTP_POOL pool = nullptr;
PTP_CALLBACK_INSTANCE callbackInstance = nullptr;
#ifdef _WIN32
if (!InitOnceExecuteOnce(&init_once_module, init_module, nullptr, nullptr))
return;
if (pSubmitThreadpoolWork)
{
pSubmitThreadpoolWork(pwk);
return;
}
#endif
WINPR_ASSERT(pwk);
WINPR_ASSERT(pwk->CallbackEnvironment);
pool = pwk->CallbackEnvironment->Pool;
callbackInstance = (PTP_CALLBACK_INSTANCE)calloc(1, sizeof(TP_CALLBACK_INSTANCE));
if (callbackInstance)
{
callbackInstance->Work = pwk;
CountdownEvent_AddCount(pool->WorkComplete, 1);
if (!Queue_Enqueue(pool->PendingQueue, callbackInstance))
free(callbackInstance);
}
// NOLINTNEXTLINE(clang-analyzer-unix.Malloc): Queue_Enqueue takes ownership of callbackInstance
}
BOOL winpr_TrySubmitThreadpoolCallback(WINPR_ATTR_UNUSED PTP_SIMPLE_CALLBACK pfns,
WINPR_ATTR_UNUSED PVOID pv,
WINPR_ATTR_UNUSED PTP_CALLBACK_ENVIRON pcbe)
{
#ifdef _WIN32
if (!InitOnceExecuteOnce(&init_once_module, init_module, nullptr, nullptr))
return FALSE;
if (pTrySubmitThreadpoolCallback)
return pTrySubmitThreadpoolCallback(pfns, pv, pcbe);
#endif
WLog_ERR(TAG, "TrySubmitThreadpoolCallback is not implemented");
return FALSE;
}
VOID winpr_WaitForThreadpoolWorkCallbacks(PTP_WORK pwk,
WINPR_ATTR_UNUSED BOOL fCancelPendingCallbacks)
{
HANDLE event = nullptr;
PTP_POOL pool = nullptr;
#ifdef _WIN32
if (!InitOnceExecuteOnce(&init_once_module, init_module, nullptr, nullptr))
return;
if (pWaitForThreadpoolWorkCallbacks)
{
pWaitForThreadpoolWorkCallbacks(pwk, fCancelPendingCallbacks);
return;
}
#endif
WINPR_ASSERT(pwk);
WINPR_ASSERT(pwk->CallbackEnvironment);
pool = pwk->CallbackEnvironment->Pool;
WINPR_ASSERT(pool);
event = CountdownEvent_WaitHandle(pool->WorkComplete);
if (WaitForSingleObject(event, INFINITE) != WAIT_OBJECT_0)
WLog_ERR(TAG, "error waiting on work completion");
}
#endif /* WINPR_THREAD_POOL defined */