Skip to content

Commit 05bb0b9

Browse files
authored
Fix/concurrent launches (#635)
* concurrent process gate * add tests cases for concurrent launch * show dialog to choose terminate or bring foreground 2nd instance spawn * job to ensure cef processes terminate * improve pipe connection behavior on UI launch * improve behavior under test
1 parent 5ade52e commit 05bb0b9

14 files changed

Lines changed: 767 additions & 86 deletions

File tree

IntelPresentMon/AppCef/CefNano.vcxproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
<ClInclude Include="source\util\SignalManager.h" />
7777
<ClInclude Include="source\util\FontEnumerator.h" />
7878
<ClInclude Include="source\util\ThreadCompass.h" />
79+
<ClInclude Include="source\util\UiProcessGuard.h" />
7980
<ClInclude Include="source\util\V8Transfer.h" />
8081
</ItemGroup>
8182
<ItemGroup>
@@ -215,4 +216,4 @@
215216
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
216217
<ImportGroup Label="ExtensionTargets">
217218
</ImportGroup>
218-
</Project>
219+
</Project>

IntelPresentMon/AppCef/source/util/CliOptions.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
22
#include <CommonUtilities/cli/CliFramework.h>
33
#include <CommonUtilities/log/Level.h>
4+
#include "UiProcessGuard.h"
45

56
namespace p2c::client::util::cli
67
{
@@ -21,6 +22,7 @@ namespace p2c::client::util::cli
2122
Flag traceExceptions{ this, "--p2c-trace-exceptions", "Add stack trace to all thrown exceptions (including SEH exceptions)" };
2223
Flag enableUiDevOptions{ this, "--p2c-enable-ui-dev-options", "Enable advanced UI elements useful during development" };
2324
Option<std::string> webRoot{ this, "--p2c-web-root", "", "Filesystem path to directory holding SPA assets" };
25+
Option<std::string> uiMutexName{ this, "--p2c-ui-mutex-name", DefaultUiBrowserProcessMutexSuffix, "Suffix for the UI browser process mutex name" };
2426
Flag enableChromiumDebug{ this, "--p2c-enable-chromium-debug", "Enable Chromium devtools connections on port 9009" };
2527

2628

@@ -45,4 +47,4 @@ namespace p2c::client::util::cli
4547
NoForward noForward_{ cefType, logPipeName, logVerboseModules };
4648
AllowExtras ext_{ this };
4749
};
48-
}
50+
}
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
#pragma once
2+
#include <CommonUtilities/win/Handle.h>
3+
#include <CommonUtilities/win/ProcessMapBuilder.h>
4+
#include <CommonUtilities/win/WinAPI.h>
5+
#include <array>
6+
#include <algorithm>
7+
#include <string>
8+
#include <string_view>
9+
#include <utility>
10+
#include <vector>
11+
12+
namespace p2c::client::util
13+
{
14+
constexpr const wchar_t* UiBrowserProcessMutexPrefix = L"Local\\IntelPresentMon.";
15+
constexpr const char* DefaultUiBrowserProcessMutexSuffix = "UiBrowserProcess";
16+
constexpr const wchar_t* UiBrowserWindowClassName = L"BrowserWindowClass";
17+
constexpr const wchar_t* UiBrowserWindowTitle = L"Intel PresentMon";
18+
constexpr const wchar_t* UiBrowserWindowMutexSuffixProperty = L"IntelPresentMon.UiMutexSuffixAtom";
19+
constexpr int UiAlreadyRunningExitCode = 2;
20+
21+
inline std::wstring MakeUiBrowserProcessMutexName(std::string_view suffix)
22+
{
23+
std::wstring name{ UiBrowserProcessMutexPrefix };
24+
if (suffix.empty()) {
25+
suffix = DefaultUiBrowserProcessMutexSuffix;
26+
}
27+
name.append(suffix.begin(), suffix.end());
28+
return name;
29+
}
30+
31+
inline bool IsUiBrowserProcessActive(std::string_view mutexSuffix)
32+
{
33+
const auto mutexName = MakeUiBrowserProcessMutexName(mutexSuffix);
34+
::pmon::util::win::Handle hMutex{ OpenMutexW(SYNCHRONIZE, FALSE, mutexName.c_str()) };
35+
return bool(hMutex);
36+
}
37+
38+
inline std::pair<::pmon::util::win::Handle, bool> TryAcquireUiBrowserProcessMutex(std::string_view mutexSuffix)
39+
{
40+
const auto mutexName = MakeUiBrowserProcessMutexName(mutexSuffix);
41+
::pmon::util::win::Handle hMutex{ CreateMutexW(nullptr, FALSE, mutexName.c_str()) };
42+
if (!hMutex) {
43+
return { {}, false };
44+
}
45+
return { std::move(hMutex), GetLastError() != ERROR_ALREADY_EXISTS };
46+
}
47+
48+
inline void SetUiBrowserWindowMutexSuffix(HWND hWnd, std::string_view mutexSuffix)
49+
{
50+
if (mutexSuffix.empty()) {
51+
mutexSuffix = DefaultUiBrowserProcessMutexSuffix;
52+
}
53+
const ATOM suffixAtom = GlobalAddAtomA(std::string{ mutexSuffix }.c_str());
54+
if (suffixAtom != 0) {
55+
if (!SetPropW(hWnd, UiBrowserWindowMutexSuffixProperty, (HANDLE)(ULONG_PTR)suffixAtom)) {
56+
GlobalDeleteAtom(suffixAtom);
57+
}
58+
}
59+
}
60+
61+
inline void ClearUiBrowserWindowMutexSuffix(HWND hWnd)
62+
{
63+
if (const auto suffixAtom = (ATOM)(ULONG_PTR)RemovePropW(hWnd, UiBrowserWindowMutexSuffixProperty)) {
64+
GlobalDeleteAtom(suffixAtom);
65+
}
66+
}
67+
68+
inline bool UiBrowserWindowMutexSuffixMatches(HWND hWnd, std::string_view mutexSuffix)
69+
{
70+
if (mutexSuffix.empty()) {
71+
mutexSuffix = DefaultUiBrowserProcessMutexSuffix;
72+
}
73+
const auto suffixAtom = (ATOM)(ULONG_PTR)GetPropW(hWnd, UiBrowserWindowMutexSuffixProperty);
74+
if (suffixAtom == 0) {
75+
return false;
76+
}
77+
std::array<char, MAX_PATH> suffix{};
78+
if (GlobalGetAtomNameA(suffixAtom, suffix.data(), (int)suffix.size()) == 0) {
79+
return false;
80+
}
81+
return std::string_view{ suffix.data() } == mutexSuffix;
82+
}
83+
84+
inline BOOL CALLBACK FindUiBrowserWindowCallback(HWND hWnd, LPARAM lParam)
85+
{
86+
auto& params = *reinterpret_cast<std::pair<std::string_view, HWND>*>(lParam);
87+
if (!IsWindowVisible(hWnd)) {
88+
return TRUE;
89+
}
90+
91+
std::array<wchar_t, MAX_PATH> className{};
92+
if (GetClassNameW(hWnd, className.data(), (int)className.size()) == 0 ||
93+
std::wstring_view{ className.data() } != UiBrowserWindowClassName) {
94+
return TRUE;
95+
}
96+
97+
std::array<wchar_t, MAX_PATH> title{};
98+
if (GetWindowTextW(hWnd, title.data(), (int)title.size()) == 0 ||
99+
std::wstring_view{ title.data() } != UiBrowserWindowTitle) {
100+
return TRUE;
101+
}
102+
103+
if (!UiBrowserWindowMutexSuffixMatches(hWnd, params.first)) {
104+
return TRUE;
105+
}
106+
107+
params.second = hWnd;
108+
return FALSE;
109+
}
110+
111+
inline HWND FindUiBrowserWindow(std::string_view mutexSuffix)
112+
{
113+
std::pair<std::string_view, HWND> params{ mutexSuffix, nullptr };
114+
EnumWindows(FindUiBrowserWindowCallback, reinterpret_cast<LPARAM>(&params));
115+
return params.second;
116+
}
117+
118+
inline DWORD FindUiBrowserWindowProcessId(std::string_view mutexSuffix)
119+
{
120+
const auto hWnd = FindUiBrowserWindow(mutexSuffix);
121+
if (hWnd == nullptr) {
122+
return 0;
123+
}
124+
DWORD processId = 0;
125+
GetWindowThreadProcessId(hWnd, &processId);
126+
return processId;
127+
}
128+
129+
inline DWORD FindUiInstanceRootProcessId(std::string_view mutexSuffix)
130+
{
131+
const auto uiProcessId = FindUiBrowserWindowProcessId(mutexSuffix);
132+
if (uiProcessId == 0) {
133+
return 0;
134+
}
135+
136+
const ::pmon::util::win::ProcessMapBuilder processMap;
137+
const auto& processes = processMap.Peek();
138+
const auto uiIt = processes.find(uiProcessId);
139+
if (uiIt == processes.end()) {
140+
return uiProcessId;
141+
}
142+
143+
const auto parentIt = processes.find(uiIt->second.parentId);
144+
if (parentIt == processes.end()) {
145+
return uiProcessId;
146+
}
147+
148+
if (parentIt->second.name == L"PresentMon.exe") {
149+
return parentIt->second.pid;
150+
}
151+
return uiProcessId;
152+
}
153+
154+
inline bool TerminateUiInstanceProcessTree(std::string_view mutexSuffix)
155+
{
156+
const auto rootProcessId = FindUiInstanceRootProcessId(mutexSuffix);
157+
if (rootProcessId == 0 || rootProcessId == GetCurrentProcessId()) {
158+
return false;
159+
}
160+
161+
const ::pmon::util::win::ProcessMapBuilder processMap;
162+
const auto& processes = processMap.Peek();
163+
if (!processes.contains(rootProcessId)) {
164+
return false;
165+
}
166+
167+
auto processIds = processMap.GetChildTreePostOrder(rootProcessId);
168+
std::erase(processIds, GetCurrentProcessId());
169+
170+
bool terminatedAny = false;
171+
std::vector<::pmon::util::win::Handle> handles;
172+
for (const auto processId : processIds) {
173+
::pmon::util::win::Handle process{ OpenProcess(PROCESS_TERMINATE | SYNCHRONIZE, FALSE, processId) };
174+
if (process) {
175+
terminatedAny = TerminateProcess(process.Get(), 1) != FALSE || terminatedAny;
176+
handles.push_back(std::move(process));
177+
}
178+
}
179+
for (const auto& process : handles) {
180+
WaitForSingleObject(process.Get(), 5000);
181+
}
182+
return terminatedAny;
183+
}
184+
185+
inline bool BringUiBrowserWindowToFront(std::string_view mutexSuffix)
186+
{
187+
const auto hWnd = FindUiBrowserWindow(mutexSuffix);
188+
if (hWnd == nullptr) {
189+
return false;
190+
}
191+
if (IsIconic(hWnd)) {
192+
ShowWindow(hWnd, SW_RESTORE);
193+
}
194+
else {
195+
ShowWindow(hWnd, SW_SHOW);
196+
}
197+
return SetForegroundWindow(hWnd) != FALSE;
198+
}
199+
}

