aboutsummaryrefslogtreecommitdiff
path: root/zencore/thread.cpp
diff options
context:
space:
mode:
authorPer Larsson <[email protected]>2021-12-14 12:34:47 +0100
committerPer Larsson <[email protected]>2021-12-14 12:34:47 +0100
commitb6c6568e1618f10d2160d836b65e35586e3c740f (patch)
treef6a929cf918850bbba87d0ee67cd3482b2d50e24 /zencore/thread.cpp
parentFixed bug in z$ service returning partial cache records and enable small obje... (diff)
parentPartial revert b363c5b (diff)
downloadzen-b6c6568e1618f10d2160d836b65e35586e3c740f.tar.xz
zen-b6c6568e1618f10d2160d836b65e35586e3c740f.zip
Merged main.
Diffstat (limited to 'zencore/thread.cpp')
-rw-r--r--zencore/thread.cpp803
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