From e9842584442f3b3d61721c27d6c5cf613f838159 Mon Sep 17 00:00:00 2001
From: Rob Watson <rob@netflux.io>
Date: Sun, 23 Feb 2025 03:12:24 +0100
Subject: [PATCH] fix: context handling

---
 container/container.go |  1 +
 mediaserver/actor.go   | 15 +++++++++++----
 2 files changed, 12 insertions(+), 4 deletions(-)

diff --git a/container/container.go b/container/container.go
index 8e41f0d..c51b50a 100644
--- a/container/container.go
+++ b/container/container.go
@@ -255,6 +255,7 @@ func (a *Client) runContainerLoop(
 				containerErrC <- err
 				return
 			case <-ctx.Done():
+				containerErrC <- ctx.Err()
 				return
 			}
 		}
diff --git a/mediaserver/actor.go b/mediaserver/actor.go
index 6cafc2b..587fe61 100644
--- a/mediaserver/actor.go
+++ b/mediaserver/actor.go
@@ -32,6 +32,8 @@ type action func()
 
 // Actor is responsible for managing the media server.
 type Actor struct {
+	ctx                       context.Context
+	cancel                    context.CancelFunc
 	actorC                    chan action
 	stateC                    chan domain.Source
 	containerClient           *container.Client
@@ -61,8 +63,11 @@ type StartActorParams struct {
 // Callers must consume the state channel exposed via [C].
 func StartActor(ctx context.Context, params StartActorParams) *Actor {
 	chanSize := cmp.Or(params.ChanSize, defaultChanSize)
+	ctx, cancel := context.WithCancel(ctx)
 
 	actor := &Actor{
+		ctx:                       ctx,
+		cancel:                    cancel,
 		apiPort:                   cmp.Or(params.APIPort, defaultAPIPort),
 		rtmpPort:                  cmp.Or(params.RTMPPort, defaultRTMPPort),
 		fetchIngressStateInterval: cmp.Or(params.FetchIngressStateInterval, defaultFetchIngressStateInterval),
@@ -138,13 +143,13 @@ func (s *Actor) Close() error {
 		return fmt.Errorf("remove containers: %w", err)
 	}
 
-	close(s.actorC)
+	s.cancel()
 
 	return nil
 }
 
-// actorLoop is the main loop of the media server actor. It only exits when the
-// actor is closed.
+// actorLoop is the main loop of the media server actor. It exits when the
+// actor is closed, or the parent context is cancelled.
 func (s *Actor) actorLoop(containerStateC <-chan domain.Container, errC <-chan error) {
 	fetchStateT := time.NewTicker(s.fetchIngressStateInterval)
 	defer fetchStateT.Stop()
@@ -198,9 +203,11 @@ func (s *Actor) actorLoop(containerStateC <-chan domain.Container, errC <-chan e
 			}
 		case action, ok := <-s.actorC:
 			if !ok {
-				return
+				continue
 			}
 			action()
+		case <-s.ctx.Done():
+			return
 		}
 	}
 }