Add websocket_idle_timeout_seconds config for idle WebSocket cleanup (#21493)

This commit is contained in:
Gustavo Trott 2024-10-23 12:19:01 -03:00 committed by GitHub
parent 36d14eddc8
commit 1cda716da5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 38 additions and 0 deletions

View File

@ -40,6 +40,9 @@ func main() {
log.Infof("Json Patch Disabled!")
}
// Routine to check for idle connections and close them
go websrv.InvalidateIdleBrowserConnectionsRoutine()
// Websocket listener
rateLimiter := common.NewCustomRateLimiter(cfg.Server.MaxConnectionsPerSecond)

View File

@ -29,6 +29,7 @@ type Config struct {
JsonPatchDisabled bool `yaml:"json_patch_disabled"`
SubscriptionAllowedList string `yaml:"subscriptions_allowed_list"`
SubscriptionsDeniedList string `yaml:"subscriptions_denied_list"`
WebsocketIdleTimeoutSeconds int `yaml:"websocket_idle_timeout_seconds"`
} `yaml:"server"`
Redis struct {
Host string `yaml:"host"`

View File

@ -12,6 +12,7 @@ server:
json_patch_disabled: false
subscriptions_allowed_list:
subscriptions_denied_list:
websocket_idle_timeout_seconds: 60
redis:
host: 127.0.0.1
port: 6379

View File

@ -6,6 +6,7 @@ import (
"github.com/sirupsen/logrus"
"net/http"
"sync"
"time"
"nhooyr.io/websocket"
)
@ -56,6 +57,8 @@ type BrowserConnection struct {
FromBrowserToHasuraChannel *SafeChannelByte // channel to transmit messages from Browser to Hasura
FromBrowserToGqlActionsChannel *SafeChannelByte // channel to transmit messages from Browser to Graphq-Actions
FromHasuraToBrowserChannel *SafeChannelByte // channel to transmit messages from Hasura/GqlActions to Browser
LastBrowserMessageTime time.Time // stores the time of the last message to control browser idleness
LastBrowserMessageTimeMutex sync.RWMutex // mutex for LastBrowserMessageTime
Logger *logrus.Entry // connection logger populated with connection info
}

View File

@ -70,6 +70,7 @@ func HasuraClient(
defer func() {
//When Hasura sends an CloseError, it will forward the error to the browser and close the connection
if thisConnection.WebsocketCloseError != nil {
browserConnection.Logger.Infof("Closing browser connection because Hasura connection was closed, reason: %s", thisConnection.WebsocketCloseError.Reason)
browserConnection.Websocket.Close(thisConnection.WebsocketCloseError.Code, thisConnection.WebsocketCloseError.Reason)
browserConnection.ContextCancelFunc()
}

View File

@ -98,6 +98,7 @@ func ConnectionHandler(w http.ResponseWriter, r *http.Request) {
FromBrowserToHasuraChannel: common.NewSafeChannelByte(bufferSize),
FromBrowserToGqlActionsChannel: common.NewSafeChannelByte(bufferSize),
FromHasuraToBrowserChannel: common.NewSafeChannelByte(bufferSize),
LastBrowserMessageTime: time.Now(),
Logger: connectionLogger,
}
@ -494,3 +495,27 @@ func disconnectWithError(
return
}
var websocketIdleTimeoutSeconds = config.GetConfig().Server.WebsocketIdleTimeoutSeconds
func InvalidateIdleBrowserConnectionsRoutine() {
for {
time.Sleep(15 * time.Second)
BrowserConnectionsMutex.RLock()
for _, browserConnection := range BrowserConnections {
browserConnection.LastBrowserMessageTimeMutex.RLock()
browserIdleSince := time.Since(browserConnection.LastBrowserMessageTime)
browserConnection.LastBrowserMessageTimeMutex.RUnlock()
if browserIdleSince > time.Duration(websocketIdleTimeoutSeconds)*time.Second {
browserConnection.Logger.Info("Closing browser connection, reason: idle timeout")
errCloseWs := browserConnection.Websocket.Close(websocket.StatusNormalClosure, "idle timeout")
if errCloseWs != nil {
browserConnection.Logger.Debugf("Error on close websocket: %v", errCloseWs)
}
}
}
BrowserConnectionsMutex.RUnlock()
}
}

View File

@ -35,6 +35,7 @@ func BrowserConnectionReader(
for {
messageType, message, err := browserConnection.Websocket.Read(browserConnection.Context)
if err != nil {
if errors.Is(err, context.Canceled) {
browserConnection.Logger.Debugf("Closing Browser ws connection as Context was cancelled!")
@ -45,6 +46,9 @@ func BrowserConnectionReader(
}
browserConnection.Logger.Tracef("received from browser: %s", string(message))
browserConnection.LastBrowserMessageTimeMutex.Lock()
browserConnection.LastBrowserMessageTime = time.Now()
browserConnection.LastBrowserMessageTimeMutex.Unlock()
if messageType != websocket.MessageText {
browserConnection.Logger.Warnf("received non-text message: %v", messageType)