ClientWatcher.cpp revision d7a2c5e4cc83588b277fd8f28a288773173c3638
/** @file
*
* VirtualBox API client session crash watcher
*/
/*
* Copyright (C) 2006-2014 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
#include <iprt/semaphore.h>
#include <vector>
#include "VirtualBoxBase.h"
#include "AutoCaller.h"
#include "ClientWatcher.h"
#include "ClientToken.h"
#include "VirtualBoxImpl.h"
#include "MachineImpl.h"
#if defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER) || defined(VBOX_WITH_GENERIC_SESSION_WATCHER)
/** Table for adaptive timeouts. After an update the counter starts at the
* maximum value and decreases to 0, i.e. first the short timeouts are used
* and then the longer ones. This minimizes the detection latency in the
* cases where a change is expected, for crashes. */
#endif
{
}
{
if (mThread != NIL_RTTHREAD)
{
/* signal the client watcher thread, should be exiting now */
update();
/* wait for termination */
}
mProcesses.clear();
#if defined(RT_OS_WINDOWS)
if (mUpdateReq != NULL)
{
mUpdateReq = NULL;
}
#elif defined(RT_OS_OS2) || defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER) || defined(VBOX_WITH_GENERIC_SESSION_WATCHER)
if (mUpdateReq != NIL_RTSEMEVENT)
{
}
#else
# error "Port me!"
#endif
}
{
#if defined(RT_OS_WINDOWS)
/* start with high timeouts, nothing to do */
#else
# error "Port me!"
#endif
(void *)this,
0,
"Watcher");
}
{
return mThread != NIL_RTTHREAD;
}
/**
*/
{
/* sent an update request */
#if defined(RT_OS_WINDOWS)
::SetEvent(mUpdateReq);
#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
/* use short timeouts, as we expect changes */
#elif defined(VBOX_WITH_GENERIC_SESSION_WATCHER)
#else
# error "Port me!"
#endif
}
/**
* Adds a process to the list of processes to be reaped. This call should be
* followed by a call to update() to cause the necessary actions immediately,
* in case the process crashes straight away.
*/
{
/* @todo r=klaus, do the reaping on all platforms! */
#ifndef RT_OS_WINDOWS
#endif
}
/**
* Thread worker function that watches the termination of all client processes
* that have open sessions using IMachine::LockMachine()
*/
/*static*/
{
size_t cntSpawned = 0;
#if defined(RT_OS_WINDOWS)
/// @todo (dmik) processes reaping!
do
{
/* VirtualBox has been early uninitialized, terminate */
if (!autoCaller.isOk())
break;
do
{
/* release the caller to let uninit() ever proceed */
INFINITE);
/* Restore the caller before using VirtualBox. If it fails, this
* means VirtualBox is being uninitialized and we must terminate. */
autoCaller.add();
if (!autoCaller.isOk())
break;
bool update = false;
if (rc == WAIT_OBJECT_0)
{
/* update event is signaled */
update = true;
}
{
/* machine mutex is released */
update = true;
}
{
/* machine mutex is abandoned due to client process termination */
update = true;
}
{
/* spawned VM process has terminated (normally or abnormally) */
update = true;
}
if (update)
{
/* close old process handles */
CloseHandle(handles[i]);
// get reference to the machines list in VirtualBox
// lock the machines list for reading
/* obtain a new set of opened machines */
cnt = 0;
++it)
{
/// @todo handle situations with more than 64 objects
("MAXIMUM_WAIT_OBJECTS reached"));
{
{
if (ct)
{
++cnt;
}
}
}
}
/* obtain a new set of spawned machines */
cntSpawned = 0;
++it)
{
/// @todo handle situations with more than 64 objects
("MAXIMUM_WAIT_OBJECTS reached"));
if ((*it)->i_isSessionSpawning())
{
{
pid, GetLastError()));
{
++cntSpawned;
}
}
}
}
// machines lock unwinds here
}
}
while (true);
}
while (0);
/* close old process handles */
CloseHandle(handles[i]);
/* release sets of machines if any */
::CoUninitialize();
/// @todo (dmik) processes reaping!
/* according to PMREF, 64 is the maximum for the muxwait list */
do
{
/* VirtualBox has been early uninitialized, terminate */
if (!autoCaller.isOk())
break;
do
{
/* release the caller to let uninit() ever proceed */
/* Restore the caller before using VirtualBox. If it fails, this
* means VirtualBox is being uninitialized and we must terminate. */
autoCaller.add();
if (!autoCaller.isOk())
break;
bool update = false;
bool updateSpawned = false;
if (RT_SUCCESS(vrc))
{
/* update event is signaled */
update = true;
updateSpawned = true;
}
else
{
("RTSemEventWait returned %Rrc\n", vrc));
/* are there any mutexes? */
if (cnt > 0)
{
/* figure out what's going on with machines */
unsigned long semId = 0;
{
/* machine mutex is normally released */
{
#if 0//def DEBUG
{
LogFlowFunc(("released mutex: machine='%ls'\n",
}
#endif
}
update = true;
}
else if (arc == ERROR_SEM_OWNER_DIED)
{
/* machine mutex is abandoned due to client process
* termination; find which mutex is in the Owner Died
* state */
{
unsigned long reqCnt;
if (arc == ERROR_SEM_OWNER_DIED)
{
/* close the dead mutex as asked by PMREF */
if (i >= 0 && i < cnt)
{
#if 0//def DEBUG
{
LogFlowFunc(("mutex owner dead: machine='%ls'\n",
}
#endif
machines[i]->i_checkForDeath();
}
}
}
update = true;
}
else
("DosWaitMuxWaitSem returned %d\n", arc));
}
/* are there any spawning sessions? */
if (cntSpawned > 0)
{
for (size_t i = 0; i < cntSpawned; ++i)
updateSpawned |= (spawnedMachines[i])->
}
}
if (update || updateSpawned)
{
// get reference to the machines list in VirtualBox
// lock the machines list for reading
if (update)
{
/* close the old muxsem */
if (muxSem != NULLHANDLE)
/* obtain a new set of opened machines */
cnt = 0;
{
/// @todo handle situations with more than 64 objects
("maximum of 64 mutex semaphores reached (%d)",
cnt));
{
{
if (ct)
{
++cnt;
}
}
}
}
if (cnt > 0)
{
/* create a new muxsem */
("DosCreateMuxWaitSem returned %d\n", arc));
}
}
if (updateSpawned)
{
/* obtain a new set of spawned machines */
{
if ((*it)->i_isSessionSpawning())
}
}
}
}
while (true);
}
while (0);
/* close the muxsem */
if (muxSem != NULLHANDLE)
/* release sets of machines if any */
#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
bool update = false;
bool updateSpawned = false;
do
{
if (!autoCaller.isOk())
break;
do
{
/* release the caller to let uninit() ever proceed */
/* determine wait timeout adaptively: after updating information
* relevant to the client watcher, check a few times more
* frequently. This ensures good reaction time when the signalling
* has to be done a bit before the actual change for technical
* reasons, and saves CPU cycles when no activities are expected. */
{
do
{
}
/*
* Restore the caller before using VirtualBox. If it fails, this
* means VirtualBox is being uninitialized and we must terminate.
*/
autoCaller.add();
if (!autoCaller.isOk())
break;
{
/* RT_SUCCESS(rc) means an update event is signaled */
// get reference to the machines list in VirtualBox
// lock the machines list for reading
{
/* obtain a new set of opened machines */
++it)
{
}
}
{
/* obtain a new set of spawned machines */
++it)
{
if ((*it)->i_isSessionSpawning())
}
}
// machines lock unwinds here
}
update = false;
updateSpawned = false;
for (size_t i = 0; i < cntSpawned; ++i)
/* reap child processes */
{
{
LogFlowFunc(("UPDATE: child process count = %d\n",
{
if (vrc == VINF_SUCCESS)
{
{
{
default:
case RTPROCEXITREASON_NORMAL:
LogRel(("Reaper: Pid %d (%x) exited normally: %d (%#x)\n",
break;
case RTPROCEXITREASON_ABEND:
LogRel(("Reaper: Pid %d (%x) abended: %d (%#x)\n",
break;
case RTPROCEXITREASON_SIGNAL:
LogRel(("Reaper: Pid %d (%x) was signalled: %d (%#x)\n",
break;
}
}
else
LogFlowFunc(("pid %d (%x) was reaped, status=%d, reason=%d\n",
}
else
{
LogFlowFunc(("pid %d (%x) was NOT reaped, vrc=%Rrc\n",
if (vrc != VERR_PROCESS_RUNNING)
{
/* remove the process if it is not already running */
}
else
++it;
}
}
}
}
}
while (true);
}
while (0);
/* release sets of machines if any */
#elif defined(VBOX_WITH_GENERIC_SESSION_WATCHER)
bool updateSpawned = false;
do
{
if (!autoCaller.isOk())
break;
do
{
/* release the caller to let uninit() ever proceed */
/* determine wait timeout adaptively: after updating information
* relevant to the client watcher, check a few times more
* frequently. This ensures good reaction time when the signalling
* has to be done a bit before the actual change for technical
* reasons, and saves CPU cycles when no activities are expected. */
{
do
{
}
/*
* Restore the caller before using VirtualBox. If it fails, this
* means VirtualBox is being uninitialized and we must terminate.
*/
autoCaller.add();
if (!autoCaller.isOk())
break;
/** @todo this quite big effort for catching machines in spawning
* state which can't be caught by the token mechanism (as the token
* can't be in the other process yet) could be eliminated if the
* reaping is made smarter, having cross-reference information
* from the pid to the corresponding machine object. Both cases do
* more or less the same thing anyway. */
{
/* RT_SUCCESS(rc) means an update event is signaled */
// get reference to the machines list in VirtualBox
// lock the machines list for reading
{
/* obtain a new set of spawned machines */
++it)
{
if ((*it)->i_isSessionSpawning())
}
}
// machines lock unwinds here
}
updateSpawned = false;
for (size_t i = 0; i < cntSpawned; ++i)
/* reap child processes */
{
{
LogFlowFunc(("UPDATE: child process count = %d\n",
{
if (vrc == VINF_SUCCESS)
{
{
{
default:
case RTPROCEXITREASON_NORMAL:
LogRel(("Reaper: Pid %d (%x) exited normally: %d (%#x)\n",
break;
case RTPROCEXITREASON_ABEND:
LogRel(("Reaper: Pid %d (%x) abended: %d (%#x)\n",
break;
case RTPROCEXITREASON_SIGNAL:
LogRel(("Reaper: Pid %d (%x) was signalled: %d (%#x)\n",
break;
}
}
else
LogFlowFunc(("pid %d (%x) was reaped, status=%d, reason=%d\n",
}
else
{
LogFlowFunc(("pid %d (%x) was NOT reaped, vrc=%Rrc\n",
if (vrc != VERR_PROCESS_RUNNING)
{
/* remove the process if it is not already running */
}
else
++it;
}
}
}
}
}
while (true);
}
while (0);
/* release sets of machines if any */
#else
# error "Port me!"
#endif
return 0;
}
/* vi: set tabstop=4 shiftwidth=4 expandtab: */