BareRTC/client/deadlock_watch.go
Noah Petherbridge fd82a463f3 Deadlock detection, DND, and Frontend Fixes
* Deadlock detection: the chatbot handlers will spin off a background goroutine
  to ping DMs at itself and test for responsiveness. If the echoes don't return
  for a minute, issue a /api/shutdown command to the HTTP server to force a
  reboot.
* New admin API endpoint: /api/shutdown, equivalent to the operator '/shutdown'
  command sent in chat. Requires your AdminAPIKey to call it. Used by the chatbot
  as part of deadlock detection.
* Adjust some uses of mutexes to hopefully mitigate deadlocks a bit.
* Do Not Disturb: if users opt to "Ignore unsolicited DMs" they will set a DND
  status on the server which will grey-out their DM icon for other chatters.
* Bring back an option for ChatServer to notify you when somebody begins watching
  your camera (on by default).
* Automatically focus the message entry box when changing channels.
* Lower webcam resolution hints to 480p to test performance implications.
2023-08-29 15:54:40 -07:00

96 lines
2.3 KiB
Go

package client
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"time"
"git.kirsle.net/apps/barertc/client/config"
"git.kirsle.net/apps/barertc/pkg/log"
"git.kirsle.net/apps/barertc/pkg/messages"
)
const deadlockTTL = time.Minute
/*
Deadlock detection for the chat server.
Part of the chatbot handlers. The bot will send DMs to itself on an interval
and test whether the server is responsive; if it goes down, it will issue the
/api/shutdown command to reboot the server automatically.
This function is a goroutine spawned in the background.
*/
func (h *BotHandlers) watchForDeadlock() {
log.Info("Deadlock monitor engaged!")
h.deadlockLastOK = time.Now()
for {
time.Sleep(15 * time.Second)
h.client.Send(messages.Message{
Action: messages.ActionMessage,
Channel: "@" + h.client.Username(),
Message: "deadlock ping",
})
// Has it been a while since our last ping?
if time.Since(h.deadlockLastOK) > deadlockTTL {
log.Error("Deadlock detected! Rebooting the chat server!")
h.deadlockLastOK = time.Now()
h.rebootChatServer()
}
}
}
// onMessageFromSelf handles DMs sent to ourself, e.g. for deadlock detection.
func (h *BotHandlers) onMessageFromSelf(msg messages.Message) {
// If it is our own DM channel thread, it's for deadlock detection.
if msg.Channel == "@"+h.client.Username() {
h.deadlockLastOK = time.Now()
}
}
// Reboot the chat server via web API, in case of deadlock.
func (h *BotHandlers) rebootChatServer() error {
// API request struct for BareRTC /api/shutdown endpoint.
var request = struct {
APIKey string
}{
APIKey: config.Current.BareRTC.AdminAPIKey,
}
// JSON request body.
jsonStr, err := json.Marshal(request)
if err != nil {
return err
}
// Make the API request to BareRTC.
var url = strings.TrimSuffix(config.Current.BareRTC.URL, "/") + "/api/shutdown"
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
client := &http.Client{
Timeout: 10 * time.Second,
}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("RebootChatServer: error posting to BareRTC: status %d body %s", resp.StatusCode, body)
}
return nil
}