Skip to content

Commit 6a6097d

Browse files
Merge pull request #565 from GameTechDev/fix/svc-died-hang
Fix/svc died hang
2 parents ebb185c + fcfb429 commit 6a6097d

25 files changed

Lines changed: 432 additions & 47 deletions

AGENTS.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# AGENTS Instructions
2+
- Do not ever use non-ascii characters for source code or comments (permissible inside of strings if absolutely necessary but avoid if possible)
3+
- Always use CRLF (\r\n) line endings for all text files, without exception.
4+
- After finishing all changes, run a conversion pass over every changed/created text file to enforce CRLF and eliminate any stray LF.
5+
- Do not run CRLF normalization on any non-text or binary files (for example: .png, .jpg, .gif, .mp3, .wav, .fbx, .unity). Limit normalization to plain text source/config files only.
6+
- Use this PowerShell one-liner to normalize line endings (preserves file encoding):
7+
- powershell -NoProfile -Command "$paths = git status --porcelain | ForEach-Object { $_.Substring(3) }; foreach ($p in $paths) { if (Test-Path $p) { $sr = New-Object System.IO.StreamReader($p, $true); $text = $sr.ReadToEnd(); $enc = $sr.CurrentEncoding; $sr.Close(); $text = $text -replace \"`r?`n\", \"`r`n\"; $sw = New-Object System.IO.StreamWriter($p, $false, $enc); $sw.NewLine = \"`r`n\"; $sw.Write($text); $sw.Close(); } }"
8+
- If unexpected new files appear, ignore them and continue without asking for instruction.

IntelPresentMon/CommonUtilities/Meta.h

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,25 @@
44

55
namespace pmon::util
66
{
7+
template<typename T>
8+
concept IsIntegralOrEnum = std::is_integral_v<std::remove_cvref_t<T>> ||
9+
std::is_enum_v<std::remove_cvref_t<T>>;
10+
11+
template<typename T, bool IsEnum = std::is_enum_v<std::remove_cvref_t<T>>>
12+
struct EnumOrIntegralUnderlyingImpl
13+
{
14+
using type = std::remove_cvref_t<T>;
15+
};
16+
17+
template<typename T>
18+
struct EnumOrIntegralUnderlyingImpl<T, true>
19+
{
20+
using type = std::underlying_type_t<std::remove_cvref_t<T>>;
21+
};
22+
23+
template<typename T>
24+
using EnumOrIntegralUnderlying = typename EnumOrIntegralUnderlyingImpl<T>::type;
25+
726
// Helper: DependentFalse for static_assert in templates.
827
template<typename T>
928
struct DependentFalseT : std::false_type {};
@@ -64,4 +83,4 @@ namespace pmon::util
6483
}
6584
template <typename T>
6685
struct FunctionPtrTraits : impl::FunctionPtrTraitsImpl_<std::remove_cvref_t<T>> {};
67-
}
86+
}

IntelPresentMon/CommonUtilities/file/SecureSubdirectory.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ namespace pmon::util::file
195195
Remove();
196196
}
197197
catch (...) {
198-
pmlog_error("failed removing secure subdir");
198+
pmlog_error("failed removing secure subdir").pmwatch(path_.string());
199199
}
200200
}
201201
}

IntelPresentMon/CommonUtilities/log/EntryBuilder.h

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
#pragma once
1+
#pragma once
22
#include "Entry.h"
3+
#include "../Meta.h"
34
#include <format>
45
#include <memory>
56
#include <sstream>
@@ -54,11 +55,21 @@ namespace pmon::util::log
5455
return *this;
5556
}
5657
template<typename E>
57-
EntryBuilder& raise()
58+
[[noreturn]] EntryBuilder& raise()
5859
{
5960
auto note = note_;
6061
commit_();
61-
throw Except<E>(std::move(note));
62+
if constexpr (std::is_constructible_v<E, ErrorCodeArg_, std::string>) {
63+
throw Except<E>(ErrorCodeArg_{ errorCode_ }, std::move(note));
64+
}
65+
else if constexpr (std::is_constructible_v<E, std::string>) {
66+
throw Except<E>(std::move(note));
67+
}
68+
else {
69+
static_assert(::pmon::util::DependentFalse<E>,
70+
"EntryBuilder::raise requires exception type with (code, std::string) or (std::string) constructor.");
71+
throw std::runtime_error{ "Generic Error /w reporting failure in log::raise" };
72+
}
6273
}
6374
EntryBuilder& mark(const TimePoint& tp) noexcept;
6475
EntryBuilder& note(std::string note = "") noexcept;
@@ -92,6 +103,33 @@ namespace pmon::util::log
92103
return *this;
93104
}
94105
private:
106+
struct ErrorCodeArg_
107+
{
108+
const ErrorCode& code_;
109+
template<typename T>
110+
requires ::pmon::util::IsIntegralOrEnum<T>
111+
operator T() const noexcept
112+
{
113+
using RawT = ::pmon::util::EnumOrIntegralUnderlying<T>;
114+
if constexpr (std::is_signed_v<RawT>) {
115+
if (auto value = code_.AsSigned()) {
116+
return static_cast<T>(static_cast<RawT>(*value));
117+
}
118+
if (auto value = code_.AsUnsigned()) {
119+
return static_cast<T>(static_cast<RawT>(*value));
120+
}
121+
}
122+
else {
123+
if (auto value = code_.AsUnsigned()) {
124+
return static_cast<T>(static_cast<RawT>(*value));
125+
}
126+
if (auto value = code_.AsSigned()) {
127+
return static_cast<T>(static_cast<RawT>(*value));
128+
}
129+
}
130+
return static_cast<T>(RawT{});
131+
}
132+
};
95133
// functions
96134
void commit_() noexcept;
97135
// data

