@startuml 25_failure_node_gc title F7 — Crash nœud → GC indexeur + AfterDelete participant "Node\n(crashé)" as N participant "Indexer A" as IA participant "Indexer B" as IB note over N, IB: État nominal : N heartbeatait vers IA et IB == Crash Node == N ->x IA: stream reset (heartbeat coupé) N ->x IB: stream reset (heartbeat coupé) == GC côté Indexer A == note over IA: HandleHeartbeat : stream reset détecté\nStreamRecords[ProtocolHeartbeat][N].Expiry figé loop ticker GC (30s) — StartGC(30*time.Second) IA -> IA: gc()\nnow.After(Expiry) où Expiry = lastHBTime + 2min\n→ si 2min sans heartbeat → éviction IA -> IA: delete(StreamRecords[ProtocolHeartbeat][N])\nAfterDelete(N, name, did) appelé hors lock note over IA: N retiré du registre vivant.\nFillRate recalculé : (n-1) / MaxNodesConn() end == Impact fill rate == note over IA: FillRate diminue.\nProchain BuildHeartbeatResponse\ninclura FillRate mis à jour.\nSi fillRate revient < 80% :\n→ offload.inBatch et alreadyTried réinitialisés. == GC côté Indexer B == note over IB: Même GC effectué.\nN retiré de StreamRecords[ProtocolHeartbeat]. == Reconnexion éventuelle du nœud == N -> N: redémarrage N -> IA: SendHeartbeat /opencloud/heartbeat/1.0\nHeartbeat{name, PeerID_N, IndexersBinded, need, record} IA -> IA: HandleHeartbeat → UptimeTracker(FirstSeen=now)\nStreamRecords[ProtocolHeartbeat][N] recréé\nRepublish PeerRecord N dans DHT note over IA: N de retour avec FirstSeen frais.\ndynamicMinScore élevé tant que age < 24h.\n(phase de grâce : 2 ticks avant scoring) @enduml