Unbreak more things

This commit is contained in:
Tulir Asokan 2019-06-15 17:04:08 +03:00
parent a55ea42d7f
commit 160b035c4d
10 changed files with 218 additions and 56 deletions

1
.gitignore vendored
View File

@ -7,3 +7,4 @@ gomuks
coverage.out coverage.out
coverage.html coverage.html
deb/usr deb/usr
*.prof

View File

@ -58,8 +58,6 @@ func NewGomuks(uiProvider ifc.UIProvider, configDir, cacheDir string) *Gomuks {
// Save saves the active session and message history. // Save saves the active session and message history.
func (gmx *Gomuks) Save() { func (gmx *Gomuks) Save() {
gmx.config.SaveAll() gmx.config.SaveAll()
//debug.Print("Saving history...")
//gmx.ui.MainView().SaveAllHistory()
} }
// StartAutosave calls Save() every minute until it receives a stop signal // StartAutosave calls Save() every minute until it receives a stop signal
@ -70,7 +68,9 @@ func (gmx *Gomuks) StartAutosave() {
for { for {
select { select {
case <-ticker.C: case <-ticker.C:
if gmx.config.AuthCache.InitialSyncDone {
gmx.Save() gmx.Save()
}
case val := <-gmx.stop: case val := <-gmx.stop:
if val { if val {
return return

View File

@ -221,7 +221,15 @@ func (c *Container) OnLogin() {
c.syncer.InitDoneCallback = func() { c.syncer.InitDoneCallback = func() {
debug.Print("Initial sync done") debug.Print("Initial sync done")
c.config.AuthCache.InitialSyncDone = true c.config.AuthCache.InitialSyncDone = true
c.config.SaveAuthCache() debug.Print("Updating title caches")
for _, room := range c.config.Rooms.Map {
room.GetTitle()
}
debug.Print("Cleaning cached rooms from memory")
c.config.Rooms.ForceClean()
debug.Print("Saving all data")
c.config.SaveAll()
debug.Print("Adding rooms to UI")
c.ui.MainView().SetRooms(c.config.Rooms) c.ui.MainView().SetRooms(c.config.Rooms)
c.ui.Render() c.ui.Render()
} }
@ -294,17 +302,21 @@ func (c *Container) SendPreferencesToMatrix() {
// HandleMessage is the event handler for the m.room.message timeline event. // HandleMessage is the event handler for the m.room.message timeline event.
func (c *Container) HandleMessage(source EventSource, evt *mautrix.Event) { func (c *Container) HandleMessage(source EventSource, evt *mautrix.Event) {
if source&EventSourceLeave != 0 || source&EventSourceState != 0 { room := c.GetOrCreateRoom(evt.RoomID)
if source&EventSourceLeave != 0 {
room.HasLeft = true
return
} else if source&EventSourceState != 0 {
return return
} }
room := c.GetOrCreateRoom(evt.RoomID)
err := c.history.Append(room, []*mautrix.Event{evt}) err := c.history.Append(room, []*mautrix.Event{evt})
if err != nil { if err != nil {
debug.Printf("Failed to add event %s to history: %v", evt.ID, err) debug.Printf("Failed to add event %s to history: %v", evt.ID, err)
} }
if !c.config.AuthCache.InitialSyncDone { if !c.config.AuthCache.InitialSyncDone || !room.Loaded() {
room.LastReceivedMessage = time.Unix(evt.Timestamp/1000, evt.Timestamp%1000*1000)
return return
} }
@ -327,7 +339,7 @@ func (c *Container) HandleMessage(source EventSource, evt *mautrix.Event) {
c.ui.Render() c.ui.Render()
} }
} else { } else {
debug.Printf("Parsing event %s type %s %v from %s in %s failed (ParseEvent() returned nil).", evt.ID, evt.Type, evt.Content.Raw, evt.Sender, evt.RoomID) debug.Printf("Parsing event %s type %s %v from %s in %s failed (ParseEvent() returned nil).", evt.ID, evt.Type.String(), evt.Content.Raw, evt.Sender, evt.RoomID)
} }
} }
@ -335,6 +347,9 @@ func (c *Container) HandleMessage(source EventSource, evt *mautrix.Event) {
func (c *Container) HandleMembership(source EventSource, evt *mautrix.Event) { func (c *Container) HandleMembership(source EventSource, evt *mautrix.Event) {
isLeave := source&EventSourceLeave != 0 isLeave := source&EventSourceLeave != 0
isTimeline := source&EventSourceTimeline != 0 isTimeline := source&EventSourceTimeline != 0
if isLeave {
c.GetOrCreateRoom(evt.RoomID).HasLeft = true
}
isNonTimelineLeave := isLeave && !isTimeline isNonTimelineLeave := isLeave && !isTimeline
if !c.config.AuthCache.InitialSyncDone && isNonTimelineLeave { if !c.config.AuthCache.InitialSyncDone && isNonTimelineLeave {
return return
@ -437,7 +452,7 @@ func (c *Container) parseDirectChatInfo(evt *mautrix.Event) map[*rooms.Room]bool
continue continue
} }
room := c.GetRoom(roomID) room := c.GetOrCreateRoom(roomID)
if room != nil && !room.HasLeft { if room != nil && !room.HasLeft {
directChats[room] = true directChats[room] = true
} }
@ -473,7 +488,7 @@ func (c *Container) HandlePushRules(source EventSource, evt *mautrix.Event) {
// HandleTag is the event handler for the m.tag account data event. // HandleTag is the event handler for the m.tag account data event.
func (c *Container) HandleTag(source EventSource, evt *mautrix.Event) { func (c *Container) HandleTag(source EventSource, evt *mautrix.Event) {
room := c.config.GetRoom(evt.RoomID) room := c.GetOrCreateRoom(evt.RoomID)
newTags := make([]rooms.RoomTag, len(evt.Content.RoomTags)) newTags := make([]rooms.RoomTag, len(evt.Content.RoomTags))
index := 0 index := 0

View File

@ -115,6 +115,12 @@ type Room struct {
cache *RoomCache cache *RoomCache
// Lock for state and other room stuff. // Lock for state and other room stuff.
lock sync.RWMutex lock sync.RWMutex
// Function to call when room state is unloaded.
onUnload func() bool
// Function to call when room state is loaded.
onLoad func() bool
// Whether or not the room state has changed
changed bool
// Room state cache linked list. // Room state cache linked list.
prev *Room prev *Room
@ -133,10 +139,13 @@ func (room *Room) Loaded() bool {
} }
func (room *Room) Load() { func (room *Room) Load() {
room.cache.TouchNode(room)
if room.Loaded() { if room.Loaded() {
return return
} }
room.cache.TouchNode(room) if room.onLoad != nil && !room.onLoad() {
return
}
room.lock.Lock() room.lock.Lock()
room.load() room.load()
room.lock.Unlock() room.lock.Unlock()
@ -146,7 +155,7 @@ func (room *Room) load() {
if room.Loaded() { if room.Loaded() {
return return
} }
debug.Print("Loading state for room", room.ID) debug.Print("Loading state for room", room.ID, "from disk")
room.state = make(map[mautrix.EventType]map[string]*mautrix.Event) room.state = make(map[mautrix.EventType]map[string]*mautrix.Event)
file, err := os.OpenFile(room.path, os.O_RDONLY, 0600) file, err := os.OpenFile(room.path, os.O_RDONLY, 0600)
if err != nil { if err != nil {
@ -168,9 +177,17 @@ func (room *Room) load() {
if err = dec.Decode(&room.state); err != nil { if err = dec.Decode(&room.state); err != nil {
debug.Print("Failed to decode room state:", err) debug.Print("Failed to decode room state:", err)
} }
room.changed = false
} }
func (room *Room) Unload() { func (room *Room) Touch() {
room.cache.TouchNode(room)
}
func (room *Room) Unload() bool {
if room.onUnload != nil && !room.onUnload() {
return false
}
debug.Print("Unloading", room.ID) debug.Print("Unloading", room.ID)
room.Save() room.Save()
room.state = nil room.state = nil
@ -179,14 +196,27 @@ func (room *Room) Unload() {
room.canonicalAliasCache = "" room.canonicalAliasCache = ""
room.firstMemberCache = nil room.firstMemberCache = nil
room.secondMemberCache = nil room.secondMemberCache = nil
return true
}
func (room *Room) SetOnUnload(fn func() bool) {
room.onUnload = fn
}
func (room *Room) SetOnLoad(fn func() bool) {
room.onLoad = fn
} }
func (room *Room) Save() { func (room *Room) Save() {
if !room.Loaded() { if !room.Loaded() {
debug.Print("Failed to save room state: room not loaded") debug.Print("Failed to save room", room.ID, "state: room not loaded")
return return
} }
debug.Print("Saving state for room", room.ID) if !room.changed {
debug.Print("Not saving", room.ID, "as state hasn't changed")
return
}
debug.Print("Saving state for room", room.ID, "to disk")
file, err := os.OpenFile(room.path, os.O_WRONLY|os.O_CREATE, 0600) file, err := os.OpenFile(room.path, os.O_WRONLY|os.O_CREATE, 0600)
if err != nil { if err != nil {
debug.Print("Failed to open room state file for writing:", err) debug.Print("Failed to open room state file for writing:", err)
@ -298,40 +328,51 @@ func (room *Room) UpdateState(event *mautrix.Event) {
room.Load() room.Load()
room.lock.Lock() room.lock.Lock()
defer room.lock.Unlock() defer room.lock.Unlock()
room.changed = true
_, exists := room.state[event.Type] _, exists := room.state[event.Type]
if !exists { if !exists {
room.state[event.Type] = make(map[string]*mautrix.Event) room.state[event.Type] = make(map[string]*mautrix.Event)
} }
switch event.Type { switch event.Type {
case mautrix.StateRoomName: case mautrix.StateRoomName:
room.NameCache = "" room.NameCache = event.Content.Name
room.nameCacheSource = ExplicitRoomName
case mautrix.StateCanonicalAlias: case mautrix.StateCanonicalAlias:
if room.nameCacheSource <= CanonicalAliasRoomName { if room.nameCacheSource <= CanonicalAliasRoomName {
room.NameCache = "" room.NameCache = event.Content.Alias
room.nameCacheSource = CanonicalAliasRoomName
} }
room.canonicalAliasCache = "" room.canonicalAliasCache = event.Content.Alias
case mautrix.StateAliases: case mautrix.StateAliases:
if room.nameCacheSource <= AliasRoomName { if room.nameCacheSource <= AliasRoomName {
room.NameCache = "" room.NameCache = ""
} }
room.aliasesCache = nil room.aliasesCache = nil
case mautrix.StateMember: case mautrix.StateMember:
room.memberCache = nil if room.memberCache != nil {
room.firstMemberCache = nil userID := event.GetStateKey()
room.secondMemberCache = nil if event.Content.Membership == mautrix.MembershipLeave || event.Content.Membership == mautrix.MembershipBan {
delete(room.memberCache, userID)
} else if event.Content.Membership == mautrix.MembershipInvite || event.Content.Membership == mautrix.MembershipJoin {
member := room.eventToMember(userID, &event.Content)
existingMember, ok := room.memberCache[userID]
if ok {
*existingMember = *member
} else {
room.memberCache[userID] = member
room.updateNthMemberCache(userID, member)
}
}
}
if room.nameCacheSource <= MemberRoomName { if room.nameCacheSource <= MemberRoomName {
room.NameCache = "" room.NameCache = ""
} }
case mautrix.StateTopic: case mautrix.StateTopic:
room.topicCache = "" room.topicCache = event.Content.Topic
} }
stateKey := ""
if event.StateKey != nil {
stateKey = *event.StateKey
}
if event.Type != mautrix.StateMember { if event.Type != mautrix.StateMember {
debug.Printf("Updating state %s#%s for %s", event.Type, stateKey, room.ID) debug.Printf("Updating state %s#%s for %s", event.Type.String(), event.GetStateKey(), room.ID)
} }
if event.StateKey == nil { if event.StateKey == nil {
@ -477,6 +518,25 @@ func (room *Room) GetTitle() string {
return room.NameCache return room.NameCache
} }
func (room *Room) eventToMember(userID string, content *mautrix.Content) *mautrix.Member {
member := &content.Member
member.Membership = content.Membership
if len(member.Displayname) == 0 {
member.Displayname = userID
}
return member
}
func (room *Room) updateNthMemberCache(userID string, member *mautrix.Member) {
if userID != room.SessionUserID {
if room.firstMemberCache == nil {
room.firstMemberCache = member
} else if room.secondMemberCache == nil {
room.secondMemberCache = member
}
}
}
// createMemberCache caches all member events into a easily processable MXID -> *Member map. // createMemberCache caches all member events into a easily processable MXID -> *Member map.
func (room *Room) createMemberCache() map[string]*mautrix.Member { func (room *Room) createMemberCache() map[string]*mautrix.Member {
if len(room.memberCache) > 0 { if len(room.memberCache) > 0 {
@ -489,20 +549,10 @@ func (room *Room) createMemberCache() map[string]*mautrix.Member {
room.secondMemberCache = nil room.secondMemberCache = nil
if events != nil { if events != nil {
for userID, event := range events { for userID, event := range events {
member := &event.Content.Member member := room.eventToMember(userID, &event.Content)
member.Membership = event.Content.Membership
if len(member.Displayname) == 0 {
member.Displayname = userID
}
if userID != room.SessionUserID {
if room.firstMemberCache == nil {
room.firstMemberCache = member
} else if room.secondMemberCache == nil {
room.secondMemberCache = member
}
}
if member.Membership == mautrix.MembershipJoin || member.Membership == mautrix.MembershipInvite { if member.Membership == mautrix.MembershipJoin || member.Membership == mautrix.MembershipInvite {
cache[userID] = member cache[userID] = member
room.updateNthMemberCache(userID, member)
} }
} }
} }

View File

@ -106,7 +106,7 @@ func (cache *RoomCache) LoadList() error {
func (cache *RoomCache) SaveLoadedRooms() { func (cache *RoomCache) SaveLoadedRooms() {
cache.Lock() cache.Lock()
defer cache.Unlock() defer cache.Unlock()
cache.clean() cache.clean(false)
for node := cache.head; node != nil; node = node.prev { for node := cache.head; node != nil; node = node.prev {
node.Save() node.Save()
} }
@ -194,8 +194,7 @@ func (cache *RoomCache) GetOrCreate(roomID string) *Room {
func (cache *RoomCache) get(roomID string) *Room { func (cache *RoomCache) get(roomID string) *Room {
node, ok := cache.Map[roomID] node, ok := cache.Map[roomID]
if ok && node != nil && node.Loaded() { if ok && node != nil {
cache.touch(node)
return node return node
} }
return nil return nil
@ -273,18 +272,29 @@ func (cache *RoomCache) llPush(node *Room) {
cache.tail = node cache.tail = node
} }
cache.size++ cache.size++
cache.clean() cache.clean(false)
} }
func (cache *RoomCache) clean() { func (cache *RoomCache) ForceClean() {
cache.Lock()
cache.clean(true)
cache.Unlock()
}
func (cache *RoomCache) clean(force bool) {
origSize := cache.size origSize := cache.size
maxTS := time.Now().Unix() - cache.maxAge maxTS := time.Now().Unix() - cache.maxAge
for cache.size > cache.maxSize { for cache.size > cache.maxSize {
if cache.tail.touch > maxTS { if cache.tail.touch > maxTS && !force {
break break
} }
cache.tail.Unload() ok := cache.tail.Unload()
cache.llPop(cache.tail) node := cache.tail
cache.llPop(node)
if !ok {
debug.Print("Unload returned false, pushing node back")
cache.llPush(node)
}
} }
if cleaned := origSize - cache.size; cleaned > 0 { if cleaned := origSize - cache.size; cleaned > 0 {
debug.Print("Cleaned", cleaned, "rooms") debug.Print("Cleaned", cleaned, "rooms")
@ -295,7 +305,11 @@ func (cache *RoomCache) Unload(node *Room) {
cache.Lock() cache.Lock()
defer cache.Unlock() defer cache.Unlock()
cache.llPop(node) cache.llPop(node)
node.Unload() ok := node.Unload()
if !ok {
debug.Print("Unload returned false, pushing node back")
cache.llPush(node)
}
} }
func (cache *RoomCache) newRoom(roomID string) *Room { func (cache *RoomCache) newRoom(roomID string) *Room {

View File

@ -108,6 +108,7 @@ func NewCommandProcessor(parent *MainView) *CommandProcessor {
"rainbow": cmdRainbow, "rainbow": cmdRainbow,
"invite": cmdInvite, "invite": cmdInvite,
"hprof": cmdHeapProfile, "hprof": cmdHeapProfile,
"cprof": cmdCPUProfile,
}, },
} }
} }

View File

@ -21,8 +21,11 @@ import (
"fmt" "fmt"
"os" "os"
"runtime" "runtime"
dbg "runtime/debug"
"runtime/pprof" "runtime/pprof"
"strconv"
"strings" "strings"
"time"
"unicode" "unicode"
"github.com/lucasb-eyer/go-colorful" "github.com/lucasb-eyer/go-colorful"
@ -71,17 +74,58 @@ var rainbow = GradientTable{
} }
func cmdHeapProfile(cmd *Command) { func cmdHeapProfile(cmd *Command) {
dbg.FreeOSMemory()
runtime.GC() runtime.GC()
memProfile, err := os.Create("gomuks.prof") memProfile, err := os.Create("gomuks.heap.prof")
if err != nil { if err != nil {
debug.Print(err) debug.Print("Failed to open gomuks.heap.prof:", err)
return
} }
defer memProfile.Close() defer func() {
err := memProfile.Close()
if err != nil {
debug.Print("Failed to close gomuks.heap.prof:", err)
}
}()
if err := pprof.WriteHeapProfile(memProfile); err != nil { if err := pprof.WriteHeapProfile(memProfile); err != nil {
debug.Print(err) debug.Print("Heap profile error:", err)
} }
} }
func cmdCPUProfile(cmd *Command) {
if len(cmd.Args) == 0 {
cmd.Reply("Usage: /cprof <seconds>")
return
}
dur, err := strconv.Atoi(cmd.Args[0])
if err != nil || dur < 0 {
cmd.Reply("Usage: /cprof <seconds>")
return
}
cpuProfile, err := os.Create("gomuks.cpu.prof")
if err != nil {
debug.Print("Failed to open gomuks.cpu.prof:", err)
return
}
err = pprof.StartCPUProfile(cpuProfile)
if err != nil {
_ = cpuProfile.Close()
debug.Print("CPU profile error:", err)
return
}
cmd.Reply("Started CPU profiling for %d seconds", dur)
go func() {
time.Sleep(time.Duration(dur) * time.Second)
pprof.StopCPUProfile()
cmd.Reply("CPU profiling finished.")
err := cpuProfile.Close()
if err != nil {
debug.Print("Failed to close gomuks.cpu.prof:", err)
}
}()
}
// TODO this command definitely belongs in a plugin once we have a plugin system. // TODO this command definitely belongs in a plugin once we have a plugin system.
func cmdRainbow(cmd *Command) { func cmdRainbow(cmd *Command) {
text := strings.Join(cmd.Args, " ") text := strings.Join(cmd.Args, " ")

View File

@ -89,6 +89,23 @@ func NewMessageView(parent *RoomView) *MessageView {
} }
} }
func (view *MessageView) Unload() {
debug.Print("Unloading message view", view.parent.Room.ID)
view.messagesLock.Lock()
view.msgBufferLock.Lock()
view.messageIDLock.Lock()
view.messageIDs = make(map[string]*messages.UIMessage)
view.msgBuffer = make([]*messages.UIMessage, 0)
view.messages = make([]*messages.UIMessage, 0)
view.initialHistoryLoaded = false
view.ScrollOffset = 0
view._widestSender = 5
view.prevMsgCount = -1
view.messagesLock.Unlock()
view.msgBufferLock.Unlock()
view.messageIDLock.Unlock()
}
func (view *MessageView) updateWidestSender(sender string) { func (view *MessageView) updateWidestSender(sender string) {
if len(sender) > int(view._widestSender) { if len(sender) > int(view._widestSender) {
if len(sender) > view.MaxSenderWidth { if len(sender) > view.MaxSenderWidth {

View File

@ -423,8 +423,10 @@ func (list *RoomList) OnMouseEvent(event mauview.MouseEvent) bool {
switch event.Buttons() { switch event.Buttons() {
case tcell.WheelUp: case tcell.WheelUp:
list.AddScrollOffset(-WheelScrollOffsetDiff) list.AddScrollOffset(-WheelScrollOffsetDiff)
return true
case tcell.WheelDown: case tcell.WheelDown:
list.AddScrollOffset(WheelScrollOffsetDiff) list.AddScrollOffset(WheelScrollOffsetDiff)
return true
case tcell.Button1: case tcell.Button1:
x, y := event.Position() x, y := event.Position()
return list.clickRoom(y, x, event.Modifiers() == tcell.ModCtrl) return list.clickRoom(y, x, event.Modifiers() == tcell.ModCtrl)
@ -486,7 +488,8 @@ func (list *RoomList) clickRoom(line, column int, mod bool) bool {
if trl.maxShown < 10 { if trl.maxShown < 10 {
trl.maxShown = 10 trl.maxShown = 10
} }
break list.RUnlock()
return true
} }
} }
// Tag footer // Tag footer

View File

@ -93,6 +93,17 @@ func NewRoomView(parent *MainView, room *rooms.Room) *RoomView {
config: parent.config, config: parent.config,
} }
view.content = NewMessageView(view) view.content = NewMessageView(view)
view.Room.SetOnUnload(func() bool {
if view.parent.currentRoom == view {
return false
}
view.content.Unload()
return true
})
view.Room.SetOnLoad(func() bool {
view.loadTyping()
return true
})
view.input. view.input.
SetBackgroundColor(tcell.ColorDefault). SetBackgroundColor(tcell.ColorDefault).
@ -270,14 +281,20 @@ func (view *RoomView) SetCompletions(completions []string) {
view.completions.time = time.Now() view.completions.time = time.Now()
} }
func (view *RoomView) SetTyping(users []string) { func (view *RoomView) loadTyping() {
for index, user := range users { for index, user := range view.typing {
member := view.Room.GetMember(user) member := view.Room.GetMember(user)
if member != nil { if member != nil {
users[index] = member.Displayname view.typing[index] = member.Displayname
} }
} }
}
func (view *RoomView) SetTyping(users []string) {
view.typing = users view.typing = users
if view.Room.Loaded() {
view.loadTyping()
}
} }
type completion struct { type completion struct {