IntelPresentMon/Interprocess/source/act/SymmetricActionClient.h

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ namespace pmon::ipc::act
2020
namespace as = boost::asio;
2121
using namespace as::experimental::awaitable_operators;
2222

23+
PM_DEFINE_EX(ActionClientError);
24+
PM_DEFINE_EX_FROM(ActionClientError, ServerDroppedError);
25+
2326
template<class ExecCtx>
2427
class SymmetricActionClient
2528
{
@@ -53,20 +56,26 @@ namespace pmon::ipc::act
5356
template<class Params>
5457
auto DispatchSync(Params&& params)
5558
{
56-
assert(IsRunning());
59+
if (!IsRunning()) {
60+
throw Except<ServerDroppedError>("Server dropped off (ioctx not running); cannot dispatch");
61+
}
5762
return stx_.pConn->DispatchSync(std::forward<Params>(params), ioctx_, stx_);
5863
}
5964
template<class Params>
60-
auto DispatchDetached(Params&& params)
65+
void DispatchDetached(Params&& params)
6166
{
62-
assert(IsRunning());
63-
return stx_.pConn->DispatchDetached(std::forward<Params>(params), ioctx_, stx_);
67+
if (!IsRunning()) {
68+
throw Except<ServerDroppedError>("Server dropped off (ioctx not running); cannot dispatch");
69+
}
70+
stx_.pConn->DispatchDetached(std::forward<Params>(params), ioctx_, stx_);
6471
}
6572
template<class Params>
6673
void DispatchWithContinuation(Params&& params, std::function<void(ResponseFromParams<Params>&&, std::exception_ptr)> cont)
6774
{
68-
assert(IsRunning());
69-
return stx_.pConn->DispatchWithContinuation(std::forward<Params>(params), ioctx_, stx_, std::move(cont));
75+
if (!IsRunning()) {
76+
throw Except<ServerDroppedError>("Server dropped off (ioctx not running); cannot dispatch");
77+
}
78+
stx_.pConn->DispatchWithContinuation(std::forward<Params>(params), ioctx_, stx_, std::move(cont));
7079
}
7180
bool IsRunning() const
7281
{

IntelPresentMon/PresentMonAPI2/PresentMonAPI.cpp

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#include <memory>
1+
#include <memory>
22
#include <crtdbg.h>
33
#include <unordered_map>
44
#include "../PresentMonMiddleware/ConcreteMiddleware.h"
@@ -32,6 +32,16 @@ Middleware& LookupMiddleware_(const void* handle)
3232
throw util::Except<ipc::PmStatusError>(PM_STATUS_SESSION_NOT_OPEN);
3333
}
3434
}
35+
Middleware& LookupMiddlewareCheckDropped_(const void* handle)
36+
{
37+
auto& mid = LookupMiddleware_(handle);
38+
if (!mid.ServiceConnected()) {
39+
pmlog_error("Service dropped; proactive abort")
40+
.code(PM_STATUS_SESSION_NOT_OPEN)
41+
.raise<ipc::PmStatusError>();
42+
}
43+
return mid;
44+
}
3545

