Code is more robust and should now never try to send a notification to a web client that can block, using a context with cancellation.

This commit is contained in:
Erik Brakkee 2024-08-10 17:27:50 +02:00
parent 0bb9812b59
commit 5b7d51f210
2 changed files with 16 additions and 5 deletions

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"context"
"converge/pkg/server/converge" "converge/pkg/server/converge"
"converge/pkg/support/websocketutil" "converge/pkg/support/websocketutil"
"fmt" "fmt"
@ -134,13 +135,14 @@ func main() {
// for the web browser getting live status updates. // for the web browser getting live status updates.
sessionService := websocketutil.WebSocketService{ sessionService := websocketutil.WebSocketService{
Handler: func(w http.ResponseWriter, r *http.Request, conn net.Conn) { Handler: func(w http.ResponseWriter, r *http.Request, conn net.Conn) {
websession := websessions.NewSession(conn) ctx, cancel := context.WithCancel(context.Background())
websession := websessions.NewSession(conn, ctx)
defer websessions.SessionClosed(websession) defer websessions.SessionClosed(websession)
location, err := converge.GetUserLocation(r) location, err := converge.GetUserLocation(r)
if err != nil { if err != nil {
panic(err) panic(err)
} }
websession.WriteNotifications(location) websession.WriteNotifications(location, cancel)
}, },
Text: true, Text: true,
} }

View File

@ -20,6 +20,7 @@ type WebSessions struct {
type WebSession struct { type WebSession struct {
notifications chan *models.State notifications chan *models.State
conn net.Conn conn net.Conn
ctx context.Context
} }
func NewWebSessions(notifications chan *models.State) *WebSessions { func NewWebSessions(notifications chan *models.State) *WebSessions {
@ -41,16 +42,22 @@ func (sessions *WebSessions) notifyWebSessions(notification *models.State) {
defer sessions.mutex.Unlock() defer sessions.mutex.Unlock()
sessions.lastNotification = notification sessions.lastNotification = notification
for session, _ := range sessions.sessions { for session, _ := range sessions.sessions {
session.notifications <- notification select {
case <-session.ctx.Done():
// session is closed, will be removed at higher level when session is done.
case session.notifications <- notification:
// Sent notification
}
} }
} }
func (sessions *WebSessions) NewSession(wsConnection net.Conn) *WebSession { func (sessions *WebSessions) NewSession(wsConnection net.Conn, ctx context.Context) *WebSession {
sessions.mutex.Lock() sessions.mutex.Lock()
defer sessions.mutex.Unlock() defer sessions.mutex.Unlock()
session := &WebSession{ session := &WebSession{
notifications: make(chan *models.State, 10), notifications: make(chan *models.State, 10),
conn: wsConnection, conn: wsConnection,
ctx: ctx,
} }
sessions.sessions[session] = true sessions.sessions[session] = true
sessions.logSessions() sessions.logSessions()
@ -70,9 +77,11 @@ func GetUserLocation(r *http.Request) (*time.Location, error) {
return time.LoadLocation(tzName) return time.LoadLocation(tzName)
} }
func (session *WebSession) WriteNotifications(location *time.Location) { func (session *WebSession) WriteNotifications(location *time.Location, cancel context.CancelFunc) {
timer := time.NewTicker(10 * time.Second) timer := time.NewTicker(10 * time.Second)
defer timer.Stop() defer timer.Stop()
// if for some reason we cannot send notifications to the web client then the context is canceled.
defer cancel()
for { for {
select { select {
case notification, ok := <-session.notifications: case notification, ok := <-session.notifications: