From 9f23eb950c5eb8551f72b428b8e35ced70ad74f8 Mon Sep 17 00:00:00 2001 From: SinuS Von SifriduS Date: Fri, 14 Aug 2026 19:22:56 +0200 Subject: [PATCH] v0.1.2-pre.004-fix.001 --- Cargo.toml | 4 +- crates/ksp-logging-lib/src/runtime.rs | 12 +- deltas/0.1.2/pre.004-fix.001.md | 157 ++++++++++++++++++ .../004-V0_1_2_LOGGING_FOUNDATION_PLAN.md | 18 +- 4 files changed, 178 insertions(+), 13 deletions(-) create mode 100644 deltas/0.1.2/pre.004-fix.001.md diff --git a/Cargo.toml b/Cargo.toml index 07f620a..34b6cc3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ # file: Cargo.toml -# version: 31 +# version: 32 [workspace] resolver = "3" members = ["crates/ksp-core-lib", "crates/ksp-logging-lib"] [workspace.package] -version = "0.1.2-pre.4" +version = "0.1.2-pre.4.fix.1" edition = "2024" license = "MIT" repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project" diff --git a/crates/ksp-logging-lib/src/runtime.rs b/crates/ksp-logging-lib/src/runtime.rs index beb140d..55e615a 100644 --- a/crates/ksp-logging-lib/src/runtime.rs +++ b/crates/ksp-logging-lib/src/runtime.rs @@ -1,5 +1,5 @@ // file: crates/ksp-logging-lib/src/runtime.rs -// version: 3 +// version: 4 use tracing_subscriber::Layer; // rust-rules: trait-import use tracing_subscriber::layer::SubscriberExt; // rust-rules: trait-import @@ -135,17 +135,21 @@ pub fn initialize(settings: &crate::LoggingSettings) -> ksp_core_lib::Result ksp_core_lib::Result<()> { return prepare_runtime(settings).and_then(|prepared| -> ksp_core_lib::Result<()> { let PreparedRuntime { layers, outputs } = prepared; - let reload_result = guard.reload_handle.reload(layers); + let mut retired_layers = RuntimeLayers::new(); + let reload_result = guard.reload_handle.modify(|active_layers| { + retired_layers = std::mem::replace(active_layers, layers); + }); return match reload_result { std::result::Result::Ok(()) => { guard.retired_dropped_lines = guard.retired_dropped_lines.saturating_add(guard.outputs.dropped_lines()); let retired_outputs = std::mem::replace(&mut guard.outputs, outputs); guard.settings = settings.clone(); + drop(retired_layers); drop(retired_outputs); std::result::Result::Ok(()) }, diff --git a/deltas/0.1.2/pre.004-fix.001.md b/deltas/0.1.2/pre.004-fix.001.md new file mode 100644 index 0000000..6ef91f8 --- /dev/null +++ b/deltas/0.1.2/pre.004-fix.001.md @@ -0,0 +1,157 @@ + + + +# Delta 0.1.2-pre.004-fix.001 + +## Base requise + +Livraison précédente : + +```text +0.1.2-pre.004 +``` + +La base porte : + +```text +workspace.package.version = "0.1.2-pre.4" +Cargo.toml header version = 31 +``` + +## Validations remontées + +Les validations utilisateur de `pre.004` sont : + +```text +cargo fmt --all OK +cargo check --workspace OK +cargo clippy --workspace --all-targets OK +cargo test --workspace ECHEC +cargo tree -p ksp-logging-lib OK +cargo tree -p ksp-logging-lib -d OK — aucun doublon +cargo tree -p ksp-logging-lib -e features inspecté +``` + +Tous les tests unitaires, de callsite et de façade publique passent. Le seul échec est : + +```text +global_runtime_supports_takeover_non_blocking_outputs_hot_reload_and_single_initialization +``` + +sur : + +```text +assertion failed: file_text.contains("file output marker") +``` + +Le test émet une ligne sur le sink fichier, retire immédiatement ce sink par hot reload, puis lit le fichier. Il constitue donc un test direct du contrat de drain/flush lors d'un reload. + +## Cause de lifecycle + +`pre.004` faisait conceptuellement : + +```text +reload_handle.reload(new_layers) +retire counters +replace outputs +drop(old WorkerGuard) +``` + +Le layer `fmt` retiré possède les clones `NonBlocking` utilisés pour alimenter le worker. KSP ne récupérait cependant pas explicitement l'ancien `Vec` de layers ; l'ordre entre la destruction effective de ces anciens layers et la destruction des `WorkerGuard` n'était donc pas exprimé dans notre lifecycle. + +Pour un sink non bloquant, l'ordre voulu est explicite : + +```text +1. préparer complètement le nouveau runtime +2. remplacer le Vec actif et récupérer l'ancien Vec +3. mémoriser les dropped-line counters +4. remplacer les outputs actifs +5. détruire les anciens layers / NonBlocking senders +6. détruire les anciens WorkerGuard +7. retourner du reinitialize() +``` + +`WorkerGuard` envoie le signal de shutdown au worker et attend son drain/flush de manière bornée. Les anciens senders doivent donc être libérés avant cette étape lorsqu'un sink vient d'être retiré. + +## Correction + +`reinitialize()` n'utilise plus : + +```text +Handle::reload(new_layers) +``` + +pour les changements de runtime. + +Il utilise : + +```text +Handle::modify(... mem::replace(active_layers, new_layers) ...) +``` + +et récupère ainsi l'ancien `RuntimeLayers`. + +Après succès du swap : + +```text +drop(retired_layers) +drop(retired_outputs) +``` + +est exécuté dans cet ordre. + +Cette correction : + +- ne change pas l'API publique ; +- conserve le subscriber global unique ; +- conserve le takeover KSP ; +- conserve la préparation transactionnelle des nouveaux sinks avant le swap ; +- conserve l'ancienne configuration lorsqu'une validation ou une construction de sink échoue avant le swap ; +- rend explicite le lifecycle de retrait des `NonBlocking` writers avant leurs `WorkerGuard` ; +- évite d'ajouter un sleep ou un polling temporel au test. + +Le test d'intégration qui a révélé le défaut reste inchangé et sert directement de test de non-régression. + +## Référence backend + +`tracing-appender 0.2.5` documente `WorkerGuard` comme responsable du flush des logs bufferisés à sa destruction. Son implémentation de `Drop` envoie un `Msg::Shutdown` au worker puis attend le signal de fin de drain de manière bornée. KSP doit donc contrôler clairement l'ordre de destruction des senders/layers et du guard au moment d'un hot reload. + +## Version technique + +Ce correctif modifie du Rust. La version workspace devient : + +```text +workspace.package.version = "0.1.2-pre.4.fix.1" +``` + +et l'en-tête du `Cargo.toml` racine devient : + +```text +# version: 32 +``` + +## Fichiers du delta + +```text +Cargo.toml +crates/ksp-logging-lib/src/runtime.rs +docs/plans/004-V0_1_2_LOGGING_FOUNDATION_PLAN.md +deltas/0.1.2/pre.004-fix.001.md +``` + +## Validations à exécuter + +```bash +cargo fmt --all +cargo check --workspace +cargo clippy --workspace --all-targets +cargo test --workspace +``` + +Le graphe Cargo/features de `pre.004` a déjà été remonté sans doublon. Il pourra être réaudité dans `pre.005` avec les validations d'intégration finales. + +Si ces validations sont propres, la tranche suivante reste : + +```text +0.1.2-pre.005 — intégration + concurrence + saturation + audits +``` diff --git a/docs/plans/004-V0_1_2_LOGGING_FOUNDATION_PLAN.md b/docs/plans/004-V0_1_2_LOGGING_FOUNDATION_PLAN.md index 75530ab..046a69d 100644 --- a/docs/plans/004-V0_1_2_LOGGING_FOUNDATION_PLAN.md +++ b/docs/plans/004-V0_1_2_LOGGING_FOUNDATION_PLAN.md @@ -1,13 +1,13 @@ - + # Plan KSP 0.1.2 — Logging foundation ## Statut -Plan actif de `0.1.2`, établi par `0.1.2-pre.001`, corrigé par `0.1.2-pre.001-fix.001`, concrétisé par la façade de `0.1.2-pre.002`, étendu au runtime subscriber par `0.1.2-pre.003`/`pre.003-fix.001`, puis complété par les sorties non bloquantes de `0.1.2-pre.004`. +Plan actif de `0.1.2`, établi par `0.1.2-pre.001`, corrigé par `0.1.2-pre.001-fix.001`, concrétisé par la façade de `0.1.2-pre.002`, étendu au runtime subscriber par `0.1.2-pre.003`/`pre.003-fix.001`, puis complété par les sorties non bloquantes de `0.1.2-pre.004` et leur lifecycle de retrait corrigé par `pre.004-fix.001`. -`pre.003-fix.001` a été validé dans l'environnement de développement avec `cargo fmt --all`, `cargo check --workspace`, `cargo clippy --workspace --all-targets` et `cargo test --workspace` propres sur la version Cargo `0.1.2-pre.3.fix.1`. `pre.004` ajoute `tracing-appender`, remplace la console synchrone par un writer non bloquant, introduit le fichier `Never/Hourly/Daily`, possède les `WorkerGuard`, expose des compteurs cumulés de dropped lines, déporte le stripping ANSI côté worker fichier et conserve le hot reload transactionnel des sinks. Les validations Cargo de cette nouvelle tranche restent à exécuter dans le workspace utilisateur avant validation. +`pre.003-fix.001` a été validé dans l'environnement de développement avec `cargo fmt --all`, `cargo check --workspace`, `cargo clippy --workspace --all-targets` et `cargo test --workspace` propres sur la version Cargo `0.1.2-pre.3.fix.1`. `pre.004` ajoute `tracing-appender`, remplace la console synchrone par un writer non bloquant, introduit le fichier `Never/Hourly/Daily`, possède les `WorkerGuard`, expose des compteurs cumulés de dropped lines, déporte le stripping ANSI côté worker fichier et conserve le hot reload transactionnel des sinks. Les validations de `pre.004` ont montré un défaut de lifecycle lors du retrait immédiat du sink fichier : le fichier pouvait être lu avant que le worker n'ait observé un shutdown avec tous les anciens `NonBlocking` senders effectivement libérés. `pre.004-fix.001` retire donc explicitement les anciens layers du runtime, les détruit, puis détruit leurs `WorkerGuard`. ## Base auditée @@ -552,8 +552,10 @@ Contrat : - console et fichier peuvent chacun posséder leur `WorkerGuard` ; - `LoggingGuard` conserve les guards actifs, les compteurs de dropped lines et l'état nécessaire au hot reload ; - l'appelant conserve `LoggingGuard` pendant toute la durée de vie du logging ; -- un reload construit d'abord les nouveaux sinks/filters, puis bascule vers eux, puis flush/détruit les anciens guards ; -- aucun `WorkerGuard` n'est détruit à la fin d'une fonction d'initialisation ou de reconfiguration avant que son sink ne soit effectivement retiré ; +- un reload construit d'abord les nouveaux sinks/filters, puis bascule vers eux ; +- le swap récupère explicitement les anciens layers avec `reload::Handle::modify` + `mem::replace` ; +- les anciens layers sont détruits avant leurs `WorkerGuard`, afin que les clones `NonBlocking` qu'ils possèdent soient libérés avant l'envoi du shutdown/drain au worker ; +- aucun `WorkerGuard` n'est détruit à la fin d'une fonction d'initialisation ou de reconfiguration avant que son sink ne soit effectivement retiré et que ses anciens layers aient été détruits ; - aucune fuite volontaire (`mem::forget`) n'est utilisée pour prolonger artificiellement la durée de vie. Le subscriber global reste installé jusqu'à la fin du processus. Le lifecycle KSP devient : @@ -971,11 +973,13 @@ Réalisé : - `LoggingGuard::dropped_lines()` expose un `DroppedLines` cumulatif pour console/fichier/total, sans perdre les compteurs des sinks retirés lors d'un reload ; - formatter humain enrichi avec target, source file et line, ANSI du formatter désactivé ; - stripping ANSI fichier effectué côté worker, avant persistence, avec état conservé entre buffers ; -- hot reload prépare les nouveaux writers/guards avant le swap, remplace les layers, mémorise les dropped lines des anciens sinks puis détruit leurs guards pour provoquer leur flush ; +- hot reload prépare les nouveaux writers/guards avant le swap, remplace explicitement les layers via `Handle::modify`, mémorise les dropped lines, détruit les anciens layers puis détruit leurs guards pour provoquer le drain/flush ; - une erreur de construction du nouveau file appender retourne `logging.file_output_initialization_failed` et conserve la configuration précédente ; - test d'intégration étendu pour couvrir fichier, erreur transactionnelle, flush lors du retrait du sink, takeover externe et stripping ANSI ; - tests unitaires ajoutés pour rotations, runtime disabled/non bloquant, statistiques et stripper ANSI. +Le premier test d'intégration `pre.004` a révélé que l'ordre de destruction n'était pas assez explicite lorsque le sink fichier était retiré immédiatement après une émission : `pre.004-fix.001` récupère désormais les anciens layers lors du swap et les détruit avant les `WorkerGuard`. Le test d'intégration existant reste le test de non-régression de cette garantie. + La saturation déterministe des queues, les reloads concurrents et l'audit complet des dépendances restent à renforcer dans `pre.005`. ### `0.1.2-pre.005` — intégration + concurrence + tests + audits @@ -1058,7 +1062,7 @@ Les deux questions d'API propres à `pre.002` sont résolues : 1. les macros KSP délèguent aux macros `tracing` au point d'appel via un bridge interne caché et exigent un `target:` explicite ; 2. la surface span publique est `Span::in_scope(...)` pour le synchrone et `instrument(span, future)` pour l'async, avec type de future retourné opaque. -La composition de reload est désormais fixée pour cette release à un `Vec` de layers boxed derrière une `reload::Layer`, ce qui autorise l'activation/désactivation des sinks et le remplacement de leurs paramètres sans second subscriber global. Le takeover `Targets` est un layer global distinct dans ce `Vec`; les layers de sortie reloadables ne doivent pas être des `Filtered` remplacés directement par `Handle::reload`. +La composition de reload est désormais fixée pour cette release à un `Vec` de layers boxed derrière une `reload::Layer`, ce qui autorise l'activation/désactivation des sinks et le remplacement de leurs paramètres sans second subscriber global. Le takeover `Targets` est un layer global distinct dans ce `Vec`; les layers de sortie reloadables ne doivent pas être des `Filtered` remplacés directement. Pour les changements de sinks, `pre.004-fix.001` utilise `Handle::modify` afin de récupérer le `Vec` retiré : les anciens layers sont détruits avant les `WorkerGuard` correspondants, ce qui fixe explicitement le lifecycle de drain des writers non bloquants. Restent à confirmer par les prereleases suivantes sans remettre en cause ce contrat :