IntelPresentMon/AppCef/source/winmain.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "../resource.h"
66
#include "util/Logging.h"
77
#include "util/LogSetup.h"
8+
#include "util/UiProcessGuard.h"
89
#include <Core/source/infra/util/FolderResolver.h>
910
#include "util/CliOptions.h"
1011
#include <CommonUtilities/log/IdentificationTable.h>
@@ -221,6 +222,13 @@ int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLi
221222
}
222223

223224
// code from here on is only executed by the root process (browser window process)
225+
auto uiInstanceMutex = client::util::TryAcquireUiBrowserProcessMutex(*opt.uiMutexName);
226+
if (!uiInstanceMutex.first) {
227+
return -1;
228+
}
229+
if (!uiInstanceMutex.second) {
230+
return client::util::UiAlreadyRunningExitCode;
231+
}
224232

225233
pmlog_info(std::format("== UI client root process starting build#{} clean:{} CEF:{} ==",
226234
BuildIdShortHash(), !BuildIdDirtyFlag(), CEF_VERSION));
@@ -245,6 +253,7 @@ int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLi
245253
CefInitialize(main_args, settings, app.get(), nullptr);
246254
}
247255
auto hwndBrowser = CreateBrowserWindow(hInstance, nCmdShow);
256+
client::util::SetUiBrowserWindowMutexSuffix(hwndBrowser, *opt.uiMutexName);
248257
hwndAppMsg = CreateMessageWindow(hInstance);
249258

