diff --git a/lib/isc/netmgr/netmgr-int.h b/lib/isc/netmgr/netmgr-int.h index ff3e943439..a161564e87 100644 --- a/lib/isc/netmgr/netmgr-int.h +++ b/lib/isc/netmgr/netmgr-int.h @@ -234,6 +234,13 @@ struct isc_nm { atomic_uint_fast32_t workers_paused; atomic_uint_fast32_t maxudp; atomic_bool paused; + /* + * A worker is actively waiting for other workers, to for example + * stop listening, that means no other thread can do the same thing + * or pause or we'll deadlock. We have to either re-enqueue our event + * or wait for the finish if we want to pause. + */ + atomic_bool interlocked; }; typedef enum isc_nmsocket_type { @@ -516,3 +523,12 @@ isc___nm_uverr2result(int uverr, bool dolog, * of this function should add any expected errors that are * not already there. */ + +bool +isc__nm_acquire_interlocked(isc_nm_t *mgr); + +void +isc__nm_drop_interlocked(isc_nm_t *mgr); + +void +isc__nm_acquire_interlocked_force(isc_nm_t *mgr); diff --git a/lib/isc/netmgr/netmgr.c b/lib/isc/netmgr/netmgr.c index 8e31df1d2f..d55df09817 100644 --- a/lib/isc/netmgr/netmgr.c +++ b/lib/isc/netmgr/netmgr.c @@ -186,6 +186,7 @@ isc_nm_pause(isc_nm_t *mgr) { REQUIRE(!isc__nm_in_netthread()); atomic_store(&mgr->paused, true); + isc__nm_acquire_interlocked_force(mgr); for (size_t i = 0; i < mgr->nworkers; i++) { isc__netievent_t *event = NULL; @@ -222,6 +223,7 @@ isc_nm_resume(isc_nm_t *mgr) { SIGNAL(&mgr->workers[i].cond); UNLOCK(&mgr->workers[i].lock); } + isc__nm_drop_interlocked(mgr); /* * We're not waiting for all the workers to come back to life; @@ -965,3 +967,38 @@ isc_nm_send(isc_nmhandle_t *handle, isc_region_t *region, ISC_UNREACHABLE(); } } + +/* + * Try to acquire interlocked state - true if successful. + */ +bool +isc__nm_acquire_interlocked(isc_nm_t *mgr) { + LOCK(&mgr->lock); + bool success = atomic_compare_exchange_strong(&mgr->interlocked, &(bool){false}, true); + UNLOCK(&mgr->lock); + return (success); +} + +/* + * Lose interlocked state, signal waiters + */ +void +isc__nm_drop_interlocked(isc_nm_t *mgr) { + LOCK(&mgr->lock); + bool success = atomic_compare_exchange_strong(&mgr->interlocked, &(bool){true}, false); + INSIST(success == true); + BROADCAST(&mgr->wkstatecond); + UNLOCK(&mgr->lock); +} + +/* + * Actively wait for interlocked state + */ +void +isc__nm_acquire_interlocked_force(isc_nm_t *mgr) { + LOCK(&mgr->lock); + while (!atomic_compare_exchange_strong(&mgr->interlocked, &(bool){false}, true)) { + WAIT(&mgr->wkstatecond, &mgr->lock); + } + UNLOCK(&mgr->lock); +} diff --git a/lib/isc/netmgr/udp.c b/lib/isc/netmgr/udp.c index aacb2ba18b..293a456711 100644 --- a/lib/isc/netmgr/udp.c +++ b/lib/isc/netmgr/udp.c @@ -174,20 +174,6 @@ stoplistening(isc_nmsocket_t *sock) { return; } - /* - * If this is a child socket; stop listening and return. - */ - if (sock->parent != NULL) { - stop_udp_child(sock); - return; - } - - /* - * ... but if it's a parent socket, we need to send stop- - * listening events to all the children first, and wait for - * them to be executed. - */ - INSIST(sock->type == isc_nm_udplistener); for (int i = 0; i < sock->nchildren; i++) { @@ -224,18 +210,18 @@ isc_nm_udp_stoplistening(isc_nmsocket_t *sock) { REQUIRE(sock->type == isc_nm_udplistener); /* - * If the manager is paused, re-enqueue this as an asynchronous + * If the manager is interlocked, re-enqueue this as an asynchronous * event. Otherwise, go ahead and stop listening right away. */ - if (isc_nm_paused(sock->mgr)) { + if (!isc__nm_acquire_interlocked(sock->mgr)) { ievent = isc__nm_get_ievent(sock->mgr, netievent_udpstoplisten); ievent->sock = sock; isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], (isc__netievent_t *) ievent); - return; + } else { + stoplistening(sock); + isc__nm_drop_interlocked(sock->mgr); } - - stoplistening(sock); } /* @@ -250,23 +236,30 @@ isc__nm_async_udpstoplisten(isc__networker_t *worker, isc_nmsocket_t *sock = ievent->sock; REQUIRE(sock->iface != NULL); - UNUSED(worker); + /* + * If this is a child socket; stop listening and return. + */ + if (sock->parent != NULL) { + stop_udp_child(sock); + return; + } + /* * The network manager is pausing; re-enqueue this event for later. */ - if (isc_nm_paused(sock->mgr)) { + if (!isc__nm_acquire_interlocked(sock->mgr)) { isc__netievent_udplisten_t *event = NULL; event = isc__nm_get_ievent(sock->mgr, netievent_udpstoplisten); event->sock = sock; isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], (isc__netievent_t *) event); - return; + } else { + stoplistening(sock); + isc__nm_drop_interlocked(sock->mgr); } - - stoplistening(sock); } /*