Define a new 'interlocked' status of netmgr - if it's active

that means there's an operation that depends on many workers.
No two such operation can be launched at once without risking
a deadlock, so other ones must either wait or requeue.
This commit is contained in:
Witold Kręcicki
2019-11-05 12:54:45 +01:00
parent fdeb8c4f48
commit b29b2bfa2e
3 changed files with 70 additions and 24 deletions
+16
View File
@@ -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);
+37
View File
@@ -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);
}
+17 -24
View File
@@ -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);
}
/*