On Fri, Mar 11, 2016 at 01:18:54AM +0100, Szabolcs Nagy wrote:
> * Rich Felker <dal...@libc.org> [2016-03-10 18:28:20 -0500]:
> > On Thu, Mar 10, 2016 at 07:03:31PM +0100, Ingo Molnar wrote:
> > > 
> > > * Rich Felker <dal...@libc.org> wrote:
> > > 
> > > > > So instead of a sticky cancellation flag, we could introduce a sticky 
> > > > > cancellation signal.
> > > > > 
> > > > > A 'sticky signal' is not cleared from signal_pending() when the 
> > > > > signal handler 
> > > > > executes, but it's automatically blocked so no signal handler 
> > > > > recursion 
> > > > > occurs. (A sticky signal could still be cleared via a separate 
> > > > > mechanism, by 
> > > > > the cancellation cleanup code.)
> > > > > 
> > > > > Such a 'sticky cancellation signal' would, in the racy situation, 
> > > > > cause new 
> > > > > blocking system calls to immediately return with -EINTR. Non-blocking 
> > > > > syscalls 
> > > > > could still be used. (So the cancellation signal handler itself would 
> > > > > still 
> > > > > have access to various fundamental system calls.)
> > > > > 
> > > > > I think this would avoid messy coupling between the kernel's 
> > > > > increasingly more 
> > > > > varied system call entry code and C libraries.
> > > > > 
> > > > > Sticky signals could be requested via a new SA_ flag.
> > > > > 
> > > > > What do you think?
> > > > 
> > > > This still doesn't address the issue that the code making the syscall 
> > > > needs to 
> > > > be able to control whether it's cancellable or not. Not only do some 
> > > > syscalls 
> > > > whose public functions are cancellation points need to be used 
> > > > internally in 
> > > > non-cancellable ways; there's also the pthread_setcancelstate interface 
> > > > that 
> > > > allows deferring cancellation so that it's possible to call functions 
> > > > which are 
> > > > cancellation points without invoking cancellation.
> > > 
> > > I don't think there's a problem - but I might be wrong:
> > > 
> > > One way I think it would work is the following: a sticky signal is not 
> > > the 
> > > cancellation flag - it's a helper construct to implement the flag in 
> > > user-space in 
> > > a race-free way.
> > > 
> > > Say you have RT signal-32 as the cancellation signal, and it's a sticky 
> > > signal.
> > > 
> > > When pthread_cancel() wants to cancel another thread, it first 
> > > (atomically) sets 
> > > the desired cancel state of the target thread. If that state signals that 
> > > the 
> > > thread is cancellable right now, and that we initiated its cancellation, 
> > > then we 
> > > send signal-32. I.e. the signal only ever gets sent if the thread is in a 
> > > cancellable state.
> > > 
> > > libc internal functions and the pthread_setcancelstate() API can 
> > > temporarily 
> > > change the cancel state of a thread to non-cancellable - but 
> > > pthread_cancel() 
> > > observes those state transitions.
> > > 
> > > The 'sticky' nature of signal-32 will make a difference in the following 
> > > race 
> > > condition, if the cancellation flag is checked before a system call by 
> > > the C 
> > > library, and signal-32 arrives before the system call is executed. In 
> > > that case 
> > > the 'sticky' nature of the signal makes sure that all subsequent system 
> > > calls 
> > > return immediately.
> > > 
> > > The sticky signal is only ever sent when the thread is in cancellable 
> > > state - and 
> > > if the target thread notices the cancellation request before the signal 
> > > arrives, 
> > > it first waits for its arrival before executing any new system calls (as 
> > > part of 
> > > the teardown, etc.).
> > > 
> > > So the C library never has to do complex work with a sticky signal 
> > > pending.
> > > 
> > > Does that make more sense to you?
> > 
> > No, it doesn't work. Cancellability of the target thread at the time
> > of the cancellation request (when you would decide whether or not to
> > send the signal) has no relation to cancellability at the time of
> > calling the cancellation point. Consider 2 threads A and B and the
> > following sequence of events:
> > 
> > 1. A has cancellation enabled
> > 2. B calls pthread_cancel(A) and sets sticky pending signal
> > 3. A disables cancellation
> > 4. A calls cancellation point and syscall wrongly gets interrupted
> > 
> > This can be solved with more synchronization in pthread_cancel and
> > pthread_setcancelstate, but it seems costly. pthread_setcancelstate
> > would have to clear pending sticky cancellation signals, and any
> > internal non-cancellable syscalls would have to be made using the same
> > mechanism (effectively calling pthread_setcancelstate). A naive
> > implementation of such clearing would involve a syscall itself,
> 
> i think a syscall in setcancelstate in case of pending sticky signal
> is not that bad given that cancellation is very rarely used.

I agree, but it's not clear to me whether you could eliminate syscalls
in the case where it's not pending, since AS-safe lock machinery is
hard to get right. I don't see a way it can be done with just atomics
because the syscall that sends the signal cannot be atomic with the
memory operating setting a flag, which suggests a lock is needed, and
then there are all sorts of issues to deal with.

> however maintaining two completely different cancellation designs
> is expensive and only the current one works on old kernels.

Indeed. I think it would be hard to justify supporting a new one in
musl unless there's some easy way to isolate the complexity of having
both, being that vdso syscall is of marginal value to begin with
anyway...

Rich

Reply via email to