add Stop function to module

This commit is contained in:
Joel Wetzell
2026-02-07 09:53:38 -06:00
parent 8f5091cf9b
commit 33ecc94097
19 changed files with 156 additions and 18 deletions

View File

@@ -17,6 +17,7 @@ type HTTPClient struct {
client *http.Client client *http.Client
router route.RouteIO router route.RouteIO
logger *slog.Logger logger *slog.Logger
cancel context.CancelFunc
} }
func init() { func init() {
@@ -44,7 +45,9 @@ func (hc *HTTPClient) Run(ctx context.Context) error {
return errors.New("http.client unable to get router from context") return errors.New("http.client unable to get router from context")
} }
hc.router = router hc.router = router
hc.ctx = ctx moduleContext, cancel := context.WithCancel(ctx)
hc.ctx = moduleContext
hc.cancel = cancel
hc.client = &http.Client{ hc.client = &http.Client{
Timeout: 10 * time.Second, Timeout: 10 * time.Second,
@@ -79,3 +82,7 @@ func (hc *HTTPClient) Output(ctx context.Context, payload any) error {
return nil return nil
} }
func (hc *HTTPClient) Stop() {
hc.cancel()
}

View File

@@ -19,6 +19,7 @@ type HTTPServer struct {
ctx context.Context ctx context.Context
router route.RouteIO router route.RouteIO
logger *slog.Logger logger *slog.Logger
cancel context.CancelFunc
} }
type ResponseIOError struct { type ResponseIOError struct {
@@ -153,7 +154,9 @@ func (hs *HTTPServer) Run(ctx context.Context) error {
return errors.New("http.server unable to get router from context") return errors.New("http.server unable to get router from context")
} }
hs.router = router hs.router = router
hs.ctx = ctx moduleContext, cancel := context.WithCancel(ctx)
hs.ctx = moduleContext
hs.cancel = cancel
httpServer := &http.Server{ httpServer := &http.Server{
Addr: fmt.Sprintf(":%d", hs.Port), Addr: fmt.Sprintf(":%d", hs.Port),
@@ -199,3 +202,7 @@ func (hs *HTTPServer) Output(ctx context.Context, payload any) error {
responseWriter.Write(payloadResponse.Body) responseWriter.Write(payloadResponse.Body)
return nil return nil
} }
func (hs *HTTPServer) Stop() {
hs.cancel()
}

View File

@@ -21,6 +21,7 @@ type MIDIInput struct {
Port string Port string
SendFunc func(midi.Message) error SendFunc func(midi.Message) error
logger *slog.Logger logger *slog.Logger
cancel context.CancelFunc
} }
func init() { func init() {
@@ -61,7 +62,9 @@ func (mi *MIDIInput) Run(ctx context.Context) error {
return errors.New("midi.input unable to get router from context") return errors.New("midi.input unable to get router from context")
} }
mi.router = router mi.router = router
mi.ctx = ctx moduleContext, cancel := context.WithCancel(ctx)
mi.ctx = moduleContext
mi.cancel = cancel
in, err := midi.FindInPort(mi.Port) in, err := midi.FindInPort(mi.Port)
if err != nil { if err != nil {
@@ -88,3 +91,7 @@ func (mi *MIDIInput) Run(ctx context.Context) error {
func (mi *MIDIInput) Output(ctx context.Context, payload any) error { func (mi *MIDIInput) Output(ctx context.Context, payload any) error {
return errors.New("midi.input output is not implemented") return errors.New("midi.input output is not implemented")
} }
func (mi *MIDIInput) Stop() {
mi.cancel()
}

View File

@@ -21,6 +21,7 @@ type MIDIOutput struct {
Port string Port string
SendFunc func(midi.Message) error SendFunc func(midi.Message) error
logger *slog.Logger logger *slog.Logger
cancel context.CancelFunc
} }
func init() { func init() {
@@ -62,7 +63,9 @@ func (mo *MIDIOutput) Run(ctx context.Context) error {
return errors.New("midi.output unable to get router from context") return errors.New("midi.output unable to get router from context")
} }
mo.router = router mo.router = router
mo.ctx = ctx moduleContext, cancel := context.WithCancel(ctx)
mo.ctx = moduleContext
mo.cancel = cancel
out, err := midi.FindOutPort(mo.Port) out, err := midi.FindOutPort(mo.Port)
@@ -95,3 +98,7 @@ func (mo *MIDIOutput) Output(ctx context.Context, payload any) error {
return mo.SendFunc(payloadMessage) return mo.SendFunc(payloadMessage)
} }
func (mo *MIDIOutput) Stop() {
mo.cancel()
}

View File

@@ -19,6 +19,7 @@ type Module interface {
Id() string Id() string
Type() string Type() string
Run(context.Context) error Run(context.Context) error
Stop()
Output(context.Context, any) error Output(context.Context, any) error
} }

View File

@@ -19,6 +19,7 @@ type MQTTClient struct {
Topic string Topic string
client mqtt.Client client mqtt.Client
logger *slog.Logger logger *slog.Logger
cancel context.CancelFunc
} }
func init() { func init() {
@@ -82,7 +83,9 @@ func (mc *MQTTClient) Run(ctx context.Context) error {
return errors.New("mqtt.client unable to get router from context") return errors.New("mqtt.client unable to get router from context")
} }
mc.router = router mc.router = router
mc.ctx = ctx moduleContext, cancel := context.WithCancel(ctx)
mc.ctx = moduleContext
mc.cancel = cancel
opts := mqtt.NewClientOptions() opts := mqtt.NewClientOptions()
opts.AddBroker(mc.Broker) opts.AddBroker(mc.Broker)
@@ -98,6 +101,7 @@ func (mc *MQTTClient) Run(ctx context.Context) error {
} }
mc.client = mqtt.NewClient(opts) mc.client = mqtt.NewClient(opts)
defer mc.client.Disconnect(250)
token := mc.client.Connect() token := mc.client.Connect()
@@ -133,3 +137,7 @@ func (mc *MQTTClient) Output(ctx context.Context, payload any) error {
return token.Error() return token.Error()
} }
func (mc *MQTTClient) Stop() {
mc.cancel()
}

View File

@@ -19,6 +19,7 @@ type NATSClient struct {
Subject string Subject string
client *nats.Conn client *nats.Conn
logger *slog.Logger logger *slog.Logger
cancel context.CancelFunc
} }
func init() { func init() {
@@ -71,7 +72,9 @@ func (nc *NATSClient) Run(ctx context.Context) error {
} }
nc.router = router nc.router = router
nc.ctx = ctx moduleContext, cancel := context.WithCancel(ctx)
nc.ctx = moduleContext
nc.cancel = cancel
client, err := nats.Connect(nc.URL, nats.RetryOnFailedConnect(true)) client, err := nats.Connect(nc.URL, nats.RetryOnFailedConnect(true))
@@ -121,3 +124,7 @@ func (nc *NATSClient) Output(ctx context.Context, payload any) error {
return err return err
} }
func (nc *NATSClient) Stop() {
nc.cancel()
}

View File

@@ -20,6 +20,7 @@ type PSNClient struct {
router route.RouteIO router route.RouteIO
decoder *psn.Decoder decoder *psn.Decoder
logger *slog.Logger logger *slog.Logger
cancel context.CancelFunc
} }
func init() { func init() {
@@ -47,7 +48,9 @@ func (pc *PSNClient) Run(ctx context.Context) error {
return errors.New("psn.client unable to get router from context") return errors.New("psn.client unable to get router from context")
} }
pc.router = router pc.router = router
pc.ctx = ctx moduleContext, cancel := context.WithCancel(ctx)
pc.ctx = moduleContext
pc.cancel = cancel
addr, err := net.ResolveUDPAddr("udp", "236.10.10.10:56565") addr, err := net.ResolveUDPAddr("udp", "236.10.10.10:56565")
if err != nil { if err != nil {
@@ -104,3 +107,7 @@ func (pc *PSNClient) Run(ctx context.Context) error {
func (pc *PSNClient) Output(ctx context.Context, payload any) error { func (pc *PSNClient) Output(ctx context.Context, payload any) error {
return fmt.Errorf("psn.client output is not implemented") return fmt.Errorf("psn.client output is not implemented")
} }
func (pc *PSNClient) Stop() {
pc.cancel()
}

View File

@@ -24,6 +24,7 @@ type SerialClient struct {
Mode *serial.Mode Mode *serial.Mode
port serial.Port port serial.Port
logger *slog.Logger logger *slog.Logger
cancel context.CancelFunc
} }
func init() { func init() {
@@ -108,7 +109,9 @@ func (sc *SerialClient) Run(ctx context.Context) error {
} }
sc.router = router sc.router = router
sc.ctx = ctx moduleContext, cancel := context.WithCancel(ctx)
sc.ctx = moduleContext
sc.cancel = cancel
// TODO(jwetzell): shutdown with router.Context properly // TODO(jwetzell): shutdown with router.Context properly
go func() { go func() {
@@ -180,3 +183,7 @@ func (sc *SerialClient) Output(ctx context.Context, payload any) error {
_, err := sc.port.Write(sc.Framer.Encode(payloadBytes)) _, err := sc.port.Write(sc.Framer.Encode(payloadBytes))
return err return err
} }
func (sc *SerialClient) Stop() {
sc.cancel()
}

View File

@@ -29,6 +29,7 @@ type SIPCallServer struct {
UserAgent string UserAgent string
dg *diago.Diago dg *diago.Diago
logger *slog.Logger logger *slog.Logger
cancel context.CancelFunc
} }
type SIPCallMessage struct { type SIPCallMessage struct {
@@ -118,7 +119,9 @@ func (scs *SIPCallServer) Run(ctx context.Context) error {
return errors.New("sip.call.server unable to get router from context") return errors.New("sip.call.server unable to get router from context")
} }
scs.router = router scs.router = router
scs.ctx = ctx moduleContext, cancel := context.WithCancel(ctx)
scs.ctx = moduleContext
scs.cancel = cancel
diagoLogger := slog.New(slog.NewJSONHandler(io.Discard, nil)) diagoLogger := slog.New(slog.NewJSONHandler(io.Discard, nil))
@@ -228,3 +231,7 @@ func (scs *SIPCallServer) Output(ctx context.Context, payload any) error {
} }
return errors.New("sip.dtmf.server can only output SipDTMFResponse or SipAudioFileResponse") return errors.New("sip.dtmf.server can only output SipDTMFResponse or SipAudioFileResponse")
} }
func (scs *SIPCallServer) Stop() {
scs.cancel()
}

View File

@@ -29,6 +29,7 @@ type SIPDTMFServer struct {
Transport string Transport string
Separator string Separator string
logger *slog.Logger logger *slog.Logger
cancel context.CancelFunc
} }
type SIPDTMFMessage struct { type SIPDTMFMessage struct {
@@ -120,7 +121,9 @@ func (sds *SIPDTMFServer) Run(ctx context.Context) error {
return errors.New("sip.dtmf.server unable to get router from context") return errors.New("sip.dtmf.server unable to get router from context")
} }
sds.router = router sds.router = router
sds.ctx = ctx moduleContext, cancel := context.WithCancel(ctx)
sds.ctx = moduleContext
sds.cancel = cancel
diagoLogger := slog.New(slog.NewJSONHandler(io.Discard, nil)) diagoLogger := slog.New(slog.NewJSONHandler(io.Discard, nil))
@@ -243,3 +246,7 @@ func (sds *SIPDTMFServer) Output(ctx context.Context, payload any) error {
return errors.New("sip.dtmf.server can only output SipDTMFResponse or SipAudioFileResponse") return errors.New("sip.dtmf.server can only output SipDTMFResponse or SipAudioFileResponse")
} }
func (sds *SIPDTMFServer) Stop() {
sds.cancel()
}

View File

@@ -21,6 +21,7 @@ type TCPClient struct {
router route.RouteIO router route.RouteIO
Addr *net.TCPAddr Addr *net.TCPAddr
logger *slog.Logger logger *slog.Logger
cancel context.CancelFunc
} }
func init() { func init() {
@@ -94,7 +95,9 @@ func (tc *TCPClient) Run(ctx context.Context) error {
return errors.New("net.tcp.client unable to get router from context") return errors.New("net.tcp.client unable to get router from context")
} }
tc.router = router tc.router = router
tc.ctx = ctx moduleContext, cancel := context.WithCancel(ctx)
tc.ctx = moduleContext
tc.cancel = cancel
// TODO(jwetzell): shutdown with router.Context properly // TODO(jwetzell): shutdown with router.Context properly
go func() { go func() {
@@ -176,3 +179,7 @@ func (tc *TCPClient) Output(ctx context.Context, payload any) error {
_, err := tc.conn.Write(tc.framer.Encode(payloadBytes)) _, err := tc.conn.Write(tc.framer.Encode(payloadBytes))
return err return err
} }
func (tc *TCPClient) Stop() {
tc.cancel()
}

View File

@@ -27,6 +27,7 @@ type TCPServer struct {
connections []*net.TCPConn connections []*net.TCPConn
connectionsMu sync.RWMutex connectionsMu sync.RWMutex
logger *slog.Logger logger *slog.Logger
cancel context.CancelFunc
} }
func init() { func init() {
@@ -105,6 +106,15 @@ ClientRead:
for { for {
select { select {
case <-ts.quit: case <-ts.quit:
client.Close()
ts.connectionsMu.Lock()
for i := 0; i < len(ts.connections); i++ {
if ts.connections[i] == client {
ts.connections = slices.Delete(ts.connections, i, i+1)
break
}
}
ts.connectionsMu.Unlock()
return return
default: default:
client.SetDeadline(time.Now().Add(time.Millisecond * 200)) client.SetDeadline(time.Now().Add(time.Millisecond * 200))
@@ -166,7 +176,9 @@ func (ts *TCPServer) Run(ctx context.Context) error {
return errors.New("net.tcp.server unable to get router from context") return errors.New("net.tcp.server unable to get router from context")
} }
ts.router = router ts.router = router
ts.ctx = ctx moduleContext, cancel := context.WithCancel(ctx)
ts.ctx = moduleContext
ts.cancel = cancel
listener, err := net.ListenTCP("tcp", ts.Addr) listener, err := net.ListenTCP("tcp", ts.Addr)
if err != nil { if err != nil {
@@ -226,3 +238,8 @@ func (ts *TCPServer) Output(ctx context.Context, payload any) error {
} }
return fmt.Errorf("net.tcp.server error during output: %s", errorString) return fmt.Errorf("net.tcp.server error during output: %s", errorString)
} }
func (ts *TCPServer) Stop() {
ts.cancel()
ts.wg.Wait()
}

View File

@@ -17,6 +17,7 @@ type TimeInterval struct {
router route.RouteIO router route.RouteIO
ticker *time.Ticker ticker *time.Ticker
logger *slog.Logger logger *slog.Logger
cancel context.CancelFunc
} }
func init() { func init() {
@@ -56,7 +57,9 @@ func (i *TimeInterval) Run(ctx context.Context) error {
return errors.New("time.interval unable to get router from context") return errors.New("time.interval unable to get router from context")
} }
i.router = router i.router = router
i.ctx = ctx moduleContext, cancel := context.WithCancel(ctx)
i.ctx = moduleContext
i.cancel = cancel
ticker := time.NewTicker(time.Millisecond * time.Duration(i.Duration)) ticker := time.NewTicker(time.Millisecond * time.Duration(i.Duration))
i.ticker = ticker i.ticker = ticker
@@ -80,3 +83,7 @@ func (i *TimeInterval) Output(ctx context.Context, payload any) error {
i.ticker.Reset(time.Millisecond * time.Duration(i.Duration)) i.ticker.Reset(time.Millisecond * time.Duration(i.Duration))
return nil return nil
} }
func (i *TimeInterval) Stop() {
i.cancel()
}

View File

@@ -17,6 +17,7 @@ type TimeTimer struct {
router route.RouteIO router route.RouteIO
timer *time.Timer timer *time.Timer
logger *slog.Logger logger *slog.Logger
cancel context.CancelFunc
} }
func init() { func init() {
@@ -57,7 +58,9 @@ func (t *TimeTimer) Run(ctx context.Context) error {
return errors.New("net.tcp.client unable to get router from context") return errors.New("net.tcp.client unable to get router from context")
} }
t.router = router t.router = router
t.ctx = ctx moduleContext, cancel := context.WithCancel(ctx)
t.ctx = moduleContext
t.cancel = cancel
t.timer = time.NewTimer(time.Millisecond * time.Duration(t.Duration)) t.timer = time.NewTimer(time.Millisecond * time.Duration(t.Duration))
defer t.timer.Stop() defer t.timer.Stop()
@@ -79,3 +82,7 @@ func (t *TimeTimer) Output(ctx context.Context, payload any) error {
t.timer.Reset(time.Millisecond * time.Duration(t.Duration)) t.timer.Reset(time.Millisecond * time.Duration(t.Duration))
return nil return nil
} }
func (t *TimeTimer) Stop() {
t.cancel()
}

View File

@@ -19,6 +19,7 @@ type UDPClient struct {
ctx context.Context ctx context.Context
router route.RouteIO router route.RouteIO
logger *slog.Logger logger *slog.Logger
cancel context.CancelFunc
} }
func init() { func init() {
@@ -80,7 +81,9 @@ func (uc *UDPClient) Run(ctx context.Context) error {
return errors.New("net.udp.client unable to get router from context") return errors.New("net.udp.client unable to get router from context")
} }
uc.router = router uc.router = router
uc.ctx = ctx moduleContext, cancel := context.WithCancel(ctx)
uc.ctx = moduleContext
uc.cancel = cancel
err := uc.SetupConn() err := uc.SetupConn()
if err != nil { if err != nil {
@@ -112,3 +115,7 @@ func (uc *UDPClient) Output(ctx context.Context, payload any) error {
} }
return nil return nil
} }
func (uc *UDPClient) Stop() {
uc.cancel()
}

View File

@@ -19,6 +19,7 @@ type UDPMulticast struct {
router route.RouteIO router route.RouteIO
Addr *net.UDPAddr Addr *net.UDPAddr
logger *slog.Logger logger *slog.Logger
cancel context.CancelFunc
} }
func init() { func init() {
@@ -74,7 +75,9 @@ func (um *UDPMulticast) Run(ctx context.Context) error {
return errors.New("net.udp.multicast unable to get router from context") return errors.New("net.udp.multicast unable to get router from context")
} }
um.router = router um.router = router
um.ctx = ctx moduleContext, cancel := context.WithCancel(ctx)
um.ctx = moduleContext
um.cancel = cancel
client, err := net.ListenMulticastUDP("udp", nil, um.Addr) client, err := net.ListenMulticastUDP("udp", nil, um.Addr)
if err != nil { if err != nil {
@@ -130,3 +133,7 @@ func (um *UDPMulticast) Output(ctx context.Context, payload any) error {
_, err := um.conn.Write(payloadBytes) _, err := um.conn.Write(payloadBytes)
return err return err
} }
func (um *UDPMulticast) Stop() {
um.cancel()
}

View File

@@ -20,6 +20,7 @@ type UDPServer struct {
ctx context.Context ctx context.Context
router route.RouteIO router route.RouteIO
logger *slog.Logger logger *slog.Logger
cancel context.CancelFunc
} }
func init() { func init() {
@@ -88,7 +89,9 @@ func (us *UDPServer) Run(ctx context.Context) error {
return errors.New("net.udp.server unable to get router from context") return errors.New("net.udp.server unable to get router from context")
} }
us.router = router us.router = router
us.ctx = ctx moduleContext, cancel := context.WithCancel(ctx)
us.ctx = moduleContext
us.cancel = cancel
listener, err := net.ListenUDP("udp", us.Addr) listener, err := net.ListenUDP("udp", us.Addr)
if err != nil { if err != nil {
@@ -129,3 +132,7 @@ func (us *UDPServer) Run(ctx context.Context) error {
func (us *UDPServer) Output(ctx context.Context, payload any) error { func (us *UDPServer) Output(ctx context.Context, payload any) error {
return errors.New("net.udp.server output is not implemented") return errors.New("net.udp.server output is not implemented")
} }
func (us *UDPServer) Stop() {
us.cancel()
}

View File

@@ -25,6 +25,7 @@ type MockCounterModule struct {
outputCount int outputCount int
router route.RouteIO router route.RouteIO
logger *slog.Logger logger *slog.Logger
cancel context.CancelFunc
} }
func (mcm *MockCounterModule) Id() string { func (mcm *MockCounterModule) Id() string {
@@ -43,7 +44,9 @@ func (mcm *MockCounterModule) Run(ctx context.Context) error {
return fmt.Errorf("mock.counter could not get router from context") return fmt.Errorf("mock.counter could not get router from context")
} }
mcm.router = router mcm.router = router
mcm.ctx = ctx moduleContext, cancel := context.WithCancel(ctx)
mcm.ctx = moduleContext
mcm.cancel = cancel
<-mcm.ctx.Done() <-mcm.ctx.Done()
return nil return nil
} }
@@ -52,6 +55,10 @@ func (mcm *MockCounterModule) Type() string {
return mcm.config.Type return mcm.config.Type
} }
func (mcm *MockCounterModule) Stop() {
mcm.cancel()
}
func init() { func init() {
module.RegisterModule(module.ModuleRegistration{ module.RegisterModule(module.ModuleRegistration{
Type: "mock.counter", Type: "mock.counter",