Phase 2b: server/client timeouts, atomic state writes, log mutex, panic and shutdown fixes
- http.Server with ReadHeaderTimeout/IdleTimeout (no read/write timeouts: /stream/ is long-lived) - shared outbound clients: providerHTTPClient (5m), apiHTTPClient (30s), imgcache client (30s) - imgcache: download outside the lock, per-item helper, cache URL uses the file name not the fs path - writeFileAtomic (temp + fsync + rename) for settings/xepg/pms/urls/authentication JSON - one package-level logMu for WebScreenLog and notifications; ring buffer keeps the newest lines - notifications evict the oldest instead of random map entries - xepg XMLTV file removal rebuilt after the loop; data.go range-mutation removed - API handler returns after error body; WS request/response fresh per command - checked type assertions in data/backup/provider/screen - SIGINT/SIGTERM handled in main via src.Shutdown(); fatal paths exit 1
This commit is contained in:
+144
-39
@@ -10,6 +10,82 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// logMu guards WebScreenLog (its slice and counters) and System.Notification.
|
||||
// Log lines are appended from every goroutine (buffer, maintenance, web
|
||||
// handlers) and read by the websocket handler.
|
||||
var logMu sync.Mutex
|
||||
|
||||
// logAppend : Appends a timestamped line to the in-memory log and trims it
|
||||
// to Settings.LogEntriesRAM.
|
||||
func logAppend(logMsg string) {
|
||||
|
||||
logMu.Lock()
|
||||
defer logMu.Unlock()
|
||||
|
||||
WebScreenLog.Log = append(WebScreenLog.Log, time.Now().Format("2006-01-02 15:04:05")+" "+logMsg)
|
||||
logCleanUpLocked()
|
||||
}
|
||||
|
||||
// logAppendCounted : Appends a warning / error line and bumps the matching
|
||||
// counter. Like the original code this does not trim the log; the next
|
||||
// info / debug line does.
|
||||
func logAppendCounted(logMsg string, isError bool) {
|
||||
|
||||
logMu.Lock()
|
||||
defer logMu.Unlock()
|
||||
|
||||
WebScreenLog.Log = append(WebScreenLog.Log, time.Now().Format("2006-01-02 15:04:05")+" "+logMsg)
|
||||
|
||||
if isError {
|
||||
WebScreenLog.Errors++
|
||||
} else {
|
||||
WebScreenLog.Warnings++
|
||||
}
|
||||
}
|
||||
|
||||
// resetWebScreenLog : Clears the in-memory log and its counters.
|
||||
func resetWebScreenLog() {
|
||||
|
||||
logMu.Lock()
|
||||
defer logMu.Unlock()
|
||||
|
||||
WebScreenLog.Log = make([]string, 0)
|
||||
WebScreenLog.Errors = 0
|
||||
WebScreenLog.Warnings = 0
|
||||
}
|
||||
|
||||
// webScreenLogSnapshot : Copy of the log for the web client. The slice is
|
||||
// copied so the JSON encoder never races with a concurrent append.
|
||||
func webScreenLogSnapshot() (snapshot WebScreenLogStruct) {
|
||||
|
||||
logMu.Lock()
|
||||
defer logMu.Unlock()
|
||||
|
||||
snapshot = WebScreenLog
|
||||
snapshot.Log = make([]string, len(WebScreenLog.Log))
|
||||
copy(snapshot.Log, WebScreenLog.Log)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// notificationsSnapshot : Copy of the notifications map for the web client.
|
||||
func notificationsSnapshot() (snapshot map[string]Notification) {
|
||||
|
||||
logMu.Lock()
|
||||
defer logMu.Unlock()
|
||||
|
||||
if System.Notification == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
snapshot = make(map[string]Notification, len(System.Notification))
|
||||
for k, v := range System.Notification {
|
||||
snapshot[k] = v
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func showInfo(str string) {
|
||||
|
||||
if System.Flag.Info {
|
||||
@@ -33,9 +109,7 @@ func showInfo(str string) {
|
||||
|
||||
printLogOnScreen(logMsg, "info")
|
||||
|
||||
logMsg = strings.Replace(logMsg, " ", " ", -1)
|
||||
WebScreenLog.Log = append(WebScreenLog.Log, time.Now().Format("2006-01-02 15:04:05")+" "+logMsg)
|
||||
logCleanUp()
|
||||
logAppend(strings.Replace(logMsg, " ", " ", -1))
|
||||
|
||||
}
|
||||
|
||||
@@ -51,7 +125,6 @@ func showDebug(str string, level int) {
|
||||
var msg = strings.SplitN(str, ":", 2)
|
||||
var length = len(msg[0])
|
||||
var space string
|
||||
var mutex = sync.RWMutex{}
|
||||
|
||||
if len(msg) == 2 {
|
||||
|
||||
@@ -64,11 +137,7 @@ func showDebug(str string, level int) {
|
||||
|
||||
printLogOnScreen(logMsg, "debug")
|
||||
|
||||
mutex.Lock()
|
||||
logMsg = strings.Replace(logMsg, " ", " ", -1)
|
||||
WebScreenLog.Log = append(WebScreenLog.Log, time.Now().Format("2006-01-02 15:04:05")+" "+logMsg)
|
||||
logCleanUp()
|
||||
mutex.Unlock()
|
||||
logAppend(strings.Replace(logMsg, " ", " ", -1))
|
||||
|
||||
}
|
||||
|
||||
@@ -99,7 +168,8 @@ func showHighlight(str string) {
|
||||
}
|
||||
|
||||
notification.Type = "info"
|
||||
notification.Message = msg[1]
|
||||
// Messages without a "key:value" prefix are shown as they are.
|
||||
notification.Message = msg[len(msg)-1]
|
||||
|
||||
addNotification(notification)
|
||||
|
||||
@@ -109,31 +179,22 @@ func showWarning(errCode int) {
|
||||
|
||||
var errMsg = getErrMsg(errCode)
|
||||
var logMsg = fmt.Sprintf("[%s] [WARNING] %s", System.Name, errMsg)
|
||||
var mutex = sync.RWMutex{}
|
||||
|
||||
printLogOnScreen(logMsg, "warning")
|
||||
|
||||
mutex.Lock()
|
||||
WebScreenLog.Log = append(WebScreenLog.Log, time.Now().Format("2006-01-02 15:04:05")+" "+logMsg)
|
||||
WebScreenLog.Warnings++
|
||||
mutex.Unlock()
|
||||
logAppendCounted(logMsg, false)
|
||||
|
||||
}
|
||||
|
||||
// ShowError : Zeigt die Fehlermeldungen in der Konsole
|
||||
func ShowError(err error, errCode int) {
|
||||
|
||||
var mutex = sync.RWMutex{}
|
||||
|
||||
var errMsg = getErrMsg(errCode)
|
||||
var logMsg = fmt.Sprintf("[%s] [ERROR] %s (%s) - EC: %d", System.Name, err, errMsg, errCode)
|
||||
|
||||
printLogOnScreen(logMsg, "error")
|
||||
|
||||
mutex.Lock()
|
||||
WebScreenLog.Log = append(WebScreenLog.Log, time.Now().Format("2006-01-02 15:04:05")+" "+logMsg)
|
||||
WebScreenLog.Errors++
|
||||
mutex.Unlock()
|
||||
logAppendCounted(logMsg, true)
|
||||
|
||||
}
|
||||
|
||||
@@ -174,24 +235,17 @@ func printLogOnScreen(logMsg string, logType string) {
|
||||
|
||||
}
|
||||
|
||||
func logCleanUp() {
|
||||
// logCleanUpLocked : Trims the log to the newest Settings.LogEntriesRAM
|
||||
// lines and recounts warnings / errors. The caller must hold logMu.
|
||||
func logCleanUpLocked() {
|
||||
|
||||
WebScreenLog.Log = keepLastEntries(WebScreenLog.Log, Settings.LogEntriesRAM)
|
||||
|
||||
var logEntriesRAM = Settings.LogEntriesRAM
|
||||
var logs = WebScreenLog.Log
|
||||
|
||||
WebScreenLog.Warnings = 0
|
||||
WebScreenLog.Errors = 0
|
||||
|
||||
if len(logs) > logEntriesRAM {
|
||||
|
||||
var tmp = make([]string, 0)
|
||||
for i := len(logs) - logEntriesRAM; i < logEntriesRAM; i++ {
|
||||
tmp = append(tmp, logs[i])
|
||||
}
|
||||
|
||||
logs = tmp
|
||||
}
|
||||
|
||||
for _, log := range logs {
|
||||
|
||||
if strings.Contains(log, "WARNING") {
|
||||
@@ -204,8 +258,25 @@ func logCleanUp() {
|
||||
|
||||
}
|
||||
|
||||
WebScreenLog.Log = logs
|
||||
}
|
||||
|
||||
// keepLastEntries : Returns the last max entries of logs (a copy, so the
|
||||
// old backing array does not keep growing). A max <= 0 keeps nothing, which
|
||||
// matches the original behaviour for an unset LogEntriesRAM.
|
||||
func keepLastEntries(logs []string, max int) []string {
|
||||
|
||||
if max < 0 {
|
||||
max = 0
|
||||
}
|
||||
|
||||
if len(logs) <= max {
|
||||
return logs
|
||||
}
|
||||
|
||||
var tmp = make([]string, max)
|
||||
copy(tmp, logs[len(logs)-max:])
|
||||
|
||||
return tmp
|
||||
}
|
||||
|
||||
// Fehlercodes
|
||||
@@ -386,30 +457,64 @@ func getErrMsg(errCode int) (errMsg string) {
|
||||
return errMsg
|
||||
}
|
||||
|
||||
// maxNotifications : Number of notifications kept for the web interface.
|
||||
const maxNotifications = 10
|
||||
|
||||
func addNotification(notification Notification) (err error) {
|
||||
|
||||
var i int
|
||||
var t = time.Now().UnixNano() / (int64(time.Millisecond) / int64(time.Nanosecond))
|
||||
notification.Time = strconv.FormatInt(t, 10)
|
||||
|
||||
insertNotification(notification)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// insertNotification : Stores the notification under its Time key and evicts
|
||||
// the oldest ones once more than maxNotifications are kept.
|
||||
func insertNotification(notification Notification) {
|
||||
|
||||
notification.New = true
|
||||
|
||||
if len(notification.Headline) == 0 {
|
||||
notification.Headline = strings.ToUpper(notification.Type)
|
||||
}
|
||||
|
||||
logMu.Lock()
|
||||
defer logMu.Unlock()
|
||||
|
||||
if len(System.Notification) == 0 {
|
||||
System.Notification = make(map[string]Notification)
|
||||
}
|
||||
|
||||
System.Notification[notification.Time] = notification
|
||||
|
||||
for key := range System.Notification {
|
||||
for len(System.Notification) > maxNotifications {
|
||||
delete(System.Notification, oldestNotificationKey(System.Notification))
|
||||
}
|
||||
|
||||
if i < len(System.Notification)-10 {
|
||||
delete(System.Notification, key)
|
||||
}
|
||||
|
||||
// oldestNotificationKey : Key of the notification with the smallest
|
||||
// timestamp. Keys are millisecond timestamps as decimal strings; anything
|
||||
// that does not parse sorts before everything that does.
|
||||
func oldestNotificationKey(notifications map[string]Notification) (oldest string) {
|
||||
|
||||
var oldestTime int64
|
||||
var found bool
|
||||
|
||||
for key, n := range notifications {
|
||||
|
||||
t, err := strconv.ParseInt(n.Time, 10, 64)
|
||||
if err != nil {
|
||||
return key
|
||||
}
|
||||
|
||||
i++
|
||||
if !found || t < oldestTime {
|
||||
oldest = key
|
||||
oldestTime = t
|
||||
found = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user