package main import ( "fmt" "math/rand" "sync" "time" ) // Component simulates a service that sends health pings func Component(id int, pingCh chan<- bool) { fmt.Printf("Component %d: Started. ", id) for i := 0; ; i++ { p := rand.Intn(100) // Simulate some work that might fail if p < 10 && i > 5 { // Simulate failure after some pings fmt.Printf("Component %d: !!! Simulating failure and stopping pings !!! ", id) return // Component "crashes" or stops responding } pingCh <- true // Send a healthy ping time.Sleep(150 * time.Millisecond) if i % 5 == 0 { fmt.Printf("Component %d: Pinged healthy. ", id) } } } // Monitor watches the component's health pings func Monitor(monitorID int, pingCh <-chan bool, restartTrigger chan<- int) { fmt.Printf("Monitor %d: Started watching component. ", monitorID) const pingTimeout = 500 * time.Millisecond for { select { case <-pingCh: // Received a ping, component is healthy // fmt.Printf("Monitor %d: Component is healthy. ", monitorID) case <-time.After(pingTimeout): // No ping received within timeout, component is unhealthy fmt.Printf("Monitor %d: !!! Component (monitored by %d) unhealthy (timeout) !!! Triggering restart. ", monitorID, monitorID) restartTrigger <- monitorID // Signal to restart the component corresponding to this monitor return // Monitor exits, to be restarted or handle restart } } } // Restarter listens for restart triggers and restarts components func Restarter(restartTrigger <-chan int, componentWG *sync.WaitGroup) { // A map to hold current ping channels for components. Indexed by component ID. // This simplified example ties component ID to monitor ID for simplicity. // In a real system, you'd manage a registry of components and their channels. activePingChannels := make(map[int]chan bool) var mutex sync.Mutex // Protects activePingChannels map // Initial components for demonstration for i := 0; i < 2; i++ { pingCh := make(chan bool) mutex.Lock() activePingChannels[i] = pingCh mutex.Unlock() componentWG.Add(1) go func(id int, ch chan bool) { defer componentWG.Done() Component(id, ch) }(i, pingCh) go Monitor(i, pingCh, restartTrigger) } for componentID := range restartTrigger { fmt.Printf("Restarter: Received restart trigger for Component %d. Attempting to restart... ", componentID) // Close the old ping channel (if still open), so the old monitor goroutine exits gracefully if it was stuck. mutex.Lock() oldPingCh, exists := activePingChannels[componentID] if exists { close(oldPingCh) delete(activePingChannels, componentID) } mutex.Unlock() // Simulate restarting component newPingCh := make(chan bool) mutex.Lock() activePingChannels[componentID] = newPingCh mutex.Unlock() componentWG.Add(1) go func(id int, ch chan bool) { defer componentWG.Done() Component(id, ch) }(componentID, newPingCh) // Restart the monitor as well, tied to the new component and its channel go Monitor(componentID, newPingCh, restartTrigger) time.Sleep(200 * time.Millisecond) // Give component/monitor a moment to start fmt.Printf("Restarter: Component %d (and new monitor) restarted. ", componentID) } } func main() { rand.Seed(time.Now().UnixNano()) restartTrigger := make(chan int) // Monitor sends restart signals (component ID) here var componentWG sync.WaitGroup // To wait for the Component goroutines // Start the restarter. It will initially start the components and their monitors. go Restarter(restartTrigger, &componentWG) time.Sleep(7 * time.Second) // Let the system run for a while to observe multiple restarts fmt.Println(" Main: Exiting after 7 seconds. See logs for component behavior.") // In a real app, you'd have a way to gracefully shut down these loops, e.g., using context. // For demonstration, we just let main exit. The goroutines will eventually exit or be garbage collected. close(restartTrigger) // Signal the restarter to stop processing new restart requests. componentWG.Wait() // Wait for all (current and restarted) components to finish. }