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