250259

@@ -256,6 +265,7 @@ int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLi
256265

257266
DestroyWindow(hwndAppMsg);
258267
CefShutdown();
268+
client::util::ClearUiBrowserWindowMutexSuffix(hwndBrowser);
259269
DestroyWindow(hwndBrowser);
260270

261271
UnregisterClass(BrowserWindowClassName, hInstance);

IntelPresentMon/CommonUtilities/win/ProcessMapBuilder.cpp

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,18 @@ namespace pmon::util::win
4545
}
4646

4747
void ProcessMapBuilder::FilterHavingAncestor(DWORD pidRoot)
48+
{
49+
std::unordered_set<DWORD> whiteList;
50+
for (const auto pid : GetChildTreePostOrder(pidRoot)) {
51+
whiteList.emplace(pid);
52+
}
53+
// prune all processes not in ancestry set
54+
std::erase_if(map, [&](const ProcMap::value_type& v) {
55+
return !whiteList.contains(v.second.pid);
56+
});
57+
}
58+
59+
std::vector<DWORD> ProcessMapBuilder::GetChildTreePostOrder(DWORD pidRoot) const
4860
{
4961
// building a map of parentId => childId
5062
std::unordered_multimap<DWORD, DWORD> parentMap;
@@ -53,23 +65,10 @@ namespace pmon::util::win
5365
parentMap.emplace(e.second.parentId, e.second.pid);
5466
}
5567
}
56-
std::unordered_set<DWORD> whiteList;
57-
std::function<void(DWORD)> visit;
58-
visit = [&](DWORD pidRoot) {
59-
// add root to whitelist
60-
whiteList.emplace(pidRoot);
61-
// visit all children
62-
const auto r = parentMap.equal_range(pidRoot);
63-
for (auto i = r.first; i != r.second; i++) {
64-
visit(i->second);
65-
}
66-
};
67-
// recursive building of ancestry tree (set)
68-
visit(pidRoot);
69-
// prune all processes not in ancestry set
70-
std::erase_if(map, [&](const ProcMap::value_type& v) {
71-
return !whiteList.contains(v.second.pid);
72-
});
68+
69+
std::vector<DWORD> order;
70+
AppendChildTreePostOrder_(pidRoot, parentMap, order);
71+
return order;
7372
}
7473

