Dette passe 1, lot L5 : boucle MQTT du serveur, livraison de la configuration et journaux #5
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "pr/dette-l5"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Objet
Lot L5 de la passe 1 de dette : la boucle MQTT du serveur ne dépend plus de la base (files bornées par worker, accusé MQTT après écriture, session persistante), la configuration descendante est livrée jusqu'à son accusé, l'arrêt est propre et les journaux sont structurés, sans payload.
Dettes traitées et preuves
#remplacé par+, réabonnement à chaqueConnAck; plus de topic à plusieurs niveaux ni de$SYSaquaserveur/src/mqtt/settings.rs,server.rsset_max_packet_sizedu client (8 Kio de payload plus le plus long topic) ; broker :message_size_limit 8192,max_connections 1000,max_inflight_messages 20,max_queued_messages 100000; reconnexionBackoff1 s à 60 s ;MQTT_BROKER_URLetMQTT_CLIENT_IDvalidés au démarragemqtt/settings.rs,server.rs,configs/mosquitto/mosquitto.confINGEST_WORKERS(défaut 4) ; un boîtier a un seul worker (ordre conservé) ; erreurs passagères de la base rejouées (1 s doublé jusqu'à 30 s) sans accuser, erreurs durables journalisées puis accuséesaquaserveur/src/server.rs,ingest.rs,worker.rs,database/retry.rs,database/store.rsclean_session = false), client id stable, accusé manuel seulement après traitement ; rejeu sans effet grâce à l'empreinte des mesures (L4)mqtt/settings.rs,worker.rsrun_delivery: version publiée, renvoi tant que l'accusé manque (60 s doublé jusqu'à 30 min), message retenu par boîtier surmaj_serv-{id}, état relu au démarrage depuis la tableboitier_config_deliveriesaquaserveur/src/config_delivery.rs,scripts/db/migrations/002_config_delivery.sqlDISCONNECT, fermeture du pool ; vidage borné à 8 sserver.rs,main.rstracing_subscriberavecRUST_LOG; ni payload ni donnée personnelle (topic tronqué à 128 octets, erreur serde réduite à sa nature) ; rotationjson-file10 Mo x 5 dans les deux composes ; Mosquitto sur stdoutaquaserveur/src/logging.rs,provisioning/error.rs, composesTests et résultats
logging7 sur 7,MqttSettings7 sur 7,ingest8 sur 8,config_delivery9 sur 9, boucle serveur contre broker réel, SIGTERM).clean_session(true), limite client à 10 Kio, boucle bloquée par la base, payload journalisé, workers sans vidage, accusé avant écriture,maj_servnon retenu, renvoi désactivé, suivi non relu au démarrage.tests/mqtt_server_integration.rs,tests/config_delivery_integration.rs), dont un serveur coupé brutalement au tiers d'un flux de 90 trames puis redémarré : 90 lignes, 90 epochs distincts, rejeu de 15 trames sans nouvelle ligne.cargo fmt --check,cargo clippy ... -D warnings: OK.cargo test --workspace: 332 passés, 0 échec, 68 ignorés (L4 : 288 et 57).docker stoptraité en 369 ms, livraison de configuration sur quatre démarrages successifs, migration 002 sur MariaDB 10.6.22 jetable (version de la prod).Migrations et actions de déploiement
scripts/db/migrations/002_config_delivery.sqlavant le nouveau serveur :scripts/db/apply-migration.sh <conteneur> <fichier_env> scripts/db/migrations/002_config_delivery.sql(mot de passe parMYSQL_PWD). Table dédiéeboitier_config_deliveries, clé étrangère versboitiers(id)en cascade ;boitiersn'est pas modifiée. Sans cette table, le serveur démarre avec un suivi en mémoire et le signale.configs/mosquitto/mosquitto.confet redémarrer Mosquitto.MQTT_CLIENT_IDvide (défautaquaserveur) ou valeur stable par environnement ; deux serveurs ne doivent jamais partager un client id.INGEST_WORKERSfacultatif.Choix faits
+au lieu dedata-+et des autres préfixes, invalides en MQTT 3.1.1 (un+occupe un niveau entier).maj_serv-{id}tant que les ACL (L6) ne sont pas posées.boitiers(table gérée par Laravel).INGEST_WORKERS=1rend l'ordre strict.Duplicateau rejeu, sans alerte (à revoir avec la décision L4-bis « alerte même si l'écriture échoue »).Hors périmètre
Authentification et ACL Mosquitto (L6) ; Dockerfiles,
STOPSIGNAL, healthcheck réel N14 (L7) ; session persistante côté boîtier ; volumesmosquitto_log_*devenus inutiles ; documentation générale (L9).Retour arrière
mosquitto.conf.boitier_config_deliveriesn'est lue que par ce serveur ; la laisser ou la supprimer (DROP TABLE boitier_config_deliveries).maj_serv-{id}en publiant un message vide retenu sur chaque topic (mosquitto_pub -r -n -t maj_serv-<id>).Pile de PR
Base de cette PR :
pr/dette-l4. Fusion dans l'ordre de la pile ; apres chaque fusion,vigie.py retargetrebase la PR suivante surmain.pr/dette-l2: Dette passe 1, lot L2 : contrats partagés dans aquashared et client MQTT du boîtierpr/dette-l3: Dette passe 1, lot L3 : provisioning non destructif et identifiant validé à l'entrée MQTTpr/dette-l4: Dette passe 1, lot L4 : mesures, alertes et déduplicationpr/dette-l5: Dette passe 1, lot L5 : boucle MQTT du serveur, livraison de la configuration et journaux (cette PR)pr/dette-l4bis: Dette passe 1, lot L4-bis : alertes même si l'écriture échoue, délai unique de 600 s, typage des capteurs, ordre des accusés MQTTpr/dette-l7: Dette passe 1, lot L7 : builds reproductibles, dépendances, durcissement des conteneurspr/dette-l6: Dette passe 1, lot L6 : authentification Mosquitto, ACL par compte, healthchecks réels, pile e2e isoléepr/dette-l8: Dette passe 1, lot L8 : tests non destructifs, fixtures synthétiques, script de tests sur base jetableCommits du lot
5995bdffeat: structured server logs without payloadsceb3b1afeat: bound server MQTT packets, narrow its subscription and back off6b6736afeat: write MQTT messages through bounded worker queues, ack after write74df6f6feat: deliver boitier configuration until acknowledgedThe server subscribed to '#': any PUBLISH above the 10 KiB rumqttc default on any topic reset its connection, and it reconnected without delay (N11, Q9, Q2). It now: - sets max_packet_size from aquashared::mqtt::MQTT_MAX_PACKET_SIZE; - subscribes to '+' only. Prefix filters such as 'data-+' are invalid MQTT (a wildcard must fill a whole level, MQTT-4.7.1-3), so with single-level topic names '+' is the narrowest filter that still receives firstco-{id} from unknown boitiers; multi-level and $SYS topics are no longer received; - reconnects with aquashared::backoff::Backoff (1 s doubling to 60 s); - reads MQTT_BROKER_URL and MQTT_CLIENT_ID through MqttSettings and refuses to start on an invalid value. Mosquitto refuses payloads above 8 KiB (message_size_limit, below the client limit), caps connections on the listener, bounds QoS 1 inflight messages and sizes the persistent session queue. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UH3JcaVWsmFLC6kyEsVL18last_push was set when the maj_serv publication was queued, an offline boitier missed the configuration, AckTimeout was never used and a restart republished every boitier (N7). The delivery task now: - tracks per boitier the version sent, its send count and the last version acknowledged on maj_boitier_ACK (ok or error), persisted in the new boitier_config_deliveries table (migration 002, idempotent, no change to the Laravel boitiers table); - republishes an unacknowledged version after a timeout doubling from 60 s to 30 min (ConfigError::AckTimeout), never an acknowledged one, so a restart only resumes pending deliveries; - publishes maj_serv-{id} retained: an offline boitier gets the latest configuration as soon as it resubscribes, with both firmwares and no per-boitier persistent session; the boitier re-applies an equal version idempotently. firstco of an already provisioned boitier triggers an immediate push through the same task. The unused send_config_and_wait_ack stub and the non-retained push_config_to_mqtt are removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UH3JcaVWsmFLC6kyEsVL18Revue statique Vigie : PR #5
Tete
74df6f6fa5, basepr/dette-l4(2886a1291a), 4 commit(s), 28 fichier(s) ajoutes ou modifies.Statut
aquaprocess/revue-statique: failure (1 constat(s) bloquant(s) de la revue)Constats : 1 bloquant, 3 majeur, 19 mineur, 1 style. Commentaires en ligne : 2 (mode
important, plafond 10).Verifications automatiques
Analyse qualitative
Verdict : lot solide et bien documenté, 1 bloquant (fragment de payload d'une trame refusée recopié dans les journaux, contraire au contrat annoncé), 3 majeurs à traiter avant la prod, le reste en améliorations.
La séparation boucle MQTT, workers et tâche de livraison est propre, l'accusé après écriture est correctement placé, aucun
unwrapniexpecthors tests, et les références fichier:ligne du compte rendu sont exactes. Les réserves portent sur des chemins de saturation (files, canaux, inflight) qui ne se rétablissent pas seuls, et sur un trou dans le nettoyage des journaux.Sécurité
ProvisioningError::log_summarysont bien faites, mais une tramedata-malformée journalise le message deparse_custom_frame, qui recopie le jeton fautif (Invalid ext sensor value: '...',Invalid epoch: '...') sans échappement ni borne (aquaserveur/src/worker.rs:183). Avec un broker encore anonyme, n'importe quel client peut écrire des lignes arbitraires dans les journaux. Même famille, en moins grave : l'accusé de configuration invalide (worker.rs:277).max_queued_messages 100000vaut pour toute session persistante, combiné àallow_anonymous trueet à l'absence depersistent_client_expirationet demax_queued_bytes(configs/mosquitto/mosquitto.conf:27). Amplification de déni de service à fermer avant ou avec le lot L6.message_size_limitcohérentes (8 Kio de payload, limite client supérieure, contrôle compilé dans aquashared),max_connections 1000, files de 256 par worker, canal de requêtes de 1000. Abonnement réduit à+: justification MQTT-4.7.1-3 correcte,$SYSet topics à plusieurs niveaux exclus, tests à l'appui.maj_serv-{id}: lisibles par tout client tant que les ACL manquent, conservés après suppression du boîtier ; reconnu par le compte rendu (renvoi L6).boitiers. La clé étrangère vers une table gérée par Laravel est un couplage à signaler (002_config_delivery.sql:35).apply-migration.sh: mot de passe parMYSQL_PWDetdocker exec -esans valeur, ni en argument ni à l'écran, noms SQL validés par expression régulière. Seule la lecture du fichier d'environnement est fragile (apply-migration.sh:26).Correction
try_acken échec garde une place inflight que seule une reconnexion libère, et rien ne la provoque (server.rs:282). Les rechargements de règles contournent la réserve QoS 1 (ingest.rs:193), ce qui rend ce cas atteignable lors d'une modification groupée des configurations, surtout avecINGEST_WORKERS=1.notifyattend une place dans le canal de livraison pendant que la tâche de livraison attend une place dans la file d'un worker (worker.rs:300etconfig_delivery.rs:355). La perte d'événement étant déjà admise par conception,try_sendsuffit.ConnAck(server.rs:199).ProtocoletTlsclassés passagers (worker.rs:99).server.rs:177), et le pire cas total dépasse les 10 s dedocker stop(server.rs:48).decide/on_ackest juste et bien testée. Le message retenu fait réappliquer une configuration refusée à chaque reconnexion et réinsère une alerte 15 (config_delivery.rs:291).Duplicateet les erreurs d'insertion d'alerte accusées sans rejeu reposent sur « alertes seulement sur Inserted » (worker.rs:172), que la décision de l'utilisateur (alerte même si l'écriture échoue) va changer.Cohérence avec le compte rendu
Plus de 30 références vérifiées à la tête
74df6f6, toutes exactes :server.rs(run,dispatch, abonnement auConnAck, délai de reconnexion,disconnect, signaux, délai de vidage),ingest.rs(réserve 64 avec contrôle compilé, admission, répartition, bornes 1 à 8, cléfirstco),worker.rs(accusé après traitement,PushNow, transmission de l'accusé),mqtt/settings.rs,database/retry.rs,store.rs,data_handler.rs:181,provisioning/error.rs:50,logging.rs,config_delivery.rs, migration,mosquitto.conf, composes etmain.rs. Plus aucunprintln!nieprintln!dansaquaserveur/srchors--health-check.Écarts :
worker.rs:183).ingest.rs:193).server.rs:282).errorrejoué réinsère une alerte 15 (config_delivery.rs:291).rumqttcà la reconnexion (PUBACK en canal jetés) : la version 0.24 lue les replace danspending; le verrou cite 0.25.1, à vérifier (worker.rs:121).config_delivery_integration.rs:426).Tests
Bonne couverture unitaire des fonctions pures (admission, répartition, échéances, classification, journaux) et tests d'intégration réels convaincants pour l'absence de perte après coupure brutale, les PINGREQ pendant une base bloquée, le vidage à l'arrêt et le paquet de 20 Kio. Limites :
mqtt_server_integration.rs:389) ;PushNowni des saturations (canal de livraison plein, refus QoS 1, échec d'abonnement) ;tests/common/mqtt.rs:38) ;tests/common/mqtt.rs:99et suivants) rendent certains tests sensibles à la charge.Questions ouvertes
EventLoop::clean()en 0.25.1 pour les accusés restés en canal.error, ou le garder pour qu'un boîtier réessaie à chaque connexion (avec alerte 15 dédupliquée) ?mosquitto.confest-il prévu avant L6 ? Si oui,persistent_client_expirationetmax_queued_bytesdevraient l'accompagner.Autres constats (non publies en ligne)
aquaserveur/src/worker.rs:183error = %e: pourDataError::Parse, le message vient deaquashared::parse_custom_frame, qui recopie le jeton fautif (Invalid ext sensor value: '{t}',Invalid epoch: '{epoch_str}'), etEpochErrorrecopie l'epoch. Le payload, choisi par l'émetteur (broker anonyme), atteint donc les journaux, sans échappement (%= Display, retours à la ligne possibles, donc fausses lignes de journal) et sans borne de taille (jusqu'à 8 Kio par ligne). C'est contraire au contrat annoncé (« ni payload », « valeurs de la trame plus journalisées ») : journaliser seulement la nature de l'erreur (commeProvisioningError::log_summary) et ajouter une trame malformée au test des journaux. (non publie en ligne : Forgejo l'ancrerait a la ligne 180 du commit6b6736a, decalee dans la vue de la PR)aquaserveur/src/server.rs:282try_ackqui échoue dansack_now(canal de requêtes plein, l.298) non plus : chacun garde une place inflight du broker. Mosquitto ne renvoie qu'à la reconnexion, et rien ne la provoque ici. Après 20 cas, le broker cesse d'envoyer tout QoS 1 au serveur, sans erreur visible autre que ces journaux. Le commentaire « renvoyé par le broker à la reconnexion » suppose une reconnexion qui n'arrive pas. Piste : forcer une reconnexion (déconnexion du client) après un refus, ou garder les accusés en échec pour les renvoyer. (non publie en ligne : Forgejo l'ancrerait a la ligne 280 du commit6b6736a, decalee dans la vue de la PR)aquaserveur/src/config_delivery.rs:291retain, chaque reconnexion d'un boîtier lui redonne la configuration ; s'il échoue à l'appliquer, il renvoie un accuséerrorethandle_config_ackinsère une nouvelle alerte 15 à chaque fois (seulement bornée paradmit_insert). Cela contredit « un accusé error : pas de renvoi de cette version » et l'idempotence de l'accusé de configuration invoquée pour l'accusé après écriture (worker.rs:14). Piste : ne pas réinsérer l'alerte 15 si(version, error)est déjà enregistré, ou effacer le message retenu après un accuséerror.aquaserveur/src/data_handler.rs:360no-unwrap-in-production(WARNING) :.unwrap()ou.expect()peut paniquer en production. 1 occurrence(s) sur lignes ajoutees (360), y compris d'eventuels modules de test internes.aquaserveur/src/ingest.rs:193reloaddépose unJob::Reloadparsend().await, sans tenir compte deQOS1_RESERVE: une rafale de rechargements (modification groupée deboitiers.updated_at, environ 113 par worker pour 450 boîtiers, 450 avecINGEST_WORKERS=1) peut occuper les places réservées au QoS 1. L'invariant de l'en-tête du module (« la réserve leur garde toujours de la place ») ne tient donc pas, et le refus qui en résulte bloque une place inflight (voir server.rs:282). Piste : borner les rechargements (try_sendau-delà de la réserve, ou déduplication par boîtier).aquaserveur/src/ingest.rs:204no-unwrap-in-production(WARNING) :.unwrap()ou.expect()peut paniquer en production. 2 occurrence(s) sur lignes ajoutees (204, 208), y compris d'eventuels modules de test internes.aquaserveur/src/logging.rs:91no-unwrap-in-production(WARNING) :.unwrap()ou.expect()peut paniquer en production. 4 occurrence(s) sur lignes ajoutees (91, 108, 108, 155), y compris d'eventuels modules de test internes.aquaserveur/src/mqtt/settings.rs:231no-unwrap-in-production(WARNING) :.unwrap()ou.expect()peut paniquer en production. 5 occurrence(s) sur lignes ajoutees (231, 242, 250, 255, 261), y compris d'eventuels modules de test internes.aquaserveur/src/provisioning/error.rs:97no-unwrap-in-production(WARNING) :.unwrap()ou.expect()peut paniquer en production. 1 occurrence(s) sur lignes ajoutees (97), y compris d'eventuels modules de test internes.aquaserveur/src/server.rs:48docker stopdans le pire cas : 8 s de vidage, puis jusqu'à 2 s d'attente du DISCONNECT (l.318), puisbackground.shutdown()etpool.close()(main.rs:80). Un SIGKILL peut donc tomber pendant la fermeture. Piste : réduireDRAIN_TIMEOUT(6 s par exemple) ou fixerstop_grace_perioddans les composes.aquaserveur/src/server.rs:177queue = Nonene ferme pas les files : la tâche de livraison garde un clone d'IngestQueueet ne s'arrête qu'en revenant à sonselect!. Si elle est au milieu d'un passage (base lente,reloaden attente), les workers ne voient jamais leur canal fermé et l'arrêt attend toujours les 8 s deDRAIN_TIMEOUT. Piste : donner à la tâche de livraison un émetteur faible (WeakSender) ou fermer les files explicitement.aquaserveur/src/server.rs:199try_subscribe_manyéchoue (canal de requêtes de 1000 places plein au moment duConnAck, par exemple après une coupure pendant laquelle publications de livraison et accusés se sont accumulés), l'erreur est journalisée et l'abonnement n'est jamais retenté. Avecsession_present = false(première connexion, broker redémarré sans persistance), le serveur reste connecté sans rien recevoir. Piste : retenter à l'itération suivante, ou vérifier la réception du SUBACK et se reconnecter sinon.aquaserveur/src/worker.rs:99database/retry.rs:22classe aussiProtocoletTlscomme passagères ; si l'une d'elles était déterministe pour une requête donnée, ce message bloquerait le worker indéfiniment. Piste : compteur exposé et alerte au-delà de N essais, et classification plus restrictive.aquaserveur/src/worker.rs:121Duplicateet accusée une seconde fois. Si le broker a entre-temps réattribué ce pkid, ce second PUBACK accuserait un autre message avant son écriture. Le risque semble faible avec Mosquitto (pkid croissants), mais le compte rendu affirme querumqttcjette les PUBACK en canal à la reconnexion, alors que dans la version 0.24 consultéeEventLoop::clean()les déplace danspendingpour les renvoyer : à vérifier pour 0.25.1.aquaserveur/src/worker.rs:172Inserted». Une trame écrite dont la réponse s'est perdue, ou un arrêt entre l'écriture de la mesure et celle des alertes, revient enDuplicateet ses alertes ne partent jamais. De plus, une erreur passagère pendant l'insertion d'alertes est seulement journalisée (data_handler.rs, « alertes légionellose non insérées ») et le message est accusé. La décision « alerte même si l'écriture échoue » et le délai unique de 600 s (server.rs:54) devront être raccordés àStep::Retrysans doubler les alertes au rejeu.aquaserveur/src/worker.rs:277error = %e; pourConfigAckError::InvalidPayload, ce message est celui deserde_jsonet peut recopier une valeur du payload (échappée). Même raisonnement que pourfirstco(log_summary) : réduire à la nature de l'erreur.aquaserveur/tests/common/mqtt.rs:38maj_serv-{id}retenus sur le broker partagé et écrivent dansboitier_config_deliveriessi la table existe. Les deux binaires de test partagent donc un état que le verrouSERIAL(par binaire) ne protège pas. Piste : un réglage qui désactive la livraison dansserver_config, ou un nettoyage explicite.aquaserveur/tests/config_delivery_integration.rs:426save_state, puis un seul serveur démarre. Le chemin réel (suivi écrit par une instance, relu par la suivante, envoi non accusé repris à l'échéance et non au démarrage) n'est couvert que par le test unitaire dedecide. Piste : démarrer une première instance, l'arrêter avant l'accusé, puis vérifier l'absence de publication immédiate au redémarrage.aquaserveur/tests/mqtt_server_integration.rs:389data-malformée ni unmaj_boitier_ACK-invalide, qui sont justement les chemins qui recopient le payload (worker.rs:183, :277). Ajouter ces deux cas avec un marqueur dans le payload.scripts/db/apply-migration.sh:26set -euo pipefail, une clé absente du fichier d'environnement fait échouergrepdans la substitution : le script s'arrête sans message, et le contrôle « DB_PASSWORD vide » (l.37) n'est atteint que pour une valeur vide. Une valeur entre guillemets (DB_PASSWORD="...", courant dans un.env) est transmise avec ses guillemets. Le passage parMYSQL_PWDhors argument est correct.scripts/db/migrations/002_config_delivery.sql:35boitiers(id)lie une table du serveur à une table possédée par Laravel : unTRUNCATE boitiers, une migration Laravel qui modifie le type deboitiers.idou recrée la table échouera désormais. Le choix se défend (nettoyage en cascade, refus d'un id inconnu), mais il mérite d'être signalé à l'équipe Laravel dans la documentation de déploiement.aquaserveur/tests/common/mqtt.rs:99wait_untilsur une condition.Limites
Revue publiee en
COMMENT: l'auteur des PR et le relecteur sont le meme compte, Forgejo refuse APPROVE et REQUEST_CHANGES. Le blocage s'exprime par le statut de commitaquaprocess/revue-statique. Analyse statique et tests automatises seulement, sans fusion ni deploiement.@ -0,0 +297,4 @@/// l'arrêt du serveur : l'événement est alors perdu, la livraison reprend/// sur l'état enregistré au démarrage suivant).async fn notify(deps: &WorkerDeps, event: DeliveryEvent) {if deps.delivery.send(event).await.is_err() {[majeur]
notifyattend une place dans le canal de livraison (send().await, 256 places) alors que la tâche de livraison, pendant un passage, attend elle-même une place dans la file d'un worker (queue.reload(id).await, config_delivery.rs:355) sans lire ses événements. Si le canal de livraison est plein pendant qu'une file de worker est pleine, le worker bloqué ne vide plus sa file et la tâche bloquée ne vide plus le canal : interblocage permanent, sans coupure ni journal, et l'ingestion de ce worker s'arrête (puis des autres, bloqués à leur tour surnotify). Même sans interblocage, un passage long (450 publications et écritures séquentielles,intervalen mode rafale) suspend tous les workers qui reçoivent un accusé de configuration. La perte d'un événement étant déjà admise (commentaire l.296), utilisertry_sendet journaliser, ou découpler les rechargements dans une tâche dédiée.Source : revue.
@ -7,0 +24,4 @@# File d'une session persistante pendant une coupure du serveur. Ordre de# grandeur : 450 boîtiers à une trame par minute, soit environ 3 h 40 avant# que le broker ne commence à écarter des messages.max_queued_messages 100000[majeur]
max_queued_messages 100000s'applique à toutes les sessions persistantes, pas seulement à celle du serveur (défaut Mosquitto : 1000). Avecallow_anonymous true(l.1, inchangé), sansmax_queued_bytesnipersistent_client_expiration, n'importe quel client peut ouvrir des sessions persistantes sous des client id différents, s'abonner puis se déconnecter : chacune accumule jusqu'à 100 000 messages en mémoire et dans la persistance, sans expiration (max_connectionsne borne pas les sessions déconnectées). Ajoutermax_queued_bytesetpersistent_client_expiration, et ne pas déployer cette valeur avant les ACL du lot L6.Source : revue.
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.Merge
Merge the changes and update on Forgejo.Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.