v0.1.2-pre.004-fix.001

This commit is contained in:
2026-08-14 19:22:56 +02:00
parent 151590422d
commit 9f23eb950c
4 changed files with 178 additions and 13 deletions

View File

@@ -1,12 +1,12 @@
# file: Cargo.toml # file: Cargo.toml
# version: 31 # version: 32
[workspace] [workspace]
resolver = "3" resolver = "3"
members = ["crates/ksp-core-lib", "crates/ksp-logging-lib"] members = ["crates/ksp-core-lib", "crates/ksp-logging-lib"]
[workspace.package] [workspace.package]
version = "0.1.2-pre.4" version = "0.1.2-pre.4.fix.1"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project" repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-logging-lib/src/runtime.rs // file: crates/ksp-logging-lib/src/runtime.rs
// version: 3 // version: 4
use tracing_subscriber::Layer; // rust-rules: trait-import use tracing_subscriber::Layer; // rust-rules: trait-import
use tracing_subscriber::layer::SubscriberExt; // 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<cra
/// Replaces the active KSP logging settings and non-blocking outputs without reinstalling the global subscriber. /// Replaces the active KSP logging settings and non-blocking outputs without reinstalling the global subscriber.
/// ///
/// New runtime layers, writers and guards are fully prepared before the reload is attempted. If validation or preparation fails, the currently active /// New runtime layers, writers and guards are fully prepared before the reload is attempted. If validation or preparation fails, the currently active
/// configuration remains unchanged. After a successful layer swap, dropped-line counters from the retired outputs are retained cumulatively and the old /// configuration remains unchanged. After a successful layer swap, dropped-line counters from the retired outputs are retained cumulatively. Retired
/// worker guards are dropped so their queues can be flushed. /// layers are then dropped before their worker guards so all retired `NonBlocking` senders are released before shutdown asks the workers to drain/flush.
pub fn reinitialize(guard: &mut crate::LoggingGuard, settings: &crate::LoggingSettings) -> ksp_core_lib::Result<()> { pub fn reinitialize(guard: &mut crate::LoggingGuard, settings: &crate::LoggingSettings) -> ksp_core_lib::Result<()> {
return prepare_runtime(settings).and_then(|prepared| -> ksp_core_lib::Result<()> { return prepare_runtime(settings).and_then(|prepared| -> ksp_core_lib::Result<()> {
let PreparedRuntime { layers, outputs } = prepared; 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 { return match reload_result {
std::result::Result::Ok(()) => { std::result::Result::Ok(()) => {
guard.retired_dropped_lines = guard.retired_dropped_lines.saturating_add(guard.outputs.dropped_lines()); guard.retired_dropped_lines = guard.retired_dropped_lines.saturating_add(guard.outputs.dropped_lines());
let retired_outputs = std::mem::replace(&mut guard.outputs, outputs); let retired_outputs = std::mem::replace(&mut guard.outputs, outputs);
guard.settings = settings.clone(); guard.settings = settings.clone();
drop(retired_layers);
drop(retired_outputs); drop(retired_outputs);
std::result::Result::Ok(()) std::result::Result::Ok(())
}, },

View File

@@ -0,0 +1,157 @@
<!-- file: deltas/0.1.2/pre.004-fix.001.md -->
<!-- version: 1 -->
# 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
```

View File

@@ -1,13 +1,13 @@
<!-- file: docs/plans/004-V0_1_2_LOGGING_FOUNDATION_PLAN.md --> <!-- file: docs/plans/004-V0_1_2_LOGGING_FOUNDATION_PLAN.md -->
<!-- version: 7 --> <!-- version: 8 -->
# Plan KSP 0.1.2 — Logging foundation # Plan KSP 0.1.2 — Logging foundation
## Statut ## 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 ## Base auditée
@@ -552,8 +552,10 @@ Contrat :
- console et fichier peuvent chacun posséder leur `WorkerGuard` ; - 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 ; - `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 ; - 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 ; - un reload construit d'abord les nouveaux sinks/filters, puis bascule vers eux ;
- aucun `WorkerGuard` n'est détruit à la fin d'une fonction d'initialisation ou de reconfiguration avant que son sink ne soit effectivement retiré ; - 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. - 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 : 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 ; - `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é ; - 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 ; - 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 ; - 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 ; - 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. - 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`. 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 ### `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 ; 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. 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 : Restent à confirmer par les prereleases suivantes sans remettre en cause ce contrat :