23#include <winpr/config.h>
25#include <winpr/winpr.h>
26#include <winpr/assert.h>
28#include <winpr/handle.h>
30#include <winpr/thread.h>
32#if defined(__FreeBSD__)
33#include <pthread_np.h>
34#elif defined(__linux__)
35#include <sys/syscall.h>
39#define MIN(x, y) (((x) < (y)) ? (x) : (y))
43#define MAX(x, y) (((x) > (y)) ? (x) : (y))
89#include <winpr/platform.h>
92#ifdef WINPR_HAVE_UNISTD_H
96#ifdef WINPR_HAVE_SYS_EVENTFD_H
97#include <sys/eventfd.h>
100#include <winpr/debug.h>
105#include <winpr/collections.h>
110#include "../handle/handle.h"
112#define TAG WINPR_TAG("thread")
114static WINPR_THREAD mainThread;
116#if defined(WITH_THREAD_LIST)
117static wListDictionary* thread_list = NULL;
120static BOOL ThreadCloseHandle(HANDLE handle);
121static void cleanup_handle(
void* obj);
123static BOOL ThreadIsHandled(HANDLE handle)
125 return WINPR_HANDLE_IS_HANDLED(handle, HANDLE_TYPE_THREAD, FALSE);
128static int ThreadGetFd(HANDLE handle)
130 WINPR_THREAD* pThread = (WINPR_THREAD*)handle;
132 if (!ThreadIsHandled(handle))
135 return pThread->event.fds[0];
138#define run_mutex_init(fkt, mux, arg) run_mutex_init_(fkt, #fkt, mux, arg)
139static BOOL run_mutex_init_(
int (*fkt)(pthread_mutex_t*,
const pthread_mutexattr_t*),
140 const char* name, pthread_mutex_t* mutex,
141 const pthread_mutexattr_t* mutexattr)
148 rc = fkt(mutex, mutexattr);
151 char ebuffer[256] = { 0 };
152 WLog_WARN(TAG,
"[%s] failed with [%s]", name, winpr_strerror(rc, ebuffer,
sizeof(ebuffer)));
157#define run_mutex_fkt(fkt, mux) run_mutex_fkt_(fkt, #fkt, mux)
158static BOOL run_mutex_fkt_(
int (*fkt)(pthread_mutex_t* mux),
const char* name,
159 pthread_mutex_t* mutex)
169 char ebuffer[256] = { 0 };
170 WLog_WARN(TAG,
"[%s] failed with [%s]", name, winpr_strerror(rc, ebuffer,
sizeof(ebuffer)));
175#define run_cond_init(fkt, cond, arg) run_cond_init_(fkt, #fkt, cond, arg)
176static BOOL run_cond_init_(
int (*fkt)(pthread_cond_t*,
const pthread_condattr_t*),
const char* name,
177 pthread_cond_t* condition,
const pthread_condattr_t* conditionattr)
182 WINPR_ASSERT(condition);
184 rc = fkt(condition, conditionattr);
187 char ebuffer[256] = { 0 };
188 WLog_WARN(TAG,
"[%s] failed with [%s]", name, winpr_strerror(rc, ebuffer,
sizeof(ebuffer)));
193#define run_cond_fkt(fkt, cond) run_cond_fkt_(fkt, #fkt, cond)
194static BOOL run_cond_fkt_(
int (*fkt)(pthread_cond_t* mux),
const char* name,
195 pthread_cond_t* condition)
200 WINPR_ASSERT(condition);
205 char ebuffer[256] = { 0 };
206 WLog_WARN(TAG,
"[%s] failed with [%s]", name, winpr_strerror(rc, ebuffer,
sizeof(ebuffer)));
211static int pthread_mutex_checked_unlock(pthread_mutex_t* mutex)
214 WINPR_ASSERT(pthread_mutex_trylock(mutex) == EBUSY);
215 return pthread_mutex_unlock(mutex);
220 WINPR_ASSERT(bundle);
223 if (!run_mutex_init(pthread_mutex_init, &bundle->mux, NULL))
226 if (!run_cond_init(pthread_cond_init, &bundle->cond, NULL))
235 WINPR_ASSERT(bundle);
237 run_cond_fkt(pthread_cond_destroy, &bundle->cond);
238 run_mutex_fkt(pthread_mutex_destroy, &bundle->mux);
245 WINPR_ASSERT(bundle);
247 if (!run_mutex_fkt(pthread_mutex_lock, &bundle->mux))
250 if (!run_cond_fkt(pthread_cond_signal, &bundle->cond))
252 if (!run_mutex_fkt(pthread_mutex_checked_unlock, &bundle->mux))
259 WINPR_ASSERT(bundle);
260 return run_mutex_fkt(pthread_mutex_lock, &bundle->mux);
265 WINPR_ASSERT(bundle);
266 return run_mutex_fkt(pthread_mutex_checked_unlock, &bundle->mux);
273 WINPR_ASSERT(bundle);
275 WINPR_ASSERT(pthread_mutex_trylock(&bundle->mux) == EBUSY);
279 int r = pthread_cond_wait(&bundle->cond, &bundle->mux);
282 char ebuffer[256] = { 0 };
283 WLog_ERR(TAG,
"failed to wait for %s [%s]", name,
284 winpr_strerror(r, ebuffer,
sizeof(ebuffer)));
287 case ENOTRECOVERABLE:
305static BOOL signal_thread_ready(WINPR_THREAD* thread)
307 WINPR_ASSERT(thread);
309 return mux_condition_bundle_signal(&thread->isCreated);
312static BOOL signal_thread_is_running(WINPR_THREAD* thread)
314 WINPR_ASSERT(thread);
316 return mux_condition_bundle_signal(&thread->isRunning);
319static DWORD ThreadCleanupHandle(HANDLE handle)
321 DWORD status = WAIT_FAILED;
322 WINPR_THREAD* thread = (WINPR_THREAD*)handle;
324 if (!ThreadIsHandled(handle))
327 if (!run_mutex_fkt(pthread_mutex_lock, &thread->mutex))
332 int rc = pthread_join(thread->thread, NULL);
336 char ebuffer[256] = { 0 };
337 WLog_ERR(TAG,
"pthread_join failure: [%d] %s", rc,
338 winpr_strerror(rc, ebuffer,
sizeof(ebuffer)));
342 thread->joined = TRUE;
345 status = WAIT_OBJECT_0;
348 if (!run_mutex_fkt(pthread_mutex_checked_unlock, &thread->mutex))
376static void dump_thread(WINPR_THREAD* thread)
378#if defined(WITH_DEBUG_THREADS)
379 void* stack = winpr_backtrace(20);
382 WLog_DBG(TAG,
"Called from:");
383 msg = winpr_backtrace_symbols(stack, &used);
385 for (
size_t i = 0; i < used; i++)
386 WLog_DBG(TAG,
"[%" PRIdz
"]: %s", i, msg[i]);
389 winpr_backtrace_free(stack);
390 WLog_DBG(TAG,
"Thread handle created still not closed!");
391 msg = winpr_backtrace_symbols(thread->create_stack, &used);
393 for (
size_t i = 0; i < used; i++)
394 WLog_DBG(TAG,
"[%" PRIdz
"]: %s", i, msg[i]);
400 WLog_DBG(TAG,
"Thread still running!");
402 else if (!thread->exit_stack)
404 WLog_DBG(TAG,
"Thread suspended.");
408 WLog_DBG(TAG,
"Thread exited at:");
409 msg = winpr_backtrace_symbols(thread->exit_stack, &used);
411 for (
size_t i = 0; i < used; i++)
412 WLog_DBG(TAG,
"[%" PRIdz
"]: %s", i, msg[i]);
417 WINPR_UNUSED(thread);
425static BOOL set_event(WINPR_THREAD* thread)
427 return winpr_event_set(&thread->event);
430static BOOL reset_event(WINPR_THREAD* thread)
432 return winpr_event_reset(&thread->event);
435#if defined(WITH_THREAD_LIST)
436static BOOL thread_compare(
const void* a,
const void* b)
438 const pthread_t* p1 = a;
439 const pthread_t* p2 = b;
440 BOOL rc = pthread_equal(*p1, *p2);
445static INIT_ONCE threads_InitOnce = INIT_ONCE_STATIC_INIT;
446static pthread_t mainThreadId;
447static DWORD currentThreadTlsIndex = TLS_OUT_OF_INDEXES;
449static BOOL initializeThreads(WINPR_ATTR_UNUSED
PINIT_ONCE InitOnce,
450 WINPR_ATTR_UNUSED PVOID Parameter, WINPR_ATTR_UNUSED PVOID* Context)
452 if (!apc_init(&mainThread.apc))
454 WLog_ERR(TAG,
"failed to initialize APC");
458 mainThread.common.Type = HANDLE_TYPE_THREAD;
459 mainThreadId = pthread_self();
461 currentThreadTlsIndex = TlsAlloc();
462 if (currentThreadTlsIndex == TLS_OUT_OF_INDEXES)
464 WLog_ERR(TAG,
"Major bug, unable to allocate a TLS value for currentThread");
467#if defined(WITH_THREAD_LIST)
468 thread_list = ListDictionary_New(TRUE);
472 WLog_ERR(TAG,
"Couldn't create global thread list");
473 goto error_thread_list;
476 thread_list->objectKey.fnObjectEquals = thread_compare;
483static BOOL signal_and_wait_for_ready(WINPR_THREAD* thread)
487 WINPR_ASSERT(thread);
489 if (!mux_condition_bundle_lock(&thread->isRunning))
492 if (!signal_thread_ready(thread))
495 if (!mux_condition_bundle_wait(&thread->isRunning,
"threadIsRunning"))
498#if defined(WITH_THREAD_LIST)
499 if (!ListDictionary_Contains(thread_list, &thread->thread))
501 WLog_ERR(TAG,
"Thread not in thread_list, startup failed!");
509 if (!mux_condition_bundle_unlock(&thread->isRunning))
518static void* thread_launcher(
void* arg)
521 WINPR_THREAD* thread = (WINPR_THREAD*)arg;
522 LPTHREAD_START_ROUTINE fkt = NULL;
526 WLog_ERR(TAG,
"Called with invalid argument %p", arg);
530 if (!TlsSetValue(currentThreadTlsIndex, thread))
532 WLog_ERR(TAG,
"thread %d, unable to set current thread value", pthread_self());
536 if (!(fkt = thread->lpStartAddress))
540 LPTHREAD_START_ROUTINE fkt;
544 WLog_ERR(TAG,
"Thread function argument is %p", cnv.pv);
548 if (!signal_and_wait_for_ready(thread))
551 rc = fkt(thread->lpParameter);
556 apc_cleanupThread(thread);
559 thread->dwExitCode = rc;
563 (void)signal_thread_ready(thread);
565 if (thread->detached || !thread->started)
566 cleanup_handle(thread);
572static BOOL winpr_StartThread(WINPR_THREAD* thread)
576 pthread_attr_t attr = { 0 };
578 if (!mux_condition_bundle_lock(&thread->isCreated))
582 pthread_attr_init(&attr);
583 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
585 if (thread->dwStackSize > 0)
586 pthread_attr_setstacksize(&attr, thread->dwStackSize);
588 thread->started = TRUE;
591#if defined(WITH_THREAD_LIST)
592 if (!ListDictionary_Add(thread_list, &thread->thread, thread))
594 WLog_ERR(TAG,
"failed to add the thread to the thread list");
599 if (pthread_create(&thread->thread, &attr, thread_launcher, thread))
602 if (!mux_condition_bundle_wait(&thread->isCreated,
"threadIsCreated"))
606 if (!mux_condition_bundle_unlock(&thread->isCreated))
609 if (!signal_thread_is_running(thread))
611 WLog_ERR(TAG,
"failed to signal the thread was ready");
619 if (!mux_condition_bundle_unlock(&thread->isCreated))
623 pthread_attr_destroy(&attr);
631BOOL SetThreadPriority(HANDLE hThread,
int nPriority)
636 if (!winpr_Handle_GetInfo(hThread, &Type, &Object) || Object->Type != HANDLE_TYPE_THREAD)
641 const int diff = (max - min);
642 const int normal = min + diff / 2;
643 const int off = MIN(1, diff / 4);
644 int sched_priority = -1;
646 switch (nPriority & ~(THREAD_MODE_BACKGROUND_BEGIN | THREAD_MODE_BACKGROUND_END))
648 case THREAD_PRIORITY_ABOVE_NORMAL:
649 sched_priority = MIN(normal + off, max);
651 case THREAD_PRIORITY_BELOW_NORMAL:
652 sched_priority = MAX(normal - off, min);
654 case THREAD_PRIORITY_HIGHEST:
655 sched_priority = max;
657 case THREAD_PRIORITY_IDLE:
658 sched_priority = min;
660 case THREAD_PRIORITY_LOWEST:
661 sched_priority = min;
663 case THREAD_PRIORITY_TIME_CRITICAL:
664 sched_priority = max;
667 case THREAD_PRIORITY_NORMAL:
668 sched_priority = normal;
671#if defined(_POSIX_C_SOURCE) && (_POSIX_C_SOURCE >= 200809L) && defined(PTHREAD_SETSCHEDPRIO)
672 WINPR_THREAD* thread = (WINPR_THREAD*)Object;
673 const int rc = pthread_setschedprio(thread->thread, sched_priority);
676 char buffer[256] = { 0 };
677 WLog_ERR(TAG,
"pthread_setschedprio(%d) %s [%d]", sched_priority,
678 winpr_strerror(rc, buffer,
sizeof(buffer)), rc);
682 WLog_WARN(TAG,
"pthread_setschedprio(%d) not implemented, requires POSIX 2008 or later",
688HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,
size_t dwStackSize,
689 LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter,
690 DWORD dwCreationFlags, WINPR_ATTR_UNUSED LPDWORD lpThreadId)
692 HANDLE handle = NULL;
693 WINPR_THREAD* thread = (WINPR_THREAD*)calloc(1,
sizeof(WINPR_THREAD));
698 thread->dwStackSize = dwStackSize;
699 thread->lpParameter = lpParameter;
700 thread->lpStartAddress = lpStartAddress;
701 thread->lpThreadAttributes = lpThreadAttributes;
702 thread->common.ops = &ops;
703#if defined(WITH_DEBUG_THREADS)
704 thread->create_stack = winpr_backtrace(20);
708 if (!winpr_event_init(&thread->event))
710 WLog_ERR(TAG,
"failed to create event");
714 if (!run_mutex_init(pthread_mutex_init, &thread->mutex, NULL))
716 WLog_ERR(TAG,
"failed to initialize thread mutex");
720 if (!apc_init(&thread->apc))
722 WLog_ERR(TAG,
"failed to initialize APC");
726 if (!mux_condition_bundle_init(&thread->isCreated))
728 if (!mux_condition_bundle_init(&thread->isRunning))
731 WINPR_HANDLE_SET_TYPE_AND_MODE(thread, HANDLE_TYPE_THREAD, WINPR_FD_READ);
732 handle = (HANDLE)thread;
734 InitOnceExecuteOnce(&threads_InitOnce, initializeThreads, NULL, NULL);
736 if (!(dwCreationFlags & CREATE_SUSPENDED))
738 if (!winpr_StartThread(thread))
743 if (!set_event(thread))
749 cleanup_handle(thread);
753void cleanup_handle(
void* obj)
755 WINPR_THREAD* thread = (WINPR_THREAD*)obj;
759 if (!apc_uninit(&thread->apc))
760 WLog_ERR(TAG,
"failed to destroy APC");
762 mux_condition_bundle_uninit(&thread->isCreated);
763 mux_condition_bundle_uninit(&thread->isRunning);
764 run_mutex_fkt(pthread_mutex_destroy, &thread->mutex);
766 winpr_event_uninit(&thread->event);
768#if defined(WITH_THREAD_LIST)
769 ListDictionary_Remove(thread_list, &thread->thread);
771#if defined(WITH_DEBUG_THREADS)
773 if (thread->create_stack)
774 winpr_backtrace_free(thread->create_stack);
776 if (thread->exit_stack)
777 winpr_backtrace_free(thread->exit_stack);
783BOOL ThreadCloseHandle(HANDLE handle)
785 WINPR_THREAD* thread = (WINPR_THREAD*)handle;
787#if defined(WITH_THREAD_LIST)
790 WLog_ERR(TAG,
"Thread list does not exist, check call!");
793 else if (!ListDictionary_Contains(thread_list, &thread->thread))
795 WLog_ERR(TAG,
"Thread list does not contain this thread! check call!");
800 ListDictionary_Lock(thread_list);
804 if ((thread->started) && (WaitForSingleObject(thread, 0) != WAIT_OBJECT_0))
806 WLog_DBG(TAG,
"Thread running, setting to detached state!");
807 thread->detached = TRUE;
808 pthread_detach(thread->thread);
812 cleanup_handle(thread);
815#if defined(WITH_THREAD_LIST)
816 ListDictionary_Unlock(thread_list);
823HANDLE CreateRemoteThread(WINPR_ATTR_UNUSED HANDLE hProcess,
824 WINPR_ATTR_UNUSED LPSECURITY_ATTRIBUTES lpThreadAttributes,
825 WINPR_ATTR_UNUSED
size_t dwStackSize,
826 WINPR_ATTR_UNUSED LPTHREAD_START_ROUTINE lpStartAddress,
827 WINPR_ATTR_UNUSED LPVOID lpParameter,
828 WINPR_ATTR_UNUSED DWORD dwCreationFlags,
829 WINPR_ATTR_UNUSED LPDWORD lpThreadId)
831 WLog_ERR(TAG,
"not implemented");
832 SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
836VOID ExitThread(DWORD dwExitCode)
838#if defined(WITH_THREAD_LIST)
840 pthread_t tid = pthread_self();
844 WLog_ERR(TAG,
"function called without existing thread list!");
845#if defined(WITH_DEBUG_THREADS)
850 else if (!ListDictionary_Contains(thread_list, &tid))
852 WLog_ERR(TAG,
"function called, but no matching entry in thread list!");
853#if defined(WITH_DEBUG_THREADS)
860 WINPR_THREAD* thread;
861 ListDictionary_Lock(thread_list);
862 thread = ListDictionary_GetItemValue(thread_list, &tid);
863 WINPR_ASSERT(thread);
864 thread->exited = TRUE;
865 thread->dwExitCode = dwExitCode;
866#if defined(WITH_DEBUG_THREADS)
867 thread->exit_stack = winpr_backtrace(20);
869 ListDictionary_Unlock(thread_list);
871 rc = thread->dwExitCode;
873 if (thread->detached || !thread->started)
874 cleanup_handle(thread);
876 pthread_exit((
void*)(
size_t)rc);
879 WINPR_UNUSED(dwExitCode);
883BOOL GetExitCodeThread(HANDLE hThread, LPDWORD lpExitCode)
887 WINPR_THREAD* thread = NULL;
889 if (!winpr_Handle_GetInfo(hThread, &Type, &Object) || Object->Type != HANDLE_TYPE_THREAD)
891 WLog_ERR(TAG,
"hThread is not a thread");
892 SetLastError(ERROR_INVALID_PARAMETER);
896 thread = (WINPR_THREAD*)Object;
897 *lpExitCode = thread->dwExitCode;
901WINPR_THREAD* winpr_GetCurrentThread(VOID)
903 WINPR_THREAD* ret = NULL;
905 InitOnceExecuteOnce(&threads_InitOnce, initializeThreads, NULL, NULL);
906 if (mainThreadId == pthread_self())
907 return (HANDLE)&mainThread;
909 ret = TlsGetValue(currentThreadTlsIndex);
913HANDLE _GetCurrentThread(VOID)
915 return (HANDLE)winpr_GetCurrentThread();
918DWORD GetCurrentThreadId(VOID)
920#if defined(__FreeBSD__)
921 return WINPR_CXX_COMPAT_CAST(DWORD, pthread_getthreadid_np());
922#elif defined(__linux__)
923 return WINPR_CXX_COMPAT_CAST(DWORD, syscall(SYS_gettid));
925 pthread_t tid = pthread_self();
928 uintptr_t ptid = WINPR_REINTERPRET_CAST(tid, pthread_t, uintptr_t);
929 return (ptid & UINT32_MAX) ^ (ptid >> 32);
937 ULONG_PTR completionArg;
940static void userAPC(LPVOID arg)
942 UserApcItem* userApc = (UserApcItem*)arg;
944 userApc->completion(userApc->completionArg);
946 userApc->apc.markedForRemove = TRUE;
949DWORD QueueUserAPC(PAPCFUNC pfnAPC, HANDLE hThread, ULONG_PTR dwData)
953 WINPR_APC_ITEM* apc = NULL;
954 UserApcItem* apcItem = NULL;
959 if (!winpr_Handle_GetInfo(hThread, &Type, &Object) || Object->Type != HANDLE_TYPE_THREAD)
961 WLog_ERR(TAG,
"hThread is not a thread");
962 SetLastError(ERROR_INVALID_PARAMETER);
966 apcItem = calloc(1,
sizeof(*apcItem));
969 SetLastError(ERROR_INVALID_PARAMETER);
974 apc->type = APC_TYPE_USER;
975 apc->markedForFree = TRUE;
976 apc->alwaysSignaled = TRUE;
977 apc->completion = userAPC;
978 apc->completionArgs = apc;
979 apcItem->completion = pfnAPC;
980 apcItem->completionArg = dwData;
981 apc_register(hThread, apc);
985DWORD ResumeThread(HANDLE hThread)
989 WINPR_THREAD* thread = NULL;
991 if (!winpr_Handle_GetInfo(hThread, &Type, &Object) || Object->Type != HANDLE_TYPE_THREAD)
993 WLog_ERR(TAG,
"hThread is not a thread");
994 SetLastError(ERROR_INVALID_PARAMETER);
998 thread = (WINPR_THREAD*)Object;
1000 if (!run_mutex_fkt(pthread_mutex_lock, &thread->mutex))
1003 if (!thread->started)
1005 if (!winpr_StartThread(thread))
1007 run_mutex_fkt(pthread_mutex_checked_unlock, &thread->mutex);
1012 WLog_WARN(TAG,
"Thread already started!");
1014 if (!run_mutex_fkt(pthread_mutex_checked_unlock, &thread->mutex))
1020DWORD SuspendThread(WINPR_ATTR_UNUSED HANDLE hThread)
1022 WLog_ERR(TAG,
"not implemented");
1023 SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
1027BOOL SwitchToThread(VOID)
1033 if (sched_yield() != 0)
1039BOOL TerminateThread(HANDLE hThread, DWORD dwExitCode)
1043 WINPR_THREAD* thread = NULL;
1045 if (!winpr_Handle_GetInfo(hThread, &Type, &Object) || Object->Type != HANDLE_TYPE_THREAD)
1048 thread = (WINPR_THREAD*)Object;
1049 thread->exited = TRUE;
1050 thread->dwExitCode = dwExitCode;
1052 if (!run_mutex_fkt(pthread_mutex_lock, &thread->mutex))
1056 pthread_cancel(thread->thread);
1058 WLog_ERR(TAG,
"Function not supported on this platform!");
1061 if (!run_mutex_fkt(pthread_mutex_checked_unlock, &thread->mutex))
1068VOID DumpThreadHandles(
void)
1070#if defined(WITH_DEBUG_THREADS)
1073 void* stack = winpr_backtrace(20);
1074 WLog_DBG(TAG,
"---------------- Called from ----------------------------");
1075 msg = winpr_backtrace_symbols(stack, &used);
1077 for (
size_t i = 0; i < used; i++)
1079 WLog_DBG(TAG,
"[%" PRIdz
"]: %s", i, msg[i]);
1083 winpr_backtrace_free(stack);
1084 WLog_DBG(TAG,
"---------------- Start Dumping thread handles -----------");
1086#if defined(WITH_THREAD_LIST)
1089 WLog_DBG(TAG,
"All threads properly shut down and disposed of.");
1093 ULONG_PTR* keys = NULL;
1094 ListDictionary_Lock(thread_list);
1095 int x, count = ListDictionary_GetKeys(thread_list, &keys);
1096 WLog_DBG(TAG,
"Dumping %d elements", count);
1098 for (
size_t x = 0; x < count; x++)
1100 WINPR_THREAD* thread = ListDictionary_GetItemValue(thread_list, (
void*)keys[x]);
1101 WLog_DBG(TAG,
"Thread [%d] handle created still not closed!", x);
1102 msg = winpr_backtrace_symbols(thread->create_stack, &used);
1104 for (
size_t i = 0; i < used; i++)
1106 WLog_DBG(TAG,
"[%" PRIdz
"]: %s", i, msg[i]);
1111 if (thread->started)
1113 WLog_DBG(TAG,
"Thread [%d] still running!", x);
1117 WLog_DBG(TAG,
"Thread [%d] exited at:", x);
1118 msg = winpr_backtrace_symbols(thread->exit_stack, &used);
1120 for (
size_t i = 0; i < used; i++)
1121 WLog_DBG(TAG,
"[%" PRIdz
"]: %s", i, msg[i]);
1128 ListDictionary_Unlock(thread_list);
1132 WLog_DBG(TAG,
"---------------- End Dumping thread handles -------------");