VirtualBox

source: vbox/trunk/src/VBox/Main/src-server/ClientWatcher.cpp@ 98110

Last change on this file since 98110 was 98103, checked in by vboxsync, 23 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 38.4 KB
Line 
1/* $Id: ClientWatcher.cpp 98103 2023-01-17 14:15:46Z vboxsync $ */
2/** @file
3 * VirtualBox API client session crash watcher
4 */
5
6/*
7 * Copyright (C) 2006-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28#define LOG_GROUP LOG_GROUP_MAIN
29#include <iprt/asm.h>
30#include <iprt/assert.h>
31#include <iprt/semaphore.h>
32#include <iprt/process.h>
33
34#include <VBox/log.h>
35#include <VBox/com/defs.h>
36
37#include <vector>
38
39#include "VirtualBoxBase.h"
40#include "AutoCaller.h"
41#include "ClientWatcher.h"
42#include "ClientToken.h"
43#include "VirtualBoxImpl.h"
44#include "MachineImpl.h"
45
46#if defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER) || defined(VBOX_WITH_GENERIC_SESSION_WATCHER)
47/** Table for adaptive timeouts. After an update the counter starts at the
48 * maximum value and decreases to 0, i.e. first the short timeouts are used
49 * and then the longer ones. This minimizes the detection latency in the
50 * cases where a change is expected, for crashes. */
51static const RTMSINTERVAL s_aUpdateTimeoutSteps[] = { 500, 200, 100, 50, 20, 10, 5 };
52#endif
53
54
55
56VirtualBox::ClientWatcher::ClientWatcher() :
57 mLock(LOCKCLASS_OBJECTSTATE)
58{
59 AssertReleaseFailed();
60}
61
62VirtualBox::ClientWatcher::~ClientWatcher()
63{
64 if (mThread != NIL_RTTHREAD)
65 {
66 /* signal the client watcher thread, should be exiting now */
67 update();
68 /* wait for termination */
69 RTThreadWait(mThread, RT_INDEFINITE_WAIT, NULL);
70 mThread = NIL_RTTHREAD;
71 }
72 mProcesses.clear();
73#if defined(RT_OS_WINDOWS)
74 if (mUpdateReq != NULL)
75 {
76 ::CloseHandle(mUpdateReq);
77 mUpdateReq = NULL;
78 }
79#elif defined(RT_OS_OS2) || defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER) || defined(VBOX_WITH_GENERIC_SESSION_WATCHER)
80 if (mUpdateReq != NIL_RTSEMEVENT)
81 {
82 RTSemEventDestroy(mUpdateReq);
83 mUpdateReq = NIL_RTSEMEVENT;
84 }
85#else
86# error "Port me!"
87#endif
88}
89
90VirtualBox::ClientWatcher::ClientWatcher(const ComObjPtr<VirtualBox> &pVirtualBox) :
91 mVirtualBox(pVirtualBox),
92 mThread(NIL_RTTHREAD),
93 mUpdateReq(CWUPDATEREQARG),
94 mLock(LOCKCLASS_OBJECTSTATE)
95{
96#if defined(RT_OS_WINDOWS)
97 /* Misc state. */
98 mfTerminate = false;
99 mcMsWait = INFINITE;
100 mcActiveSubworkers = 0;
101
102 /* Update request. The UpdateReq event is also used to wake up subthreads. */
103 mfUpdateReq = false;
104 mUpdateReq = ::CreateEvent(NULL /*pSecAttr*/, TRUE /*fManualReset*/, FALSE /*fInitialState*/, NULL /*pszName*/);
105 AssertRelease(mUpdateReq != NULL);
106
107 /* Initialize the handle array. */
108 for (uint32_t i = 0; i < RT_ELEMENTS(mahWaitHandles); i++)
109 mahWaitHandles[i] = NULL;
110 for (uint32_t i = 0; i < RT_ELEMENTS(mahWaitHandles); i += CW_MAX_HANDLES_PER_THREAD)
111 mahWaitHandles[i] = mUpdateReq;
112 mcWaitHandles = 1;
113
114#elif defined(RT_OS_OS2)
115 RTSemEventCreate(&mUpdateReq);
116#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER) || defined(VBOX_WITH_GENERIC_SESSION_WATCHER)
117 RTSemEventCreate(&mUpdateReq);
118 /* start with high timeouts, nothing to do */
119 ASMAtomicUoWriteU8(&mUpdateAdaptCtr, 0);
120#else
121# error "Port me!"
122#endif
123
124 int vrc = RTThreadCreate(&mThread,
125 worker,
126 (void *)this,
127 0,
128 RTTHREADTYPE_MAIN_WORKER,
129 RTTHREADFLAGS_WAITABLE,
130 "Watcher");
131 AssertRC(vrc);
132}
133
134bool VirtualBox::ClientWatcher::isReady()
135{
136 return mThread != NIL_RTTHREAD;
137}
138
139/**
140 * Sends a signal to the thread to rescan the clients/VMs having open sessions.
141 */
142void VirtualBox::ClientWatcher::update()
143{
144 AssertReturnVoid(mThread != NIL_RTTHREAD);
145 LogFlowFunc(("ping!\n"));
146
147 /* sent an update request */
148#if defined(RT_OS_WINDOWS)
149 ASMAtomicWriteBool(&mfUpdateReq, true);
150 ::SetEvent(mUpdateReq);
151
152#elif defined(RT_OS_OS2)
153 RTSemEventSignal(mUpdateReq);
154
155#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
156 /* use short timeouts, as we expect changes */
157 ASMAtomicUoWriteU8(&mUpdateAdaptCtr, RT_ELEMENTS(s_aUpdateTimeoutSteps) - 1);
158 RTSemEventSignal(mUpdateReq);
159
160#elif defined(VBOX_WITH_GENERIC_SESSION_WATCHER)
161 RTSemEventSignal(mUpdateReq);
162
163#else
164# error "Port me!"
165#endif
166}
167
168/**
169 * Adds a process to the list of processes to be reaped. This call should be
170 * followed by a call to update() to cause the necessary actions immediately,
171 * in case the process crashes straight away.
172 */
173void VirtualBox::ClientWatcher::addProcess(RTPROCESS pid)
174{
175 AssertReturnVoid(mThread != NIL_RTTHREAD);
176 AutoWriteLock alock(mLock COMMA_LOCKVAL_SRC_POS);
177 mProcesses.push_back(pid);
178}
179
180/**
181 * Reaps dead processes in the mProcesses list.
182 *
183 * @returns Number of reaped processes.
184 */
185uint32_t VirtualBox::ClientWatcher::reapProcesses(void)
186{
187 uint32_t cReaped = 0;
188
189 AutoWriteLock alock(mLock COMMA_LOCKVAL_SRC_POS);
190 if (mProcesses.size())
191 {
192 LogFlowFunc(("UPDATE: child process count = %zu\n", mProcesses.size()));
193 VirtualBox::ClientWatcher::ProcessList::iterator it = mProcesses.begin();
194 while (it != mProcesses.end())
195 {
196 RTPROCESS pid = *it;
197 RTPROCSTATUS Status;
198 int vrc = ::RTProcWait(pid, RTPROCWAIT_FLAGS_NOBLOCK, &Status);
199 if (vrc == VINF_SUCCESS)
200 {
201 if ( Status.enmReason != RTPROCEXITREASON_NORMAL
202 || Status.iStatus != RTEXITCODE_SUCCESS)
203 {
204 switch (Status.enmReason)
205 {
206 default:
207 case RTPROCEXITREASON_NORMAL:
208 LogRel(("Reaper: Pid %d (%#x) exited normally: %d (%#x)\n",
209 pid, pid, Status.iStatus, Status.iStatus));
210 break;
211 case RTPROCEXITREASON_ABEND:
212 LogRel(("Reaper: Pid %d (%#x) abended: %d (%#x)\n",
213 pid, pid, Status.iStatus, Status.iStatus));
214 break;
215 case RTPROCEXITREASON_SIGNAL:
216 LogRel(("Reaper: Pid %d (%#x) was signalled: %s (%d / %#x)\n",
217 pid, pid, RTProcSignalName(Status.iStatus), Status.iStatus, Status.iStatus));
218 break;
219 }
220 }
221 else
222 LogFlowFunc(("pid %d (%x) was reaped, status=%d, reason=%d\n", pid, pid, Status.iStatus, Status.enmReason));
223 it = mProcesses.erase(it);
224 cReaped++;
225 }
226 else
227 {
228 LogFlowFunc(("pid %d (%x) was NOT reaped, vrc=%Rrc\n", pid, pid, vrc));
229 if (vrc != VERR_PROCESS_RUNNING)
230 {
231 /* remove the process if it is not already running */
232 it = mProcesses.erase(it);
233 cReaped++;
234 }
235 else
236 ++it;
237 }
238 }
239 }
240
241 return cReaped;
242}
243
244#ifdef RT_OS_WINDOWS
245
246/**
247 * Closes all the client process handles in mahWaitHandles.
248 *
249 * The array is divided into two ranges, first range are mutext handles of
250 * established sessions, the second range is zero or more process handles of
251 * spawning sessions. It's the latter that we close here, the former will just
252 * be NULLed out.
253 *
254 * @param cProcHandles The number of process handles.
255 */
256void VirtualBox::ClientWatcher::winResetHandleArray(uint32_t cProcHandles)
257{
258 uint32_t idxHandle = mcWaitHandles;
259 Assert(cProcHandles < idxHandle);
260 Assert(idxHandle > 0);
261
262 /* Spawning process handles. */
263 while (cProcHandles-- > 0 && idxHandle > 0)
264 {
265 idxHandle--;
266 if (idxHandle % CW_MAX_HANDLES_PER_THREAD)
267 {
268 Assert(mahWaitHandles[idxHandle] != mUpdateReq);
269 LogFlow(("UPDATE: closing %p\n", mahWaitHandles[idxHandle]));
270 CloseHandle(mahWaitHandles[idxHandle]);
271 mahWaitHandles[idxHandle] = NULL;
272 }
273 else
274 Assert(mahWaitHandles[idxHandle] == mUpdateReq);
275 }
276
277 /* Mutex handles (not to be closed). */
278 while (idxHandle-- > 0)
279 if (idxHandle % CW_MAX_HANDLES_PER_THREAD)
280 {
281 Assert(mahWaitHandles[idxHandle] != mUpdateReq);
282 mahWaitHandles[idxHandle] = NULL;
283 }
284 else
285 Assert(mahWaitHandles[idxHandle] == mUpdateReq);
286
287 /* Reset the handle count. */
288 mcWaitHandles = 1;
289}
290
291/**
292 * Does the waiting on a section of the handle array.
293 *
294 * @param pSubworker Pointer to the calling thread's data.
295 * @param cMsWait Number of milliseconds to wait.
296 */
297void VirtualBox::ClientWatcher::subworkerWait(VirtualBox::ClientWatcher::PerSubworker *pSubworker, uint32_t cMsWait)
298{
299 /*
300 * Figure out what section to wait on and do the waiting.
301 */
302 uint32_t idxHandle = pSubworker->iSubworker * CW_MAX_HANDLES_PER_THREAD;
303 uint32_t cHandles = CW_MAX_HANDLES_PER_THREAD;
304 if (idxHandle + cHandles > mcWaitHandles)
305 {
306 cHandles = mcWaitHandles - idxHandle;
307 AssertStmt(idxHandle < mcWaitHandles, cHandles = 1);
308 }
309 Assert(mahWaitHandles[idxHandle] == mUpdateReq);
310
311 DWORD dwWait = ::WaitForMultipleObjects(cHandles,
312 &mahWaitHandles[idxHandle],
313 FALSE /*fWaitAll*/,
314 cMsWait);
315 pSubworker->dwWait = dwWait;
316
317 /*
318 * If we didn't wake up because of the UpdateReq handle, signal it to make
319 * sure everyone else wakes up too.
320 */
321 if (dwWait != WAIT_OBJECT_0)
322 {
323 BOOL fRc = SetEvent(mUpdateReq);
324 Assert(fRc); NOREF(fRc);
325 }
326
327 /*
328 * Last one signals the main thread.
329 */
330 if (ASMAtomicDecU32(&mcActiveSubworkers) == 0)
331 {
332 int vrc = RTThreadUserSignal(maSubworkers[0].hThread);
333 AssertLogRelMsg(RT_SUCCESS(vrc), ("RTThreadUserSignal -> %Rrc\n", vrc));
334 }
335
336}
337
338/**
339 * Thread worker function that watches the termination of all client processes
340 * that have open sessions using IMachine::LockMachine()
341 */
342/*static*/
343DECLCALLBACK(int) VirtualBox::ClientWatcher::subworkerThread(RTTHREAD hThreadSelf, void *pvUser)
344{
345 VirtualBox::ClientWatcher::PerSubworker *pSubworker = (VirtualBox::ClientWatcher::PerSubworker *)pvUser;
346 VirtualBox::ClientWatcher *pThis = pSubworker->pSelf;
347 int vrc;
348 while (!pThis->mfTerminate)
349 {
350 /* Before we start waiting, reset the event semaphore. */
351 vrc = RTThreadUserReset(pSubworker->hThread);
352 AssertLogRelMsg(RT_SUCCESS(vrc), ("RTThreadUserReset [iSubworker=%#u] -> %Rrc", pSubworker->iSubworker, vrc));
353
354 /* Do the job. */
355 pThis->subworkerWait(pSubworker, pThis->mcMsWait);
356
357 /* Wait for the next job. */
358 do
359 {
360 vrc = RTThreadUserWaitNoResume(hThreadSelf, RT_INDEFINITE_WAIT);
361 Assert(vrc == VINF_SUCCESS || vrc == VERR_INTERRUPTED);
362 }
363 while ( vrc != VINF_SUCCESS
364 && !pThis->mfTerminate);
365 }
366 return VINF_SUCCESS;
367}
368
369
370#endif /* RT_OS_WINDOWS */
371
372/**
373 * Thread worker function that watches the termination of all client processes
374 * that have open sessions using IMachine::LockMachine()
375 */
376/*static*/
377DECLCALLBACK(int) VirtualBox::ClientWatcher::worker(RTTHREAD hThreadSelf, void *pvUser)
378{
379 LogFlowFuncEnter();
380 NOREF(hThreadSelf);
381
382 VirtualBox::ClientWatcher *that = (VirtualBox::ClientWatcher *)pvUser;
383 Assert(that);
384
385 typedef std::vector<ComObjPtr<Machine> > MachineVector;
386 typedef std::vector<ComObjPtr<SessionMachine> > SessionMachineVector;
387
388 SessionMachineVector machines;
389 MachineVector spawnedMachines;
390
391 size_t cnt = 0;
392 size_t cntSpawned = 0;
393
394 VirtualBoxBase::initializeComForThread();
395
396#if defined(RT_OS_WINDOWS)
397
398 int vrc;
399
400 /* Initialize all the subworker data. */
401 that->maSubworkers[0].hThread = hThreadSelf;
402 for (uint32_t iSubworker = 1; iSubworker < RT_ELEMENTS(that->maSubworkers); iSubworker++)
403 that->maSubworkers[iSubworker].hThread = NIL_RTTHREAD;
404 for (uint32_t iSubworker = 0; iSubworker < RT_ELEMENTS(that->maSubworkers); iSubworker++)
405 {
406 that->maSubworkers[iSubworker].pSelf = that;
407 that->maSubworkers[iSubworker].iSubworker = iSubworker;
408 }
409
410 do
411 {
412 /* VirtualBox has been early uninitialized, terminate. */
413 AutoCaller autoCaller(that->mVirtualBox);
414 if (!autoCaller.isOk())
415 break;
416
417 bool fPidRace = false; /* We poll if the PID of a spawning session hasn't been established yet. */
418 bool fRecentDeath = false; /* We slowly poll if a session has recently been closed to do reaping. */
419 for (;;)
420 {
421 /* release the caller to let uninit() ever proceed */
422 autoCaller.release();
423
424 /* Kick of the waiting. */
425 uint32_t const cSubworkers = (that->mcWaitHandles + CW_MAX_HANDLES_PER_THREAD - 1) / CW_MAX_HANDLES_PER_THREAD;
426 uint32_t const cMsWait = fPidRace ? 500 : fRecentDeath ? 5000 : INFINITE;
427 LogFlowFunc(("UPDATE: Waiting. %u handles, %u subworkers, %u ms wait\n", that->mcWaitHandles, cSubworkers, cMsWait));
428
429 that->mcMsWait = cMsWait;
430 ASMAtomicWriteU32(&that->mcActiveSubworkers, cSubworkers);
431 RTThreadUserReset(hThreadSelf);
432
433 for (uint32_t iSubworker = 1; iSubworker < cSubworkers; iSubworker++)
434 {
435 if (that->maSubworkers[iSubworker].hThread != NIL_RTTHREAD)
436 {
437 vrc = RTThreadUserSignal(that->maSubworkers[iSubworker].hThread);
438 AssertLogRelMsg(RT_SUCCESS(vrc), ("RTThreadUserSignal -> %Rrc\n", vrc));
439 }
440 else
441 {
442 vrc = RTThreadCreateF(&that->maSubworkers[iSubworker].hThread,
443 VirtualBox::ClientWatcher::subworkerThread, &that->maSubworkers[iSubworker],
444 _128K, RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "Watcher%u", iSubworker);
445 AssertLogRelMsgStmt(RT_SUCCESS(vrc), ("%Rrc iSubworker=%u\n", vrc, iSubworker),
446 that->maSubworkers[iSubworker].hThread = NIL_RTTHREAD);
447 }
448 if (RT_FAILURE(vrc))
449 that->subworkerWait(&that->maSubworkers[iSubworker], 1);
450 }
451
452 /* Wait ourselves. */
453 that->subworkerWait(&that->maSubworkers[0], cMsWait);
454
455 /* Make sure all waiters are done waiting. */
456 BOOL fRc = SetEvent(that->mUpdateReq);
457 Assert(fRc); NOREF(fRc);
458
459 vrc = RTThreadUserWait(hThreadSelf, RT_INDEFINITE_WAIT);
460 AssertLogRelMsg(RT_SUCCESS(vrc), ("RTThreadUserWait -> %Rrc\n", vrc));
461 Assert(that->mcActiveSubworkers == 0);
462
463 /* Consume pending update request before proceeding with processing the wait results. */
464 fRc = ResetEvent(that->mUpdateReq);
465 Assert(fRc);
466
467 bool update = ASMAtomicXchgBool(&that->mfUpdateReq, false);
468 if (update)
469 LogFlowFunc(("UPDATE: Update request pending\n"));
470 update |= fPidRace;
471
472 /* Process the wait results. */
473 autoCaller.add();
474 if (!autoCaller.isOk())
475 break;
476 fRecentDeath = false;
477 for (uint32_t iSubworker = 0; iSubworker < cSubworkers; iSubworker++)
478 {
479 DWORD dwWait = that->maSubworkers[iSubworker].dwWait;
480 LogFlowFunc(("UPDATE: subworker #%u: dwWait=%#x\n", iSubworker, dwWait));
481 if ( (dwWait > WAIT_OBJECT_0 && dwWait < WAIT_OBJECT_0 + CW_MAX_HANDLES_PER_THREAD)
482 || (dwWait > WAIT_ABANDONED_0 && dwWait < WAIT_ABANDONED_0 + CW_MAX_HANDLES_PER_THREAD) )
483 {
484 uint32_t idxHandle = iSubworker * CW_MAX_HANDLES_PER_THREAD;
485 if (dwWait > WAIT_OBJECT_0 && dwWait < WAIT_OBJECT_0 + CW_MAX_HANDLES_PER_THREAD)
486 idxHandle += dwWait - WAIT_OBJECT_0;
487 else
488 idxHandle += dwWait - WAIT_ABANDONED_0;
489
490 uint32_t const idxMachine = idxHandle - (iSubworker + 1);
491 if (idxMachine < cnt)
492 {
493 /* Machine mutex is released or abandond due to client process termination. */
494 LogFlowFunc(("UPDATE: Calling i_checkForDeath on idxMachine=%u (idxHandle=%u) dwWait=%#x\n",
495 idxMachine, idxHandle, dwWait));
496 fRecentDeath |= (machines[idxMachine])->i_checkForDeath();
497 }
498 else if (idxMachine < cnt + cntSpawned)
499 {
500 /* Spawned VM process has terminated normally. */
501 Assert(dwWait < WAIT_ABANDONED_0);
502 LogFlowFunc(("UPDATE: Calling i_checkForSpawnFailure on idxMachine=%u/%u idxHandle=%u dwWait=%#x\n",
503 idxMachine, idxMachine - cnt, idxHandle, dwWait));
504 fRecentDeath |= (spawnedMachines[idxMachine - cnt])->i_checkForSpawnFailure();
505 }
506 else
507 AssertFailed();
508 update = true;
509 }
510 else
511 Assert(dwWait == WAIT_OBJECT_0 || dwWait == WAIT_TIMEOUT);
512 }
513
514 if (update)
515 {
516 LogFlowFunc(("UPDATE: Update pending (cnt=%u cntSpawned=%u)...\n", cnt, cntSpawned));
517
518 /* close old process handles */
519 that->winResetHandleArray((uint32_t)cntSpawned);
520
521 // get reference to the machines list in VirtualBox
522 VirtualBox::MachinesOList &allMachines = that->mVirtualBox->i_getMachinesList();
523
524 // lock the machines list for reading
525 AutoReadLock thatLock(allMachines.getLockHandle() COMMA_LOCKVAL_SRC_POS);
526
527 /* obtain a new set of opened machines */
528 cnt = 0;
529 machines.clear();
530 uint32_t idxHandle = 0;
531
532 for (MachinesOList::iterator it = allMachines.begin();
533 it != allMachines.end();
534 ++it)
535 {
536 AssertMsgBreak(idxHandle < CW_MAX_CLIENTS, ("CW_MAX_CLIENTS reached"));
537
538 ComObjPtr<SessionMachine> sm;
539 if ((*it)->i_isSessionOpenOrClosing(sm))
540 {
541 AutoCaller smCaller(sm);
542 if (smCaller.isOk())
543 {
544 AutoReadLock smLock(sm COMMA_LOCKVAL_SRC_POS);
545 Machine::ClientToken *ct = sm->i_getClientToken();
546 if (ct)
547 {
548 HANDLE ipcSem = ct->getToken();
549 machines.push_back(sm);
550 if (!(idxHandle % CW_MAX_HANDLES_PER_THREAD))
551 idxHandle++;
552 that->mahWaitHandles[idxHandle++] = ipcSem;
553 ++cnt;
554 }
555 }
556 }
557 }
558
559 LogFlowFunc(("UPDATE: direct session count = %d\n", cnt));
560
561 /* obtain a new set of spawned machines */
562 fPidRace = false;
563 cntSpawned = 0;
564 spawnedMachines.clear();
565
566 for (MachinesOList::iterator it = allMachines.begin();
567 it != allMachines.end();
568 ++it)
569 {
570 AssertMsgBreak(idxHandle < CW_MAX_CLIENTS, ("CW_MAX_CLIENTS reached"));
571
572 if ((*it)->i_isSessionSpawning())
573 {
574 ULONG pid;
575 HRESULT hrc = (*it)->COMGETTER(SessionPID)(&pid);
576 if (SUCCEEDED(hrc))
577 {
578 if (pid != NIL_RTPROCESS)
579 {
580 HANDLE hProc = OpenProcess(SYNCHRONIZE, FALSE, pid);
581 AssertMsg(hProc != NULL, ("OpenProcess (pid=%d) failed with %d\n", pid, GetLastError()));
582 if (hProc != NULL)
583 {
584 spawnedMachines.push_back(*it);
585 if (!(idxHandle % CW_MAX_HANDLES_PER_THREAD))
586 idxHandle++;
587 that->mahWaitHandles[idxHandle++] = hProc;
588 ++cntSpawned;
589 }
590 }
591 else
592 fPidRace = true;
593 }
594 }
595 }
596
597 LogFlowFunc(("UPDATE: spawned session count = %d\n", cntSpawned));
598
599 /* Update mcWaitHandles and make sure there is at least one handle to wait on. */
600 that->mcWaitHandles = RT_MAX(idxHandle, 1);
601
602 // machines lock unwinds here
603 }
604 else
605 LogFlowFunc(("UPDATE: No update pending.\n"));
606
607 /* reap child processes */
608 that->reapProcesses();
609
610 } /* for ever (well, till autoCaller fails). */
611
612 } while (0);
613
614 /* Terminate subworker threads. */
615 ASMAtomicWriteBool(&that->mfTerminate, true);
616 for (uint32_t iSubworker = 1; iSubworker < RT_ELEMENTS(that->maSubworkers); iSubworker++)
617 if (that->maSubworkers[iSubworker].hThread != NIL_RTTHREAD)
618 RTThreadUserSignal(that->maSubworkers[iSubworker].hThread);
619 for (uint32_t iSubworker = 1; iSubworker < RT_ELEMENTS(that->maSubworkers); iSubworker++)
620 if (that->maSubworkers[iSubworker].hThread != NIL_RTTHREAD)
621 {
622 vrc = RTThreadWait(that->maSubworkers[iSubworker].hThread, RT_MS_1MIN, NULL /*prc*/);
623 if (RT_SUCCESS(vrc))
624 that->maSubworkers[iSubworker].hThread = NIL_RTTHREAD;
625 else
626 AssertLogRelMsgFailed(("RTThreadWait -> %Rrc\n", vrc));
627 }
628
629 /* close old process handles */
630 that->winResetHandleArray((uint32_t)cntSpawned);
631
632 /* release sets of machines if any */
633 machines.clear();
634 spawnedMachines.clear();
635
636 ::CoUninitialize();
637
638#elif defined(RT_OS_OS2)
639
640 /* according to PMREF, 64 is the maximum for the muxwait list */
641 SEMRECORD handles[64];
642
643 HMUX muxSem = NULLHANDLE;
644
645 do
646 {
647 AutoCaller autoCaller(that->mVirtualBox);
648 /* VirtualBox has been early uninitialized, terminate */
649 if (!autoCaller.isOk())
650 break;
651
652 for (;;)
653 {
654 /* release the caller to let uninit() ever proceed */
655 autoCaller.release();
656
657 int vrc = RTSemEventWait(that->mUpdateReq, 500);
658
659 /* Restore the caller before using VirtualBox. If it fails, this
660 * means VirtualBox is being uninitialized and we must terminate. */
661 autoCaller.add();
662 if (!autoCaller.isOk())
663 break;
664
665 bool update = false;
666 bool updateSpawned = false;
667
668 if (RT_SUCCESS(vrc))
669 {
670 /* update event is signaled */
671 update = true;
672 updateSpawned = true;
673 }
674 else
675 {
676 AssertMsg(vrc == VERR_TIMEOUT || vrc == VERR_INTERRUPTED,
677 ("RTSemEventWait returned %Rrc\n", vrc));
678
679 /* are there any mutexes? */
680 if (cnt > 0)
681 {
682 /* figure out what's going on with machines */
683
684 unsigned long semId = 0;
685 APIRET arc = ::DosWaitMuxWaitSem(muxSem,
686 SEM_IMMEDIATE_RETURN, &semId);
687
688 if (arc == NO_ERROR)
689 {
690 /* machine mutex is normally released */
691 Assert(semId >= 0 && semId < cnt);
692 if (semId >= 0 && semId < cnt)
693 {
694#if 0//def DEBUG
695 {
696 AutoReadLock machineLock(machines[semId] COMMA_LOCKVAL_SRC_POS);
697 LogFlowFunc(("released mutex: machine='%ls'\n",
698 machines[semId]->name().raw()));
699 }
700#endif
701 machines[semId]->i_checkForDeath();
702 }
703 update = true;
704 }
705 else if (arc == ERROR_SEM_OWNER_DIED)
706 {
707 /* machine mutex is abandoned due to client process
708 * termination; find which mutex is in the Owner Died
709 * state */
710 for (size_t i = 0; i < cnt; ++i)
711 {
712 PID pid; TID tid;
713 unsigned long reqCnt;
714 arc = DosQueryMutexSem((HMTX)handles[i].hsemCur, &pid, &tid, &reqCnt);
715 if (arc == ERROR_SEM_OWNER_DIED)
716 {
717 /* close the dead mutex as asked by PMREF */
718 ::DosCloseMutexSem((HMTX)handles[i].hsemCur);
719
720 Assert(i >= 0 && i < cnt);
721 if (i >= 0 && i < cnt)
722 {
723#if 0//def DEBUG
724 {
725 AutoReadLock machineLock(machines[semId] COMMA_LOCKVAL_SRC_POS);
726 LogFlowFunc(("mutex owner dead: machine='%ls'\n",
727 machines[i]->name().raw()));
728 }
729#endif
730 machines[i]->i_checkForDeath();
731 }
732 }
733 }
734 update = true;
735 }
736 else
737 AssertMsg(arc == ERROR_INTERRUPT || arc == ERROR_TIMEOUT,
738 ("DosWaitMuxWaitSem returned %d\n", arc));
739 }
740
741 /* are there any spawning sessions? */
742 if (cntSpawned > 0)
743 {
744 for (size_t i = 0; i < cntSpawned; ++i)
745 updateSpawned |= (spawnedMachines[i])->
746 i_checkForSpawnFailure();
747 }
748 }
749
750 if (update || updateSpawned)
751 {
752 // get reference to the machines list in VirtualBox
753 VirtualBox::MachinesOList &allMachines = that->mVirtualBox->i_getMachinesList();
754
755 // lock the machines list for reading
756 AutoReadLock thatLock(allMachines.getLockHandle() COMMA_LOCKVAL_SRC_POS);
757
758 if (update)
759 {
760 /* close the old muxsem */
761 if (muxSem != NULLHANDLE)
762 ::DosCloseMuxWaitSem(muxSem);
763
764 /* obtain a new set of opened machines */
765 cnt = 0;
766 machines.clear();
767
768 for (MachinesOList::iterator it = allMachines.begin();
769 it != allMachines.end(); ++it)
770 {
771 /// @todo handle situations with more than 64 objects
772 AssertMsg(cnt <= 64 /* according to PMREF */,
773 ("maximum of 64 mutex semaphores reached (%d)",
774 cnt));
775
776 ComObjPtr<SessionMachine> sm;
777 if ((*it)->i_isSessionOpenOrClosing(sm))
778 {
779 AutoCaller smCaller(sm);
780 if (smCaller.isOk())
781 {
782 AutoReadLock smLock(sm COMMA_LOCKVAL_SRC_POS);
783 ClientToken *ct = sm->i_getClientToken();
784 if (ct)
785 {
786 HMTX ipcSem = ct->getToken();
787 machines.push_back(sm);
788 handles[cnt].hsemCur = (HSEM)ipcSem;
789 handles[cnt].ulUser = cnt;
790 ++cnt;
791 }
792 }
793 }
794 }
795
796 LogFlowFunc(("UPDATE: direct session count = %d\n", cnt));
797
798 if (cnt > 0)
799 {
800 /* create a new muxsem */
801 APIRET arc = ::DosCreateMuxWaitSem(NULL, &muxSem, cnt,
802 handles,
803 DCMW_WAIT_ANY);
804 AssertMsg(arc == NO_ERROR,
805 ("DosCreateMuxWaitSem returned %d\n", arc));
806 NOREF(arc);
807 }
808 }
809
810 if (updateSpawned)
811 {
812 /* obtain a new set of spawned machines */
813 spawnedMachines.clear();
814
815 for (MachinesOList::iterator it = allMachines.begin();
816 it != allMachines.end(); ++it)
817 {
818 if ((*it)->i_isSessionSpawning())
819 spawnedMachines.push_back(*it);
820 }
821
822 cntSpawned = spawnedMachines.size();
823 LogFlowFunc(("UPDATE: spawned session count = %d\n", cntSpawned));
824 }
825 }
826
827 /* reap child processes */
828 that->reapProcesses();
829
830 } /* for ever (well, till autoCaller fails). */
831
832 } while (0);
833
834 /* close the muxsem */
835 if (muxSem != NULLHANDLE)
836 ::DosCloseMuxWaitSem(muxSem);
837
838 /* release sets of machines if any */
839 machines.clear();
840 spawnedMachines.clear();
841
842#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
843
844 bool update = false;
845 bool updateSpawned = false;
846
847 do
848 {
849 AutoCaller autoCaller(that->mVirtualBox);
850 if (!autoCaller.isOk())
851 break;
852
853 do
854 {
855 /* release the caller to let uninit() ever proceed */
856 autoCaller.release();
857
858 /* determine wait timeout adaptively: after updating information
859 * relevant to the client watcher, check a few times more
860 * frequently. This ensures good reaction time when the signalling
861 * has to be done a bit before the actual change for technical
862 * reasons, and saves CPU cycles when no activities are expected. */
863 RTMSINTERVAL cMillies;
864 {
865 uint8_t uOld, uNew;
866 do
867 {
868 uOld = ASMAtomicUoReadU8(&that->mUpdateAdaptCtr);
869 uNew = uOld ? uOld - 1 : uOld;
870 } while (!ASMAtomicCmpXchgU8(&that->mUpdateAdaptCtr, uNew, uOld));
871 Assert(uOld <= RT_ELEMENTS(s_aUpdateTimeoutSteps) - 1);
872 cMillies = s_aUpdateTimeoutSteps[uOld];
873 }
874
875 int rc = RTSemEventWait(that->mUpdateReq, cMillies);
876
877 /*
878 * Restore the caller before using VirtualBox. If it fails, this
879 * means VirtualBox is being uninitialized and we must terminate.
880 */
881 autoCaller.add();
882 if (!autoCaller.isOk())
883 break;
884
885 if (RT_SUCCESS(rc) || update || updateSpawned)
886 {
887 /* RT_SUCCESS(rc) means an update event is signaled */
888
889 // get reference to the machines list in VirtualBox
890 VirtualBox::MachinesOList &allMachines = that->mVirtualBox->i_getMachinesList();
891
892 // lock the machines list for reading
893 AutoReadLock thatLock(allMachines.getLockHandle() COMMA_LOCKVAL_SRC_POS);
894
895 if (RT_SUCCESS(rc) || update)
896 {
897 /* obtain a new set of opened machines */
898 machines.clear();
899
900 for (MachinesOList::iterator it = allMachines.begin();
901 it != allMachines.end();
902 ++it)
903 {
904 ComObjPtr<SessionMachine> sm;
905 if ((*it)->i_isSessionOpenOrClosing(sm))
906 machines.push_back(sm);
907 }
908
909 cnt = machines.size();
910 LogFlowFunc(("UPDATE: direct session count = %d\n", cnt));
911 }
912
913 if (RT_SUCCESS(rc) || updateSpawned)
914 {
915 /* obtain a new set of spawned machines */
916 spawnedMachines.clear();
917
918 for (MachinesOList::iterator it = allMachines.begin();
919 it != allMachines.end();
920 ++it)
921 {
922 if ((*it)->i_isSessionSpawning())
923 spawnedMachines.push_back(*it);
924 }
925
926 cntSpawned = spawnedMachines.size();
927 LogFlowFunc(("UPDATE: spawned session count = %d\n", cntSpawned));
928 }
929
930 // machines lock unwinds here
931 }
932
933 update = false;
934 for (size_t i = 0; i < cnt; ++i)
935 update |= (machines[i])->i_checkForDeath();
936
937 updateSpawned = false;
938 for (size_t i = 0; i < cntSpawned; ++i)
939 updateSpawned |= (spawnedMachines[i])->i_checkForSpawnFailure();
940
941 /* reap child processes */
942 that->reapProcesses();
943 }
944 while (true);
945 }
946 while (0);
947
948 /* release sets of machines if any */
949 machines.clear();
950 spawnedMachines.clear();
951
952#elif defined(VBOX_WITH_GENERIC_SESSION_WATCHER)
953
954 bool updateSpawned = false;
955
956 do
957 {
958 AutoCaller autoCaller(that->mVirtualBox);
959 if (!autoCaller.isOk())
960 break;
961
962 do
963 {
964 /* release the caller to let uninit() ever proceed */
965 autoCaller.release();
966
967 /* determine wait timeout adaptively: after updating information
968 * relevant to the client watcher, check a few times more
969 * frequently. This ensures good reaction time when the signalling
970 * has to be done a bit before the actual change for technical
971 * reasons, and saves CPU cycles when no activities are expected. */
972 RTMSINTERVAL cMillies;
973 {
974 uint8_t uOld, uNew;
975 do
976 {
977 uOld = ASMAtomicUoReadU8(&that->mUpdateAdaptCtr);
978 uNew = uOld ? (uint8_t)(uOld - 1) : uOld;
979 } while (!ASMAtomicCmpXchgU8(&that->mUpdateAdaptCtr, uNew, uOld));
980 Assert(uOld <= RT_ELEMENTS(s_aUpdateTimeoutSteps) - 1);
981 cMillies = s_aUpdateTimeoutSteps[uOld];
982 }
983
984 int rc = RTSemEventWait(that->mUpdateReq, cMillies);
985
986 /*
987 * Restore the caller before using VirtualBox. If it fails, this
988 * means VirtualBox is being uninitialized and we must terminate.
989 */
990 autoCaller.add();
991 if (!autoCaller.isOk())
992 break;
993
994 /** @todo this quite big effort for catching machines in spawning
995 * state which can't be caught by the token mechanism (as the token
996 * can't be in the other process yet) could be eliminated if the
997 * reaping is made smarter, having cross-reference information
998 * from the pid to the corresponding machine object. Both cases do
999 * more or less the same thing anyway. */
1000 if (RT_SUCCESS(rc) || updateSpawned)
1001 {
1002 /* RT_SUCCESS(rc) means an update event is signaled */
1003
1004 // get reference to the machines list in VirtualBox
1005 VirtualBox::MachinesOList &allMachines = that->mVirtualBox->i_getMachinesList();
1006
1007 // lock the machines list for reading
1008 AutoReadLock thatLock(allMachines.getLockHandle() COMMA_LOCKVAL_SRC_POS);
1009
1010 if (RT_SUCCESS(rc) || updateSpawned)
1011 {
1012 /* obtain a new set of spawned machines */
1013 spawnedMachines.clear();
1014
1015 for (MachinesOList::iterator it = allMachines.begin();
1016 it != allMachines.end();
1017 ++it)
1018 {
1019 if ((*it)->i_isSessionSpawning())
1020 spawnedMachines.push_back(*it);
1021 }
1022
1023 cntSpawned = spawnedMachines.size();
1024 LogFlowFunc(("UPDATE: spawned session count = %d\n", cntSpawned));
1025 }
1026
1027 NOREF(cnt);
1028 // machines lock unwinds here
1029 }
1030
1031 updateSpawned = false;
1032 for (size_t i = 0; i < cntSpawned; ++i)
1033 updateSpawned |= (spawnedMachines[i])->i_checkForSpawnFailure();
1034
1035 /* reap child processes */
1036 that->reapProcesses();
1037 }
1038 while (true);
1039 }
1040 while (0);
1041
1042 /* release sets of machines if any */
1043 machines.clear();
1044 spawnedMachines.clear();
1045
1046#else
1047# error "Port me!"
1048#endif
1049
1050 VirtualBoxBase::uninitializeComForThread();
1051
1052 LogFlowFuncLeave();
1053 return 0;
1054}
1055/* vi: set tabstop=4 shiftwidth=4 expandtab: */
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette