package hostfiles import ( "context" "log" "sync" "time" ) // PlatformStager is the part of a session's Publisher a PlatformRelay // needs. *Publisher implements it. type PlatformStager interface { StagePlatform(ctx context.Context, name string, body []byte) error } // PlatformRelay hands one platform item to a running session's publisher // without making its caller wait on the store (instance-key plan ruling // 22). Publish records the latest body and returns at once; Run stages it // with StagePlatform, retrying until it is staged, and the publisher's own // continuous loop (Run in run.go) commits it within its deadline, as it // commits every staged change. A body staged once is not staged again // unless Publish is called with it again. type PlatformRelay struct { Name string Stage PlatformStager Retry time.Duration // between failed stages; 0 is one second mu sync.Mutex pending []byte wake chan struct{} } func (r *PlatformRelay) signal() chan struct{} { r.mu.Lock() defer r.mu.Unlock() if r.wake == nil { r.wake = make(chan struct{}, 1) } return r.wake } // Publish queues body as the item's next value. It never blocks. func (r *PlatformRelay) Publish(body []byte) { w := r.signal() r.mu.Lock() r.pending = append([]byte(nil), body...) r.mu.Unlock() select { case w <- struct{}{}: default: } } // Run stages queued bodies until ctx ends, and returns ctx.Err(). func (r *PlatformRelay) Run(ctx context.Context) error { retry := r.Retry if retry <= 0 { retry = time.Second } w := r.signal() failing := false // logged on the transitions only, as Run in run.go logs a scan for { r.mu.Lock() body := r.pending r.mu.Unlock() if body != nil { if err := r.Stage.StagePlatform(ctx, r.Name, body); err != nil { if ctx.Err() != nil { return ctx.Err() } if !failing { failing = true log.Printf("hostfiles: %s: stage failing, retrying every %s: %v", r.Name, retry, err) } select { case <-ctx.Done(): return ctx.Err() case <-time.After(retry): } continue } if failing { failing = false log.Printf("hostfiles: %s: stage recovered", r.Name) } r.mu.Lock() if string(r.pending) == string(body) { r.pending = nil } r.mu.Unlock() continue } select { case <-ctx.Done(): return ctx.Err() case <-w: } } }