2026-03-25 11:13:12 +01:00
|
|
|
package workflow_builder
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"fmt"
|
|
|
|
|
"strconv"
|
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
|
|
"cloud.o-forge.io/core/oc-lib/logs"
|
|
|
|
|
"cloud.o-forge.io/core/oc-lib/tools"
|
|
|
|
|
)
|
|
|
|
|
|
2026-05-27 16:09:45 +02:00
|
|
|
// ── considersCache (signal-only) ─────────────────────────────────────────────
|
|
|
|
|
|
2026-03-25 11:13:12 +01:00
|
|
|
// considersCache stocke les canaux en attente d'un PB_CONSIDERS,
|
|
|
|
|
// indexés par "executionsId:dataType". Un même message NATS réveille
|
|
|
|
|
// tous les waiters enregistrés sous la même clé (broadcast).
|
|
|
|
|
type considersCache struct {
|
|
|
|
|
mu sync.Mutex
|
|
|
|
|
pending map[string][]chan struct{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var globalConsidersCache = &considersCache{
|
|
|
|
|
pending: make(map[string][]chan struct{}),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// considersKey construit la clé du cache à partir de l'ID d'exécution,
|
|
|
|
|
// du type de données et du peer compute (SourcePeerID).
|
|
|
|
|
// peerID permet de différencier plusieurs waiters COMPUTE_RESOURCE du même
|
|
|
|
|
// executionsId (1 local + N distants en parallèle).
|
|
|
|
|
func considersKey(executionsId string, dataType tools.DataType, peerID string) string {
|
|
|
|
|
key := executionsId + ":" + strconv.Itoa(dataType.EnumIndex())
|
|
|
|
|
if peerID != "" {
|
|
|
|
|
key += ":" + peerID
|
|
|
|
|
}
|
|
|
|
|
return key
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// register inscrit un nouveau canal d'attente pour la clé donnée.
|
|
|
|
|
// Retourne le canal à lire et une fonction de désinscription à appeler en defer.
|
|
|
|
|
func (c *considersCache) register(key string) (<-chan struct{}, func()) {
|
|
|
|
|
ch := make(chan struct{}, 1)
|
|
|
|
|
c.mu.Lock()
|
|
|
|
|
c.pending[key] = append(c.pending[key], ch)
|
|
|
|
|
c.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
unregister := func() {
|
|
|
|
|
c.mu.Lock()
|
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
|
list := c.pending[key]
|
|
|
|
|
for i, existing := range list {
|
|
|
|
|
if existing == ch {
|
|
|
|
|
c.pending[key] = append(list[:i], list[i+1:]...)
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if len(c.pending[key]) == 0 {
|
|
|
|
|
delete(c.pending, key)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return ch, unregister
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// confirm réveille tous les waiters enregistrés sous la clé donnée
|
|
|
|
|
// et les supprime du cache.
|
|
|
|
|
func (c *considersCache) confirm(key string) {
|
|
|
|
|
c.mu.Lock()
|
|
|
|
|
list := c.pending[key]
|
|
|
|
|
delete(c.pending, key)
|
|
|
|
|
c.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
for _, ch := range list {
|
|
|
|
|
select {
|
|
|
|
|
case ch <- struct{}{}:
|
|
|
|
|
default:
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-27 16:09:45 +02:00
|
|
|
// ── sourcePresignedCache (value-bearing) ─────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
// sourcePresignedCache stocke les canaux en attente d'une URL pré-signée pour
|
|
|
|
|
// une source privée (isReachable=false), indexés par la clé sourceConsidersKey.
|
|
|
|
|
// La valeur transportée est l'URL pré-signée elle-même.
|
|
|
|
|
type sourcePresignedCache struct {
|
|
|
|
|
mu sync.Mutex
|
|
|
|
|
pending map[string][]chan string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var globalSourceCache = &sourcePresignedCache{
|
|
|
|
|
pending: make(map[string][]chan string),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// sourceConsidersKey construit une clé unique pour une demande de source privée.
|
|
|
|
|
// La clé encode l'executionsID, le peerID du propriétaire et le resourceID
|
|
|
|
|
// pour permettre des requêtes parallèles distinctes.
|
|
|
|
|
func sourceConsidersKey(executionsID, peerID, resourceID string) string {
|
|
|
|
|
return executionsID + ":src:" + peerID + ":" + resourceID
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// register inscrit un nouveau canal d'attente pour la clé donnée.
|
|
|
|
|
// Retourne le canal à lire et une fonction de désinscription à appeler en defer.
|
|
|
|
|
func (c *sourcePresignedCache) register(key string) (<-chan string, func()) {
|
|
|
|
|
ch := make(chan string, 1)
|
|
|
|
|
c.mu.Lock()
|
|
|
|
|
c.pending[key] = append(c.pending[key], ch)
|
|
|
|
|
c.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
unregister := func() {
|
|
|
|
|
c.mu.Lock()
|
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
|
list := c.pending[key]
|
|
|
|
|
for i, existing := range list {
|
|
|
|
|
if existing == ch {
|
|
|
|
|
c.pending[key] = append(list[:i], list[i+1:]...)
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if len(c.pending[key]) == 0 {
|
|
|
|
|
delete(c.pending, key)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return ch, unregister
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// confirm réveille tous les waiters enregistrés sous la clé donnée
|
|
|
|
|
// en leur transmettant l'URL pré-signée, puis les supprime du cache.
|
|
|
|
|
func (c *sourcePresignedCache) confirm(key, url string) {
|
|
|
|
|
c.mu.Lock()
|
|
|
|
|
list := c.pending[key]
|
|
|
|
|
delete(c.pending, key)
|
|
|
|
|
c.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
for _, ch := range list {
|
|
|
|
|
select {
|
|
|
|
|
case ch <- url:
|
|
|
|
|
default:
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── StartConsidersListener ────────────────────────────────────────────────────
|
|
|
|
|
|
2026-03-25 11:13:12 +01:00
|
|
|
// StartConsidersListener démarre un abonné NATS global via ListenNats (oclib)
|
2026-05-27 16:09:45 +02:00
|
|
|
// qui reçoit les messages CONSIDERS_EVENT et réveille les goroutines en attente.
|
|
|
|
|
//
|
|
|
|
|
// Deux chemins de dispatch :
|
|
|
|
|
// - Si presigned_url est présent dans le payload → globalSourceCache (Phase 4).
|
|
|
|
|
// - Sinon → globalConsidersCache (Phases COMPUTE / STORAGE, signal sans valeur).
|
|
|
|
|
//
|
|
|
|
|
// Doit être appelé une seule fois au démarrage.
|
2026-03-25 11:13:12 +01:00
|
|
|
func StartConsidersListener() {
|
|
|
|
|
log := logs.GetLogger()
|
|
|
|
|
log.Info().Msg("Considers NATS listener starting on " + tools.CONSIDERS_EVENT.GenerateKey())
|
|
|
|
|
go tools.NewNATSCaller().ListenNats(map[tools.NATSMethod]func(tools.NATSResponse){
|
|
|
|
|
tools.CONSIDERS_EVENT: func(resp tools.NATSResponse) {
|
|
|
|
|
fmt.Println("CONSIDERS")
|
|
|
|
|
var body struct {
|
|
|
|
|
ExecutionsID string `json:"executions_id"`
|
|
|
|
|
PeerID string `json:"peer_id,omitempty"`
|
2026-05-27 16:09:45 +02:00
|
|
|
// PresignedURL est non-vide uniquement pour les réponses de source privée (Phase 4).
|
|
|
|
|
PresignedURL string `json:"presigned_url,omitempty"`
|
|
|
|
|
// ResourceID identifie la ressource Processing/Data pour la Phase 4.
|
|
|
|
|
ResourceID string `json:"resource_id,omitempty"`
|
2026-03-25 11:13:12 +01:00
|
|
|
}
|
|
|
|
|
if err := json.Unmarshal(resp.Payload, &body); err != nil {
|
|
|
|
|
log.Error().Msg("CONSIDERS_EVENT: cannot unmarshal payload: " + err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-05-27 16:09:45 +02:00
|
|
|
|
|
|
|
|
if body.PresignedURL != "" {
|
|
|
|
|
// Phase 4 — source privée : transmettre l'URL pré-signée.
|
|
|
|
|
key := sourceConsidersKey(body.ExecutionsID, body.PeerID, body.ResourceID)
|
|
|
|
|
log.Info().Msg(fmt.Sprintf("CONSIDERS_EVENT (presigned) dispatched for key=%s", key))
|
|
|
|
|
globalSourceCache.confirm(key, body.PresignedURL)
|
|
|
|
|
} else {
|
|
|
|
|
// Phases COMPUTE / STORAGE — simple signal.
|
|
|
|
|
key := considersKey(body.ExecutionsID, resp.Datatype, body.PeerID)
|
|
|
|
|
log.Info().Msg(fmt.Sprintf("CONSIDERS_EVENT dispatched for key=%s", key))
|
|
|
|
|
globalConsidersCache.confirm(key)
|
|
|
|
|
}
|
2026-03-25 11:13:12 +01:00
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|