diff --git a/group/group.go b/group/group.go index dd1d4ad..9e45b3f 100644 --- a/group/group.go +++ b/group/group.go @@ -97,11 +97,10 @@ type Group struct { mu sync.Mutex description *description - // indicates that the group no longer exists, but it still has clients - dead bool - locked *string - clients map[string]Client - history []ChatHistoryEntry + locked *string + clients map[string]Client + history []ChatHistoryEntry + timestamp time.Time } func (g *Group) Name() string { @@ -201,6 +200,7 @@ func Add(name string, desc *description) (*Group, error) { name: name, description: desc, clients: make(map[string]Client), + timestamp: time.Now(), } groups.groups[name] = g return g, nil @@ -211,11 +211,10 @@ func Add(name string, desc *description) (*Group, error) { if desc != nil { g.description = desc - g.dead = false return g, nil } - if g.dead || time.Since(g.description.loadTime) > 5*time.Second { + if time.Since(g.description.loadTime) > 5*time.Second { if descriptionChanged(name, g.description) { desc, err := GetDescription(name) if err != nil { @@ -223,11 +222,9 @@ func Add(name string, desc *description) (*Group, error) { log.Printf("Reading group %v: %v", name, err) } - g.dead = true - delGroupUnlocked(name) + deleteUnlocked(g) return nil, err } - g.dead = false g.description = desc } else { g.description.loadTime = time.Now() @@ -266,20 +263,57 @@ func Get(name string) *Group { return groups.groups[name] } -func delGroupUnlocked(name string) bool { +func Delete(name string) bool { + groups.mu.Lock() + defer groups.mu.Unlock() g := groups.groups[name] if g == nil { - return true + return false; } + g.mu.Lock() + defer g.mu.Unlock() + return deleteUnlocked(g) +} + +// Called with both groups.mu and g.mu taken. +func deleteUnlocked(g *Group) bool { if len(g.clients) != 0 { return false } - delete(groups.groups, name) + delete(groups.groups, g.name) return true } +func Expire() { + names := GetNames() + now := time.Now() + + for _, name := range names { + g := Get(name) + if g == nil { + continue + } + + old := false + + g.mu.Lock() + empty := len(g.clients) == 0 + if empty && !g.description.Public { + age := now.Sub(g.timestamp) + old = age > maxHistoryAge(g.description) + } + // We cannot take groups.mu at this point without a deadlock. + g.mu.Unlock() + + if empty && old { + // Delete will check if the group is still empty + Delete(name) + } + } +} + func AddClient(group string, c Client) (*Group, error) { g, err := Add(group, nil) if err != nil { @@ -317,6 +351,7 @@ func AddClient(group string, c Client) (*Group, error) { } g.clients[c.Id()] = c + g.timestamp = time.Now() go func(clients []Client) { u := c.Username() @@ -344,6 +379,7 @@ func DelClient(c Client) { return } delete(g.clients, c.Id()) + g.timestamp = time.Now() go func(clients []Client) { for _, cc := range clients { @@ -437,16 +473,10 @@ func (g *Group) AddToChatHistory(id, user string, time int64, kind, value string ) } -func discardObsoleteHistory(h []ChatHistoryEntry, seconds int) []ChatHistoryEntry { - now := time.Now() - d := 4 * time.Hour - if seconds > 0 { - d = time.Duration(seconds) * time.Second - } - +func discardObsoleteHistory(h []ChatHistoryEntry, duration time.Duration) []ChatHistoryEntry { i := 0 for i < len(h) { - if now.Sub(FromJSTime(h[i].Time)) <= d { + if time.Since(FromJSTime(h[i].Time)) <= duration { break } i++ @@ -462,7 +492,9 @@ func (g *Group) GetChatHistory() []ChatHistoryEntry { g.mu.Lock() defer g.mu.Unlock() - g.history = discardObsoleteHistory(g.history, g.description.MaxHistoryAge) + g.history = discardObsoleteHistory( + g.history, maxHistoryAge(g.description), + ) h := make([]ChatHistoryEntry, len(g.history)) copy(h, g.history) @@ -504,6 +536,15 @@ type description struct { Other []ClientCredentials `json:"other,omitempty"` } +const DefaultMaxHistoryAge = 4 * time.Hour + +func maxHistoryAge(desc *description) time.Duration { + if desc.MaxHistoryAge != 0 { + return time.Duration(desc.MaxHistoryAge) * time.Second + } + return DefaultMaxHistoryAge +} + func openDescriptionFile(name string) (*os.File, string, bool, error) { isParent := false for name != "" { diff --git a/sfu.go b/sfu.go index 09ad4ca..33bbd6d 100644 --- a/sfu.go +++ b/sfu.go @@ -14,6 +14,7 @@ import ( "runtime" "runtime/pprof" "syscall" + "time" "sfu/diskwriter" "sfu/group" @@ -94,10 +95,19 @@ func main() { terminate := make(chan os.Signal, 1) signal.Notify(terminate, syscall.SIGINT, syscall.SIGTERM) - select { - case <-terminate: - webserver.Shutdown() - case <-serverDone: - os.Exit(1) + + ticker := time.NewTicker(15 * time.Minute) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + go group.Expire() + case <-terminate: + webserver.Shutdown() + return + case <-serverDone: + os.Exit(1) + } } }