7574
ProcessMapBuilder::NameMap ProcessMapBuilder::AsNameMap(bool lowercase) const
@@ -134,6 +133,19 @@ namespace pmon::util::win
134133
}
135134
}
136135

136+
void ProcessMapBuilder::AppendChildTreePostOrder_(
137+
DWORD pidRoot,
138+
const std::unordered_multimap<DWORD, DWORD>& parentMap,
139+
std::vector<DWORD>& order) const
140+
{
141+
// visit all children first so callers can act child-before-parent
142+
const auto r = parentMap.equal_range(pidRoot);
143+
for (auto i = r.first; i != r.second; i++) {
144+
AppendChildTreePostOrder_(i->second, parentMap, order);
145+
}
146+
order.push_back(pidRoot);
147+
}
148+
137149
bool ProcessMapBuilder::WindowIsMain_(HWND hWnd)
138150
{
139151
return GetWindow(hWnd, GW_OWNER) == nullptr && IsWindowVisible(hWnd);

IntelPresentMon/CommonUtilities/win/ProcessMapBuilder.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "Process.h"
66
#include <unordered_map>
77
#include <tlhelp32.h>
8+
#include <vector>
89

910

1011
namespace pmon::util::win
@@ -21,13 +22,15 @@ namespace pmon::util::win
2122
void FillWindowHandles();
2223
void FilterHavingWindow();
2324
void FilterHavingAncestor(DWORD pidRoot);
25+
std::vector<DWORD> GetChildTreePostOrder(DWORD pidRoot) const;
2426
NameMap AsNameMap(bool lowercase = false) const;
2527
std::wstring ToString() const;
2628
static std::wstring MapToString(const ProcMap& map);
2729
private:
30+
void AppendChildTreePostOrder_(DWORD pidRoot, const std::unordered_multimap<DWORD, DWORD>& parentMap, std::vector<DWORD>& order) const;
2831
void PopulateProcessMap_();
2932
static bool WindowIsMain_(HWND hWnd);
3033
static BOOL CALLBACK EnumWindowsCallback_(HWND hWnd, LPARAM lParam);
3134
ProcMap map;
3235
};
33-
}
36+
}

0 commit comments

Comments
 (0)