All of lore.kernel.org
 help / color / mirror / Atom feed
From: Laszlo Ersek <lersek@redhat.com>
To: Max Reitz <mreitz@redhat.com>,
	"qemu-devel@nongnu.org" <qemu-devel@nongnu.org>
Cc: Kevin Wolf <kwolf@redhat.com>, Stefan Hajnoczi <stefanha@redhat.com>
Subject: Re: Thread safety of coroutine-sigaltstack
Date: Wed, 20 Jan 2021 18:25:02 +0100	[thread overview]
Message-ID: <445268c9-d91f-af5a-3d7e-f4c6f014ca52@redhat.com> (raw)
In-Reply-To: <7b8155ad-0942-dc1c-f43c-bb5eb518a278@redhat.com>

On 01/20/21 17:26, Max Reitz wrote:
> Hi,
> 
> I’ve run into trouble with Vladimir’s async backup series on MacOS,
> namely that iotest 256 fails with qemu exiting because of a SIGUSR2.
> 
> Turns out this is because MacOS (-xcode) uses coroutine-sigaltstack,
> when I use this on Linux, I get the same error.
> 
> (You can find the series applied on my block branch e.g. here:
> 
> https://github.com/XanClic/qemu.git block
> )
> 
> Some debugging later I found that the problem seems to be two threads
> simultaneously creating a coroutine.  It makes sense that this case
> would appear with Vladimir’s series and iotest 256, because 256 runs two
> backup jobs in two different threads in a transaction, i.e. they’re
> launched simultaneously.  The async backup series makes backup use many
> concurrent coroutines and so by default launches 64+x coroutines when
> the backup is started.  Thus, the case of two coroutines created
> concurrently in two threads is very likely to occur.
> 
> I think the problem is in coroutine-sigaltstack’s qemu_coroutine_new().
> It sets up a SIGUSR2 handler, then changes the signal handling stack,
> then raises SIGUSR2, then reverts the signal handling stack and the
> SIGUSR2 handler.  As far as I’m aware, setting up signal handlers and
> changing the signal handling stack are both process-global operations,
> and so if two threads do so concurrently, they will interfere with each
> other.

Signal action (disposition) is process-wide.

Signal mask and signal stack are thread-specific.

A signal may be pending for the whole process, or for a specific thread.
In the former case, the signal is delivered to one of the threads that
are not blocking the signal.

> What usually happens is that one thread sets up everything,
> while the other is already in the process of reverting its changes: So
> the second thread reverts the SIGUSR2 handler to the default, and then
> the first thread raises SIGUSR2, thus making qemu exit.

I agree. The way SIGUSR2 is blocked (for the thread), made pending (for
the thread), and then allowed to be delivered (consequently, to the
thread), looks OK. But by the time it is delivered, the action has been
changed.

> 
> (Could be worse though.  Both threads could set up the sigaltstack, then
> both raise SIGUSR2, and then we get one coroutine_trampoline()
> invocation in each thread, but both would use the same stack.  But I
> don’t think I’ve ever seen that happen, presumably because the race time
> window is much shorter.)

No, the "alternate stack for signal handlers" that sigaltstack()
configures is thread-specific. (I mean one could theoretically mess it
up if the stack were located in the same place between different
threads, but we call qemu_alloc_stack(), so that doesn't happen.)

https://pubs.opengroup.org/onlinepubs/9699919799/functions/sigaltstack.html

> 
> Now, this all seems obvious to me, but I’m wondering...  If
> coroutine-sigaltstack really couldn’t create coroutines concurrently,
> why wouldn’t we have noticed before?  I mean, this new backup case is
> kind of a stress test, yes, but surely we would have seen the problem
> already, right?  That’s why I’m not sure whether my analysis is correct.
> 
> Anyway, I’ve attached a patch that wraps the whole SIGUSR2 handling
> section in a mutex, and that makes 256 pass reliably with Vladimir’s
> async backup series.  Besides being unsure whether the problem is really
> in coroutine-sigaltstack, I also don’t know whether getting out the big
> guns and wrapping everything in the mutex is the best solution.  So,
> it’s an RFC, I guess.

A simple grep for SIGUSR2 seems to indicate that SIGUSR2 is not used by
system emulation for anything else, in practice. Is it possible to
dedicate SIGUSR2 explicitly to coroutine-sigaltstack, and set up the
action beforehand, from some init function that executes on a "central"
thread, before qemu_coroutine_new() is ever called?

... I've tried to see if POSIX says anything on signals being delivered
with mutexen held. I can't find anything specific (the spec seems to
talk about delivery of a signal while the thread waits in
pthread_mutex_lock(), but that's not what we care about, here). I'm just
somewhat uncomfortable with bracketing this whole hackery into a mutex
even... Keeping sigaction() out of the picture could be a small
performance benefit, too.

The logic in the patch doesn't look broken, but the comments should be
updated minimally -- the signal stack is thread-specific (similarly to
how a thread has its own stack anyway, regardless of signals).

Only my two cents of course, feel free to ignore.

Thanks
Laszlo



  parent reply	other threads:[~2021-01-20 17:27 UTC|newest]

Thread overview: 26+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-01-20 16:26 Thread safety of coroutine-sigaltstack Max Reitz
2021-01-20 16:50 ` Paolo Bonzini
2021-01-20 16:58 ` Eric Blake
2021-01-20 17:25 ` Laszlo Ersek [this message]
2021-01-21  9:27   ` Max Reitz
2021-01-21 13:34     ` Laszlo Ersek
2021-01-21 15:42       ` Max Reitz
2021-01-21 16:04         ` Daniel P. Berrangé
2021-01-21 16:05         ` Laszlo Ersek
2021-01-21 15:14     ` Paolo Bonzini
2021-01-21 16:07       ` Daniel P. Berrangé
2021-01-21 16:44         ` Peter Maydell
2021-01-21 17:24           ` Paolo Bonzini
2021-01-22 20:38             ` Laszlo Ersek
2021-01-22 21:34               ` Laszlo Ersek
2021-01-22 21:41                 ` Laszlo Ersek
2021-01-22  7:55       ` Markus Armbruster
2021-01-22  8:48   ` Max Reitz
2021-01-22 10:14     ` Peter Maydell
2021-01-22 10:16       ` Max Reitz
2021-01-22 12:24       ` Laszlo Ersek
2021-01-23  0:06       ` Laszlo Ersek
2021-01-23 13:35         ` Peter Maydell
2021-01-25 22:15           ` Laszlo Ersek
2021-01-25 22:45             ` Paolo Bonzini
2021-01-26  8:57               ` Laszlo Ersek

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=445268c9-d91f-af5a-3d7e-f4c6f014ca52@redhat.com \
    --to=lersek@redhat.com \
    --cc=kwolf@redhat.com \
    --cc=mreitz@redhat.com \
    --cc=qemu-devel@nongnu.org \
    --cc=stefanha@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.