Thread-safety par type
L’ABI C déclare, par type, ce qui est appelable depuis quel thread (ADR-027 §B.4). En C, le compilateur ne vérifie rien : respecter ces politiques est votre responsabilité. Cette page les énonce avec les pièges concrets.
Tableau des politiques
| Type / fonctions | Politique | Ce que cela autorise |
|---|---|---|
AirLog (air_log_emit, air_log_lost_count) | ThreadSafe | même handle partagé entre plusieurs threads |
AirLog (air_log_close) | consomme | fermeture sans usage concurrent |
AirLogFields (air_log_fields_*) | NON thread-safe | construire/muter sur un seul thread |
Temps & identifiants (air_instant_*, air_duration_*, air_uuid_*, air_id128_*, air_monotonic_id_next, air_status_message) | réentrant / sans état partagé | appel concurrent libre |
AirLog = ThreadSafe
Un même handle AirLog peut être partagé entre plusieurs threads : son cœur est
un Arc<JournalSink> et l’émission est un envoi de datagramme atomique. Plusieurs
threads peuvent donc appeler air_log_emit / air_log_lost_count sur le même
handle, en parallèle, sans verrou de votre part :
/* thread A et thread B, même `log` — OK */
air_log_emit(log, AIR_LOG_LEVEL_INFO, "depuis A", NULL);
air_log_emit(log, AIR_LOG_LEVEL_ERROR, "depuis B", NULL);
Piège — la fermeture, elle, n’est pas concurrente.
air_log_closeconsomme le handle (il le libère). Vous devez garantir qu’aucun autre thread n’utiliselogau moment duclose, et qu’il n’est appelé qu’une fois. Schéma sûr : faire travailler tous les threads, les joindre, puisclosedepuis un seul thread.
AirLogFields = NON thread-safe
AirLogFields est un builder mutable : chaque air_log_fields_add[_bytes] le
modifie. Il n’est protégé par aucun verrou. N’y touchez jamais depuis deux threads à
la fois.
Le modèle d’usage prévu — et sûr — est « construire mono-thread, puis lire » :
/* 1. Un SEUL thread construit le builder. */
AirLogFields *f = NULL;
air_log_fields_new(&f);
air_log_fields_add(f, "REQUEST_ID", "abc-123");
air_log_fields_add(f, "USER", "alice");
/* 2. Passé en lecture (+0) à emit : sûr tant que personne ne le mute en parallèle. */
air_log_emit(log, AIR_LOG_LEVEL_INFO, "requête traitée", f);
air_log_fields_free(f); /* libéré par le thread propriétaire */
Le danger concret. Deux threads appelant
air_log_fields_addsur le mêmeAirLogFields= course de données (corruption de la structure interne, undefined behavior). Si plusieurs threads doivent enrichir un log, donnez à chacun son propreAirLogFields, ou sérialisez lesaddderrière votre mutex.
Pourquoi ce choix ? Un builder est par nature un objet de travail local : le
rendre thread-safe imposerait un verrou interne coûteux à tous, pour un besoin rare.
La politique « un thread le construit, plusieurs peuvent ensuite logger via un AirLog
partagé » couvre le cas réel sans surcoût (Principe 5 : ne pas payer pour ce qu’on
n’utilise pas).
Temps & identifiants = réentrants / sans état partagé
- Toutes les fonctions de temps (
air_instant_now,air_instant_elapsed,air_instant_duration_since,air_duration_from_*,air_duration_as_secs_f64) et d’identifiants (air_uuid_*,air_id128_*,air_status_message) sont réentrantes - elles ne font qu’une lecture d’horloge kernel et/ou un calcul sur des POD locaux à
l’appelant, sans état partagé mutable. Vous pouvez les appeler concurremment
depuis autant de threads que vous voulez, chacun avec ses propres
out:
/* N threads, chacun avec son AirInstant local — aucun partage, aucun verrou */
AirInstant t;
air_instant_now(&t);
air_monotonic_id_next est le seul à toucher un compteur global au processus,
mais l’incrément est atomique : appel concurrent sûr, chaque thread reçoit une
valeur distincte et strictement croissante. (La monotonie est garantie au sein
d’un processus ; ce n’est pas un identifiant inter-processus.)
Règle mnémotechnique
- Logger (
AirLog) : partageable entre threads pour émettre ; fermez-le seul, une fois, sans usage concurrent. - Champs (
AirLogFields) : un thread, un builder. Jamais de mutation concurrente. - Temps & IDs : sans souci, partout, en parallèle.
English version: Thread-safety by type.