Skip to content

Commit

Permalink
nbd/server: only traverse NBDExport->clients from main loop thread
Browse files Browse the repository at this point in the history
The NBD clients list is currently accessed from both the export
AioContext and the main loop thread. When the AioContext lock is removed
there will be nothing protecting the clients list.

Adding a lock around the clients list is tricky because NBDClient
structs are refcounted and may be freed from the export AioContext or
the main loop thread. nbd_export_request_shutdown() -> client_close() ->
nbd_client_put() is also tricky because the list lock would be held
while indirectly dropping references to NDBClients.

A simpler approach is to only allow nbd_client_put() and client_close()
calls from the main loop thread. Then the NBD clients list is only
accessed from the main loop thread and no fancy locking is needed.

nbd_trip() just needs to reschedule itself in the main loop AioContext
before calling nbd_client_put() and client_close(). This costs more CPU
cycles per NBD request so add nbd_client_put_nonzero() to optimize the
common case where more references to NBDClient remain.

Note that nbd_client_get() can still be called from either thread, so
make NBDClient->refcount atomic.

Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
Message-ID: <20231221192452.1785567-6-stefanha@redhat.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit f816310)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
  • Loading branch information
stefanhaRH authored and Michael Tokarev committed Mar 19, 2024
1 parent 0d83579 commit aee1039
Showing 1 changed file with 51 additions and 10 deletions.
61 changes: 51 additions & 10 deletions nbd/server.c
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ struct NBDMetaContexts {
};

struct NBDClient {
int refcount;
int refcount; /* atomic */
void (*close_fn)(NBDClient *client, bool negotiated);

NBDExport *exp;
Expand Down Expand Up @@ -1501,14 +1501,17 @@ static int coroutine_fn nbd_receive_request(NBDClient *client, NBDRequest *reque

#define MAX_NBD_REQUESTS 16

/* Runs in export AioContext and main loop thread */
void nbd_client_get(NBDClient *client)
{
client->refcount++;
qatomic_inc(&client->refcount);
}

void nbd_client_put(NBDClient *client)
{
if (--client->refcount == 0) {
assert(qemu_in_main_thread());

if (qatomic_fetch_dec(&client->refcount) == 1) {
/* The last reference should be dropped by client->close,
* which is called by client_close.
*/
Expand All @@ -1529,8 +1532,35 @@ void nbd_client_put(NBDClient *client)
}
}

/*
* Tries to release the reference to @client, but only if other references
* remain. This is an optimization for the common case where we want to avoid
* the expense of scheduling nbd_client_put() in the main loop thread.
*
* Returns true upon success or false if the reference was not released because
* it is the last reference.
*/
static bool nbd_client_put_nonzero(NBDClient *client)
{
int old = qatomic_read(&client->refcount);
int expected;

do {
if (old == 1) {
return false;
}

expected = old;
old = qatomic_cmpxchg(&client->refcount, expected, expected - 1);
} while (old != expected);

return true;
}

static void client_close(NBDClient *client, bool negotiated)
{
assert(qemu_in_main_thread());

if (client->closing) {
return;
}
Expand Down Expand Up @@ -2936,26 +2966,30 @@ static coroutine_fn int nbd_handle_request(NBDClient *client,
static coroutine_fn void nbd_trip(void *opaque)
{
NBDClient *client = opaque;
NBDRequestData *req;
NBDRequestData *req = NULL;
NBDRequest request = { 0 }; /* GCC thinks it can be used uninitialized */
int ret;
Error *local_err = NULL;

/*
* Note that nbd_client_put() and client_close() must be called from the
* main loop thread. Use aio_co_reschedule_self() to switch AioContext
* before calling these functions.
*/

trace_nbd_trip();
if (client->closing) {
nbd_client_put(client);
return;
goto done;
}

if (client->quiescing) {
/*
* We're switching between AIO contexts. Don't attempt to receive a new
* request and kick the main context which may be waiting for us.
*/
nbd_client_put(client);
client->recv_coroutine = NULL;
aio_wait_kick();
return;
goto done;
}

req = nbd_request_get(client);
Expand Down Expand Up @@ -3015,15 +3049,22 @@ static coroutine_fn void nbd_trip(void *opaque)

qio_channel_set_cork(client->ioc, false);
done:
nbd_request_put(req);
nbd_client_put(client);
if (req) {
nbd_request_put(req);
}
if (!nbd_client_put_nonzero(client)) {
aio_co_reschedule_self(qemu_get_aio_context());
nbd_client_put(client);
}
return;

disconnect:
if (local_err) {
error_reportf_err(local_err, "Disconnect client, due to: ");
}
nbd_request_put(req);

aio_co_reschedule_self(qemu_get_aio_context());
client_close(client, true);
nbd_client_put(client);
}
Expand Down

0 comments on commit aee1039

Please sign in to comment.