aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDan Engelbrecht <[email protected]>2023-08-17 22:52:28 +0200
committerGitHub <[email protected]>2023-08-17 22:52:28 +0200
commitdc320d6bfe81f149e8276caabcd165a931711ae7 (patch)
tree4fd245d817876f15e90eb30f6577d4550c76f8fc /src
parentsingle thread async cache log (#361) (diff)
downloadzen-dc320d6bfe81f149e8276caabcd165a931711ae7.tar.xz
zen-dc320d6bfe81f149e8276caabcd165a931711ae7.zip
Cache process handles for FormatPackageMessage (#360)
Diffstat (limited to 'src')
-rw-r--r--src/zenhttp/httpserver.cpp1
-rw-r--r--src/zenhttp/httpshared.cpp268
-rw-r--r--src/zenhttp/include/zenhttp/httpshared.h8
-rw-r--r--src/zenserver/cache/httpstructuredcache.cpp19
-rw-r--r--src/zenserver/cache/httpstructuredcache.h2
-rw-r--r--src/zenutil/include/zenutil/openprocesscache.h39
-rw-r--r--src/zenutil/openprocesscache.cpp150
7 files changed, 350 insertions, 137 deletions
diff --git a/src/zenhttp/httpserver.cpp b/src/zenhttp/httpserver.cpp
index dbf284ab5..5312e80a2 100644
--- a/src/zenhttp/httpserver.cpp
+++ b/src/zenhttp/httpserver.cpp
@@ -498,6 +498,7 @@ HttpServerRequest::WriteResponse(HttpResponseCode ResponseCode, HttpContentType
std::span<const SharedBuffer> Segments = Payload.GetSegments();
std::vector<IoBuffer> Buffers;
+ Buffers.reserve(Segments.size());
for (auto& Segment : Segments)
{
diff --git a/src/zenhttp/httpshared.cpp b/src/zenhttp/httpshared.cpp
index 7aade56d2..4a67b69e7 100644
--- a/src/zenhttp/httpshared.cpp
+++ b/src/zenhttp/httpshared.cpp
@@ -14,6 +14,7 @@
#include <zencore/stream.h>
#include <zencore/testing.h>
#include <zencore/testutils.h>
+#include <zencore/trace.h>
#include <span>
#include <vector>
@@ -27,20 +28,20 @@ namespace zen {
const std::string_view HandlePrefix(":?#:");
std::vector<IoBuffer>
-FormatPackageMessage(const CbPackage& Data, int TargetProcessPid)
+FormatPackageMessage(const CbPackage& Data, void* TargetProcessHandle)
{
- return FormatPackageMessage(Data, FormatFlags::kDefault, TargetProcessPid);
+ return FormatPackageMessage(Data, FormatFlags::kDefault, TargetProcessHandle);
}
CompositeBuffer
-FormatPackageMessageBuffer(const CbPackage& Data, int TargetProcessPid)
+FormatPackageMessageBuffer(const CbPackage& Data, void* TargetProcessHandle)
{
- return FormatPackageMessageBuffer(Data, FormatFlags::kDefault, TargetProcessPid);
+ return FormatPackageMessageBuffer(Data, FormatFlags::kDefault, TargetProcessHandle);
}
CompositeBuffer
-FormatPackageMessageBuffer(const CbPackage& Data, FormatFlags Flags, int TargetProcessPid)
+FormatPackageMessageBuffer(const CbPackage& Data, FormatFlags Flags, void* TargetProcessHandle)
{
- std::vector<IoBuffer> Message = FormatPackageMessage(Data, Flags, TargetProcessPid);
+ std::vector<IoBuffer> Message = FormatPackageMessage(Data, Flags, TargetProcessHandle);
std::vector<SharedBuffer> Buffers;
@@ -52,43 +53,125 @@ FormatPackageMessageBuffer(const CbPackage& Data, FormatFlags Flags, int TargetP
return CompositeBuffer(std::move(Buffers));
}
-std::vector<IoBuffer>
-FormatPackageMessage(const CbPackage& Data, FormatFlags Flags, int TargetProcessPid)
+static void
+MarshalLocal(CbAttachmentEntry*& AttachmentInfo,
+ const std::string& Path8,
+ CbAttachmentReferenceHeader& LocalRef,
+ const IoHash& AttachmentHash,
+ bool IsCompressed,
+ std::vector<IoBuffer>& ResponseBuffers)
{
- void* TargetProcessHandle = nullptr;
-#if ZEN_PLATFORM_WINDOWS
- std::vector<HANDLE> DuplicatedHandles;
- auto _ = MakeGuard([&DuplicatedHandles, &TargetProcessHandle]() {
- if (TargetProcessHandle == nullptr)
- {
- return;
- }
-
- for (HANDLE DuplicatedHandle : DuplicatedHandles)
- {
- HANDLE ClosingHandle;
- if (::DuplicateHandle((HANDLE)TargetProcessHandle,
- DuplicatedHandle,
- GetCurrentProcess(),
- &ClosingHandle,
- 0,
- FALSE,
- DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS) == TRUE)
- {
- ::CloseHandle(ClosingHandle);
- }
- }
- ::CloseHandle((HANDLE)TargetProcessHandle);
- TargetProcessHandle = nullptr;
- });
-
- if (EnumHasAllFlags(Flags, FormatFlags::kAllowLocalReferences) && TargetProcessPid != 0)
+ IoBuffer RefBuffer(sizeof(CbAttachmentReferenceHeader) + Path8.size());
+
+ CbAttachmentReferenceHeader* RefHdr = RefBuffer.MutableData<CbAttachmentReferenceHeader>();
+ *RefHdr++ = LocalRef;
+ memcpy(RefHdr, Path8.data(), Path8.size());
+
+ *AttachmentInfo++ = {.PayloadSize = RefBuffer.GetSize(),
+ .Flags = (IsCompressed ? uint32_t(CbAttachmentEntry::kIsCompressed) : 0u) | CbAttachmentEntry::kIsLocalRef,
+ .AttachmentHash = AttachmentHash};
+
+ ResponseBuffers.push_back(std::move(RefBuffer));
+};
+
+static bool
+IsLocalRef(tsl::robin_map<void*, std::string>& FileNameMap,
+ std::vector<void*>& DuplicatedHandles,
+ const CompositeBuffer& AttachmentBinary,
+ bool DenyPartialLocalReferences,
+ void* TargetProcessHandle,
+ CbAttachmentReferenceHeader& LocalRef,
+ std::string& Path8)
+{
+ const SharedBuffer& Segment = AttachmentBinary.GetSegments().front();
+ IoBufferFileReference Ref;
+ const IoBuffer& SegmentBuffer = Segment.AsIoBuffer();
+
+ if (!SegmentBuffer.GetFileReference(Ref))
+ {
+ return false;
+ }
+
+ if (DenyPartialLocalReferences && !SegmentBuffer.IsWholeFile())
+ {
+ return false;
+ }
+
+ if (auto It = FileNameMap.find(Ref.FileHandle); It != FileNameMap.end())
{
- TargetProcessHandle = OpenProcess(PROCESS_DUP_HANDLE, FALSE, TargetProcessPid);
+ Path8 = It->second;
}
-#else
- ZEN_UNUSED(TargetProcessPid);
- void* DuplicatedHandles = nullptr;
+ else
+ {
+ bool UseFilePath = true;
+#if ZEN_PLATFORM_WINDOWS
+ if (TargetProcessHandle != nullptr)
+ {
+ HANDLE TargetHandle = INVALID_HANDLE_VALUE;
+ BOOL OK = ::DuplicateHandle(GetCurrentProcess(),
+ Ref.FileHandle,
+ (HANDLE)TargetProcessHandle,
+ &TargetHandle,
+ FILE_GENERIC_READ,
+ FALSE,
+ 0);
+ if (OK)
+ {
+ DuplicatedHandles.push_back((void*)TargetHandle);
+ Path8 = fmt::format("{}{}", HandlePrefix, reinterpret_cast<uint64_t>(TargetHandle));
+ UseFilePath = false;
+ }
+ }
+#else // ZEN_PLATFORM_WINDOWS
+ ZEN_UNUSED(TargetProcessHandle);
+ ZEN_UNUSED(DuplicatedHandles);
+ // Not supported on Linux/Mac. Could potentially use pidfd_getfd() but that requires a fairly new Linux kernel/includes and to
+ // deal with acceess rights etc.
+#endif // ZEN_PLATFORM_WINDOWS
+ if (UseFilePath)
+ {
+ ExtendablePathBuilder<256> LocalRefFile;
+ LocalRefFile.Append(std::filesystem::absolute(PathFromHandle(Ref.FileHandle)));
+ Path8 = LocalRefFile.ToUtf8();
+ }
+ FileNameMap.insert_or_assign(Ref.FileHandle, Path8);
+ }
+
+ LocalRef.AbsolutePathLength = gsl::narrow<uint16_t>(Path8.size());
+ LocalRef.PayloadByteOffset = Ref.FileChunkOffset;
+ LocalRef.PayloadByteSize = Ref.FileChunkSize;
+
+ return true;
+};
+
+std::vector<IoBuffer>
+FormatPackageMessage(const CbPackage& Data, FormatFlags Flags, void* TargetProcessHandle)
+{
+ ZEN_TRACE_CPU("FormatPackageMessage");
+
+ std::vector<void*> DuplicatedHandles;
+#if ZEN_PLATFORM_WINDOWS
+ auto _ = MakeGuard([&DuplicatedHandles, &TargetProcessHandle]() {
+ if (TargetProcessHandle == nullptr)
+ {
+ return;
+ }
+
+ for (void* DuplicatedHandle : DuplicatedHandles)
+ {
+ HANDLE ClosingHandle;
+ if (::DuplicateHandle((HANDLE)TargetProcessHandle,
+ (HANDLE)DuplicatedHandle,
+ GetCurrentProcess(),
+ &ClosingHandle,
+ 0,
+ FALSE,
+ DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS) == TRUE)
+ {
+ ::CloseHandle(ClosingHandle);
+ }
+ }
+ });
#endif // ZEN_PLATFORM_WINDOWS
const std::span<const CbAttachment>& Attachments = Data.GetAttachments();
@@ -118,91 +201,8 @@ FormatPackageMessage(const CbPackage& Data, FormatFlags Flags, int TargetProcess
*AttachmentInfo++ = {.PayloadSize = RootIoBuffer.Size(), .Flags = CbAttachmentEntry::kIsObject, .AttachmentHash = Data.GetObjectHash()};
// Attachment payloads
-
- auto MarshalLocal = [&AttachmentInfo, &ResponseBuffers](const std::string& Path8,
- CbAttachmentReferenceHeader& LocalRef,
- const IoHash& AttachmentHash,
- bool IsCompressed) {
- IoBuffer RefBuffer(sizeof(CbAttachmentReferenceHeader) + Path8.size());
-
- CbAttachmentReferenceHeader* RefHdr = RefBuffer.MutableData<CbAttachmentReferenceHeader>();
- *RefHdr++ = LocalRef;
- memcpy(RefHdr, Path8.data(), Path8.size());
-
- *AttachmentInfo++ = {.PayloadSize = RefBuffer.GetSize(),
- .Flags = (IsCompressed ? uint32_t(CbAttachmentEntry::kIsCompressed) : 0u) | CbAttachmentEntry::kIsLocalRef,
- .AttachmentHash = AttachmentHash};
-
- ResponseBuffers.push_back(std::move(RefBuffer));
- };
-
tsl::robin_map<void*, std::string> FileNameMap;
- auto IsLocalRef = [&FileNameMap, &DuplicatedHandles](const CompositeBuffer& AttachmentBinary,
- bool DenyPartialLocalReferences,
- void* TargetProcessHandle,
- CbAttachmentReferenceHeader& LocalRef,
- std::string& Path8) -> bool {
- const SharedBuffer& Segment = AttachmentBinary.GetSegments().front();
- IoBufferFileReference Ref;
- const IoBuffer& SegmentBuffer = Segment.AsIoBuffer();
-
- if (!SegmentBuffer.GetFileReference(Ref))
- {
- return false;
- }
-
- if (DenyPartialLocalReferences && !SegmentBuffer.IsWholeFile())
- {
- return false;
- }
-
- if (auto It = FileNameMap.find(Ref.FileHandle); It != FileNameMap.end())
- {
- Path8 = It->second;
- }
- else
- {
- bool UseFilePath = true;
-#if ZEN_PLATFORM_WINDOWS
- if (TargetProcessHandle != nullptr)
- {
- HANDLE TargetHandle = INVALID_HANDLE_VALUE;
- BOOL OK = ::DuplicateHandle(GetCurrentProcess(),
- Ref.FileHandle,
- (HANDLE)TargetProcessHandle,
- &TargetHandle,
- FILE_GENERIC_READ,
- FALSE,
- 0);
- if (OK)
- {
- DuplicatedHandles.push_back(TargetHandle);
- Path8 = fmt::format("{}{}", HandlePrefix, reinterpret_cast<uint64_t>(TargetHandle));
- UseFilePath = false;
- }
- }
-#else // ZEN_PLATFORM_WINDOWS
- ZEN_UNUSED(TargetProcessHandle);
- // Not supported on Linux/Mac. Could potentially use pidfd_getfd() but that requires a fairly new Linux kernel/includes and to
- // deal with acceess rights etc.
-#endif // ZEN_PLATFORM_WINDOWS
- if (UseFilePath)
- {
- ExtendablePathBuilder<256> LocalRefFile;
- LocalRefFile.Append(std::filesystem::absolute(PathFromHandle(Ref.FileHandle)));
- Path8 = LocalRefFile.ToUtf8();
- }
- FileNameMap.insert_or_assign(Ref.FileHandle, Path8);
- }
-
- LocalRef.AbsolutePathLength = gsl::narrow<uint16_t>(Path8.size());
- LocalRef.PayloadByteOffset = Ref.FileChunkOffset;
- LocalRef.PayloadByteSize = Ref.FileChunkSize;
-
- return true;
- };
-
for (const CbAttachment& Attachment : Attachments)
{
if (Attachment.IsNull())
@@ -226,7 +226,13 @@ FormatPackageMessage(const CbPackage& Data, FormatFlags Flags, int TargetProcess
if (MarshalByLocalRef)
{
- MarshalByLocalRef = IsLocalRef(Compressed, DenyPartialLocalReferences, TargetProcessHandle, LocalRef, Path8);
+ MarshalByLocalRef = IsLocalRef(FileNameMap,
+ DuplicatedHandles,
+ Compressed,
+ DenyPartialLocalReferences,
+ TargetProcessHandle,
+ LocalRef,
+ Path8);
}
if (MarshalByLocalRef)
@@ -236,7 +242,7 @@ FormatPackageMessage(const CbPackage& Data, FormatFlags Flags, int TargetProcess
#if ZEN_PLATFORM_WINDOWS
IsHandle = Path8.starts_with(HandlePrefix);
#endif
- MarshalLocal(Path8, LocalRef, AttachmentHash, IsCompressed);
+ MarshalLocal(AttachmentInfo, Path8, LocalRef, AttachmentHash, IsCompressed, ResponseBuffers);
ZEN_DEBUG("Marshalled '{}' as file {} of {} bytes", Path8, IsHandle ? "handle" : "path", Compressed.GetSize());
}
else
@@ -272,7 +278,13 @@ FormatPackageMessage(const CbPackage& Data, FormatFlags Flags, int TargetProcess
if (MarshalByLocalRef)
{
- MarshalByLocalRef = IsLocalRef(AttachmentBinary, DenyPartialLocalReferences, TargetProcessHandle, LocalRef, Path8);
+ MarshalByLocalRef = IsLocalRef(FileNameMap,
+ DuplicatedHandles,
+ AttachmentBinary,
+ DenyPartialLocalReferences,
+ TargetProcessHandle,
+ LocalRef,
+ Path8);
}
if (MarshalByLocalRef)
@@ -282,7 +294,7 @@ FormatPackageMessage(const CbPackage& Data, FormatFlags Flags, int TargetProcess
#if ZEN_PLATFORM_WINDOWS
IsHandle = Path8.starts_with(HandlePrefix);
#endif
- MarshalLocal(Path8, LocalRef, AttachmentHash, IsCompressed);
+ MarshalLocal(AttachmentInfo, Path8, LocalRef, AttachmentHash, IsCompressed, ResponseBuffers);
ZEN_DEBUG("Marshalled '{}' as file {} of {} bytes", Path8, IsHandle ? "handle" : "path", AttachmentBinary.GetSize());
}
else
@@ -332,6 +344,8 @@ IsPackageMessage(IoBuffer Payload)
CbPackage
ParsePackageMessage(IoBuffer Payload, std::function<IoBuffer(const IoHash&, uint64_t)> CreateBuffer)
{
+ ZEN_TRACE_CPU("ParsePackageMessage");
+
if (!Payload)
{
return {};
diff --git a/src/zenhttp/include/zenhttp/httpshared.h b/src/zenhttp/include/zenhttp/httpshared.h
index d335572c5..cf74b6b21 100644
--- a/src/zenhttp/include/zenhttp/httpshared.h
+++ b/src/zenhttp/include/zenhttp/httpshared.h
@@ -94,8 +94,8 @@ enum class RpcAcceptOptions : uint16_t
gsl_DEFINE_ENUM_BITMASK_OPERATORS(RpcAcceptOptions);
-std::vector<IoBuffer> FormatPackageMessage(const CbPackage& Data, FormatFlags Flags, int TargetProcessPid = 0);
-CompositeBuffer FormatPackageMessageBuffer(const CbPackage& Data, FormatFlags Flags, int TargetProcessPid = 0);
+std::vector<IoBuffer> FormatPackageMessage(const CbPackage& Data, FormatFlags Flags, void* TargetProcessHandle = nullptr);
+CompositeBuffer FormatPackageMessageBuffer(const CbPackage& Data, FormatFlags Flags, void* TargetProcessHandle = nullptr);
CbPackage ParsePackageMessage(
IoBuffer Payload,
std::function<IoBuffer(const IoHash& Cid, uint64_t Size)> CreateBuffer = [](const IoHash&, uint64_t Size) -> IoBuffer {
@@ -105,8 +105,8 @@ bool IsPackageMessage(IoBuffer Payload);
bool ParsePackageMessageWithLegacyFallback(const IoBuffer& Response, CbPackage& OutPackage);
-std::vector<IoBuffer> FormatPackageMessage(const CbPackage& Data, int TargetProcessPid = 0);
-CompositeBuffer FormatPackageMessageBuffer(const CbPackage& Data, int TargetProcessPid = 0);
+std::vector<IoBuffer> FormatPackageMessage(const CbPackage& Data, void* TargetProcessHandle = nullptr);
+CompositeBuffer FormatPackageMessageBuffer(const CbPackage& Data, void* TargetProcessHandle = nullptr);
/** Streaming reader for compact binary packages
diff --git a/src/zenserver/cache/httpstructuredcache.cpp b/src/zenserver/cache/httpstructuredcache.cpp
index 51d3d94d8..9b7ec61d7 100644
--- a/src/zenserver/cache/httpstructuredcache.cpp
+++ b/src/zenserver/cache/httpstructuredcache.cpp
@@ -1652,7 +1652,8 @@ HttpStructuredCacheService::ReplayRequestRecorder(const CacheRequestContext& Co
{
if (AcceptMagic == kCbPkgMagic)
{
- FormatFlags Flags = FormatFlags::kDefault;
+ void* TargetProcessHandle = nullptr;
+ FormatFlags Flags = FormatFlags::kDefault;
if (EnumHasAllFlags(AcceptFlags, RpcAcceptOptions::kAllowLocalReferences))
{
Flags |= FormatFlags::kAllowLocalReferences;
@@ -1660,8 +1661,9 @@ HttpStructuredCacheService::ReplayRequestRecorder(const CacheRequestContext& Co
{
Flags |= FormatFlags::kDenyPartialLocalReferences;
}
+ TargetProcessHandle = m_OpenProcessCache.GetProcessHandle(Context.SessionId, TargetPid);
}
- CompositeBuffer RpcResponseBuffer = FormatPackageMessageBuffer(RpcResult, Flags, TargetPid);
+ CompositeBuffer RpcResponseBuffer = FormatPackageMessageBuffer(RpcResult, Flags, TargetProcessHandle);
ZEN_ASSERT(RpcResponseBuffer.GetSize() > 0);
}
else
@@ -1705,6 +1707,7 @@ HttpStructuredCacheService::HandleRpcRequest(HttpServerRequest& Request)
Request.WriteResponseAsync(
[this, RequestContext, Body = Request.ReadPayload(), ContentType, AcceptType](HttpServerRequest& AsyncRequest) mutable {
+ ZEN_TRACE_CPU("z$::Http::HandleRpcRequest::WriteResponseAsync");
std::uint64_t RequestIndex =
m_RequestRecorder ? m_RequestRecorder->RecordRequest(ContentType, AcceptType, Body) : ~0ull;
uint32_t AcceptMagic = 0;
@@ -1726,7 +1729,8 @@ HttpStructuredCacheService::HandleRpcRequest(HttpServerRequest& Request)
}
if (AcceptMagic == kCbPkgMagic)
{
- FormatFlags Flags = FormatFlags::kDefault;
+ void* TargetProcessHandle = nullptr;
+ FormatFlags Flags = FormatFlags::kDefault;
if (EnumHasAllFlags(AcceptFlags, RpcAcceptOptions::kAllowLocalReferences))
{
Flags |= FormatFlags::kAllowLocalReferences;
@@ -1734,8 +1738,9 @@ HttpStructuredCacheService::HandleRpcRequest(HttpServerRequest& Request)
{
Flags |= FormatFlags::kDenyPartialLocalReferences;
}
+ TargetProcessHandle = m_OpenProcessCache.GetProcessHandle(RequestContext.SessionId, TargetProcessId);
}
- CompositeBuffer RpcResponseBuffer = FormatPackageMessageBuffer(RpcResult, Flags, TargetProcessId);
+ CompositeBuffer RpcResponseBuffer = FormatPackageMessageBuffer(RpcResult, Flags, TargetProcessHandle);
if (RequestIndex != ~0ull)
{
ZEN_ASSERT(m_RequestRecorder);
@@ -2400,9 +2405,11 @@ HttpStructuredCacheService::HandleRpcGetCacheValues(const CacheRequestContext& C
std::vector<size_t> RemoteRequestIndexes;
- const bool HasUpstream = m_UpstreamCache.IsActive();
+ const bool HasUpstream = m_UpstreamCache.IsActive();
+ CbArrayView RequestView = Params["Requests"sv].AsArrayView();
+ Requests.reserve(RequestView.Num());
- for (CbFieldView RequestField : Params["Requests"sv])
+ for (CbFieldView RequestField : RequestView)
{
Stopwatch Timer;
diff --git a/src/zenserver/cache/httpstructuredcache.h b/src/zenserver/cache/httpstructuredcache.h
index 60714d6ae..7ad3f5ac8 100644
--- a/src/zenserver/cache/httpstructuredcache.h
+++ b/src/zenserver/cache/httpstructuredcache.h
@@ -7,6 +7,7 @@
#include <zenhttp/httpstats.h>
#include <zenhttp/httpstatus.h>
#include <zenutil/cache/cache.h>
+#include <zenutil/openprocesscache.h>
#include <memory>
#include <vector>
@@ -180,6 +181,7 @@ private:
metrics::OperationTiming m_UpstreamGetRequestTiming;
CacheStats m_CacheStats;
const DiskWriteBlocker* m_DiskWriteBlocker = nullptr;
+ OpenProcessCache m_OpenProcessCache;
void ReplayRequestRecorder(const CacheRequestContext& Context, cache::IRpcRequestReplayer& Replayer, uint32_t ThreadCount);
diff --git a/src/zenutil/include/zenutil/openprocesscache.h b/src/zenutil/include/zenutil/openprocesscache.h
new file mode 100644
index 000000000..3db4e0b42
--- /dev/null
+++ b/src/zenutil/include/zenutil/openprocesscache.h
@@ -0,0 +1,39 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <zencore/thread.h>
+#include <zencore/uid.h>
+
+#include <thread>
+#include <unordered_map>
+
+namespace zen {
+
+class OpenProcessCache
+{
+public:
+ OpenProcessCache();
+ ~OpenProcessCache();
+
+ void* GetProcessHandle(Oid SessionId, int ProcessPid);
+
+private:
+#if ZEN_PLATFORM_WINDOWS
+ struct Process
+ {
+ int ProcessPid;
+ void* ProcessHandle;
+ };
+
+ void GCHandles();
+ void GcWorker();
+
+ RwLock m_SessionsLock;
+ std::unordered_map<Oid, Process, Oid::Hasher> m_Sessions;
+ std::thread m_GcThread;
+ Event m_GcExitEvent;
+#endif // ZEN_PLATFORM_WINDOWS
+};
+
+} // namespace zen
diff --git a/src/zenutil/openprocesscache.cpp b/src/zenutil/openprocesscache.cpp
new file mode 100644
index 000000000..6c8bf3de9
--- /dev/null
+++ b/src/zenutil/openprocesscache.cpp
@@ -0,0 +1,150 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "zenutil/openprocesscache.h"
+#include <zencore/fmtutils.h>
+#include <zencore/logging.h>
+
+#if ZEN_PLATFORM_WINDOWS
+# include <zencore/windows.h>
+#else
+# include <sys/mman.h>
+#endif
+
+//////////////////////////////////////////////////////////////////////////
+
+namespace zen {
+
+OpenProcessCache::OpenProcessCache()
+#if ZEN_PLATFORM_WINDOWS
+: m_GcThread(&OpenProcessCache::GcWorker, this)
+#endif // ZEN_PLATFORM_WINDOWS
+{
+}
+
+OpenProcessCache::~OpenProcessCache()
+{
+#if ZEN_PLATFORM_WINDOWS
+ try
+ {
+ m_GcExitEvent.Set();
+ if (m_GcThread.joinable())
+ {
+ m_GcThread.join();
+ }
+ RwLock::ExclusiveLockScope Lock(m_SessionsLock);
+ for (const auto& It : m_Sessions)
+ {
+ if (It.second.ProcessHandle == nullptr)
+ {
+ continue;
+ }
+ CloseHandle(It.second.ProcessHandle);
+ }
+ m_Sessions.clear();
+ }
+ catch (std::exception& Ex)
+ {
+ ZEN_ERROR("OpenProcessCache destructor failed with reason: `{}`", Ex.what());
+ }
+#endif // ZEN_PLATFORM_WINDOWS
+}
+
+void*
+OpenProcessCache::GetProcessHandle(Oid SessionId, int ProcessPid)
+{
+ if (ProcessPid == 0)
+ {
+ return nullptr;
+ }
+#if ZEN_PLATFORM_WINDOWS
+ RwLock::ExclusiveLockScope Lock(m_SessionsLock);
+
+ void* DeadHandle = nullptr;
+ if (auto It = m_Sessions.find(SessionId); It != m_Sessions.end())
+ {
+ if (ProcessPid == It->second.ProcessPid)
+ {
+ void* ProcessHandle = It->second.ProcessHandle;
+ ZEN_ASSERT(ProcessHandle != nullptr);
+ DWORD ExitCode = 0;
+ GetExitCodeProcess(It->second.ProcessHandle, &ExitCode);
+ if (ExitCode == STILL_ACTIVE)
+ {
+ return It->second.ProcessHandle;
+ }
+ }
+
+ // Remove the existing stale handle
+ ZEN_INFO("Closing stale target process pid {} for session: {}", It->second.ProcessPid, SessionId, It->second.ProcessHandle);
+ DeadHandle = It->second.ProcessHandle;
+ m_Sessions.erase(It);
+ }
+
+ // Cache new handle
+ void* ProcessHandle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_DUP_HANDLE, FALSE, ProcessPid);
+ ZEN_INFO("Opening target process pid {} for session {}: {}", ProcessPid, SessionId, ProcessHandle == nullptr ? "failed" : "success");
+ m_Sessions.insert_or_assign(SessionId, Process{.ProcessPid = ProcessPid, .ProcessHandle = ProcessHandle});
+ Lock.ReleaseNow();
+
+ if (DeadHandle != nullptr)
+ {
+ CloseHandle(DeadHandle);
+ }
+
+ return ProcessHandle;
+#else // ZEN_PLATFORM_WINDOWS
+ ZEN_UNUSED(SessionId);
+ return nullptr;
+#endif // ZEN_PLATFORM_WINDOWS
+}
+
+#if ZEN_PLATFORM_WINDOWS
+void
+OpenProcessCache::GCHandles()
+{
+ std::vector<void*> DeadHandles;
+ RwLock::ExclusiveLockScope Lock(m_SessionsLock);
+ for (auto& It : m_Sessions)
+ {
+ if (It.second.ProcessHandle == nullptr)
+ {
+ continue;
+ }
+ ZEN_ASSERT(It.second.ProcessPid != 0);
+ DWORD ExitCode = 0;
+ GetExitCodeProcess(It.second.ProcessHandle, &ExitCode);
+ if (ExitCode == STILL_ACTIVE)
+ {
+ continue;
+ }
+ ZEN_INFO("GC stale target process pid {} for session {}: {}", It.second.ProcessPid, It.first, It.second.ProcessHandle);
+ DeadHandles.push_back(It.second.ProcessHandle);
+ It.second.ProcessPid = 0;
+ It.second.ProcessHandle = nullptr;
+ }
+ Lock.ReleaseNow();
+
+ for (auto Handle : DeadHandles)
+ {
+ CloseHandle(Handle);
+ }
+}
+
+void
+OpenProcessCache::GcWorker()
+{
+ while (!m_GcExitEvent.Wait(500))
+ {
+ try
+ {
+ GCHandles();
+ }
+ catch (std::exception& Ex)
+ {
+ ZEN_ERROR("gc of open process cache failed with reason: `{}`", Ex.what());
+ }
+ }
+}
+#endif // ZEN_PLATFORM_WINDOWS
+
+} // namespace zen