Skip to content

Commit 5710a3e

Browse files
bonzinistefanhaRH
authored andcommitted
async: use explicit memory barriers
When using C11 atomics, non-seqcst reads and writes do not participate in the total order of seqcst operations. In util/async.c and util/aio-posix.c, in particular, the pattern that we use write ctx->notify_me write bh->scheduled read bh->scheduled read ctx->notify_me if !bh->scheduled, sleep if ctx->notify_me, notify needs to use seqcst operations for both the write and the read. In general this is something that we do not want, because there can be many sources that are polled in addition to bottom halves. The alternative is to place a seqcst memory barrier between the write and the read. This also comes with a disadvantage, in that the memory barrier is implicit on strongly-ordered architectures and it wastes a few dozen clock cycles. Fortunately, ctx->notify_me is never written concurrently by two threads, so we can assert that and relax the writes to ctx->notify_me. The resulting solution works and performs well on both aarch64 and x86. Note that the atomic_set/atomic_read combination is not an atomic read-modify-write, and therefore it is even weaker than C11 ATOMIC_RELAXED; on x86, ATOMIC_RELAXED compiles to a locked operation. Analyzed-by: Ying Fang <[email protected]> Signed-off-by: Paolo Bonzini <[email protected]> Tested-by: Ying Fang <[email protected]> Message-Id: <[email protected]> Signed-off-by: Stefan Hajnoczi <[email protected]>
1 parent 3c18a92 commit 5710a3e

File tree

3 files changed

+40
-9
lines changed

3 files changed

+40
-9
lines changed

util/aio-posix.c

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,11 @@ bool aio_poll(AioContext *ctx, bool blocking)
559559
int64_t timeout;
560560
int64_t start = 0;
561561

562+
/*
563+
* There cannot be two concurrent aio_poll calls for the same AioContext (or
564+
* an aio_poll concurrent with a GSource prepare/check/dispatch callback).
565+
* We rely on this below to avoid slow locked accesses to ctx->notify_me.
566+
*/
562567
assert(in_aio_context_home_thread(ctx));
563568

564569
/* aio_notify can avoid the expensive event_notifier_set if
@@ -569,7 +574,13 @@ bool aio_poll(AioContext *ctx, bool blocking)
569574
* so disable the optimization now.
570575
*/
571576
if (blocking) {
572-
atomic_add(&ctx->notify_me, 2);
577+
atomic_set(&ctx->notify_me, atomic_read(&ctx->notify_me) + 2);
578+
/*
579+
* Write ctx->notify_me before computing the timeout
580+
* (reading bottom half flags, etc.). Pairs with
581+
* smp_mb in aio_notify().
582+
*/
583+
smp_mb();
573584
}
574585

575586
qemu_lockcnt_inc(&ctx->list_lock);
@@ -590,7 +601,8 @@ bool aio_poll(AioContext *ctx, bool blocking)
590601
}
591602

592603
if (blocking) {
593-
atomic_sub(&ctx->notify_me, 2);
604+
/* Finish the poll before clearing the flag. */
605+
atomic_store_release(&ctx->notify_me, atomic_read(&ctx->notify_me) - 2);
594606
aio_notify_accept(ctx);
595607
}
596608

util/aio-win32.c

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,12 @@ bool aio_poll(AioContext *ctx, bool blocking)
321321
int count;
322322
int timeout;
323323

324+
/*
325+
* There cannot be two concurrent aio_poll calls for the same AioContext (or
326+
* an aio_poll concurrent with a GSource prepare/check/dispatch callback).
327+
* We rely on this below to avoid slow locked accesses to ctx->notify_me.
328+
*/
329+
assert(in_aio_context_home_thread(ctx));
324330
progress = false;
325331

326332
/* aio_notify can avoid the expensive event_notifier_set if
@@ -331,7 +337,13 @@ bool aio_poll(AioContext *ctx, bool blocking)
331337
* so disable the optimization now.
332338
*/
333339
if (blocking) {
334-
atomic_add(&ctx->notify_me, 2);
340+
atomic_set(&ctx->notify_me, atomic_read(&ctx->notify_me) + 2);
341+
/*
342+
* Write ctx->notify_me before computing the timeout
343+
* (reading bottom half flags, etc.). Pairs with
344+
* smp_mb in aio_notify().
345+
*/
346+
smp_mb();
335347
}
336348

337349
qemu_lockcnt_inc(&ctx->list_lock);
@@ -364,8 +376,7 @@ bool aio_poll(AioContext *ctx, bool blocking)
364376
ret = WaitForMultipleObjects(count, events, FALSE, timeout);
365377
if (blocking) {
366378
assert(first);
367-
assert(in_aio_context_home_thread(ctx));
368-
atomic_sub(&ctx->notify_me, 2);
379+
atomic_store_release(&ctx->notify_me, atomic_read(&ctx->notify_me) - 2);
369380
aio_notify_accept(ctx);
370381
}
371382

util/async.c

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,14 @@ aio_ctx_prepare(GSource *source, gint *timeout)
249249
{
250250
AioContext *ctx = (AioContext *) source;
251251

252-
atomic_or(&ctx->notify_me, 1);
252+
atomic_set(&ctx->notify_me, atomic_read(&ctx->notify_me) | 1);
253+
254+
/*
255+
* Write ctx->notify_me before computing the timeout
256+
* (reading bottom half flags, etc.). Pairs with
257+
* smp_mb in aio_notify().
258+
*/
259+
smp_mb();
253260

254261
/* We assume there is no timeout already supplied */
255262
*timeout = qemu_timeout_ns_to_ms(aio_compute_timeout(ctx));
@@ -268,7 +275,8 @@ aio_ctx_check(GSource *source)
268275
QEMUBH *bh;
269276
BHListSlice *s;
270277

271-
atomic_and(&ctx->notify_me, ~1);
278+
/* Finish computing the timeout before clearing the flag. */
279+
atomic_store_release(&ctx->notify_me, atomic_read(&ctx->notify_me) & ~1);
272280
aio_notify_accept(ctx);
273281

274282
QSLIST_FOREACH_RCU(bh, &ctx->bh_list, next) {
@@ -411,10 +419,10 @@ LuringState *aio_get_linux_io_uring(AioContext *ctx)
411419
void aio_notify(AioContext *ctx)
412420
{
413421
/* Write e.g. bh->scheduled before reading ctx->notify_me. Pairs
414-
* with atomic_or in aio_ctx_prepare or atomic_add in aio_poll.
422+
* with smp_mb in aio_ctx_prepare or aio_poll.
415423
*/
416424
smp_mb();
417-
if (ctx->notify_me) {
425+
if (atomic_read(&ctx->notify_me)) {
418426
event_notifier_set(&ctx->notifier);
419427
atomic_mb_set(&ctx->notified, true);
420428
}

0 commit comments

Comments
 (0)