Merge branch 'fixes' into main/rendor-staging
[ryzomcore.git] / nel / src / misc / win_thread.cpp
blob6f45594ca8b56a0f842f2db0294624e1f7a1eb13
1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2012-2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
6 //
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
20 #include "stdmisc.h"
21 #include "nel/misc/win_thread.h"
23 #ifdef NL_OS_WINDOWS
25 #include "nel/misc/path.h"
26 #ifndef NL_COMP_MINGW
27 #define NOMINMAX
28 #endif
29 #include <windows.h>
31 #include <typeinfo>
33 #ifdef DEBUG_NEW
34 #define new DEBUG_NEW
35 #endif
37 namespace NLMISC {
39 CWinThread MainThread ((void*)GetCurrentThread (), GetCurrentThreadId());
40 DWORD TLSThreadPointer = 0xFFFFFFFF;
42 // the IThread static creator
43 IThread *IThread::create (IRunnable *runnable, uint32 stackSize)
45 return new CWinThread (runnable, stackSize);
48 IThread *IThread::getCurrentThread ()
50 // TLS alloc must have been done
51 nlassert (TLSThreadPointer != 0xffffffff);
53 // Get the thread pointer
54 IThread *thread = (IThread*)TlsGetValue (TLSThreadPointer);
56 // Return current thread
57 return thread;
60 static unsigned long __stdcall ProxyFunc (void *arg)
62 CWinThread *parent = (CWinThread *) arg;
64 // TLS alloc must have been done
65 nlassert (TLSThreadPointer != 0xffffffff);
67 // Set the thread pointer in TLS memory
68 nlverify (TlsSetValue (TLSThreadPointer, (void*)parent) != 0);
70 // Attach exception handler
71 attachExceptionHandler();
73 // Run the thread
74 parent->Runnable->run();
76 return 0;
79 CWinThread::CWinThread (IRunnable *runnable, uint32 stackSize)
81 _StackSize = stackSize;
82 this->Runnable = runnable;
83 ThreadHandle = NULL;
84 _SuspendCount = -1;
85 _MainThread = false;
88 namespace {
89 class CWinCriticalSection
91 private:
92 CRITICAL_SECTION cs;
93 public:
94 CWinCriticalSection() { InitializeCriticalSection(&cs); }
95 ~CWinCriticalSection() { DeleteCriticalSection(&cs); }
96 inline void enter() { EnterCriticalSection(&cs); }
97 inline void leave() { LeaveCriticalSection(&cs); }
99 CWinCriticalSection s_CS;
100 }/* anonymous namespace */
102 CWinThread::CWinThread (void* threadHandle, uint32 threadId)
104 // Main thread
105 _MainThread = true;
106 this->Runnable = NULL;
107 ThreadHandle = threadHandle;
108 ThreadId = threadId;
110 // TLS alloc must have been done
111 TLSThreadPointer = TlsAlloc ();
112 nlassert (TLSThreadPointer!=0xffffffff);
114 // Set the thread pointer in TLS memory
115 nlverify (TlsSetValue (TLSThreadPointer, (void*)this) != 0);
117 if (GetCurrentThreadId() == threadId)
119 _SuspendCount = 0; // is calling thread call this itself, well, if we reach this place
120 // there are chances that it is not suspended ...
122 else
124 // initialized from another thread (very unlikely ...)
125 nlassert(0); // WARNING: following code has not tested! don't know if it work fo real ...
126 // This is just a suggestion of a possible solution, should this situation one day occur ...
127 // Ensure that this thread don't get deleted, or we could suspend the main thread
128 s_CS.enter();
129 // the 2 following statement must be executed atomicaly among the threads of the current process !
130 SuspendThread(threadHandle);
131 _SuspendCount = ResumeThread(threadHandle);
132 s_CS.leave();
137 void CWinThread::incSuspendCount()
139 nlassert(ThreadHandle); // start was not called !!
140 int newSuspendCount = ::SuspendThread(ThreadHandle) + 1;
141 nlassert(newSuspendCount != 0xffffffff); // more infos with 'GetLastError'
142 nlassert(newSuspendCount == _SuspendCount + 1); // is this assert fire , then 'SuspendThread' or 'ResumeThread'
143 // have been called outside of this object interface! (on this thread handle ...)
144 _SuspendCount = newSuspendCount;
147 void CWinThread::decSuspendCount()
149 nlassert(ThreadHandle); // 'start' was not called !!
150 nlassert(_SuspendCount > 0);
151 int newSuspendCount = ::ResumeThread(ThreadHandle) - 1;
152 nlassert(newSuspendCount != 0xffffffff); // more infos with 'GetLastError'
153 nlassert(newSuspendCount == _SuspendCount - 1); // is this assert fire , then 'SuspendThread' or 'ResumeThread'
154 // have been called outside of this object interface! (on this thread handle ...)
155 _SuspendCount = newSuspendCount;
158 void CWinThread::suspend()
160 if (getSuspendCount() == 0)
162 incSuspendCount();
166 void CWinThread::resume()
168 while (getSuspendCount() != 0)
170 decSuspendCount();
174 void CWinThread::setPriority(TThreadPriority priority)
176 nlassert(ThreadHandle); // 'start' was not called !!
177 BOOL result = SetThreadPriority(ThreadHandle, (int)priority);
178 nlassert(result);
181 void CWinThread::enablePriorityBoost(bool enabled)
183 nlassert(ThreadHandle); // 'start' was not called !!
184 SetThreadPriorityBoost(ThreadHandle, enabled ? TRUE : FALSE);
188 CWinThread::~CWinThread ()
190 // If not the main thread
191 if (_MainThread)
193 // Free TLS memory
194 nlassert (TLSThreadPointer!=0xffffffff);
195 TlsFree (TLSThreadPointer);
197 else
199 if (ThreadHandle != NULL) terminate();
203 void CWinThread::start ()
205 if (isRunning())
206 throw EThread("Starting a thread that is already started, existing thread will continue running, this should not happen");
208 // ThreadHandle = (void *) ::CreateThread (NULL, _StackSize, ProxyFunc, this, 0, (DWORD *)&ThreadId);
209 ThreadHandle = (void *) ::CreateThread (NULL, 0, ProxyFunc, this, 0, (DWORD *)&ThreadId);
210 // nldebug("NLMISC: thread %x started for runnable '%x'", typeid( Runnable ).name());
211 // OutputDebugString(toString(NL_LOC_MSG " NLMISC: thread %x started for runnable '%s'\n", ThreadId, typeid( *Runnable ).name()).c_str());
212 if (ThreadHandle == NULL)
214 throw EThread ( "Cannot create new thread" );
216 SetThreadPriorityBoost (ThreadHandle, TRUE); // FALSE == Enable Priority Boost
218 _SuspendCount = 0;
221 bool CWinThread::isRunning()
223 if (ThreadHandle == NULL)
224 return false;
226 DWORD exitCode;
227 if (!GetExitCodeThread(ThreadHandle, &exitCode))
228 return false;
230 return exitCode == STILL_ACTIVE;
234 void CWinThread::terminate ()
236 TerminateThread((HANDLE)ThreadHandle, 0);
237 CloseHandle((HANDLE)ThreadHandle);
238 ThreadHandle = NULL;
239 _SuspendCount = -1;
242 void CWinThread::wait ()
244 if (ThreadHandle == NULL) return;
246 WaitForSingleObject(ThreadHandle, INFINITE);
247 CloseHandle(ThreadHandle);
248 ThreadHandle = NULL;
249 _SuspendCount = -1;
252 bool CWinThread::setCPUMask(uint64 cpuMask)
254 // Thread must exist
255 if (ThreadHandle == NULL)
256 return false;
258 // Ask the system for number of processor available for this process
259 return SetThreadAffinityMask ((HANDLE)ThreadHandle, (DWORD_PTR)cpuMask) != 0;
262 uint64 CWinThread::getCPUMask()
264 // Thread must exist
265 if (ThreadHandle == NULL)
266 return 1;
268 // Get the current process mask
269 uint64 mask=IProcess::getCurrentProcess ()->getCPUMask ();
271 // Get thread affinity mask
272 DWORD_PTR old = SetThreadAffinityMask ((HANDLE)ThreadHandle, (DWORD_PTR)mask);
273 nlassert (old != 0);
274 if (old == 0)
275 return 1;
277 // Reset it
278 SetThreadAffinityMask ((HANDLE)ThreadHandle, old);
280 // Return the mask
281 return (uint64)old;
284 std::string CWinThread::getUserName()
286 wchar_t userName[512];
287 DWORD size = 512;
288 GetUserNameW (userName, &size);
289 return wideToUtf8(userName);
292 // **** Process
294 // The current process
295 CWinProcess CurrentProcess ((void*)GetCurrentProcess());
297 // Get the current process
298 IProcess *IProcess::getCurrentProcess ()
300 return &CurrentProcess;
303 CWinProcess::CWinProcess (void *handle)
305 // Get the current process handle
306 _ProcessHandle = handle;
309 uint64 CWinProcess::getCPUMask()
311 // Ask the system for number of processor available for this process
312 DWORD_PTR processAffinityMask;
313 DWORD_PTR systemAffinityMask;
314 if (GetProcessAffinityMask((HANDLE)_ProcessHandle, &processAffinityMask, &systemAffinityMask))
316 // Return the CPU mask
317 return (uint64)processAffinityMask;
319 else
320 return 1;
323 bool CWinProcess::setCPUMask(uint64 mask)
325 // Ask the system for number of processor available for this process
326 DWORD_PTR processAffinityMask= (DWORD_PTR)mask;
327 return SetProcessAffinityMask((HANDLE)_ProcessHandle, processAffinityMask)!=0;
330 // ****************************************************************************************************************
332 * Simple wrapper around the PSAPI library
333 * \author Nicolas Vizerie
334 * \author GameForge
335 * \date 2007
338 class CPSAPILib
340 public:
341 typedef BOOL (WINAPI *EnumProcessesFunPtr)(DWORD *lpidProcess, DWORD cb, DWORD *cbNeeded);
342 typedef DWORD (WINAPI *GetModuleFileNameExWFunPtr)(HANDLE hProcess, HMODULE hModule, LPWSTR lpFilename, DWORD nSize);
343 typedef BOOL (WINAPI *EnumProcessModulesFunPtr)(HANDLE hProcess, HMODULE *lphModule, DWORD cb, LPDWORD lpcbNeeded);
344 EnumProcessesFunPtr EnumProcesses;
345 GetModuleFileNameExWFunPtr GetModuleFileNameExW;
346 EnumProcessModulesFunPtr EnumProcessModules;
347 public:
348 CPSAPILib();
349 ~CPSAPILib();
350 bool init();
351 private:
352 HINSTANCE _PSAPILibHandle;
353 bool _LoadFailed;
356 // ****************************************************************************************************************
357 CPSAPILib::CPSAPILib()
359 _LoadFailed = false;
360 _PSAPILibHandle = NULL;
361 EnumProcesses = NULL;
362 GetModuleFileNameExW = NULL;
363 EnumProcessModules = NULL;
366 // ****************************************************************************************************************
367 CPSAPILib::~CPSAPILib()
369 if (_PSAPILibHandle)
371 FreeLibrary(_PSAPILibHandle);
375 // ****************************************************************************************************************
376 bool CPSAPILib::init()
379 if (_LoadFailed) return false;
380 if (!_PSAPILibHandle)
382 _PSAPILibHandle = LoadLibraryA("psapi.dll");
383 if (!_PSAPILibHandle)
385 nlwarning("couldn't load psapi.dll, possibly not supported by os");
386 _LoadFailed = true;
387 return false;
389 EnumProcesses = (EnumProcessesFunPtr) GetProcAddress(_PSAPILibHandle, "EnumProcesses");
390 GetModuleFileNameExW = (GetModuleFileNameExWFunPtr) GetProcAddress(_PSAPILibHandle, "GetModuleFileNameExW");
391 EnumProcessModules = (EnumProcessModulesFunPtr) GetProcAddress(_PSAPILibHandle, "EnumProcessModules");
392 if (!EnumProcesses ||
393 !GetModuleFileNameExW ||
394 !EnumProcessModules
397 nlwarning("Failed to import functions from psapi.dll!");
398 _LoadFailed = true;
399 return false;
402 return true;
406 static CPSAPILib PSAPILib;
410 // ****************************************************************************************************************
411 bool CWinProcess::enumProcessesId(std::vector<uint32> &processesId)
413 if (!PSAPILib.init()) return false;
414 // list of processes
415 std::vector<uint32> prcIds(16);
416 for (;;)
418 DWORD cbNeeded;
419 if (!PSAPILib.EnumProcesses((DWORD *) &prcIds[0], (DWORD)(prcIds.size() * sizeof(DWORD)), &cbNeeded))
421 nlwarning("Processes enumeration failed!");
422 return false;
424 if (cbNeeded < prcIds.size() * sizeof(DWORD))
426 prcIds.resize(cbNeeded / sizeof(DWORD));
427 break;
429 // make some more room
430 prcIds.resize(prcIds.size() * 2);
432 processesId.swap(prcIds);
433 return true;
436 // ****************************************************************************************************************
437 bool CWinProcess::enumProcessModules(uint32 processId, std::vector<std::string> &moduleNames)
439 if (!PSAPILib.init()) return false;
440 HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION|PROCESS_VM_READ, FALSE, (DWORD) processId);
441 if (!hProcess) return false;
442 // list of modules
443 std::vector<HMODULE> prcModules(2);
444 for (;;)
446 DWORD cbNeeded;
447 if (!PSAPILib.EnumProcessModules(hProcess, (HMODULE *) &prcModules[0], (DWORD)(prcModules.size() * sizeof(HMODULE)), &cbNeeded))
449 //nlwarning("Processe modules enumeration failed!");
450 return false;
452 if (cbNeeded < prcModules.size() * sizeof(HMODULE))
454 prcModules.resize(cbNeeded / sizeof(HMODULE));
455 break;
457 // make some more room
458 prcModules.resize(prcModules.size() * 2);
460 moduleNames.clear();
461 std::vector<std::string> resultModuleNames;
462 wchar_t moduleName[MAX_PATH + 1];
463 for (uint m = 0; m < prcModules.size(); ++m)
465 if (PSAPILib.GetModuleFileNameExW(hProcess, prcModules[m], moduleName, MAX_PATH))
467 moduleNames.push_back(wideToUtf8(moduleName));
470 CloseHandle(hProcess);
471 return true;
474 // ****************************************************************************************************************
475 uint32 CWinProcess::getProcessIdFromModuleFilename(const std::string &moduleFileName)
477 std::vector<uint32> processesId;
478 if (!enumProcessesId(processesId)) return false;
479 std::vector<std::string> moduleNames;
480 for (uint prc = 0; prc < processesId.size(); ++prc)
482 if (enumProcessModules(processesId[prc], moduleNames))
484 for (uint m = 0; m < moduleNames.size(); ++m)
486 if (nlstricmp(CFile::getFilename(moduleNames[m]), moduleFileName) == 0)
488 return processesId[prc];
493 return 0;
496 // ****************************************************************************************************************
497 bool CWinProcess::terminateProcess(uint32 processId, uint exitCode)
499 if (!processId) return false;
500 HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, (DWORD) processId);
501 if (!hProcess) return false;
502 BOOL ok = TerminateProcess(hProcess, (UINT) exitCode);
503 CloseHandle(hProcess);
504 return ok != FALSE;
507 // ****************************************************************************************************************
508 bool CWinProcess::terminateProcessFromModuleName(const std::string &moduleName, uint exitCode)
510 return terminateProcess(getProcessIdFromModuleFilename(moduleName), exitCode);
514 ///////////////////
515 // CProcessWatch //
516 ///////////////////
521 // I didn't use and test that code, eventually, but maybe useful in the future
523 class CProcessWatchTask : public IRunnable
525 public:
526 HANDLE HProcess;
527 public:
528 CProcessWatchTask(HANDLE hProcess) : HProcess(hProcess)
531 virtual void run()
533 WaitForSingleObject(HProcess, INFINITE);
537 class CProcessWatchImpl
539 public:
540 bool Launched;
541 IThread *WatchThread;
542 CProcessWatchTask *WatchTask;
543 public:
544 CProcessWatchImpl() : Launched(false), WatchThread(NULL), WatchTask(NULL)
547 ~CProcessWatchImpl()
549 reset();
551 void reset()
553 if (WatchThread)
555 if (WatchThread->isRunning())
557 WatchThread->terminate();
559 delete WatchTask;
560 delete WatchThread;
561 WatchTask = NULL;
562 WatchThread = NULL;
563 Launched = false;
566 bool launch(const std::string &programName, const std::string &arguments)
568 if (isRunning()) return false;
569 PROCESS_INFORMATION processInfo;
570 STARTUPINFO startupInfo = {0};
571 startupInfo.cb = sizeof(STARTUPINFO);
572 if (CreateProcessW(programName.c_str(), const_cast<LPTSTR>(arguments.c_str()), NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo))
574 WatchTask = new CProcessWatchTask(processInfo.hProcess);
575 WatchThread = IThread::create(WatchTask);
576 WatchThread->start();
577 Launched = true;
578 return true;
580 return false;
582 bool isRunning()
584 if (!Launched) return false;
585 nlassert(WatchThread);
586 nlassert(WatchTask);
587 if (WatchThread->isRunning()) return true;
588 reset();
589 return false;
594 CProcessWatch::CProcessWatch()
596 _PImpl = new CProcessWatchImpl;
599 CProcessWatch::~CProcessWatch()
601 delete _PImpl;
604 bool CProcessWatch::launch(const std::string &programName, const std::string &arguments)
606 return _PImpl->launch(programName, arguments);
609 bool CProcessWatch::isRunning() const
611 return _PImpl->isRunning();
615 } // NLMISC
617 #endif // NL_OS_WINDOWS