mirror of
https://github.com/jwetzell/showbridge-go.git
synced 2026-08-23 14:58:58 +00:00
Merge pull request #158 from jwetzell/module-context-handling
rework context handling throughout router and modules
This commit is contained in:
+9
-15
@@ -90,12 +90,11 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type showbridgeApp struct {
|
type showbridgeApp struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
configPath string
|
configPath string
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
router *showbridge.Router
|
router *showbridge.Router
|
||||||
routerRunner *sync.WaitGroup
|
routerMutex sync.Mutex
|
||||||
routerMutex sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func readConfig(configPath string) (config.Config, error) {
|
func readConfig(configPath string) (config.Config, error) {
|
||||||
@@ -211,10 +210,9 @@ func run(ctx context.Context, c *cli.Command) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
showbridgeApp := &showbridgeApp{
|
showbridgeApp := &showbridgeApp{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
configPath: configPath,
|
configPath: configPath,
|
||||||
logger: slog.Default().With("component", "cmd"),
|
logger: slog.Default().With("component", "cmd"),
|
||||||
routerRunner: &sync.WaitGroup{},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
config, err := readConfig(showbridgeApp.configPath)
|
config, err := readConfig(showbridgeApp.configPath)
|
||||||
@@ -236,9 +234,7 @@ func run(ctx context.Context, c *cli.Command) error {
|
|||||||
showbridgeApp.routerMutex.Lock()
|
showbridgeApp.routerMutex.Lock()
|
||||||
showbridgeApp.router = router
|
showbridgeApp.router = router
|
||||||
|
|
||||||
showbridgeApp.routerRunner.Go(func() {
|
router.Start(context.Background())
|
||||||
router.Start(context.Background())
|
|
||||||
})
|
|
||||||
showbridgeApp.routerMutex.Unlock()
|
showbridgeApp.routerMutex.Unlock()
|
||||||
|
|
||||||
go showbridgeApp.handleChannels()
|
go showbridgeApp.handleChannels()
|
||||||
@@ -246,8 +242,6 @@ func run(ctx context.Context, c *cli.Command) error {
|
|||||||
<-showbridgeApp.ctx.Done()
|
<-showbridgeApp.ctx.Done()
|
||||||
showbridgeApp.logger.Debug("shutting down router")
|
showbridgeApp.logger.Debug("shutting down router")
|
||||||
showbridgeApp.router.Stop()
|
showbridgeApp.router.Stop()
|
||||||
showbridgeApp.logger.Debug("waiting for router to exit")
|
|
||||||
showbridgeApp.routerRunner.Wait()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-6
@@ -20,7 +20,6 @@ type ApiServer struct {
|
|||||||
config config.ApiConfig
|
config config.ApiConfig
|
||||||
serverMu sync.Mutex
|
serverMu sync.Mutex
|
||||||
server *http.Server
|
server *http.Server
|
||||||
shutdown context.CancelFunc
|
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
configurableRouter config.Configurable
|
configurableRouter config.Configurable
|
||||||
eventRouter common.EventRouter
|
eventRouter common.EventRouter
|
||||||
@@ -63,7 +62,6 @@ func (as *ApiServer) Start(config config.ApiConfig) {
|
|||||||
if err != nil && err != http.ErrServerClosed {
|
if err != nil && err != http.ErrServerClosed {
|
||||||
as.logger.Error("server error", "error", err)
|
as.logger.Error("server error", "error", err)
|
||||||
}
|
}
|
||||||
as.shutdown()
|
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,16 +69,18 @@ func (as *ApiServer) Stop() {
|
|||||||
if as.server == nil {
|
if as.server == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
as.logger.Debug("stopping")
|
|
||||||
as.serverMu.Lock()
|
as.serverMu.Lock()
|
||||||
defer as.serverMu.Unlock()
|
defer as.serverMu.Unlock()
|
||||||
if as.server != nil {
|
if as.server != nil {
|
||||||
apiShutdownCtx, apiShutdownCancel := context.WithTimeout(context.Background(), 5*time.Second)
|
apiShutdownCtx, apiShutdownCancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
as.shutdown = apiShutdownCancel
|
defer apiShutdownCancel()
|
||||||
as.server.Shutdown(apiShutdownCtx)
|
err := as.server.Shutdown(apiShutdownCtx)
|
||||||
<-apiShutdownCtx.Done()
|
if err != nil {
|
||||||
|
as.logger.Error("error shutting down server", "error", err)
|
||||||
|
}
|
||||||
as.server = nil
|
as.server = nil
|
||||||
}
|
}
|
||||||
|
as.logger.Debug("done")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (as *ApiServer) handleHealthHTTP(w http.ResponseWriter, req *http.Request) {
|
func (as *ApiServer) handleHealthHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/google/jsonschema-go/jsonschema"
|
"github.com/google/jsonschema-go/jsonschema"
|
||||||
"github.com/jwetzell/showbridge-go/internal/common"
|
"github.com/jwetzell/showbridge-go/internal/common"
|
||||||
@@ -20,6 +21,8 @@ type DbSqlite struct {
|
|||||||
router common.RouteIO
|
router common.RouteIO
|
||||||
db *sql.DB
|
db *sql.DB
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
|
dbMu sync.Mutex
|
||||||
|
cancel context.CancelFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -61,25 +64,38 @@ func (t *DbSqlite) Type() string {
|
|||||||
func (t *DbSqlite) Start(ctx context.Context, router common.RouteIO) error {
|
func (t *DbSqlite) Start(ctx context.Context, router common.RouteIO) error {
|
||||||
t.logger.Debug("running")
|
t.logger.Debug("running")
|
||||||
t.router = router
|
t.router = router
|
||||||
t.ctx = ctx
|
moduleContext, cancel := context.WithCancel(ctx)
|
||||||
|
t.ctx = moduleContext
|
||||||
|
t.cancel = cancel
|
||||||
|
|
||||||
db, err := sql.Open("sqlite", t.Dsn)
|
db, err := sql.Open("sqlite", t.Dsn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("db.sqlite error opening database: %w", err)
|
return fmt.Errorf("db.sqlite error opening database: %w", err)
|
||||||
}
|
}
|
||||||
|
t.dbMu.Lock()
|
||||||
t.db = db
|
t.db = db
|
||||||
defer t.db.Close()
|
t.dbMu.Unlock()
|
||||||
<-ctx.Done()
|
<-t.ctx.Done()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *DbSqlite) Stop() {
|
func (t *DbSqlite) Stop() {
|
||||||
|
if t.cancel != nil {
|
||||||
|
t.cancel()
|
||||||
|
}
|
||||||
|
t.dbMu.Lock()
|
||||||
|
defer t.dbMu.Unlock()
|
||||||
if t.db != nil {
|
if t.db != nil {
|
||||||
t.db.Close()
|
t.db.Close()
|
||||||
|
t.db = nil
|
||||||
}
|
}
|
||||||
|
t.logger.Debug("done")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(jwetzell): get a database module layout that doesn't require handing the DB over
|
||||||
func (t *DbSqlite) Database() (*sql.DB, error) {
|
func (t *DbSqlite) Database() (*sql.DB, error) {
|
||||||
|
t.dbMu.Lock()
|
||||||
|
defer t.dbMu.Unlock()
|
||||||
if t.db == nil {
|
if t.db == nil {
|
||||||
return nil, fmt.Errorf("database not initialized")
|
return nil, fmt.Errorf("database not initialized")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/jsonschema-go/jsonschema"
|
"github.com/google/jsonschema-go/jsonschema"
|
||||||
@@ -16,12 +17,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type HTTPServer struct {
|
type HTTPServer struct {
|
||||||
config config.ModuleConfig
|
config config.ModuleConfig
|
||||||
Port uint16
|
Port uint16
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
router common.RouteIO
|
router common.RouteIO
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
|
server *http.Server
|
||||||
|
serverMu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResponseIOError struct {
|
type ResponseIOError struct {
|
||||||
@@ -169,10 +172,9 @@ func (hs *HTTPServer) Start(ctx context.Context, router common.RouteIO) error {
|
|||||||
Handler: hs,
|
Handler: hs,
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
hs.serverMu.Lock()
|
||||||
<-hs.ctx.Done()
|
hs.server = httpServer
|
||||||
httpServer.Close()
|
hs.serverMu.Unlock()
|
||||||
}()
|
|
||||||
|
|
||||||
err := httpServer.ListenAndServe()
|
err := httpServer.ListenAndServe()
|
||||||
// TODO(jwetzell): handle server closed error differently
|
// TODO(jwetzell): handle server closed error differently
|
||||||
@@ -183,7 +185,6 @@ func (hs *HTTPServer) Start(ctx context.Context, router common.RouteIO) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
<-hs.ctx.Done()
|
<-hs.ctx.Done()
|
||||||
hs.logger.Debug("done")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,5 +211,17 @@ func (hs *HTTPServer) Output(ctx context.Context, payload any) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (hs *HTTPServer) Stop() {
|
func (hs *HTTPServer) Stop() {
|
||||||
hs.cancel()
|
if hs.cancel != nil {
|
||||||
|
hs.cancel()
|
||||||
|
}
|
||||||
|
hs.serverMu.Lock()
|
||||||
|
defer hs.serverMu.Unlock()
|
||||||
|
if hs.server != nil {
|
||||||
|
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
hs.server.Shutdown(shutdownCtx)
|
||||||
|
shutdownCancel()
|
||||||
|
<-shutdownCtx.Done()
|
||||||
|
hs.server = nil
|
||||||
|
}
|
||||||
|
hs.logger.Debug("done")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type MIDIInput struct {
|
type MIDIInput struct {
|
||||||
config config.ModuleConfig
|
config config.ModuleConfig
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
router common.RouteIO
|
router common.RouteIO
|
||||||
Port string
|
Port string
|
||||||
SendFunc func(midi.Message) error
|
logger *slog.Logger
|
||||||
logger *slog.Logger
|
cancel context.CancelFunc
|
||||||
cancel context.CancelFunc
|
stop func()
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -61,7 +61,6 @@ func (mi *MIDIInput) Type() string {
|
|||||||
|
|
||||||
func (mi *MIDIInput) Start(ctx context.Context, router common.RouteIO) error {
|
func (mi *MIDIInput) Start(ctx context.Context, router common.RouteIO) error {
|
||||||
mi.logger.Debug("running")
|
mi.logger.Debug("running")
|
||||||
defer midi.CloseDriver()
|
|
||||||
mi.router = router
|
mi.router = router
|
||||||
moduleContext, cancel := context.WithCancel(ctx)
|
moduleContext, cancel := context.WithCancel(ctx)
|
||||||
mi.ctx = moduleContext
|
mi.ctx = moduleContext
|
||||||
@@ -81,14 +80,20 @@ func (mi *MIDIInput) Start(ctx context.Context, router common.RouteIO) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
mi.stop = stop
|
||||||
defer stop()
|
|
||||||
|
|
||||||
<-mi.ctx.Done()
|
<-mi.ctx.Done()
|
||||||
mi.logger.Debug("done")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mi *MIDIInput) Stop() {
|
func (mi *MIDIInput) Stop() {
|
||||||
mi.cancel()
|
if mi.cancel != nil {
|
||||||
|
mi.cancel()
|
||||||
|
}
|
||||||
|
if mi.stop != nil {
|
||||||
|
mi.stop()
|
||||||
|
mi.stop = nil
|
||||||
|
}
|
||||||
|
midi.CloseDriver()
|
||||||
|
mi.logger.Debug("done")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/google/jsonschema-go/jsonschema"
|
"github.com/google/jsonschema-go/jsonschema"
|
||||||
"github.com/jwetzell/showbridge-go/internal/common"
|
"github.com/jwetzell/showbridge-go/internal/common"
|
||||||
@@ -16,13 +17,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type MIDIOutput struct {
|
type MIDIOutput struct {
|
||||||
config config.ModuleConfig
|
config config.ModuleConfig
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
router common.RouteIO
|
router common.RouteIO
|
||||||
Port string
|
Port string
|
||||||
SendFunc func(midi.Message) error
|
sendFunc func(midi.Message) error
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
|
sendFuncMu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -63,7 +65,6 @@ func (mo *MIDIOutput) Type() string {
|
|||||||
|
|
||||||
func (mo *MIDIOutput) Start(ctx context.Context, router common.RouteIO) error {
|
func (mo *MIDIOutput) Start(ctx context.Context, router common.RouteIO) error {
|
||||||
mo.logger.Debug("running")
|
mo.logger.Debug("running")
|
||||||
defer midi.CloseDriver()
|
|
||||||
mo.router = router
|
mo.router = router
|
||||||
moduleContext, cancel := context.WithCancel(ctx)
|
moduleContext, cancel := context.WithCancel(ctx)
|
||||||
mo.ctx = moduleContext
|
mo.ctx = moduleContext
|
||||||
@@ -80,15 +81,18 @@ func (mo *MIDIOutput) Start(ctx context.Context, router common.RouteIO) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
mo.SendFunc = send
|
mo.sendFuncMu.Lock()
|
||||||
|
mo.sendFunc = send
|
||||||
|
mo.sendFuncMu.Unlock()
|
||||||
|
|
||||||
<-mo.ctx.Done()
|
<-mo.ctx.Done()
|
||||||
mo.logger.Debug("done")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mo *MIDIOutput) Output(ctx context.Context, payload any) error {
|
func (mo *MIDIOutput) Output(ctx context.Context, payload any) error {
|
||||||
if mo.SendFunc == nil {
|
mo.sendFuncMu.Lock()
|
||||||
|
defer mo.sendFuncMu.Unlock()
|
||||||
|
if mo.sendFunc == nil {
|
||||||
return errors.New("midi.output output is not setup")
|
return errors.New("midi.output output is not setup")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,9 +102,13 @@ func (mo *MIDIOutput) Output(ctx context.Context, payload any) error {
|
|||||||
return errors.New("midi.output can only output midi.Message")
|
return errors.New("midi.output can only output midi.Message")
|
||||||
}
|
}
|
||||||
|
|
||||||
return mo.SendFunc(payloadMessage)
|
return mo.sendFunc(payloadMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mo *MIDIOutput) Stop() {
|
func (mo *MIDIOutput) Stop() {
|
||||||
mo.cancel()
|
if mo.cancel != nil {
|
||||||
|
mo.cancel()
|
||||||
|
}
|
||||||
|
midi.CloseDriver()
|
||||||
|
mo.logger.Debug("done")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"sync"
|
||||||
|
|
||||||
mqtt "github.com/eclipse/paho.mqtt.golang"
|
mqtt "github.com/eclipse/paho.mqtt.golang"
|
||||||
"github.com/google/jsonschema-go/jsonschema"
|
"github.com/google/jsonschema-go/jsonschema"
|
||||||
@@ -22,6 +23,7 @@ type MQTTClient struct {
|
|||||||
client mqtt.Client
|
client mqtt.Client
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
|
clientMu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -100,8 +102,8 @@ func (mc *MQTTClient) Start(ctx context.Context, router common.RouteIO) error {
|
|||||||
token.Wait()
|
token.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mc.clientMu.Lock()
|
||||||
mc.client = mqtt.NewClient(opts)
|
mc.client = mqtt.NewClient(opts)
|
||||||
defer mc.client.Disconnect(250)
|
|
||||||
|
|
||||||
token := mc.client.Connect()
|
token := mc.client.Connect()
|
||||||
|
|
||||||
@@ -110,9 +112,9 @@ func (mc *MQTTClient) Start(ctx context.Context, router common.RouteIO) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
mc.clientMu.Unlock()
|
||||||
|
|
||||||
<-mc.ctx.Done()
|
<-mc.ctx.Done()
|
||||||
mc.logger.Debug("done")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,5 +141,14 @@ func (mc *MQTTClient) Output(ctx context.Context, payload any) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (mc *MQTTClient) Stop() {
|
func (mc *MQTTClient) Stop() {
|
||||||
mc.cancel()
|
if mc.cancel != nil {
|
||||||
|
mc.cancel()
|
||||||
|
}
|
||||||
|
mc.clientMu.Lock()
|
||||||
|
defer mc.clientMu.Unlock()
|
||||||
|
if mc.client != nil {
|
||||||
|
mc.client.Disconnect(250)
|
||||||
|
mc.client = nil
|
||||||
|
}
|
||||||
|
mc.logger.Debug("done")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/google/jsonschema-go/jsonschema"
|
"github.com/google/jsonschema-go/jsonschema"
|
||||||
"github.com/jwetzell/showbridge-go/internal/common"
|
"github.com/jwetzell/showbridge-go/internal/common"
|
||||||
@@ -13,14 +14,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type NATSClient struct {
|
type NATSClient struct {
|
||||||
config config.ModuleConfig
|
config config.ModuleConfig
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
router common.RouteIO
|
router common.RouteIO
|
||||||
URL string
|
URL string
|
||||||
Subject string
|
Subject string
|
||||||
client *nats.Conn
|
client *nats.Conn
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
|
sub *nats.Subscription
|
||||||
|
subMu sync.Mutex
|
||||||
|
clientMu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -81,10 +85,9 @@ func (nc *NATSClient) Start(ctx context.Context, router common.RouteIO) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nc.clientMu.Lock()
|
||||||
nc.client = client
|
nc.client = client
|
||||||
|
nc.clientMu.Unlock()
|
||||||
defer client.Drain()
|
|
||||||
defer client.Close()
|
|
||||||
|
|
||||||
sub, err := nc.client.Subscribe(nc.Subject, func(msg *nats.Msg) {
|
sub, err := nc.client.Subscribe(nc.Subject, func(msg *nats.Msg) {
|
||||||
if nc.router != nil {
|
if nc.router != nil {
|
||||||
@@ -95,11 +98,11 @@ func (nc *NATSClient) Start(ctx context.Context, router common.RouteIO) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
nc.subMu.Lock()
|
||||||
defer sub.Unsubscribe()
|
nc.sub = sub
|
||||||
|
nc.subMu.Unlock()
|
||||||
|
|
||||||
<-nc.ctx.Done()
|
<-nc.ctx.Done()
|
||||||
nc.logger.Debug("done")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,6 +114,9 @@ func (nc *NATSClient) Output(ctx context.Context, payload any) error {
|
|||||||
return errors.New("nats.client is only able to output NATSMessage")
|
return errors.New("nats.client is only able to output NATSMessage")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nc.clientMu.Lock()
|
||||||
|
defer nc.clientMu.Unlock()
|
||||||
|
|
||||||
if nc.client == nil {
|
if nc.client == nil {
|
||||||
return errors.New("nats.client client is not setup")
|
return errors.New("nats.client client is not setup")
|
||||||
}
|
}
|
||||||
@@ -125,5 +131,23 @@ func (nc *NATSClient) Output(ctx context.Context, payload any) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (nc *NATSClient) Stop() {
|
func (nc *NATSClient) Stop() {
|
||||||
nc.cancel()
|
if nc.cancel != nil {
|
||||||
|
nc.cancel()
|
||||||
|
}
|
||||||
|
nc.subMu.Lock()
|
||||||
|
defer nc.subMu.Unlock()
|
||||||
|
if nc.sub != nil {
|
||||||
|
nc.sub.Unsubscribe()
|
||||||
|
nc.sub = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
nc.clientMu.Lock()
|
||||||
|
defer nc.clientMu.Unlock()
|
||||||
|
if nc.client != nil {
|
||||||
|
nc.client.Drain()
|
||||||
|
// TODO(jwetzell): setup closed callback to get when client is fully closed
|
||||||
|
nc.client.Close()
|
||||||
|
nc.client = nil
|
||||||
|
}
|
||||||
|
nc.logger.Debug("done")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/jsonschema-go/jsonschema"
|
"github.com/google/jsonschema-go/jsonschema"
|
||||||
@@ -16,14 +17,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type NATSServer struct {
|
type NATSServer struct {
|
||||||
config config.ModuleConfig
|
config config.ModuleConfig
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
Ip string
|
Ip string
|
||||||
Port int
|
Port int
|
||||||
router common.RouteIO
|
router common.RouteIO
|
||||||
server *server.Server
|
server *server.Server
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
|
serverMu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -103,9 +105,10 @@ func (ns *NATSServer) Start(ctx context.Context, router common.RouteIO) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ns.serverMu.Lock()
|
||||||
ns.server = natsServer
|
ns.server = natsServer
|
||||||
|
defer ns.serverMu.Unlock()
|
||||||
natsServer.Start()
|
natsServer.Start()
|
||||||
defer natsServer.Shutdown()
|
|
||||||
|
|
||||||
if !natsServer.ReadyForConnections(5 * time.Second) {
|
if !natsServer.ReadyForConnections(5 * time.Second) {
|
||||||
return errors.New("nats.server failed to start")
|
return errors.New("nats.server failed to start")
|
||||||
@@ -114,13 +117,17 @@ func (ns *NATSServer) Start(ctx context.Context, router common.RouteIO) error {
|
|||||||
|
|
||||||
<-ns.ctx.Done()
|
<-ns.ctx.Done()
|
||||||
|
|
||||||
ns.logger.Debug("done")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ns *NATSServer) Stop() {
|
func (ns *NATSServer) Stop() {
|
||||||
ns.cancel()
|
if ns.cancel != nil {
|
||||||
|
ns.cancel()
|
||||||
|
}
|
||||||
|
ns.serverMu.Lock()
|
||||||
|
defer ns.serverMu.Unlock()
|
||||||
if ns.server != nil {
|
if ns.server != nil {
|
||||||
ns.server.Shutdown()
|
ns.server.Shutdown()
|
||||||
}
|
}
|
||||||
|
ns.logger.Debug("done")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jwetzell/psn-go"
|
"github.com/jwetzell/psn-go"
|
||||||
@@ -19,6 +20,7 @@ type PSNClient struct {
|
|||||||
decoder *psn.Decoder
|
decoder *psn.Decoder
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
|
connMu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -55,21 +57,22 @@ func (pc *PSNClient) Start(ctx context.Context, router common.RouteIO) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer client.Close()
|
|
||||||
|
|
||||||
|
pc.connMu.Lock()
|
||||||
pc.conn = client
|
pc.conn = client
|
||||||
|
pc.connMu.Unlock()
|
||||||
|
|
||||||
buffer := make([]byte, 2048)
|
buffer := make([]byte, 2048)
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-pc.ctx.Done():
|
case <-pc.ctx.Done():
|
||||||
// TODO(jwetzell): cleanup?
|
|
||||||
pc.logger.Debug("done")
|
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
|
pc.connMu.Lock()
|
||||||
pc.conn.SetDeadline(time.Now().Add(time.Millisecond * 200))
|
pc.conn.SetDeadline(time.Now().Add(time.Millisecond * 200))
|
||||||
|
|
||||||
numBytes, _, err := pc.conn.ReadFromUDP(buffer)
|
numBytes, _, err := pc.conn.ReadFromUDP(buffer)
|
||||||
|
pc.connMu.Unlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//NOTE(jwetzell) we hit deadline
|
//NOTE(jwetzell) we hit deadline
|
||||||
if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() {
|
if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() {
|
||||||
@@ -99,5 +102,14 @@ func (pc *PSNClient) Start(ctx context.Context, router common.RouteIO) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pc *PSNClient) Stop() {
|
func (pc *PSNClient) Stop() {
|
||||||
pc.cancel()
|
if pc.cancel != nil {
|
||||||
|
pc.cancel()
|
||||||
|
}
|
||||||
|
pc.connMu.Lock()
|
||||||
|
defer pc.connMu.Unlock()
|
||||||
|
if pc.conn != nil {
|
||||||
|
pc.conn.Close()
|
||||||
|
pc.conn = nil
|
||||||
|
}
|
||||||
|
pc.logger.Debug("done")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/google/jsonschema-go/jsonschema"
|
"github.com/google/jsonschema-go/jsonschema"
|
||||||
"github.com/jwetzell/showbridge-go/internal/common"
|
"github.com/jwetzell/showbridge-go/internal/common"
|
||||||
@@ -13,14 +14,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type RedisClient struct {
|
type RedisClient struct {
|
||||||
config config.ModuleConfig
|
config config.ModuleConfig
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
router common.RouteIO
|
router common.RouteIO
|
||||||
Host string
|
Host string
|
||||||
Port uint16
|
Port uint16
|
||||||
client *redis.Client
|
client *redis.Client
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
|
clientMu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -87,17 +89,25 @@ func (rc *RedisClient) Start(ctx context.Context, router common.RouteIO) error {
|
|||||||
DB: 0,
|
DB: 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
rc.clientMu.Lock()
|
||||||
rc.client = client
|
rc.client = client
|
||||||
|
rc.clientMu.Unlock()
|
||||||
defer client.Close()
|
|
||||||
|
|
||||||
<-rc.ctx.Done()
|
<-rc.ctx.Done()
|
||||||
rc.logger.Debug("done")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rc *RedisClient) Stop() {
|
func (rc *RedisClient) Stop() {
|
||||||
rc.cancel()
|
if rc.cancel != nil {
|
||||||
|
rc.cancel()
|
||||||
|
}
|
||||||
|
rc.clientMu.Lock()
|
||||||
|
defer rc.clientMu.Unlock()
|
||||||
|
if rc.client != nil {
|
||||||
|
rc.client.Close()
|
||||||
|
rc.client = nil
|
||||||
|
}
|
||||||
|
rc.logger.Debug("done")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rc *RedisClient) Get(key string) (any, error) {
|
func (rc *RedisClient) Get(key string) (any, error) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/jsonschema-go/jsonschema"
|
"github.com/google/jsonschema-go/jsonschema"
|
||||||
@@ -26,6 +27,7 @@ type SerialClient struct {
|
|||||||
port serial.Port
|
port serial.Port
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
|
portMu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -93,7 +95,8 @@ func (sc *SerialClient) Type() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sc *SerialClient) SetupPort() error {
|
func (sc *SerialClient) SetupPort() error {
|
||||||
|
sc.portMu.Lock()
|
||||||
|
defer sc.portMu.Unlock()
|
||||||
port, err := serial.Open(sc.Port, sc.Mode)
|
port, err := serial.Open(sc.Port, sc.Mode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -111,23 +114,10 @@ func (sc *SerialClient) Start(ctx context.Context, router common.RouteIO) error
|
|||||||
sc.ctx = moduleContext
|
sc.ctx = moduleContext
|
||||||
sc.cancel = cancel
|
sc.cancel = cancel
|
||||||
|
|
||||||
// TODO(jwetzell): shutdown with router.Context properly
|
for sc.ctx.Err() == nil {
|
||||||
go func() {
|
|
||||||
<-sc.ctx.Done()
|
|
||||||
sc.logger.Debug("done")
|
|
||||||
if sc.port != nil {
|
|
||||||
sc.port.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
for {
|
|
||||||
err := sc.SetupPort()
|
err := sc.SetupPort()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if sc.ctx.Err() != nil {
|
if sc.ctx.Err() != nil {
|
||||||
sc.logger.Debug("done")
|
|
||||||
if sc.port != nil {
|
|
||||||
sc.port.Close()
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
sc.logger.Error("port setup error", "port", sc.Port, "error", err.Error())
|
sc.logger.Error("port setup error", "port", sc.Port, "error", err.Error())
|
||||||
@@ -138,17 +128,12 @@ func (sc *SerialClient) Start(ctx context.Context, router common.RouteIO) error
|
|||||||
buffer := make([]byte, 1024)
|
buffer := make([]byte, 1024)
|
||||||
select {
|
select {
|
||||||
case <-sc.ctx.Done():
|
case <-sc.ctx.Done():
|
||||||
sc.logger.Debug("done")
|
|
||||||
if sc.port != nil {
|
|
||||||
sc.port.Close()
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
READ:
|
READ:
|
||||||
for {
|
for sc.ctx.Err() == nil {
|
||||||
select {
|
select {
|
||||||
case <-sc.ctx.Done():
|
case <-sc.ctx.Done():
|
||||||
sc.logger.Debug("done")
|
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
byteCount, err := sc.port.Read(buffer)
|
byteCount, err := sc.port.Read(buffer)
|
||||||
@@ -174,6 +159,7 @@ func (sc *SerialClient) Start(ctx context.Context, router common.RouteIO) error
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *SerialClient) Output(ctx context.Context, payload any) error {
|
func (sc *SerialClient) Output(ctx context.Context, payload any) error {
|
||||||
@@ -189,5 +175,14 @@ func (sc *SerialClient) Output(ctx context.Context, payload any) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sc *SerialClient) Stop() {
|
func (sc *SerialClient) Stop() {
|
||||||
sc.cancel()
|
if sc.cancel != nil {
|
||||||
|
sc.cancel()
|
||||||
|
}
|
||||||
|
sc.portMu.Lock()
|
||||||
|
defer sc.portMu.Unlock()
|
||||||
|
if sc.port != nil {
|
||||||
|
sc.port.Close()
|
||||||
|
sc.port = nil
|
||||||
|
}
|
||||||
|
sc.logger.Debug("done")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,9 +29,10 @@ type SIPCallServer struct {
|
|||||||
Port int
|
Port int
|
||||||
Transport string
|
Transport string
|
||||||
UserAgent string
|
UserAgent string
|
||||||
dg *diago.Diago
|
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
|
ua *sipgo.UserAgent
|
||||||
|
uaMu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
type SIPCallMessage struct {
|
type SIPCallMessage struct {
|
||||||
@@ -145,7 +146,9 @@ func (scs *SIPCallServer) Start(ctx context.Context, router common.RouteIO) erro
|
|||||||
sipgo.WithUserAgentTransportLayerOptions(sip.WithTransportLayerLogger(diagoLogger)),
|
sipgo.WithUserAgentTransportLayerOptions(sip.WithTransportLayerLogger(diagoLogger)),
|
||||||
sipgo.WithUserAgentTransactionLayerOptions(sip.WithTransactionLayerLogger(diagoLogger)),
|
sipgo.WithUserAgentTransactionLayerOptions(sip.WithTransactionLayerLogger(diagoLogger)),
|
||||||
)
|
)
|
||||||
defer ua.Close()
|
scs.uaMu.Lock()
|
||||||
|
scs.ua = ua
|
||||||
|
scs.uaMu.Unlock()
|
||||||
|
|
||||||
sip.SetDefaultLogger(diagoLogger)
|
sip.SetDefaultLogger(diagoLogger)
|
||||||
media.SetDefaultLogger(diagoLogger)
|
media.SetDefaultLogger(diagoLogger)
|
||||||
@@ -157,16 +160,14 @@ func (scs *SIPCallServer) Start(ctx context.Context, router common.RouteIO) erro
|
|||||||
},
|
},
|
||||||
))
|
))
|
||||||
|
|
||||||
go func() {
|
err := dg.Serve(scs.ctx, func(inDialog *diago.DialogServerSession) {
|
||||||
dg.Serve(scs.ctx, func(inDialog *diago.DialogServerSession) {
|
scs.HandleCall(inDialog)
|
||||||
scs.HandleCall(inDialog)
|
})
|
||||||
})
|
if err != nil {
|
||||||
}()
|
scs.logger.Error("diago serve error", "error", err)
|
||||||
|
}
|
||||||
scs.dg = dg
|
|
||||||
|
|
||||||
<-scs.ctx.Done()
|
<-scs.ctx.Done()
|
||||||
scs.logger.Debug("done")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,5 +248,13 @@ func (scs *SIPCallServer) Output(ctx context.Context, payload any) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (scs *SIPCallServer) Stop() {
|
func (scs *SIPCallServer) Stop() {
|
||||||
scs.cancel()
|
if scs.cancel != nil {
|
||||||
|
scs.cancel()
|
||||||
|
}
|
||||||
|
scs.uaMu.Lock()
|
||||||
|
defer scs.uaMu.Unlock()
|
||||||
|
if scs.ua != nil {
|
||||||
|
scs.ua.Close()
|
||||||
|
}
|
||||||
|
scs.logger.Debug("done")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ type SIPDTMFServer struct {
|
|||||||
Separator string
|
Separator string
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
|
ua *sipgo.UserAgent
|
||||||
|
uaMu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
type SIPDTMFMessage struct {
|
type SIPDTMFMessage struct {
|
||||||
@@ -164,7 +166,10 @@ func (sds *SIPDTMFServer) Start(ctx context.Context, router common.RouteIO) erro
|
|||||||
sipgo.WithUserAgentTransportLayerOptions(sip.WithTransportLayerLogger(diagoLogger)),
|
sipgo.WithUserAgentTransportLayerOptions(sip.WithTransportLayerLogger(diagoLogger)),
|
||||||
sipgo.WithUserAgentTransactionLayerOptions(sip.WithTransactionLayerLogger(diagoLogger)),
|
sipgo.WithUserAgentTransactionLayerOptions(sip.WithTransactionLayerLogger(diagoLogger)),
|
||||||
)
|
)
|
||||||
defer ua.Close()
|
|
||||||
|
sds.uaMu.Lock()
|
||||||
|
sds.ua = ua
|
||||||
|
sds.uaMu.Unlock()
|
||||||
|
|
||||||
sip.SetDefaultLogger(diagoLogger)
|
sip.SetDefaultLogger(diagoLogger)
|
||||||
media.SetDefaultLogger(diagoLogger)
|
media.SetDefaultLogger(diagoLogger)
|
||||||
@@ -185,7 +190,6 @@ func (sds *SIPDTMFServer) Start(ctx context.Context, router common.RouteIO) erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
<-sds.ctx.Done()
|
<-sds.ctx.Done()
|
||||||
sds.logger.Debug("done")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,5 +285,13 @@ func (sds *SIPDTMFServer) Output(ctx context.Context, payload any) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sds *SIPDTMFServer) Stop() {
|
func (sds *SIPDTMFServer) Stop() {
|
||||||
sds.cancel()
|
if sds.cancel != nil {
|
||||||
|
sds.cancel()
|
||||||
|
}
|
||||||
|
sds.uaMu.Lock()
|
||||||
|
defer sds.uaMu.Unlock()
|
||||||
|
if sds.ua != nil {
|
||||||
|
sds.ua.Close()
|
||||||
|
}
|
||||||
|
sds.logger.Debug("done")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/jsonschema-go/jsonschema"
|
"github.com/google/jsonschema-go/jsonschema"
|
||||||
@@ -23,6 +24,7 @@ type TCPClient struct {
|
|||||||
Addr *net.TCPAddr
|
Addr *net.TCPAddr
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
|
connMu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -98,20 +100,10 @@ func (tc *TCPClient) Start(ctx context.Context, router common.RouteIO) error {
|
|||||||
tc.ctx = moduleContext
|
tc.ctx = moduleContext
|
||||||
tc.cancel = cancel
|
tc.cancel = cancel
|
||||||
|
|
||||||
// TODO(jwetzell): shutdown with router.Context properly
|
for tc.ctx.Err() == nil {
|
||||||
go func() {
|
|
||||||
<-tc.ctx.Done()
|
|
||||||
tc.logger.Debug("done")
|
|
||||||
if tc.conn != nil {
|
|
||||||
tc.conn.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
for {
|
|
||||||
err := tc.SetupConn()
|
err := tc.SetupConn()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if tc.ctx.Err() != nil {
|
if tc.ctx.Err() != nil {
|
||||||
tc.logger.Debug("done")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
tc.logger.Error("connection error", "error", err.Error())
|
tc.logger.Error("connection error", "error", err.Error())
|
||||||
@@ -122,14 +114,12 @@ func (tc *TCPClient) Start(ctx context.Context, router common.RouteIO) error {
|
|||||||
buffer := make([]byte, 1024)
|
buffer := make([]byte, 1024)
|
||||||
select {
|
select {
|
||||||
case <-tc.ctx.Done():
|
case <-tc.ctx.Done():
|
||||||
tc.logger.Debug("done")
|
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
READ:
|
READ:
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-tc.ctx.Done():
|
case <-tc.ctx.Done():
|
||||||
tc.logger.Debug("done")
|
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
byteCount, err := tc.conn.Read(buffer)
|
byteCount, err := tc.conn.Read(buffer)
|
||||||
@@ -155,21 +145,22 @@ func (tc *TCPClient) Start(ctx context.Context, router common.RouteIO) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tc *TCPClient) SetupConn() error {
|
func (tc *TCPClient) SetupConn() error {
|
||||||
|
tc.connMu.Lock()
|
||||||
|
defer tc.connMu.Unlock()
|
||||||
client, err := net.DialTCP("tcp", nil, tc.Addr)
|
client, err := net.DialTCP("tcp", nil, tc.Addr)
|
||||||
tc.conn = client
|
tc.conn = client
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tc *TCPClient) Output(ctx context.Context, payload any) error {
|
func (tc *TCPClient) Output(ctx context.Context, payload any) error {
|
||||||
// NOTE(jwetzell): not sure how this would occur but
|
tc.connMu.Lock()
|
||||||
|
defer tc.connMu.Unlock()
|
||||||
if tc.conn == nil {
|
if tc.conn == nil {
|
||||||
err := tc.SetupConn()
|
return errors.New("net.tcp.client client is not setup")
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
payloadBytes, ok := common.GetAnyAsByteSlice(payload)
|
payloadBytes, ok := common.GetAnyAsByteSlice(payload)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -180,5 +171,15 @@ func (tc *TCPClient) Output(ctx context.Context, payload any) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (tc *TCPClient) Stop() {
|
func (tc *TCPClient) Stop() {
|
||||||
tc.cancel()
|
if tc.cancel != nil {
|
||||||
|
tc.cancel()
|
||||||
|
}
|
||||||
|
tc.connMu.Lock()
|
||||||
|
defer tc.connMu.Unlock()
|
||||||
|
if tc.conn != nil {
|
||||||
|
tc.conn.Close()
|
||||||
|
tc.conn = nil
|
||||||
|
}
|
||||||
|
tc.logger.Debug("done")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,12 +25,13 @@ type TCPServer struct {
|
|||||||
Framer framer.Framer
|
Framer framer.Framer
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
router common.RouteIO
|
router common.RouteIO
|
||||||
quit chan any
|
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
connections []*net.TCPConn
|
connections []*net.TCPConn
|
||||||
connectionsMu sync.RWMutex
|
connectionsMu sync.RWMutex
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
|
listener *net.TCPListener
|
||||||
|
listenerMu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -91,7 +92,7 @@ func init() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &TCPServer{Framer: framer, Addr: addr, config: moduleConfig, quit: make(chan any), logger: CreateLogger(moduleConfig)}, nil
|
return &TCPServer{Framer: framer, Addr: addr, config: moduleConfig, logger: CreateLogger(moduleConfig)}, nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -108,14 +109,14 @@ func (ts *TCPServer) handleClient(client *net.TCPConn) {
|
|||||||
ts.connectionsMu.Lock()
|
ts.connectionsMu.Lock()
|
||||||
ts.connections = append(ts.connections, client)
|
ts.connections = append(ts.connections, client)
|
||||||
ts.connectionsMu.Unlock()
|
ts.connectionsMu.Unlock()
|
||||||
ts.logger.Debug("net.tcp.server connection accepted", "remoteAddr", client.RemoteAddr().String())
|
ts.logger.Debug("connection accepted", "remoteAddr", client.RemoteAddr().String())
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
|
|
||||||
buffer := make([]byte, 1024)
|
buffer := make([]byte, 1024)
|
||||||
ClientRead:
|
ClientRead:
|
||||||
for {
|
for ts.ctx.Err() == nil {
|
||||||
select {
|
select {
|
||||||
case <-ts.quit:
|
case <-ts.ctx.Done():
|
||||||
client.Close()
|
client.Close()
|
||||||
ts.connectionsMu.Lock()
|
ts.connectionsMu.Lock()
|
||||||
for i := 0; i < len(ts.connections); i++ {
|
for i := 0; i < len(ts.connections); i++ {
|
||||||
@@ -157,7 +158,6 @@ ClientRead:
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ts.logger.Debug("stream ended", "remoteAddr", client.RemoteAddr().String())
|
|
||||||
ts.connectionsMu.Unlock()
|
ts.connectionsMu.Unlock()
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -194,21 +194,17 @@ func (ts *TCPServer) Start(ctx context.Context, router common.RouteIO) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
ts.listenerMu.Lock()
|
||||||
|
ts.listener = listener
|
||||||
|
ts.listenerMu.Unlock()
|
||||||
ts.wg.Add(1)
|
ts.wg.Add(1)
|
||||||
|
|
||||||
go func() {
|
|
||||||
<-ts.ctx.Done()
|
|
||||||
close(ts.quit)
|
|
||||||
listener.Close()
|
|
||||||
ts.logger.Debug("done")
|
|
||||||
}()
|
|
||||||
|
|
||||||
AcceptLoop:
|
AcceptLoop:
|
||||||
for {
|
for ts.ctx.Err() == nil {
|
||||||
conn, err := listener.AcceptTCP()
|
conn, err := listener.AcceptTCP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
select {
|
select {
|
||||||
case <-ts.quit:
|
case <-ts.ctx.Done():
|
||||||
break AcceptLoop
|
break AcceptLoop
|
||||||
default:
|
default:
|
||||||
ts.logger.Debug("problem with listener", "error", err)
|
ts.logger.Debug("problem with listener", "error", err)
|
||||||
@@ -220,7 +216,6 @@ AcceptLoop:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ts.wg.Done()
|
ts.wg.Done()
|
||||||
ts.wg.Wait()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,6 +243,15 @@ func (ts *TCPServer) Output(ctx context.Context, payload any) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ts *TCPServer) Stop() {
|
func (ts *TCPServer) Stop() {
|
||||||
ts.cancel()
|
if ts.cancel != nil {
|
||||||
|
ts.cancel()
|
||||||
|
}
|
||||||
|
ts.listenerMu.Lock()
|
||||||
|
defer ts.listenerMu.Unlock()
|
||||||
|
if ts.listener != nil {
|
||||||
|
ts.listener.Close()
|
||||||
|
ts.listener = nil
|
||||||
|
}
|
||||||
ts.wg.Wait()
|
ts.wg.Wait()
|
||||||
|
ts.logger.Debug("done")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,12 +66,10 @@ func (i *TimeInterval) Start(ctx context.Context, router common.RouteIO) error {
|
|||||||
|
|
||||||
ticker := time.NewTicker(time.Millisecond * time.Duration(i.Duration))
|
ticker := time.NewTicker(time.Millisecond * time.Duration(i.Duration))
|
||||||
i.ticker = ticker
|
i.ticker = ticker
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-i.ctx.Done():
|
case <-i.ctx.Done():
|
||||||
i.logger.Debug("done")
|
|
||||||
return nil
|
return nil
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
if i.router != nil {
|
if i.router != nil {
|
||||||
@@ -79,9 +77,15 @@ func (i *TimeInterval) Start(ctx context.Context, router common.RouteIO) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *TimeInterval) Stop() {
|
func (i *TimeInterval) Stop() {
|
||||||
i.cancel()
|
if i.cancel != nil {
|
||||||
|
i.cancel()
|
||||||
|
}
|
||||||
|
if i.ticker != nil {
|
||||||
|
i.ticker.Stop()
|
||||||
|
i.ticker = nil
|
||||||
|
}
|
||||||
|
i.logger.Debug("done")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,12 +66,9 @@ func (t *TimeTimer) Start(ctx context.Context, router common.RouteIO) error {
|
|||||||
t.cancel = cancel
|
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()
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-t.ctx.Done():
|
case <-t.ctx.Done():
|
||||||
t.timer.Stop()
|
|
||||||
t.logger.Debug("done")
|
|
||||||
return nil
|
return nil
|
||||||
case time := <-t.timer.C:
|
case time := <-t.timer.C:
|
||||||
if t.router != nil {
|
if t.router != nil {
|
||||||
@@ -82,5 +79,12 @@ func (t *TimeTimer) Start(ctx context.Context, router common.RouteIO) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *TimeTimer) Stop() {
|
func (t *TimeTimer) Stop() {
|
||||||
t.cancel()
|
if t.cancel != nil {
|
||||||
|
t.cancel()
|
||||||
|
}
|
||||||
|
if t.timer != nil {
|
||||||
|
t.timer.Stop()
|
||||||
|
t.timer = nil
|
||||||
|
}
|
||||||
|
t.logger.Debug("done")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/google/jsonschema-go/jsonschema"
|
"github.com/google/jsonschema-go/jsonschema"
|
||||||
"github.com/jwetzell/showbridge-go/internal/common"
|
"github.com/jwetzell/showbridge-go/internal/common"
|
||||||
@@ -21,6 +22,7 @@ type UDPClient struct {
|
|||||||
router common.RouteIO
|
router common.RouteIO
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
|
connMu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -74,6 +76,8 @@ func (uc *UDPClient) Type() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (uc *UDPClient) SetupConn() error {
|
func (uc *UDPClient) SetupConn() error {
|
||||||
|
uc.connMu.Lock()
|
||||||
|
defer uc.connMu.Unlock()
|
||||||
client, err := net.DialUDP("udp", nil, uc.Addr)
|
client, err := net.DialUDP("udp", nil, uc.Addr)
|
||||||
uc.conn = client
|
uc.conn = client
|
||||||
return err
|
return err
|
||||||
@@ -92,15 +96,12 @@ func (uc *UDPClient) Start(ctx context.Context, router common.RouteIO) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
<-uc.ctx.Done()
|
<-uc.ctx.Done()
|
||||||
uc.logger.Debug("done")
|
|
||||||
if uc.conn != nil {
|
|
||||||
uc.conn.Close()
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uc *UDPClient) Output(ctx context.Context, payload any) error {
|
func (uc *UDPClient) Output(ctx context.Context, payload any) error {
|
||||||
|
uc.connMu.Lock()
|
||||||
|
defer uc.connMu.Unlock()
|
||||||
payloadBytes, ok := common.GetAnyAsByteSlice(payload)
|
payloadBytes, ok := common.GetAnyAsByteSlice(payload)
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("net.udp.client is only able to output bytes")
|
return errors.New("net.udp.client is only able to output bytes")
|
||||||
@@ -118,5 +119,15 @@ func (uc *UDPClient) Output(ctx context.Context, payload any) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (uc *UDPClient) Stop() {
|
func (uc *UDPClient) Stop() {
|
||||||
uc.cancel()
|
if uc.cancel != nil {
|
||||||
|
uc.cancel()
|
||||||
|
}
|
||||||
|
uc.connMu.Lock()
|
||||||
|
defer uc.connMu.Unlock()
|
||||||
|
if uc.conn != nil {
|
||||||
|
uc.conn.Close()
|
||||||
|
uc.conn = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
uc.logger.Debug("done")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/jsonschema-go/jsonschema"
|
"github.com/google/jsonschema-go/jsonschema"
|
||||||
@@ -21,6 +22,7 @@ type UDPMulticast struct {
|
|||||||
Addr *net.UDPAddr
|
Addr *net.UDPAddr
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
|
connMu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -86,19 +88,21 @@ func (um *UDPMulticast) Start(ctx context.Context, router common.RouteIO) error
|
|||||||
}
|
}
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
|
|
||||||
|
um.connMu.Lock()
|
||||||
um.conn = client
|
um.conn = client
|
||||||
|
um.connMu.Unlock()
|
||||||
|
|
||||||
buffer := make([]byte, 2048)
|
buffer := make([]byte, 2048)
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-um.ctx.Done():
|
case <-um.ctx.Done():
|
||||||
// TODO(jwetzell): cleanup?
|
|
||||||
um.logger.Debug("done")
|
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
|
um.connMu.Lock()
|
||||||
um.conn.SetDeadline(time.Now().Add(time.Millisecond * 200))
|
um.conn.SetDeadline(time.Now().Add(time.Millisecond * 200))
|
||||||
|
|
||||||
numBytes, _, err := um.conn.ReadFromUDP(buffer)
|
numBytes, _, err := um.conn.ReadFromUDP(buffer)
|
||||||
|
um.connMu.Unlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//NOTE(jwetzell) we hit deadline
|
//NOTE(jwetzell) we hit deadline
|
||||||
if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() {
|
if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() {
|
||||||
@@ -136,5 +140,14 @@ func (um *UDPMulticast) Output(ctx context.Context, payload any) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (um *UDPMulticast) Stop() {
|
func (um *UDPMulticast) Stop() {
|
||||||
um.cancel()
|
if um.cancel != nil {
|
||||||
|
um.cancel()
|
||||||
|
}
|
||||||
|
um.connMu.Lock()
|
||||||
|
defer um.connMu.Unlock()
|
||||||
|
if um.conn != nil {
|
||||||
|
um.conn.Close()
|
||||||
|
um.conn = nil
|
||||||
|
}
|
||||||
|
um.logger.Debug("done")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/jsonschema-go/jsonschema"
|
"github.com/google/jsonschema-go/jsonschema"
|
||||||
@@ -22,6 +23,8 @@ type UDPServer struct {
|
|||||||
router common.RouteIO
|
router common.RouteIO
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
|
listener *net.UDPConn
|
||||||
|
listenerMu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -106,15 +109,13 @@ func (us *UDPServer) Start(ctx context.Context, router common.RouteIO) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
us.listenerMu.Lock()
|
||||||
defer listener.Close()
|
us.listener = listener
|
||||||
|
|
||||||
buffer := make([]byte, us.BufferSize)
|
buffer := make([]byte, us.BufferSize)
|
||||||
for {
|
for us.ctx.Err() == nil {
|
||||||
select {
|
select {
|
||||||
case <-us.ctx.Done():
|
case <-us.ctx.Done():
|
||||||
// TODO(jwetzell): cleanup?
|
|
||||||
us.logger.Debug("done")
|
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
listener.SetDeadline(time.Now().Add(time.Millisecond * 200))
|
listener.SetDeadline(time.Now().Add(time.Millisecond * 200))
|
||||||
@@ -135,7 +136,8 @@ func (us *UDPServer) Start(ctx context.Context, router common.RouteIO) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
us.listenerMu.Unlock()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (us *UDPServer) Output(ctx context.Context, payload any) error {
|
func (us *UDPServer) Output(ctx context.Context, payload any) error {
|
||||||
@@ -143,5 +145,14 @@ func (us *UDPServer) Output(ctx context.Context, payload any) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (us *UDPServer) Stop() {
|
func (us *UDPServer) Stop() {
|
||||||
us.cancel()
|
if us.cancel != nil {
|
||||||
|
us.cancel()
|
||||||
|
}
|
||||||
|
us.listenerMu.Lock()
|
||||||
|
defer us.listenerMu.Unlock()
|
||||||
|
if us.listener != nil {
|
||||||
|
us.listener.Close()
|
||||||
|
us.listener = nil
|
||||||
|
}
|
||||||
|
us.logger.Debug("done")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/jsonschema-go/jsonschema"
|
"github.com/google/jsonschema-go/jsonschema"
|
||||||
@@ -22,6 +24,7 @@ type WebSocketClient struct {
|
|||||||
router common.RouteIO
|
router common.RouteIO
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
|
connMu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -69,6 +72,8 @@ func (wc *WebSocketClient) Type() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (wc *WebSocketClient) SetupConn() error {
|
func (wc *WebSocketClient) SetupConn() error {
|
||||||
|
wc.connMu.Lock()
|
||||||
|
defer wc.connMu.Unlock()
|
||||||
conn, _, err := websocket.DefaultDialer.Dial(wc.URL.String(), nil)
|
conn, _, err := websocket.DefaultDialer.Dial(wc.URL.String(), nil)
|
||||||
wc.conn = conn
|
wc.conn = conn
|
||||||
return err
|
return err
|
||||||
@@ -87,17 +92,13 @@ func (wc *WebSocketClient) Start(ctx context.Context, router common.RouteIO) err
|
|||||||
wc.logger.Error("connection error", "error", err)
|
wc.logger.Error("connection error", "error", err)
|
||||||
} else {
|
} else {
|
||||||
// NOTE(jwetzell): enter read loop until an error occurs
|
// NOTE(jwetzell): enter read loop until an error occurs
|
||||||
|
wc.logger.Debug("websocket connection established entering read loop")
|
||||||
wc.readLoop()
|
wc.readLoop()
|
||||||
}
|
}
|
||||||
// NOTE(jwetzell): if connection is lost or read error wait before trying again
|
// NOTE(jwetzell): if connection is lost or read error wait before trying again
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
<-wc.ctx.Done()
|
<-wc.ctx.Done()
|
||||||
wc.logger.Debug("done")
|
|
||||||
if wc.conn != nil {
|
|
||||||
wc.conn.Close()
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,10 +108,20 @@ func (wc *WebSocketClient) readLoop() {
|
|||||||
wc.logger.Error("websocket connection is not established")
|
wc.logger.Error("websocket connection is not established")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
wc.conn.SetReadDeadline(time.Now().Add(5 * time.Second))
|
||||||
messageType, message, err := wc.conn.ReadMessage()
|
messageType, message, err := wc.conn.ReadMessage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
wc.logger.Error("read error", "error", err)
|
if opErr, ok := err.(*net.OpError); ok {
|
||||||
|
// NOTE(jwetzell) we hit deadline
|
||||||
|
if opErr.Timeout() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// NOTE(jwetzell) connection was closed
|
||||||
|
if errors.Is(opErr, net.ErrClosed) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wc.logger.Error("websocket read error", "error", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if wc.router != nil {
|
if wc.router != nil {
|
||||||
@@ -154,7 +165,8 @@ func (wc *WebSocketClient) outputString(ctx context.Context, payload string) err
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (wc *WebSocketClient) Output(ctx context.Context, payload any) error {
|
func (wc *WebSocketClient) Output(ctx context.Context, payload any) error {
|
||||||
|
wc.connMu.Lock()
|
||||||
|
defer wc.connMu.Unlock()
|
||||||
payloadBytes, ok := common.GetAnyAsByteSlice(payload)
|
payloadBytes, ok := common.GetAnyAsByteSlice(payload)
|
||||||
if ok {
|
if ok {
|
||||||
return wc.outputBytes(ctx, payloadBytes)
|
return wc.outputBytes(ctx, payloadBytes)
|
||||||
@@ -169,5 +181,14 @@ func (wc *WebSocketClient) Output(ctx context.Context, payload any) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (wc *WebSocketClient) Stop() {
|
func (wc *WebSocketClient) Stop() {
|
||||||
wc.cancel()
|
if wc.cancel != nil {
|
||||||
|
wc.cancel()
|
||||||
|
}
|
||||||
|
wc.connMu.Lock()
|
||||||
|
defer wc.connMu.Unlock()
|
||||||
|
if wc.conn != nil {
|
||||||
|
wc.conn.Close()
|
||||||
|
wc.conn = nil
|
||||||
|
}
|
||||||
|
wc.logger.Debug("done")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/jwetzell/showbridge-go/internal/api"
|
"github.com/jwetzell/showbridge-go/internal/api"
|
||||||
"github.com/jwetzell/showbridge-go/internal/common"
|
"github.com/jwetzell/showbridge-go/internal/common"
|
||||||
@@ -170,17 +171,19 @@ func (r *Router) Start(ctx context.Context) {
|
|||||||
r.contextCancel = cancel
|
r.contextCancel = cancel
|
||||||
r.startModules()
|
r.startModules()
|
||||||
r.apiServer.Start(r.GetRunningConfig().Api)
|
r.apiServer.Start(r.GetRunningConfig().Api)
|
||||||
<-r.Context.Done()
|
|
||||||
r.logger.Debug("shutting down api server")
|
|
||||||
r.apiServer.Stop()
|
|
||||||
r.logger.Debug("waiting for modules to exit")
|
|
||||||
r.moduleWait.Wait()
|
|
||||||
r.logger.Info("done")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) Stop() {
|
func (r *Router) Stop() {
|
||||||
r.logger.Info("stopping")
|
r.logger.Info("stopping")
|
||||||
|
r.logger.Debug("shutting down api server")
|
||||||
|
r.apiServer.Stop()
|
||||||
|
r.logger.Debug("stopping modules")
|
||||||
|
r.stopModules()
|
||||||
|
r.logger.Debug("waiting for modules to exit")
|
||||||
|
r.moduleWait.Wait()
|
||||||
|
r.logger.Debug("canceling router context")
|
||||||
r.contextCancel()
|
r.contextCancel()
|
||||||
|
r.logger.Info("done")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) HandleInput(ctx context.Context, sourceId string, payload any) (bool, []common.RouteIOError) {
|
func (r *Router) HandleInput(ctx context.Context, sourceId string, payload any) (bool, []common.RouteIOError) {
|
||||||
@@ -190,7 +193,7 @@ func (r *Router) HandleInput(ctx context.Context, sourceId string, payload any)
|
|||||||
spanCtx, span := otel.Tracer("router").Start(ctx, "input", trace.WithAttributes(attribute.String("source.id", sourceId)))
|
spanCtx, span := otel.Tracer("router").Start(ctx, "input", trace.WithAttributes(attribute.String("source.id", sourceId)))
|
||||||
defer span.End()
|
defer span.End()
|
||||||
var routeIOErrors []common.RouteIOError
|
var routeIOErrors []common.RouteIOError
|
||||||
routeFound := false
|
var routeFound atomic.Bool
|
||||||
|
|
||||||
r.broadcastEvent(common.Event{
|
r.broadcastEvent(common.Event{
|
||||||
Type: "input",
|
Type: "input",
|
||||||
@@ -209,7 +212,7 @@ func (r *Router) HandleInput(ctx context.Context, sourceId string, payload any)
|
|||||||
if routeInstance.Input() == sourceId {
|
if routeInstance.Input() == sourceId {
|
||||||
routeWaitGroup.Go(func() {
|
routeWaitGroup.Go(func() {
|
||||||
|
|
||||||
routeFound = true
|
routeFound.Store(true)
|
||||||
|
|
||||||
routeCtx, routeSpan := otel.Tracer("router").Start(spanCtx, "route", trace.WithAttributes(attribute.Int("route.index", routeIndex), attribute.String("route.input", routeInstance.Input())))
|
routeCtx, routeSpan := otel.Tracer("router").Start(spanCtx, "route", trace.WithAttributes(attribute.Int("route.index", routeIndex), attribute.String("route.input", routeInstance.Input())))
|
||||||
_, err := routeInstance.ProcessPayload(routeCtx, common.WrappedPayload{
|
_, err := routeInstance.ProcessPayload(routeCtx, common.WrappedPayload{
|
||||||
@@ -248,7 +251,7 @@ func (r *Router) HandleInput(ctx context.Context, sourceId string, payload any)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
routeWaitGroup.Wait()
|
routeWaitGroup.Wait()
|
||||||
return routeFound, routeIOErrors
|
return routeFound.Load(), routeIOErrors
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) HandleOutput(ctx context.Context, destinationId string, payload any) error {
|
func (r *Router) HandleOutput(ctx context.Context, destinationId string, payload any) error {
|
||||||
@@ -301,7 +304,6 @@ func (r *Router) HandleOutput(ctx context.Context, destinationId string, payload
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) startModules() {
|
func (r *Router) startModules() {
|
||||||
|
|
||||||
for moduleId := range r.ModuleInstances {
|
for moduleId := range r.ModuleInstances {
|
||||||
// TODO(jwetzell): handle module run errors
|
// TODO(jwetzell): handle module run errors
|
||||||
err := r.startModule(r.Context, moduleId)
|
err := r.startModule(r.Context, moduleId)
|
||||||
@@ -310,3 +312,13 @@ func (r *Router) startModules() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Router) stopModules() {
|
||||||
|
for moduleId := range r.ModuleInstances {
|
||||||
|
// TODO(jwetzell): handle module stop errors?
|
||||||
|
err := r.stopModule(moduleId)
|
||||||
|
if err != nil {
|
||||||
|
r.logger.Error("error stopping module", "moduleId", moduleId, "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+8
-30
@@ -5,7 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -54,7 +53,9 @@ func (mcm *MockCounterModule) Type() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (mcm *MockCounterModule) Stop() {
|
func (mcm *MockCounterModule) Stop() {
|
||||||
mcm.cancel()
|
if mcm.cancel != nil {
|
||||||
|
mcm.cancel()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -222,12 +223,7 @@ func TestRouterInputUnknownDestinationModule(t *testing.T) {
|
|||||||
t.Fatalf("router should not have returned any route errors: %v", routeErrors)
|
t.Fatalf("router should not have returned any route errors: %v", routeErrors)
|
||||||
}
|
}
|
||||||
|
|
||||||
routerRunner := sync.WaitGroup{}
|
router.Start(t.Context())
|
||||||
|
|
||||||
routerRunner.Go(func() {
|
|
||||||
router.Start(t.Context())
|
|
||||||
fmt.Println("router stopped")
|
|
||||||
})
|
|
||||||
|
|
||||||
time.Sleep(time.Second * 1)
|
time.Sleep(time.Second * 1)
|
||||||
|
|
||||||
@@ -281,12 +277,7 @@ func TestRouterInputNoMatchingRoute(t *testing.T) {
|
|||||||
t.Fatalf("router should not have returned any route errors: %v", routeErrors)
|
t.Fatalf("router should not have returned any route errors: %v", routeErrors)
|
||||||
}
|
}
|
||||||
|
|
||||||
routerRunner := sync.WaitGroup{}
|
router.Start(t.Context())
|
||||||
|
|
||||||
routerRunner.Go(func() {
|
|
||||||
router.Start(t.Context())
|
|
||||||
fmt.Println("router stopped")
|
|
||||||
})
|
|
||||||
|
|
||||||
time.Sleep(time.Second * 1)
|
time.Sleep(time.Second * 1)
|
||||||
|
|
||||||
@@ -332,12 +323,7 @@ func TestRouterInputSingleRoute(t *testing.T) {
|
|||||||
t.Fatalf("router should not have returned any route errors: %v", routeErrors)
|
t.Fatalf("router should not have returned any route errors: %v", routeErrors)
|
||||||
}
|
}
|
||||||
|
|
||||||
routerRunner := sync.WaitGroup{}
|
router.Start(t.Context())
|
||||||
|
|
||||||
routerRunner.Go(func() {
|
|
||||||
router.Start(t.Context())
|
|
||||||
fmt.Println("router stopped")
|
|
||||||
})
|
|
||||||
|
|
||||||
time.Sleep(time.Second * 1)
|
time.Sleep(time.Second * 1)
|
||||||
|
|
||||||
@@ -425,11 +411,7 @@ func TestRouterInputMultipleRoutes(t *testing.T) {
|
|||||||
t.Fatalf("router should not have returned any route errors: %v", routeErrors)
|
t.Fatalf("router should not have returned any route errors: %v", routeErrors)
|
||||||
}
|
}
|
||||||
|
|
||||||
routerRunner := sync.WaitGroup{}
|
router.Start(t.Context())
|
||||||
|
|
||||||
routerRunner.Go(func() {
|
|
||||||
router.Start(t.Context())
|
|
||||||
})
|
|
||||||
time.Sleep(time.Second * 1)
|
time.Sleep(time.Second * 1)
|
||||||
|
|
||||||
defer router.Stop()
|
defer router.Stop()
|
||||||
@@ -510,11 +492,7 @@ func TestRouterInputMultipleModules(t *testing.T) {
|
|||||||
t.Fatalf("router should not have returned any route errors: %v", routeErrors)
|
t.Fatalf("router should not have returned any route errors: %v", routeErrors)
|
||||||
}
|
}
|
||||||
|
|
||||||
routerRunner := sync.WaitGroup{}
|
router.Start(t.Context())
|
||||||
|
|
||||||
routerRunner.Go(func() {
|
|
||||||
router.Start(t.Context())
|
|
||||||
})
|
|
||||||
|
|
||||||
time.Sleep(time.Second * 1)
|
time.Sleep(time.Second * 1)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user