package anidb
-import "sync"
+import (
+ "github.com/Kovensky/go-fscache"
+ "strings"
+ "sync"
+)
+
+type notification interface{}
type intentStruct struct {
sync.Mutex
- chs []chan Cacheable
+ chs []chan notification
}
type intentMapStruct struct {
sync.Mutex
+ intentLock sync.Mutex // used by the Intent function
+
m map[string]*intentStruct
}
m: map[string]*intentStruct{},
}
+func intentKey(key ...fscache.CacheKey) string {
+ return strings.Join(fscache.Stringify(key...), "-")
+}
+
// Register a channel to be notified when the specified keys are notified.
+// Returns whether the caller was the first to register intent for the given
+// keys.
//
// Cache checks should be done after registering intent, since it's possible to
// register Intent while a Notify is running, and the Notify is done after
// setting the cache.
-func (m *intentMapStruct) Intent(ch chan Cacheable, keys ...cacheKey) bool {
- key := cachePath(keys...)
+func (m *intentMapStruct) Intent(ch chan notification, keys ...fscache.CacheKey) bool {
+ key := intentKey(keys...)
+
+ m.intentLock.Lock()
+ defer m.intentLock.Unlock()
m.Lock()
s, ok := m.m[key]
if !ok {
s = &intentStruct{}
- m.m[key] = s
}
m.Unlock()
s.chs = append(s.chs, ch)
s.Unlock()
+ m.Lock()
+ // key might have been deleted while only the struct itself was locked -- recheck
+ _, ok = m.m[key]
+ m.m[key] = s
+ m.Unlock()
+
return ok
}
-// Notify all channels that are listening for the specified keys.
+// Locks the requested keys and return the locked intentStruct.
//
-// Should be called after setting the cache.
-func (m *intentMapStruct) Notify(v Cacheable, keys ...cacheKey) {
- key := cachePath(keys...)
-
+// The intentStruct can be directly unlocked, or given to Free to also
+// remove it from the intent map.
+func (m *intentMapStruct) LockIntent(keys ...fscache.CacheKey) *intentStruct {
m.Lock()
defer m.Unlock()
- s, ok := m.m[key]
+
+ return m._lockIntent(keys...)
+}
+
+func (m *intentMapStruct) _lockIntent(keys ...fscache.CacheKey) *intentStruct {
+ s, ok := m.m[intentKey(keys...)]
if !ok {
- return
+ return nil
}
s.Lock()
- defer s.Unlock()
+ return s
+}
+
+// Removes the given intent from the intent map and unlocks the intentStruct.
+func (m *intentMapStruct) Free(is *intentStruct, keys ...fscache.CacheKey) {
+ m.Lock()
+ defer m.Unlock()
+ m._free(is, keys...)
+}
+
+func (m *intentMapStruct) _free(is *intentStruct, keys ...fscache.CacheKey) {
+ // deletes the key before unlocking, Intent needs to recheck key status
+ delete(m.m, intentKey(keys...))
+ // better than unlocking then deleting -- could delete a "brand new" entry
+ if is != nil {
+ is.Unlock()
+ }
+}
+
+// Notifies and closes all channels that are listening for the specified keys;
+// also removes them from the intent map.
+//
+// Should be called after setting the cache.
+func (m *intentMapStruct) NotifyClose(v notification, keys ...fscache.CacheKey) {
+ m.Lock()
+ defer m.Unlock()
+
+ is := m._lockIntent(keys...)
+ defer m._free(is, keys...)
+
+ if is != nil {
+ is.NotifyClose(v)
+ }
+}
+
+// Closes all channels that are listening for the specified keys
+// and removes them from the intent map.
+func (m *intentMapStruct) Close(keys ...fscache.CacheKey) {
+ m.Lock()
+ defer m.Unlock()
+
+ is := m._lockIntent(keys...)
+ defer m._free(is, keys...)
+
+ if is != nil {
+ is.Close()
+ }
+}
+
+// Notifies all channels that are listening for the specified keys,
+// but doesn't close or remove them from the intent map.
+func (m *intentMapStruct) Notify(v notification, keys ...fscache.CacheKey) {
+ m.Lock()
+ defer m.Unlock()
+
+ is := m._lockIntent(keys...)
+ if is != nil {
+ defer is.Unlock()
+
+ is.Notify(v)
+ }
+}
+
+// NOTE: does not lock the stuct
+func (s *intentStruct) Notify(v notification) {
for _, ch := range s.chs {
- go func(c chan Cacheable) { c <- v }(ch)
+ if ch != nil {
+ ch <- v
+ }
}
+}
- delete(m.m, key)
+// NOTE: does not lock the struct
+func (s *intentStruct) Close() {
+ for _, ch := range s.chs {
+ if ch != nil {
+ close(ch)
+ }
+ }
+ s.chs = nil
+}
+
+// NOTE: does not lock the struct
+func (s *intentStruct) NotifyClose(v notification) {
+ for _, ch := range s.chs {
+ if ch != nil {
+ ch <- v
+ close(ch)
+ }
+ }
+ s.chs = nil
}