diff options
| author | Per Larsson <[email protected]> | 2021-12-14 12:34:47 +0100 |
|---|---|---|
| committer | Per Larsson <[email protected]> | 2021-12-14 12:34:47 +0100 |
| commit | b6c6568e1618f10d2160d836b65e35586e3c740f (patch) | |
| tree | f6a929cf918850bbba87d0ee67cd3482b2d50e24 /zencore/thread.cpp | |
| parent | Fixed bug in z$ service returning partial cache records and enable small obje... (diff) | |
| parent | Partial revert b363c5b (diff) | |
| download | zen-b6c6568e1618f10d2160d836b65e35586e3c740f.tar.xz zen-b6c6568e1618f10d2160d836b65e35586e3c740f.zip | |
Merged main.
Diffstat (limited to 'zencore/thread.cpp')
| -rw-r--r-- | zencore/thread.cpp | 803 |
1 files changed, 764 insertions, 39 deletions
diff --git a/zencore/thread.cpp b/zencore/thread.cpp index da711fe89..6d17e6968 100644 --- a/zencore/thread.cpp +++ b/zencore/thread.cpp @@ -3,14 +3,32 @@ #include <zencore/thread.h> #include <zencore/except.h> +#include <zencore/filesystem.h> +#include <zencore/scopeguard.h> #include <zencore/string.h> +#include <zencore/testing.h> #if ZEN_PLATFORM_WINDOWS +# include <shellapi.h> +# include <Shlobj.h> # include <zencore/windows.h> #elif ZEN_PLATFORM_LINUX +# include <chrono> +# include <condition_variable> +# include <mutex> + +# include <fcntl.h> +# include <mqueue.h> +# include <pthread.h> +# include <signal.h> +# include <sys/file.h> +# include <sys/wait.h> +# include <time.h> # include <unistd.h> #endif +#include <thread> + ZEN_THIRD_PARTY_INCLUDES_START #include <fmt/format.h> ZEN_THIRD_PARTY_INCLUDES_END @@ -69,6 +87,8 @@ SetCurrentThreadName([[maybe_unused]] std::string_view ThreadName) std::string ThreadNameZ{ThreadName}; SetNameInternal(GetCurrentThreadId(), ThreadNameZ.c_str()); #else + std::string ThreadNameZ{ThreadName}; + pthread_setname_np(pthread_self(), ThreadNameZ.c_str()); #endif } // namespace zen @@ -98,40 +118,80 @@ RwLock::ReleaseExclusive() ////////////////////////////////////////////////////////////////////////// -#if ZEN_PLATFORM_WINDOWS +#if !ZEN_PLATFORM_WINDOWS +struct EventInner +{ + std::mutex Mutex; + std::condition_variable CondVar; + bool volatile bSet = false; +}; +#endif // !ZEN_PLATFORM_WINDOWS Event::Event() { - m_EventHandle = CreateEvent(nullptr, true, false, nullptr); + bool bManualReset = true; + bool bInitialState = false; + +#if ZEN_PLATFORM_WINDOWS + m_EventHandle = CreateEvent(nullptr, bManualReset, bInitialState, nullptr); +#else + ZEN_UNUSED(bManualReset); + auto* Inner = new EventInner(); + Inner->bSet = bInitialState; + m_EventHandle = Inner; +#endif } Event::~Event() { - CloseHandle(m_EventHandle); + Close(); } void Event::Set() { +#if ZEN_PLATFORM_WINDOWS SetEvent(m_EventHandle); +#else + auto* Inner = (EventInner*)m_EventHandle; + { + std::unique_lock Lock(Inner->Mutex); + Inner->bSet = true; + } + Inner->CondVar.notify_all(); +#endif } void Event::Reset() { +#if ZEN_PLATFORM_WINDOWS ResetEvent(m_EventHandle); +#else + auto* Inner = (EventInner*)m_EventHandle; + { + std::unique_lock Lock(Inner->Mutex); + Inner->bSet = false; + } +#endif } void Event::Close() { +#if ZEN_PLATFORM_WINDOWS CloseHandle(m_EventHandle); +#else + auto* Inner = (EventInner*)m_EventHandle; + delete Inner; +#endif m_EventHandle = nullptr; } bool Event::Wait(int TimeoutMs) { +#if ZEN_PLATFORM_WINDOWS using namespace std::literals; const DWORD Timeout = (TimeoutMs < 0) ? INFINITE : TimeoutMs; @@ -144,12 +204,48 @@ Event::Wait(int TimeoutMs) } return (Result == WAIT_OBJECT_0); +#else + auto* Inner = (EventInner*)m_EventHandle; + + if (TimeoutMs >= 0) + { + std::unique_lock Lock(Inner->Mutex); + + if (Inner->bSet) + { + return true; + } + + return Inner->CondVar.wait_for(Lock, std::chrono::milliseconds(TimeoutMs), [&] { return Inner->bSet; }); + } + + std::unique_lock Lock(Inner->Mutex); + + if (!Inner->bSet) + { + Inner->CondVar.wait(Lock, [&] { return Inner->bSet; }); + } + + return true; +#endif } ////////////////////////////////////////////////////////////////////////// -NamedEvent::NamedEvent(std::u8string_view EventName) : Event(nullptr) +#if ZEN_PLATFORM_LINUX +static bool +IsMessageQueueEmpty(int Fd) { + // Check if there is already a message in the queue. + mq_attr Attributes = {O_NONBLOCK, 1, 1, 0}; + mq_getattr(Fd, &Attributes); + return (Attributes.mq_curmsgs == 0); +} +#endif // ZEN_PLATFORM_LINUX + +NamedEvent::NamedEvent(std::string_view EventName) +{ +#if ZEN_PLATFORM_WINDOWS using namespace std::literals; ExtendableStringBuilder<64> Name; @@ -157,30 +253,151 @@ NamedEvent::NamedEvent(std::u8string_view EventName) : Event(nullptr) Name << EventName; m_EventHandle = CreateEventA(nullptr, true, false, Name.c_str()); +#elif ZEN_PLATFORM_LINUX + ExtendableStringBuilder<64> Name; + Name << "/"; + Name << EventName; + + mq_attr Attributes = { + 0, // flags + 1, // max message count + 1, // max message size + 0, // current messages + }; + + int Inner = mq_open(Name.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0644, &Attributes); + if (Inner < 0) + { + ThrowLastError("Failed to get message queue from mq_open()"); + } + + int LockResult = flock(Inner, LOCK_EX | LOCK_NB); + if (LockResult == 0) + { + // This is really thread safe as the message queue could be set between + // getting the exclusive lock and checking the queue. But for the our + // simple synchronising of process, this should be okay. + while (!IsMessageQueueEmpty(Inner)) + { + char Sink; + mq_receive(Inner, &Sink, sizeof(Sink), nullptr); + } + } + + m_EventHandle = (void*)intptr_t(Inner); +#else +# error Implement NamedEvent for this platform +#endif } -NamedEvent::NamedEvent(std::string_view EventName) : Event(nullptr) +NamedEvent::~NamedEvent() { - using namespace std::literals; + Close(); +} - ExtendableStringBuilder<64> Name; - Name << "Local\\"sv; - Name << EventName; +void +NamedEvent::Close() +{ + if (m_EventHandle == nullptr) + { + return; + } - m_EventHandle = CreateEventA(nullptr, true, false, Name.c_str()); +#if ZEN_PLATFORM_WINDOWS + CloseHandle(m_EventHandle); +#elif ZEN_PLATFORM_LINUX + int Inner = int(intptr_t(m_EventHandle)); + + if (flock(Inner, LOCK_EX | LOCK_NB) == 0) + { + flock(Inner, LOCK_UN | LOCK_NB); + std::filesystem::path Name = PathFromHandle((void*)(intptr_t(Inner))); + mq_unlink(Name.c_str()); + } + + close(Inner); +#endif + + m_EventHandle = nullptr; +} + +void +NamedEvent::Set() +{ +#if ZEN_PLATFORM_WINDOWS + SetEvent(m_EventHandle); +#elif ZEN_PLATFORM_LINUX + int Inner = int(intptr_t(m_EventHandle)); + + if (!IsMessageQueueEmpty(Inner)) + { + return; + } + + char Message = 0x49; + if (mq_send(Inner, &Message, sizeof(Message), 0) != 0) + { + ThrowLastError("Unable to send set message to queue"); + } +#endif } +bool +NamedEvent::Wait(int TimeoutMs) +{ +#if ZEN_PLATFORM_WINDOWS + const DWORD Timeout = (TimeoutMs < 0) ? INFINITE : TimeoutMs; + + DWORD Result = WaitForSingleObject(m_EventHandle, Timeout); + + if (Result == WAIT_FAILED) + { + using namespace std::literals; + zen::ThrowLastError("Event wait failed"sv); + } + + return (Result == WAIT_OBJECT_0); +#elif ZEN_PLATFORM_LINUX + int Inner = int(intptr_t(m_EventHandle)); + + if (!IsMessageQueueEmpty(Inner)) + { + return true; + } + + struct timeval TimeoutValue = { + .tv_sec = 0, + .tv_usec = TimeoutMs << 10, + }; + struct timeval* TimeoutPtr = (TimeoutMs < 0) ? nullptr : &TimeoutValue; + + fd_set FdSet; + FD_ZERO(&FdSet); + FD_SET(Inner, &FdSet); + return select(Inner + 1, &FdSet, nullptr, nullptr, TimeoutPtr) > 0; +#endif +} + +////////////////////////////////////////////////////////////////////////// + NamedMutex::~NamedMutex() { +#if ZEN_PLATFORM_WINDOWS if (m_MutexHandle) { CloseHandle(m_MutexHandle); } +#else + int Inner = int(intptr_t(m_MutexHandle)); + flock(Inner, LOCK_UN); + close(Inner); +#endif } bool NamedMutex::Create(std::string_view MutexName) { +#if ZEN_PLATFORM_WINDOWS ZEN_ASSERT(m_MutexHandle == nullptr); using namespace std::literals; @@ -192,11 +409,32 @@ NamedMutex::Create(std::string_view MutexName) m_MutexHandle = CreateMutexA(nullptr, /* InitialOwner */ TRUE, Name.c_str()); return !!m_MutexHandle; +#else + ExtendableStringBuilder<64> Name; + Name << "/tmp/" << MutexName; + + int Inner = open(Name.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0644); + if (Inner < 0) + { + return false; + } + + if (flock(Inner, LOCK_EX) != 0) + { + close(Inner); + Inner = 0; + return false; + } + + m_MutexHandle = (void*)(intptr_t(Inner)); + return true; +#endif // ZEN_PLATFORM_WINDOWS } bool NamedMutex::Exists(std::string_view MutexName) { +#if ZEN_PLATFORM_WINDOWS using namespace std::literals; ExtendableStringBuilder<64> Name; @@ -213,24 +451,49 @@ NamedMutex::Exists(std::string_view MutexName) CloseHandle(MutexHandle); return true; -} +#else + ExtendableStringBuilder<64> Name; + Name << "/tmp/" << MutexName; -#endif // ZEN_PLATFORM_WINDOWS + bool bExists = false; + int Fd = open(Name.c_str(), O_RDWR, 0644); + if (Fd >= 0) + { + if (flock(Fd, LOCK_EX | LOCK_NB) == 0) + { + flock(Fd, LOCK_UN | LOCK_NB); + } + else + { + bExists = true; + } + close(Fd); + } -#if ZEN_PLATFORM_WINDOWS + return bExists; +#endif // ZEN_PLATFORM_WINDOWS +} ////////////////////////////////////////////////////////////////////////// ProcessHandle::ProcessHandle() = default; +#if ZEN_PLATFORM_WINDOWS void ProcessHandle::Initialize(void* ProcessHandle) { ZEN_ASSERT(m_ProcessHandle == nullptr); + + if (ProcessHandle == INVALID_HANDLE_VALUE) + { + ProcessHandle = nullptr; + } + // TODO: perform some debug verification here to verify it's a valid handle? m_ProcessHandle = ProcessHandle; m_Pid = GetProcessId(m_ProcessHandle); } +#endif // ZEN_PLATFORM_WINDOWS ProcessHandle::~ProcessHandle() { @@ -241,12 +504,21 @@ void ProcessHandle::Initialize(int Pid) { ZEN_ASSERT(m_ProcessHandle == nullptr); - m_ProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, Pid); - using namespace fmt::literals; +#if ZEN_PLATFORM_WINDOWS + m_ProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, Pid); +#elif ZEN_PLATFORM_LINUX + if (Pid > 0) + { + m_ProcessHandle = (void*)(intptr_t(Pid)); + } +#else +# error Check process control on this platform +#endif if (!m_ProcessHandle) { + using namespace fmt::literals; ThrowLastError("ProcessHandle::Initialize(pid: {}) failed"_format(Pid)); } @@ -256,29 +528,51 @@ ProcessHandle::Initialize(int Pid) bool ProcessHandle::IsRunning() const { + bool bActive = false; + +#if ZEN_PLATFORM_WINDOWS DWORD ExitCode = 0; GetExitCodeProcess(m_ProcessHandle, &ExitCode); + bActive = (ExitCode == STILL_ACTIVE); +#elif ZEN_PLATFORM_LINUX + StringBuilder<64> ProcPath; + ProcPath << "/proc/" << m_Pid; + bActive = (access(ProcPath.c_str(), F_OK) != 0); +#else +# error Check process control on this platform +#endif - return ExitCode == STILL_ACTIVE; + return bActive; } bool ProcessHandle::IsValid() const { - return (m_ProcessHandle != nullptr) && (m_ProcessHandle != INVALID_HANDLE_VALUE); + return (m_ProcessHandle != nullptr); } void ProcessHandle::Terminate(int ExitCode) { - if (IsRunning()) + if (!IsRunning()) { - TerminateProcess(m_ProcessHandle, ExitCode); + return; } + bool bSuccess = false; + +#if ZEN_PLATFORM_WINDOWS + TerminateProcess(m_ProcessHandle, ExitCode); DWORD WaitResult = WaitForSingleObject(m_ProcessHandle, INFINITE); + bSuccess = (WaitResult != WAIT_OBJECT_0); +#elif ZEN_PLATFORM_LINUX + ZEN_UNUSED(ExitCode); + bSuccess = (kill(m_Pid, SIGKILL) == 0); +#else +# error Check kill() on this platform +#endif - if (WaitResult != WAIT_OBJECT_0) + if (!bSuccess) { // What might go wrong here, and what is meaningful to act on? } @@ -289,14 +583,20 @@ ProcessHandle::Reset() { if (IsValid()) { +#if ZEN_PLATFORM_WINDOWS CloseHandle(m_ProcessHandle); +#endif m_ProcessHandle = nullptr; + m_Pid = 0; } } bool ProcessHandle::Wait(int TimeoutMs) { + using namespace std::literals; + +#if ZEN_PLATFORM_WINDOWS const DWORD Timeout = (TimeoutMs < 0) ? INFINITE : TimeoutMs; const DWORD WaitResult = WaitForSingleObject(m_ProcessHandle, Timeout); @@ -310,19 +610,301 @@ ProcessHandle::Wait(int TimeoutMs) return false; case WAIT_FAILED: - // What might go wrong here, and what is meaningful to act on? - using namespace std::literals; - ThrowLastError("Process::Wait failed"sv); + break; } +#elif ZEN_PLATFORM_LINUX + const int SleepMs = 20; + timespec SleepTime = {0, SleepMs * 1000 * 1000}; + for (int i = 0;; i += SleepMs) + { + int WaitState = 0; + waitpid(m_Pid, &WaitState, WNOHANG | WCONTINUED | WUNTRACED); - return false; -} + if (kill(m_Pid, 0) < 0) + { + if (zen::GetLastError() == ESRCH) + { + return true; + } + break; + } -#endif // ZEN_PLATFORM_WINDOWS + if (TimeoutMs >= 0 && i >= TimeoutMs) + { + return false; + } + + nanosleep(&SleepTime, nullptr); + } +#else +# error Check kill() on this platform +#endif + + // What might go wrong here, and what is meaningful to act on? + ThrowLastError("Process::Wait failed"sv); +} ////////////////////////////////////////////////////////////////////////// +#if !ZEN_PLATFORM_WINDOWS || ZEN_WITH_TESTS +static void +BuildArgV(std::vector<char*>& Out, char* CommandLine) +{ + char* Cursor = CommandLine; + while (true) + { + // Skip leading whitespace + for (; *Cursor == ' '; ++Cursor) + ; + + // Check for nullp terminator + if (*Cursor == '\0') + { + break; + } + + Out.push_back(Cursor); + + // Extract word + int QuoteCount = 0; + do + { + QuoteCount += (*Cursor == '\"'); + if (*Cursor == ' ' && !(QuoteCount & 1)) + { + break; + } + ++Cursor; + } while (*Cursor != '\0'); + + if (*Cursor == '\0') + { + break; + } + + *Cursor = '\0'; + ++Cursor; + } +} +#endif // !WINDOWS || TESTS + +#if ZEN_PLATFORM_WINDOWS +static CreateProcResult +CreateProcNormal(const std::filesystem::path& Executable, std::string_view CommandLine, const CreateProcOptions& Options) +{ + PROCESS_INFORMATION ProcessInfo{}; + STARTUPINFO StartupInfo{.cb = sizeof(STARTUPINFO)}; + + const bool InheritHandles = false; + void* Environment = nullptr; + LPSECURITY_ATTRIBUTES ProcessAttributes = nullptr; + LPSECURITY_ATTRIBUTES ThreadAttributes = nullptr; + + DWORD CreationFlags = 0; + if (Options.Flags & CreateProcOptions::Flag_NewConsole) + { + CreationFlags |= CREATE_NEW_CONSOLE; + } + + const wchar_t* WorkingDir = nullptr; + if (Options.WorkingDirectory != nullptr) + { + WorkingDir = Options.WorkingDirectory->c_str(); + } + + ExtendableWideStringBuilder<256> CommandLineZ; + CommandLineZ << CommandLine; + + BOOL Success = CreateProcessW(Executable.c_str(), + CommandLineZ.Data(), + ProcessAttributes, + ThreadAttributes, + InheritHandles, + CreationFlags, + Environment, + WorkingDir, + &StartupInfo, + &ProcessInfo); + + if (!Success) + { + return nullptr; + } + + CloseHandle(ProcessInfo.hThread); + return ProcessInfo.hProcess; +} + +static CreateProcResult +CreateProcUnelevated(const std::filesystem::path& Executable, std::string_view CommandLine, const CreateProcOptions& Options) +{ + /* Launches a binary with the shell as its parent. The shell (such as + Explorer) should be an unelevated process. */ + + // No sense in using this route if we are not elevated in the first place + if (IsUserAnAdmin() == FALSE) + { + return CreateProcNormal(Executable, CommandLine, Options); + } + + // Get the users' shell process and open it for process creation + HWND ShellWnd = GetShellWindow(); + if (ShellWnd == nullptr) + { + return nullptr; + } + + DWORD ShellPid; + GetWindowThreadProcessId(ShellWnd, &ShellPid); + + HANDLE Process = OpenProcess(PROCESS_CREATE_PROCESS, FALSE, ShellPid); + if (Process == nullptr) + { + return nullptr; + } + auto $0 = MakeGuard([&] { CloseHandle(Process); }); + + // Creating a process as a child of another process is done by setting a + // thread-attribute list on the startup info passed to CreateProcess() + SIZE_T AttrListSize; + InitializeProcThreadAttributeList(nullptr, 1, 0, &AttrListSize); + + auto AttrList = (PPROC_THREAD_ATTRIBUTE_LIST)malloc(AttrListSize); + auto $1 = MakeGuard([&] { free(AttrList); }); + + if (!InitializeProcThreadAttributeList(AttrList, 1, 0, &AttrListSize)) + { + return nullptr; + } + + BOOL bOk = + UpdateProcThreadAttribute(AttrList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, (HANDLE*)&Process, sizeof(Process), nullptr, nullptr); + if (!bOk) + { + return nullptr; + } + + // By this point we know we are an elevated process. It is not allowed to + // create a process as a child of another unelevated process that share our + // elevated console window if we have one. So we'll need to create a new one. + uint32_t CreateProcFlags = EXTENDED_STARTUPINFO_PRESENT; + if (GetConsoleWindow() != nullptr) + { + CreateProcFlags |= CREATE_NEW_CONSOLE; + } + else + { + CreateProcFlags |= DETACHED_PROCESS; + } + + // Everything is set up now so we can proceed and launch the process + STARTUPINFOEXW StartupInfo = { + .StartupInfo = {.cb = sizeof(STARTUPINFOEXW)}, + .lpAttributeList = AttrList, + }; + PROCESS_INFORMATION ProcessInfo = {}; + + if (Options.Flags & CreateProcOptions::Flag_NewConsole) + { + CreateProcFlags |= CREATE_NEW_CONSOLE; + } + + ExtendableWideStringBuilder<256> CommandLineZ; + CommandLineZ << CommandLine; + + bOk = CreateProcessW(Executable.c_str(), + CommandLineZ.Data(), + nullptr, + nullptr, + FALSE, + CreateProcFlags, + nullptr, + nullptr, + &StartupInfo.StartupInfo, + &ProcessInfo); + if (bOk == FALSE) + { + return nullptr; + } + + CloseHandle(ProcessInfo.hThread); + return ProcessInfo.hProcess; +} + +static CreateProcResult +CreateProcElevated(const std::filesystem::path& Executable, std::string_view CommandLine, const CreateProcOptions& Options) +{ + ExtendableWideStringBuilder<256> CommandLineZ; + CommandLineZ << CommandLine; + + SHELLEXECUTEINFO ShellExecuteInfo; + ZeroMemory(&ShellExecuteInfo, sizeof(ShellExecuteInfo)); + ShellExecuteInfo.cbSize = sizeof(ShellExecuteInfo); + ShellExecuteInfo.fMask = SEE_MASK_UNICODE | SEE_MASK_NOCLOSEPROCESS; + ShellExecuteInfo.lpFile = Executable.c_str(); + ShellExecuteInfo.lpVerb = TEXT("runas"); + ShellExecuteInfo.nShow = SW_SHOW; + ShellExecuteInfo.lpParameters = CommandLineZ.c_str(); + + if (Options.WorkingDirectory != nullptr) + { + ShellExecuteInfo.lpDirectory = Options.WorkingDirectory->c_str(); + } + + if (::ShellExecuteEx(&ShellExecuteInfo)) + { + return ShellExecuteInfo.hProcess; + } + + return nullptr; +} +#endif // ZEN_PLATFORM_WINDOWS + +CreateProcResult +CreateProc(const std::filesystem::path& Executable, std::string_view CommandLine, const CreateProcOptions& Options) +{ #if ZEN_PLATFORM_WINDOWS + if (Options.Flags & CreateProcOptions::Flag_Unelevated) + { + return CreateProcUnelevated(Executable, CommandLine, Options); + } + + if (Options.Flags & CreateProcOptions::Flag_Elevated) + { + return CreateProcElevated(Executable, CommandLine, Options); + } + + return CreateProcNormal(Executable, CommandLine, Options); +#else + std::vector<char*> ArgV; + std::string CommandLineZ(CommandLine); + BuildArgV(ArgV, CommandLineZ.data()); + ArgV.push_back(nullptr); + + int ChildPid = fork(); + if (ChildPid < 0) + { + ThrowLastError("Failed to fork a new child process"); + } + else if (ChildPid == 0) + { + if (Options.WorkingDirectory != nullptr) + { + int Result = chdir(Options.WorkingDirectory->c_str()); + ZEN_UNUSED(Result); + } + + if (execv(Executable.c_str(), ArgV.data()) < 0) + { + ThrowLastError("Failed to exec() a new process image"); + } + } + + return ChildPid; +#endif +} + +////////////////////////////////////////////////////////////////////////// ProcessMonitor::ProcessMonitor() { @@ -332,9 +914,11 @@ ProcessMonitor::~ProcessMonitor() { RwLock::ExclusiveLockScope _(m_Lock); - for (HANDLE& Proc : m_ProcessHandles) + for (HandleType& Proc : m_ProcessHandles) { +#if ZEN_PLATFORM_WINDOWS CloseHandle(Proc); +#endif Proc = 0; } } @@ -346,24 +930,34 @@ ProcessMonitor::IsRunning() bool FoundOne = false; - for (HANDLE& Proc : m_ProcessHandles) + for (HandleType& Proc : m_ProcessHandles) { + bool ProcIsActive; + +#if ZEN_PLATFORM_WINDOWS DWORD ExitCode = 0; GetExitCodeProcess(Proc, &ExitCode); - if (ExitCode != STILL_ACTIVE) + ProcIsActive = (ExitCode == STILL_ACTIVE); + if (!ProcIsActive) { CloseHandle(Proc); - Proc = 0; } - else +#else + int Pid = int(intptr_t(Proc)); + ProcIsActive = IsProcessRunning(Pid); +#endif + + if (!ProcIsActive) { - // Still alive - FoundOne = true; + Proc = 0; } + + // Still alive + FoundOne |= ProcIsActive; } - std::erase_if(m_ProcessHandles, [](HANDLE Handle) { return Handle == 0; }); + std::erase_if(m_ProcessHandles, [](HandleType Handle) { return Handle == 0; }); return FoundOne; } @@ -371,7 +965,13 @@ ProcessMonitor::IsRunning() void ProcessMonitor::AddPid(int Pid) { - HANDLE ProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, Pid); + HandleType ProcessHandle; + +#if ZEN_PLATFORM_WINDOWS + ProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, Pid); +#else + ProcessHandle = HandleType(intptr_t(Pid)); +#endif if (ProcessHandle) { @@ -387,8 +987,6 @@ ProcessMonitor::IsActive() const return m_ProcessHandles.empty() == false; } -#endif // ZEN_PLATFORM_WINDOWS - ////////////////////////////////////////////////////////////////////////// bool @@ -418,7 +1016,9 @@ IsProcessRunning(int pid) return true; #else - ZEN_NOT_IMPLEMENTED(); + char Buffer[64]; + sprintf(Buffer, "/proc/%d", pid); + return access(Buffer, F_OK) == 0; #endif } @@ -428,7 +1028,17 @@ GetCurrentProcessId() #if ZEN_PLATFORM_WINDOWS return ::GetCurrentProcessId(); #else - return getpid(); + return int(getpid()); +#endif +} + +int +GetCurrentThreadId() +{ +#if ZEN_PLATFORM_WINDOWS + return ::GetCurrentThreadId(); +#else + return int(gettid()); #endif } @@ -447,9 +1057,124 @@ Sleep(int ms) // Testing related code follows... // +#if ZEN_WITH_TESTS + void thread_forcelink() { } +TEST_CASE("Thread") +{ + int Pid = GetCurrentProcessId(); + CHECK(Pid > 0); + CHECK(IsProcessRunning(Pid)); +} + +TEST_CASE("BuildArgV") +{ + const char* Words[] = {"one", "two", "three", "four", "five"}; + struct + { + int WordCount; + const char* Input; + } Cases[] = { + {0, ""}, + {0, " "}, + {1, "one"}, + {1, " one"}, + {1, "one "}, + {2, "one two"}, + {2, " one two"}, + {2, "one two "}, + {2, " one two"}, + {2, "one two "}, + {2, "one two "}, + {3, "one two three"}, + {3, "\"one\" two \"three\""}, + {5, "one two three four five"}, + }; + + for (const auto& Case : Cases) + { + std::vector<char*> OutArgs; + StringBuilder<64> Mutable; + Mutable << Case.Input; + BuildArgV(OutArgs, Mutable.Data()); + + CHECK_EQ(OutArgs.size(), Case.WordCount); + + for (int i = 0, n = int(OutArgs.size()); i < n; ++i) + { + const char* Truth = Words[i]; + size_t TruthLen = strlen(Truth); + + const char* Candidate = OutArgs[i]; + bool bQuoted = (Candidate[0] == '\"'); + Candidate += bQuoted; + + CHECK(strncmp(Truth, Candidate, TruthLen) == 0); + + if (bQuoted) + { + CHECK_EQ(Candidate[TruthLen], '\"'); + } + } + } +} + +TEST_CASE("NamedEvent") +{ + std::string Name = "zencore_test_event"; + NamedEvent TestEvent(Name); + + // Timeout test + for (uint32_t i = 0; i < 8; ++i) + { + bool bEventSet = TestEvent.Wait(100); + CHECK(!bEventSet); + } + + // Thread check + std::thread Waiter = std::thread([Name]() { + NamedEvent ReadyEvent(Name + "_ready"); + ReadyEvent.Set(); + + NamedEvent TestEvent(Name); + TestEvent.Wait(1000); + }); + + NamedEvent ReadyEvent(Name + "_ready"); + ReadyEvent.Wait(); + + zen::Sleep(500); + TestEvent.Set(); + + Waiter.join(); + + // Manual reset property + for (uint32_t i = 0; i < 8; ++i) + { + bool bEventSet = TestEvent.Wait(100); + CHECK(bEventSet); + } +} + +TEST_CASE("NamedMutex") +{ + static const char* Name = "zen_test_mutex"; + + CHECK(!NamedMutex::Exists(Name)); + + { + NamedMutex TestMutex; + CHECK(TestMutex.Create(Name)); + CHECK(NamedMutex::Exists(Name)); + } + + CHECK(!NamedMutex::Exists(Name)); +} + +#endif // ZEN_WITH_TESTS + } // namespace zen |