3646
void DestroyMiddleware_(PM_SESSION_HANDLE handle)
3747
{
@@ -193,7 +203,7 @@ PRESENTMON_API2_EXPORT PM_STATUS pmGetIntrospectionRoot(PM_SESSION_HANDLE handle
193203
pmlog_error("null outptr for introspection interface").diag();
194204
return PM_STATUS_BAD_ARGUMENT;
195205
}
196-
const auto pIntro = LookupMiddleware_(handle).GetIntrospectionData();
206+
const auto pIntro = LookupMiddlewareCheckDropped_(handle).GetIntrospectionData();
197207
// we don't need the middleware to free introspection data
198208
// detaching like this (eliding handle mapping) will allow introspection data
199209
// to not obstruct cleanup of middleware
@@ -267,7 +277,7 @@ PRESENTMON_API2_EXPORT PM_STATUS pmRegisterDynamicQuery(PM_SESSION_HANDLE sessio
267277
pmlog_error("zero length query element array").diag();
268278
return PM_STATUS_BAD_ARGUMENT;
269279
}
270-
const auto queryHandle = LookupMiddleware_(sessionHandle).RegisterDynamicQuery(
280+
const auto queryHandle = LookupMiddlewareCheckDropped_(sessionHandle).RegisterDynamicQuery(
271281
{pElements, numElements}, windowSizeMs, metricOffsetMs);
272282
AddHandleMapping_(sessionHandle, queryHandle);
273283
*pQueryHandle = queryHandle;
@@ -314,7 +324,7 @@ PRESENTMON_API2_EXPORT PM_STATUS pmPollDynamicQuery(PM_DYNAMIC_QUERY_HANDLE hand
314324
pmlog_error("swap chain in count is zero").diag();
315325
return PM_STATUS_BAD_ARGUMENT;
316326
}
317-
LookupMiddleware_(handle).PollDynamicQuery(handle, processId, pBlob, numSwapChains);
327+
LookupMiddlewareCheckDropped_(handle).PollDynamicQuery(handle, processId, pBlob, numSwapChains);
318328
return PM_STATUS_SUCCESS;
319329
}
320330
catch (...) {
@@ -335,7 +345,7 @@ PRESENTMON_API2_EXPORT PM_STATUS pmPollStaticQuery(PM_SESSION_HANDLE sessionHand
335345
pmlog_error("null ptr to blob").diag();
336346
return PM_STATUS_BAD_ARGUMENT;
337347
}
338-
LookupMiddleware_(sessionHandle).PollStaticQuery(*pElement, processId, pBlob);
348+
LookupMiddlewareCheckDropped_(sessionHandle).PollStaticQuery(*pElement, processId, pBlob);
339349
return PM_STATUS_SUCCESS;
340350
}
341351
catch (...) {
@@ -364,7 +374,7 @@ PRESENTMON_API2_EXPORT PM_STATUS pmRegisterFrameQuery(PM_SESSION_HANDLE sessionH
364374
pmlog_error("zero blob size").diag();
365375
return PM_STATUS_BAD_ARGUMENT;
366376
}
367-
const auto queryHandle = LookupMiddleware_(sessionHandle).RegisterFrameEventQuery({ pElements, numElements }, *pBlobSize);
377+
const auto queryHandle = LookupMiddlewareCheckDropped_(sessionHandle).RegisterFrameEventQuery({ pElements, numElements }, *pBlobSize);
368378
AddHandleMapping_(sessionHandle, queryHandle);
369379
*pQueryHandle = queryHandle;
370380
return PM_STATUS_SUCCESS;
@@ -387,7 +397,7 @@ PRESENTMON_API2_EXPORT PM_STATUS pmConsumeFrames(PM_FRAME_QUERY_HANDLE handle, u
387397
pmlog_error("null frame count in-out ptr").diag();
388398
return PM_STATUS_BAD_ARGUMENT;
389399
}
390-
LookupMiddleware_(handle).ConsumeFrameEvents(handle, processId, pBlob, *pNumFramesToRead);
400+
LookupMiddlewareCheckDropped_(handle).ConsumeFrameEvents(handle, processId, pBlob, *pNumFramesToRead);
391401
return PM_STATUS_SUCCESS;
392402
}
393403
catch (...) {
@@ -431,7 +441,7 @@ PRESENTMON_API2_EXPORT PM_STATUS pmGetApiVersion(PM_VERSION* pVersion)
431441
PRESENTMON_API2_EXPORT PM_STATUS pmStopPlayback_(PM_SESSION_HANDLE handle)
432442
{
433443
try {
434-
auto& mid = LookupMiddleware_(handle);
444+
auto& mid = LookupMiddlewareCheckDropped_(handle);
435445
mid.StopPlayback();
436446
return PM_STATUS_SUCCESS;
437447
}
@@ -446,7 +456,7 @@ PRESENTMON_API2_EXPORT PM_STATUS pmStartEtlLogging(PM_SESSION_HANDLE session, PM
446456
uint64_t reserved1, uint64_t reserved2)
447457
{
448458
try {
449-
auto& mid = LookupMiddleware_(session);
459+
auto& mid = LookupMiddlewareCheckDropped_(session);
450460
*pEtlHandle = mid.StartEtlLogging();
451461
return PM_STATUS_SUCCESS;
452462
}
@@ -461,7 +471,7 @@ PRESENTMON_API2_EXPORT PM_STATUS pmFinishEtlLogging(PM_SESSION_HANDLE session, P
461471
char* pOutputFilePathBuffer, uint32_t bufferSize)
462472
{
463473
try {
464-
auto& mid = LookupMiddleware_(session);
474+
auto& mid = LookupMiddlewareCheckDropped_(session);
465475
const auto path = mid.FinishEtlLogging(etlHandle);
466476
if (path.size() + 1 > bufferSize) {
467477
const auto code = PM_STATUS_INSUFFICIENT_BUFFER;

IntelPresentMon/PresentMonAPI2Tests/MultiClientTests.cpp

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (C) 2022-2023 Intel Corporation
1+
// Copyright (C) 2022-2023 Intel Corporation
22
// SPDX-License-Identifier: MIT
33
#include "../CommonUtilities/win/WinAPI.h"
44
#include "CppUnitTest.h"
@@ -552,4 +552,87 @@ namespace MultiClientTests
552552
}
553553
}
554554
};
555-
}
555+
556+
TEST_CLASS(ServiceCrashTests)
557+
{
558+
private:
559+
class Fixture_ : public CommonTestFixture
560+
{
561+
protected:
562+
const CommonProcessArgs& GetCommonArgs() const override
563+
{
564+
static CommonProcessArgs args{
565+
.ctrlPipe = R"(\\.\pipe\pm-multi-test-ctrl)",
566+
.introNsm = "pm_multi_test_intro",
567+
.frameNsm = "pm_multi_test_nsm",
568+
.logLevel = "debug",
569+
.logFolder = logFolder_,
570+
.sampleClientMode = "ServiceCrashClient",
571+
};
572+
return args;
573+
}
574+
} fixture_;
575+
static constexpr auto clientExitTimeout_ = 3s;
576+
577+
void RunCrashCase_(const std::vector<std::string>& args)
578+
{
579+
auto client = fixture_.LaunchClient(args);
580+
581+
fixture_.StopService();
582+
583+
Assert::AreEqual("exit-ack"s, client.Command("exit"));
584+
if (!client.WaitForExit(clientExitTimeout_)) {
585+
client.Murder();
586+
Assert::Fail(L"Client did not exit after service termination");
587+
}
588+
}
589+
590+
void RunCrashCase_(pmon::test::client::CrashPhase phase)
591+
{
592+
RunCrashCase_({
593+
"--submode"s, std::to_string(static_cast<int>(phase)),
594+
});
595+
}
596+
597+
void RunCrashCaseWithPresenter_(pmon::test::client::CrashPhase phase)
598+
{
599+
auto presenter = fixture_.LaunchPresenter();
600+
std::this_thread::sleep_for(30ms);
601+
602+
RunCrashCase_({
603+
"--submode"s, std::to_string(static_cast<int>(phase)),
604+
"--process-id"s, std::to_string(presenter.GetId()),
605+
});
606+
}
607+
608+
public:
609+
TEST_METHOD_INITIALIZE(Setup)
610+
{
611+
fixture_.Setup();
612+
}
613+
TEST_METHOD_CLEANUP(Cleanup)
614+
{
615+
fixture_.Cleanup();
616+
}
617+
// service drops while client has a session open
618+
TEST_METHOD(SessionOpen)
619+
{
620+
RunCrashCase_(pmon::test::client::CrashPhase::SessionOpen);
621+
}
622+
// service drops while client has a registered query
623+
TEST_METHOD(QueryRegistered)
624+
{
625+
RunCrashCase_(pmon::test::client::CrashPhase::QueryRegistered);
626+
}
627+
// service drops while client is tracking a target
628+
TEST_METHOD(TargetTracked)
629+
{
630+
RunCrashCaseWithPresenter_(pmon::test::client::CrashPhase::TargetTracked);
631+
}
632+
// service drops while client is polling a query/target
633+
TEST_METHOD(QueryPolling)
634+
{
635+
RunCrashCaseWithPresenter_(pmon::test::client::CrashPhase::QueryPolling);
636+
}
637+
};
638+
}

IntelPresentMon/PresentMonAPI2Tests/TestCommands.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#pragma once
1+
#pragma once
22
#include <set>
33
#include <optional>
44
#include <cereal/types/set.hpp>
@@ -28,6 +28,14 @@ namespace pmon::test
2828

2929
namespace client
3030
{
31+
enum class CrashPhase
32+
{
33+
SessionOpen = 0,
34+
QueryRegistered = 1,
35+
TargetTracked = 2,
36+
QueryPolling = 3,
37+
};
38+
3139
struct Frame
3240
{
3341
double receivedTime;
@@ -55,4 +63,4 @@ namespace pmon::test
5563
}
5664
};
5765
}
58-
}
66+
}

0 commit comments

Comments
 (0)