v0.1.2-pre.004-fix.001
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
@@ -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(())
|
||||||
},
|
},
|
||||||
|
|||||||
157
deltas/0.1.2/pre.004-fix.001.md
Normal file
157
deltas/0.1.2/pre.004-fix.001.md
Normal 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
|
||||||
|
```
|
||||||
@@ -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 :
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user