Bug#1008174: libc6: poll() spuriously returns EINTR

2022-03-26 Thread Rémi Denis-Courmont
Hi,

Le lauantaina 26. maaliskuuta 2022, 21.34.38 EET Aurelien Jarno a écrit :
> > As far as I understand, POSIX allows (or even requires) thread
> > cancellation to be essentially like a signal interruption, save for
> > ending the thread. But that is *only* from the moment that cancellation
> > is effected. Cancellation cannot be effected while it is disabled by
> > definition, so the behaviour from glibc seems wrong here.
> 
> poll() is a cancellation point. It appears to me that POSIX actually
> allows this behaviour for cancellation points:

I don't think so...

> "The side effects of acting upon a cancellation request while suspended
> during a call of a function are the same as the side effects that may be
> seen in a single-threaded program when a call to a function is
> interrupted by a signal and the given function returns [EINTR]. Any such
> side effects occur before any cancellation cleanup handlers are called."

AFAIU, in POSIX, "acting upon" a cancellation request means to move the 
cancellation request past the pending state, in other words, actually cancel 
the thread. That quote clarifies that the signal-like interruption is observed 
in the cancelled thread flow of execution before the cancellation clean-up 
handlers.

Otherwise the next paragraph would not make much sense, particularly the last 
sentence:

> "Whenever a thread has cancelability enabled and a cancellation request has
> been made with that thread as the target, and the thread then calls any
> function that is a cancellation point (such as pthread_testcancel() or
> read()), the cancellation request shall be acted upon before the function
> returns. If a thread has cancelability enabled and a cancellation request is
> made with the thread as a target while the thread is suspended at a
> cancellation point, the thread shall be awakened and the cancellation
> request shall be acted upon."

> "However, if the thread is suspended at a cancellation point and the event
> for which it is waiting occurs before the cancellation request is acted
> upon, it is unspecified whether the cancellation request is acted upon or
> whether the cancellation request remains pending and the thread resumes
> normal execution."

And it would get even more nonsensical / contradictory in the following 
section:

> "When a cancellation request is acted upon, (...) the thread first disables
> cancellation by setting its cancelability state to PTHREAD_CANCEL_DISABLE
> and its cancelability type to PTHREAD_CANCEL_DEFERRED. The cancelability
> state shall remain set to PTHREAD_CANCEL_DISABLE until the thread has
> terminated."

This paragraph clarifies that cancellation cannot occur recursively / more than 
once per thread.

Assuming that "acting upon a cancellation request" would be permissible with 
cancellation disabled, this sentence would imply that cancellation is disabled 
permanently, and the thread will never get cancelled at all.

-- 
Rémi Denis-Courmont
http://www.remlab.net/



Bug#1008174: libc6: poll() spuriously returns EINTR

2022-03-26 Thread Aurelien Jarno
Hi,

On 2022-03-23 18:07, Rémi Denis-Courmont wrote:
> Package: libc6
> Version: 2.34-0experimental3
> Severity: important
> 
> Dear Maintainer,
> 
> In the example below, glibc 2.34 from experimental causes a spurious
> EINTR error in the poll() call from the child thread. It seems that
> thread cancellation causes the poll() to be spuriously interrupted,
> even though the cancellation is explicitly disabled at that time.

Thanks for the example, it's very useful to reproduce and understand the
issue.

> As far as I understand, POSIX allows (or even requires) thread
> cancellation to be essentially like a signal interruption, save for
> ending the thread. But that is *only* from the moment that cancellation
> is effected. Cancellation cannot be effected while it is disabled by
> definition, so the behaviour from glibc seems wrong here.

poll() is a cancellation point. It appears to me that POSIX actually
allows this behaviour for cancellation points:

"The side effects of acting upon a cancellation request while suspended
during a call of a function are the same as the side effects that may be
seen in a single-threaded program when a call to a function is
interrupted by a signal and the given function returns [EINTR]. Any such
side effects occur before any cancellation cleanup handlers are called."

https://pubs.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_09.html

> This regression is known to break the test suite from the VLC package.
> Rolling back to 2.33 from unstable solves the problem.

The regression has been introduced by this commit:
https://sourceware.org/git/?p=glibc.git;a=commit;h=26cfbb7162ad364d53d69f6d482f2d87b5950524

Regards,
Aurelien

-- 
Aurelien Jarno  GPG: 4096R/1DDD8C9B
aurel...@aurel32.net http://www.aurel32.net



Bug#1008174: libc6: poll() spuriously returns EINTR

2022-03-23 Thread Rémi Denis-Courmont
Package: libc6
Version: 2.34-0experimental3
Severity: important

Dear Maintainer,

In the example below, glibc 2.34 from experimental causes a spurious
EINTR error in the poll() call from the child thread. It seems that
thread cancellation causes the poll() to be spuriously interrupted,
even though the cancellation is explicitly disabled at that time.

As far as I understand, POSIX allows (or even requires) thread
cancellation to be essentially like a signal interruption, save for
ending the thread. But that is *only* from the moment that cancellation
is effected. Cancellation cannot be effected while it is disabled by
definition, so the behaviour from glibc seems wrong here.

This regression is known to break the test suite from the VLC package.
Rolling back to 2.33 from unstable solves the problem.

8<

#include 
#include 
#include 
#include 
#include 
#include 

static void *thread(void *data)
{
int canc;

(void) data;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &canc);

if (poll(NULL, 0, 2000) < 0) {
perror("Unexpected poll error");
abort();
}
pthread_setcancelstate(canc, NULL);
return NULL;
}

int main(void)
{
pthread_t th;
void *ret;
struct timespec ts = { 0, 100*1000*1000 };

if (pthread_create(&th, NULL, thread, NULL)) {
perror("pthread_create");
return 1;
}


clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL);
pthread_cancel(th);
pthread_join(th, &ret);
assert(ret == NULL);
return 0;
}

>8

-- System Information:
Debian Release: bookworm/sid
  APT prefers unstable-debug
  APT policy: (500, 'unstable-debug'), (500, 'unstable'), (1, 'experimental')
Architecture: amd64 (x86_64)
Foreign Architectures: i386, arm64

Kernel: Linux 5.16.0-5-amd64 (SMP w/12 CPU threads; PREEMPT)
Kernel taint flags: TAINT_PROPRIETARY_MODULE, TAINT_OOT_MODULE, 
TAINT_UNSIGNED_MODULE
Locale: LANG=fr_FR.UTF-8, LC_CTYPE=fr_FR.UTF-8 (charmap=UTF-8) (ignored: LC_ALL 
set to fi_FI.UTF-8), LANGUAGE=fr:en_GB:fi
Shell: /bin/sh linked to /bin/dash
Init: systemd (via /run/systemd/system)

Versions of packages libc6 depends on:
ii  libgcc-s1  12-20220319-1

Versions of packages libc6 recommends:
ii  libidn2-0  2.3.2-2

Versions of packages libc6 suggests:
ii  debconf [debconf-2.0]  1.5.79
pn  glibc-doc  
ii  libc-l10n  2.34-0experimental3
ii  libnss-nis 3.1-4
ii  libnss-nisplus 1.3-4
ii  locales2.33-7

-- debconf information:
* libraries/restart-without-asking: true
  glibc/kernel-too-old:
  glibc/restart-services:
  glibc/kernel-not-supported:
  glibc/restart-failed:
* glibc/upgrade: true
  glibc/disable-screensaver: