Fix notification locking issues

This commit is contained in:
Daniel 2019-03-18 16:30:17 +01:00
parent 0a4f97d7fa
commit e50553951f
2 changed files with 40 additions and 22 deletions

View file

@ -11,6 +11,7 @@ import (
"github.com/Safing/portbase/database/query" "github.com/Safing/portbase/database/query"
"github.com/Safing/portbase/database/record" "github.com/Safing/portbase/database/record"
"github.com/Safing/portbase/database/storage" "github.com/Safing/portbase/database/storage"
"github.com/Safing/portbase/log"
) )
var ( var (
@ -44,8 +45,8 @@ type StorageInterface struct {
// Get returns a database record. // Get returns a database record.
func (s *StorageInterface) Get(key string) (record.Record, error) { func (s *StorageInterface) Get(key string) (record.Record, error) {
notsLock.Lock() notsLock.RLock()
defer notsLock.Unlock() defer notsLock.RUnlock()
// transform key // transform key
if strings.HasPrefix(key, "all/") { if strings.HasPrefix(key, "all/") {
@ -72,8 +73,8 @@ func (s *StorageInterface) Query(q *query.Query, local, internal bool) (*iterato
} }
func (s *StorageInterface) processQuery(q *query.Query, it *iterator.Iterator) { func (s *StorageInterface) processQuery(q *query.Query, it *iterator.Iterator) {
notsLock.Lock() notsLock.RLock()
defer notsLock.Unlock() defer notsLock.RUnlock()
// send all notifications // send all notifications
for _, n := range nots { for _, n := range nots {
@ -107,10 +108,9 @@ func registerAsDatabase() error {
// Put stores a record in the database. // Put stores a record in the database.
func (s *StorageInterface) Put(r record.Record) error { func (s *StorageInterface) Put(r record.Record) error {
r.Lock() // record is already locked!
key := r.DatabaseKey() key := r.DatabaseKey()
n, err := EnsureNotification(r) n, err := EnsureNotification(r)
r.Unlock()
if err != nil { if err != nil {
return ErrInvalidData return ErrInvalidData
@ -123,22 +123,32 @@ func (s *StorageInterface) Put(r record.Record) error {
return ErrInvalidPath return ErrInvalidPath
} }
notsLock.Lock() // continue in goroutine
go updateNotificationFromDatabasePut(n, key)
return nil
}
func updateNotificationFromDatabasePut(n *Notification, key string) {
// seperate goroutine in order to correctly lock notsLock
notsLock.RLock()
origN, ok := nots[key] origN, ok := nots[key]
notsLock.Unlock() notsLock.RUnlock()
if ok { if ok {
// existing notification, update selected action ID only
n.Lock() n.Lock()
defer n.Unlock() defer n.Unlock()
go origN.SelectAndExecuteAction(n.SelectedActionID) if n.SelectedActionID != "" {
log.Tracef("notifications: user selected action for %s: %s", n.ID, n.SelectedActionID)
go origN.SelectAndExecuteAction(n.SelectedActionID)
}
} else { } else {
// accept new notification as is // accept new notification as is
notsLock.Lock() notsLock.Lock()
nots[key] = n nots[key] = n
notsLock.Unlock() notsLock.Unlock()
} }
return nil
} }
// Delete deletes a record from the database. // Delete deletes a record from the database.

View file

@ -53,8 +53,8 @@ func noOpAction(n *Notification) {
// Get returns the notification identifed by the given id or nil if it doesn't exist. // Get returns the notification identifed by the given id or nil if it doesn't exist.
func Get(id string) *Notification { func Get(id string) *Notification {
notsLock.Lock() notsLock.RLock()
defer notsLock.Unlock() defer notsLock.RUnlock()
n, ok := nots[id] n, ok := nots[id]
if ok { if ok {
return n return n
@ -62,13 +62,13 @@ func Get(id string) *Notification {
return nil return nil
} }
// New returns a new Notification // Init initializes a Notification and returns it.
func (n *Notification) Init() *Notification { func (n *Notification) Init() *Notification {
n.Created = time.Now().Unix() n.Created = time.Now().Unix()
return n return n
} }
// Save saves the notification // Save saves the notification and returns it.
func (n *Notification) Save() *Notification { func (n *Notification) Save() *Notification {
notsLock.Lock() notsLock.Lock()
defer notsLock.Unlock() defer notsLock.Unlock()
@ -119,16 +119,16 @@ func (n *Notification) Save() *Notification {
// SetActionFunction sets a trigger function to be executed when the user reacted on the notification. // SetActionFunction sets a trigger function to be executed when the user reacted on the notification.
// The provided funtion will be started as its own goroutine and will have to lock everything it accesses, even the provided notification. // The provided funtion will be started as its own goroutine and will have to lock everything it accesses, even the provided notification.
func (n *Notification) SetActionFunction(fn func(*Notification)) *Notification { func (n *Notification) SetActionFunction(fn func(*Notification)) *Notification {
n.Lock() n.lock.Lock()
defer n.Unlock() defer n.lock.Unlock()
n.actionFunction = fn n.actionFunction = fn
return n return n
} }
// MakeAck sets a default "OK" action and a no-op action function. // MakeAck sets a default "OK" action and a no-op action function.
func (n *Notification) MakeAck() *Notification { func (n *Notification) MakeAck() *Notification {
n.Lock() n.lock.Lock()
defer n.Unlock() defer n.lock.Unlock()
n.AvailableActions = []*Action{ n.AvailableActions = []*Action{
&Action{ &Action{
@ -144,8 +144,8 @@ func (n *Notification) MakeAck() *Notification {
// Response waits for the user to respond to the notification and returns the selected action. // Response waits for the user to respond to the notification and returns the selected action.
func (n *Notification) Response() <-chan string { func (n *Notification) Response() <-chan string {
n.Lock() n.lock.Lock()
defer n.Unlock() defer n.lock.Unlock()
if n.actionTrigger == nil { if n.actionTrigger == nil {
n.actionTrigger = make(chan string) n.actionTrigger = make(chan string)
@ -190,12 +190,13 @@ func (n *Notification) SelectAndExecuteAction(id string) {
} }
if n.actionTrigger != nil { if n.actionTrigger != nil {
// satisfy all listeners // satisfy all listeners
triggerAll:
for { for {
select { select {
case n.actionTrigger <- n.SelectedActionID: case n.actionTrigger <- n.SelectedActionID:
executed = true executed = true
default: default:
break break triggerAll
} }
} }
} }
@ -208,6 +209,13 @@ func (n *Notification) SelectAndExecuteAction(id string) {
go n.Save() go n.Save()
} }
// AddDataSubject adds the data subject to the notification. This is the only way how a data subject should be added - it avoids locking problems.
func (n *Notification) AddDataSubject(ds sync.Locker) {
n.lock.Lock()
defer n.lock.Unlock()
n.DataSubject = ds
}
// Lock locks the Notification and the DataSubject, if available. // Lock locks the Notification and the DataSubject, if available.
func (n *Notification) Lock() { func (n *Notification) Lock() {
n.lock.Lock() n.lock.Lock()