mirror of
https://github.com/safing/portbase
synced 2025-09-02 18:50:14 +00:00
Fix notification locking issues
This commit is contained in:
parent
0a4f97d7fa
commit
e50553951f
2 changed files with 40 additions and 22 deletions
|
@ -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()
|
||||||
|
if n.SelectedActionID != "" {
|
||||||
|
log.Tracef("notifications: user selected action for %s: %s", n.ID, n.SelectedActionID)
|
||||||
go origN.SelectAndExecuteAction(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.
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Add table
Reference in a new issue