bug-gettext
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Multithreading advice on the manual


From: Miguel Ángel Arruga Vivas
Subject: Re: Multithreading advice on the manual
Date: Sat, 10 Jun 2023 06:45:09 +0200
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/28.2 (gnu/linux)

Hi Bruno,

First of all, thank you for your pointers and your will to look into the
issue.

Bruno Haible <bruno@clisp.org> writes:

> [...]
> Of course the standard has to, in the general case, say that a data race
> can cause undefined behaviour. But that does not mean that a program
> will crash just because two threads read and write from the same (4-bytes-
> long and word-aligned!) memory location.

I didn't say or even suggest the program will crash.  The read/write
operations are mandated by the standard--not its atomicity, sig_atomic_t
wouldn't be needed for signal handlers then.  The reported issue is
about the thread safety of volatile in regard of the order of
non-volatile operations with respect to volatile ones.

>> In this case, an hypothetical compiler, library and processor
>> combination may well place the read and the write together (they are
>> volatile lvalues, not memory fences), and sleep on that thread before
>> performing the call to bindtextdomain.
>
> No, it can't.

Yes, it can: 5.1.2.3(6) says otherwise, a valid program must produce
"the same result as the abstract machine execution".  Therefore:

>   - First the libfoo_initialized variable gets read,
>   - Then the bindtextdomain() function gets executed. This includes
>     acquiring a lock and releasing a lock.
>   - Then the libfoo_initialized variable gets set to true.

This program is ill-formed (in presence of multiple threads calling
create_foo as there is a "conflict" per 5.1.2.4(4).  The lock inside
bindtextdomain doesn't help, the data access is performed outside of its
scope.

>From that point on--and from the last unknown condition--, the compiler
has proven that it can only be executed on single thread.  That's what
usually means undefined behaviour for a compiler writer: things that
aren't taken into account because they "cannot occur".

With that proof in hand, I hope we can agree there is no observble
behaviour change (per 5.1.2.3(6)) when the write is performed before the
call.

> [...] the compiler is usually allowed to move either the read or the
> write of a variable across an acquire+release of a lock. But this does
> not hold for 'volatile' static variables (cf. ISO C 11 § 6.7.3.(7)).

So, yes, it does hold.  The observable behaviour rule, see example
5.1.2.3(10), allow a conforming implementation to do that and much more.
The paragraph you've cited ensures that _volatile accesses_ are
performed following strictly the abstract machine rules: the write must
be performed after the read per 5.1.2.3(3).  It doesn't imply the
volatile accesses are strictly sequenced, as shown in example
5.1.2.3(9), with _everything else_.  Only other volatile lvalues
accesses on the same thread are affected by the strict rules of the
abstract machine.

Not only that, even with a compiler in a "good mood", the processor and
the cache might reorder any volatile access: it is just a qualifier for
the source to binary translation, it doesn't exist during execution.
The standard takes this into account, that seems the reason behind
atomic types and making data races undefined behaviour.

>> After gathering this information, what do you think of changing volatile
>> to _Atomic?
>
> It still has portability problems[...]

Then the only portable solution I see is calling bindtextdomain without
any flag.  The use of volatile is wrong for most C implementations
starting with GNU GCC[1], it only "seems to work".

>> It won't produce ill-formed programs now, not by 2030.
>> bindtextdomain might be called more than once, but it's ensured that it
>> will be called before the first library call to dcgettext.
>
> That's also ensured with 'volatile'.

I understand my opinion on the standard might not be worth a change of
mind.  Nonetheless, I have to ask you to please reconsider what people
that know much more than me, like Paul Eggert and GCC developers, have
to say here.

For GCC[2] this is the status quo without _and with volatile_:

>   - the compiler could move the 'libfoo_initialized = true;' statement
>     around and thus - in the scenario you described - possibly invoke
>     foo_refcount before bindtextdomain.

Best regards,
Miguel

[1] https://gcc.gnu.org/onlinedocs/gcc/Volatiles.html
[2] Excerpt from [1]:
Accesses to non-volatile objects are not ordered with respect to
volatile accesses. [...]

volatile int vobj;
*ptr = something;
vobj = 1;

[...] it is not guaranteed that the write to *ptr occurs by the time the
update of vobj happens.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]