package main import ( "context" "sync" ) type snapshotKey struct { NodeLimit int EdgeLimit int IncludeBNodes bool GraphQueryID string } type snapshotInflight struct { ready chan struct{} snapshot GraphResponse err error } type GraphSnapshotService struct { sparql *AnzoGraphClient cfg Config mu sync.Mutex cache map[snapshotKey]GraphResponse inflight map[snapshotKey]*snapshotInflight } func NewGraphSnapshotService(sparql *AnzoGraphClient, cfg Config) *GraphSnapshotService { return &GraphSnapshotService{ sparql: sparql, cfg: cfg, cache: make(map[snapshotKey]GraphResponse), inflight: make(map[snapshotKey]*snapshotInflight), } } func (s *GraphSnapshotService) Get(ctx context.Context, nodeLimit int, edgeLimit int, graphQueryID string) (GraphResponse, error) { key := snapshotKey{NodeLimit: nodeLimit, EdgeLimit: edgeLimit, IncludeBNodes: s.cfg.IncludeBNodes, GraphQueryID: graphQueryID} s.mu.Lock() if snap, ok := s.cache[key]; ok { s.mu.Unlock() return snap, nil } if inf, ok := s.inflight[key]; ok { ready := inf.ready s.mu.Unlock() select { case <-ctx.Done(): return GraphResponse{}, ctx.Err() case <-ready: return inf.snapshot, inf.err } } inf := &snapshotInflight{ready: make(chan struct{})} s.inflight[key] = inf s.mu.Unlock() snap, err := fetchGraphSnapshot(ctx, s.sparql, s.cfg, nodeLimit, edgeLimit, graphQueryID) s.mu.Lock() inf.snapshot = snap inf.err = err delete(s.inflight, key) if err == nil { s.cache[key] = snap } close(inf.ready) s.mu.Unlock() return snap, err }