mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-11-02 19:31:18 +00:00
7882c6eca5
A deadlock situation can occur when autostarting a LXC domain 'guest' due to two threads attempting to take opposing locks while holding opposing locks (AB BA problem). Thread A takes and holds the 'vm' lock while attempting to take the 'client' lock, meanwhile, thread B takes and holds the 'client' lock while attempting to take the 'vm' lock. The potential for this can be seen as follows: Thread A: virLXCProcessAutostartDomain (takes vm lock) --> virLXCProcessStart --> virLXCProcessConnectMonitor --> virLXCMonitorNew --> virNetClientSetCloseCallback (wants client lock) Thread B: virNetClientIncomingEvent (takes client lock) --> virNetClientIOHandleInput --> virNetClientCallDispatch --> virNetClientCallDispatchMessage --> virNetClientProgramDispatch --> virLXCMonitorHandleEventInit --> virLXCProcessMonitorInitNotify (wants vm lock) Since these threads are scheduled independently and are preemptible it is possible for the deadlock scenario to occur where each thread locks their first lock but both will fail to get their second lock and just spin forever. You get something like: virLXCProcessAutostartDomain (takes vm lock) --> virLXCProcessStart --> virLXCProcessConnectMonitor --> virLXCMonitorNew <...> virNetClientIncomingEvent (takes client lock) --> virNetClientIOHandleInput --> virNetClientCallDispatch --> virNetClientCallDispatchMessage --> virNetClientProgramDispatch --> virLXCMonitorHandleEventInit --> virLXCProcessMonitorInitNotify (wants vm lock but spins) <...> --> virNetClientSetCloseCallback (wants client lock but spins) Neither thread ever gets the lock it needs to be able to continue while holding the lock that the other thread needs. The actual window for preemption which can cause this deadlock is rather small, between the calls to virNetClientProgramNew() and execution of virNetClientSetCloseCallback(), both in virLXCMonitorNew(). But it can be seen in real world use that this small window is enough. By moving the call to virNetClientSetCloseCallback() ahead of virNetClientProgramNew() we can close any possible chance of the deadlock taking place. There should be no other implications to the move since the close callback (in the unlikely event was called) will spin on the vm lock. The remaining work that takes place between the old call location of virNetClientSetCloseCallback() and the new location is unaffected by the move. Signed-off-by: Mark Asselstine <mark.asselstine@windriver.com> Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
235 lines
6.3 KiB
C
235 lines
6.3 KiB
C
/*
|
|
* Copyright (C) 2010-2012 Red Hat, Inc.
|
|
*
|
|
* lxc_monitor.c: client for LXC controller monitor
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library. If not, see
|
|
* <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include "lxc_monitor.h"
|
|
#include "lxc_conf.h"
|
|
#include "lxc_monitor_dispatch.h"
|
|
|
|
#include "viralloc.h"
|
|
|
|
#include "virerror.h"
|
|
#include "virlog.h"
|
|
#include "virthread.h"
|
|
#include "rpc/virnetclient.h"
|
|
#include "virstring.h"
|
|
|
|
#define VIR_FROM_THIS VIR_FROM_LXC
|
|
|
|
VIR_LOG_INIT("lxc.lxc_monitor");
|
|
|
|
struct _virLXCMonitor {
|
|
virObjectLockable parent;
|
|
|
|
virDomainObjPtr vm;
|
|
virLXCMonitorCallbacks cb;
|
|
|
|
virNetClientPtr client;
|
|
virNetClientProgramPtr program;
|
|
};
|
|
|
|
static virClassPtr virLXCMonitorClass;
|
|
static void virLXCMonitorDispose(void *obj);
|
|
|
|
static int virLXCMonitorOnceInit(void)
|
|
{
|
|
if (!VIR_CLASS_NEW(virLXCMonitor, virClassForObjectLockable()))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
VIR_ONCE_GLOBAL_INIT(virLXCMonitor)
|
|
|
|
static void
|
|
virLXCMonitorHandleEventExit(virNetClientProgramPtr prog,
|
|
virNetClientPtr client,
|
|
void *evdata, void *opaque);
|
|
static void
|
|
virLXCMonitorHandleEventInit(virNetClientProgramPtr prog,
|
|
virNetClientPtr client,
|
|
void *evdata, void *opaque);
|
|
|
|
static virNetClientProgramEvent virLXCMonitorEvents[] = {
|
|
{ VIR_LXC_MONITOR_PROC_EXIT_EVENT,
|
|
virLXCMonitorHandleEventExit,
|
|
sizeof(virLXCMonitorExitEventMsg),
|
|
(xdrproc_t)xdr_virLXCMonitorExitEventMsg },
|
|
{ VIR_LXC_MONITOR_PROC_INIT_EVENT,
|
|
virLXCMonitorHandleEventInit,
|
|
sizeof(virLXCMonitorInitEventMsg),
|
|
(xdrproc_t)xdr_virLXCMonitorInitEventMsg },
|
|
};
|
|
|
|
|
|
static void
|
|
virLXCMonitorHandleEventExit(virNetClientProgramPtr prog ATTRIBUTE_UNUSED,
|
|
virNetClientPtr client ATTRIBUTE_UNUSED,
|
|
void *evdata, void *opaque)
|
|
{
|
|
virLXCMonitorPtr mon = opaque;
|
|
virLXCMonitorExitEventMsg *msg = evdata;
|
|
|
|
VIR_DEBUG("Event exit %d", msg->status);
|
|
if (mon->cb.exitNotify)
|
|
mon->cb.exitNotify(mon, msg->status, mon->vm);
|
|
}
|
|
|
|
|
|
static void
|
|
virLXCMonitorHandleEventInit(virNetClientProgramPtr prog ATTRIBUTE_UNUSED,
|
|
virNetClientPtr client ATTRIBUTE_UNUSED,
|
|
void *evdata, void *opaque)
|
|
{
|
|
virLXCMonitorPtr mon = opaque;
|
|
virLXCMonitorInitEventMsg *msg = evdata;
|
|
|
|
VIR_DEBUG("Event init %lld", (long long)msg->initpid);
|
|
if (mon->cb.initNotify)
|
|
mon->cb.initNotify(mon, (pid_t)msg->initpid, mon->vm);
|
|
}
|
|
|
|
|
|
static void virLXCMonitorEOFNotify(virNetClientPtr client ATTRIBUTE_UNUSED,
|
|
int reason ATTRIBUTE_UNUSED,
|
|
void *opaque)
|
|
{
|
|
virLXCMonitorPtr mon = opaque;
|
|
virLXCMonitorCallbackEOFNotify eofNotify;
|
|
virDomainObjPtr vm;
|
|
|
|
VIR_DEBUG("EOF notify mon=%p", mon);
|
|
virObjectLock(mon);
|
|
eofNotify = mon->cb.eofNotify;
|
|
vm = mon->vm;
|
|
virObjectUnlock(mon);
|
|
|
|
if (eofNotify) {
|
|
VIR_DEBUG("EOF callback mon=%p vm=%p", mon, vm);
|
|
eofNotify(mon, vm);
|
|
} else {
|
|
VIR_DEBUG("No EOF callback");
|
|
}
|
|
}
|
|
|
|
|
|
static void virLXCMonitorCloseFreeCallback(void *opaque)
|
|
{
|
|
virLXCMonitorPtr mon = opaque;
|
|
virObjectUnref(mon);
|
|
}
|
|
|
|
|
|
virLXCMonitorPtr virLXCMonitorNew(virDomainObjPtr vm,
|
|
const char *socketdir,
|
|
virLXCMonitorCallbacksPtr cb)
|
|
{
|
|
virLXCMonitorPtr mon;
|
|
char *sockpath = NULL;
|
|
|
|
if (virLXCMonitorInitialize() < 0)
|
|
return NULL;
|
|
|
|
if (!(mon = virObjectLockableNew(virLXCMonitorClass)))
|
|
return NULL;
|
|
|
|
if (virAsprintf(&sockpath, "%s/%s.sock",
|
|
socketdir, vm->def->name) < 0)
|
|
goto error;
|
|
|
|
if (!(mon->client = virNetClientNewUNIX(sockpath, false, NULL)))
|
|
goto error;
|
|
|
|
if (virNetClientRegisterAsyncIO(mon->client) < 0)
|
|
goto error;
|
|
|
|
/* avoid deadlock by making this call before assigning virLXCMonitorEvents */
|
|
virNetClientSetCloseCallback(mon->client, virLXCMonitorEOFNotify, mon,
|
|
virLXCMonitorCloseFreeCallback);
|
|
|
|
/* close callback now has its own reference */
|
|
virObjectRef(mon);
|
|
|
|
if (!(mon->program = virNetClientProgramNew(VIR_LXC_MONITOR_PROGRAM,
|
|
VIR_LXC_MONITOR_PROGRAM_VERSION,
|
|
virLXCMonitorEvents,
|
|
ARRAY_CARDINALITY(virLXCMonitorEvents),
|
|
mon)))
|
|
goto error;
|
|
|
|
if (virNetClientAddProgram(mon->client,
|
|
mon->program) < 0)
|
|
goto error;
|
|
|
|
mon->vm = virObjectRef(vm);
|
|
memcpy(&mon->cb, cb, sizeof(mon->cb));
|
|
|
|
cleanup:
|
|
VIR_FREE(sockpath);
|
|
return mon;
|
|
|
|
error:
|
|
virObjectUnref(mon);
|
|
mon = NULL;
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
static void virLXCMonitorDispose(void *opaque)
|
|
{
|
|
virLXCMonitorPtr mon = opaque;
|
|
|
|
VIR_DEBUG("mon=%p", mon);
|
|
if (mon->cb.destroy)
|
|
(mon->cb.destroy)(mon, mon->vm);
|
|
virObjectUnref(mon->program);
|
|
virObjectUnref(mon->vm);
|
|
}
|
|
|
|
|
|
void virLXCMonitorClose(virLXCMonitorPtr mon)
|
|
{
|
|
virDomainObjPtr vm;
|
|
virNetClientPtr client;
|
|
|
|
VIR_DEBUG("mon=%p", mon);
|
|
if (mon->client) {
|
|
/* When manually closing the monitor, we don't
|
|
* want to have callbacks back into us, since
|
|
* the caller is not re-entrant safe
|
|
*/
|
|
VIR_DEBUG("Clear EOF callback mon=%p", mon);
|
|
vm = mon->vm;
|
|
client = mon->client;
|
|
mon->client = NULL;
|
|
mon->cb.eofNotify = NULL;
|
|
|
|
virObjectRef(vm);
|
|
virObjectUnlock(vm);
|
|
|
|
virNetClientClose(client);
|
|
virObjectUnref(client);
|
|
|
|
virObjectLock(vm);
|
|
virObjectUnref(vm);
|
|
}
|
|
}
|