error()/error_at_line() and threads
Florian Weimer
fw@deneb.enyo.de
Thu Jun 11 18:32:36 GMT 2026
> On Thu, Jun 11, 2026 at 12:44 PM Florian Weimer <fweimer@redhat.com> wrote:
>>
>> > the man page further has the rather over-optimistic:
>> >
>> > The internal error_one_per_line variable is accessed (without any
>> > form of synchronization, but since it's an int used once, it
>> > should be safe enough), and if error_one_per_line is set nonzero,
>> > the internal static variables (not exposed to users) used to hold
>> > the last printed filename and line number are accessed and
>> > modified without synchronization; the update is not atomic and it
>> > occurs before disabling cancelation, so it can be interrupted only
>> > after one of the two variables is modified. After that,
>> > error_at_line() is very much like error().
>> >
>> > but neither say anything about error_message_count. a quick test
>> > program shows that, as you'd expect, some increments get lost in the
>> > presence of many threads.
>>
>> With glibc? That's surprising because we lock stderr before updating
>> error_message_count.
>
> yeah, i reproduced this on debian glibc 2.42. the following program
> often comes up one short...
>
> #include <error.h>
> #include <fcntl.h>
> #include <stdio.h>
> #include <unistd.h>
> #include <thread>
> #include <vector>
>
> int main() {
> int old_stderr = dup(STDERR_FILENO);
> int dev_null = open("/dev/null", O_WRONLY);
> dup2(dev_null, STDERR_FILENO);
> close(dev_null);
>
> error_message_count = 0;
>
> const int kNumThreads = 50;
> const int kIterations = 10000;
> std::vector<std::thread> threads;
> for (int i = 0; i < kNumThreads; ++i) {
> threads.emplace_back([]() {
> for (int j = 0; j < kIterations; ++j) {
> error(0, 0, "test %d", j);
> }
> });
> }
> for (auto& t : threads) {
> t.join();
> }
>
> dup2(old_stderr, STDERR_FILENO);
> close(old_stderr);
>
> printf("%d\n", kNumThreads * kIterations);
> printf("%u\n", error_message_count);
> return 0;
> }
Indeed. Removing the output redirection, we can see there is plenty
of interleaving:
./a.out: test 15
./a.out: ./a.out: test 0
./a.out: ./a.out: ./a.out: test 1
./a.out:
./a.out: test 0./a.out: ./a.out: test 0
./a.out: test 1./a.out:
test 0
./a.out: test 6test 1test 1test 0
./a.out: test 1
./a.out: test 2
./a.out: test 0
./a.out: ./a.out: test 0
./a.out: test 1test 16
./a.out: ./a.out: ./a.out: test 2./a.out: test 0
./a.out: test 1
./a.out: test 2
./a.out: test 3
This is definitely something we should fix as a QoI matter.
This is probably related to the long-standing bug that we do not
define _IO_MTSAFE_IO consistently across the codebase.
More information about the Libc-alpha
mailing list