}
if !cache.CheckValid(keys...) {
- intentMap.Notify((*Anime)(nil), keys...)
+ intentMap.NotifyClose((*Anime)(nil), keys...)
return ch
}
anime := aid.Anime()
if !anime.IsStale() {
- intentMap.Notify(anime, keys...)
+ intentMap.NotifyClose(anime, keys...)
return ch
}
if ok {
cache.Set(anime, keys...)
}
- intentMap.Notify(anime, keys...)
+ intentMap.NotifyClose(anime, keys...)
} else {
- intentMap.Notify((*Anime)(nil), keys...)
+ intentMap.NotifyClose((*Anime)(nil), keys...)
}
}()
return ch
}
if !cache.CheckValid(keys...) {
- intentMap.Notify((*Episode)(nil), keys...)
+ intentMap.NotifyClose((*Episode)(nil), keys...)
return ch
}
e := eid.Episode()
if !e.IsStale() {
- intentMap.Notify(e, keys...)
+ intentMap.NotifyClose(e, keys...)
return ch
}
}
}
}
- intentMap.Notify(e, keys...)
+ intentMap.NotifyClose(e, keys...)
}()
return ch
}
}
if !cache.CheckValid(keys...) {
- intentMap.Notify((*File)(nil), keys...)
+ intentMap.NotifyClose((*File)(nil), keys...)
return ch
}
f := fid.File()
if !f.IsStale() {
- intentMap.Notify(f, keys...)
+ intentMap.NotifyClose(f, keys...)
return ch
}
cache.MarkInvalid(keys...)
}
- intentMap.Notify(f, keys...)
+ intentMap.NotifyClose(f, keys...)
}()
return ch
}
}
if !cache.CheckValid(keys...) {
- intentMap.Notify(FID(0), keys...)
+ intentMap.NotifyClose(FID(0), keys...)
return ch
}
var ec fidCache
if cache.Get(&ec, keys...) == nil && !ec.IsStale() {
- intentMap.Notify(ec.FID, keys...)
+ intentMap.NotifyClose(ec.FID, keys...)
return ch
}
fid = ec.FID
panic("Don't know what to do with " + strings.Join(reply.Lines(), "\n"))
}
- intentMap.Notify(fid, keys...)
+ intentMap.NotifyClose(fid, keys...)
}()
return ch
}
}
if !cache.CheckValid(keys...) {
- intentMap.Notify((*Group)(nil), keys...)
+ intentMap.NotifyClose((*Group)(nil), keys...)
return ch
}
g := gid.Group()
if !g.IsStale() {
- intentMap.Notify(g, keys...)
+ intentMap.NotifyClose(g, keys...)
return ch
}
cache.Delete(keys...) // deleted group?
}
- intentMap.Notify(g, keys...)
+ intentMap.NotifyClose(g, keys...)
}()
return ch
}
}
if !cache.CheckValid(keys...) {
- intentMap.Notify(GID(0), keys...)
+ intentMap.NotifyClose(GID(0), keys...)
return ch
}
var gc gidCache
if cache.Get(&gc, keys...) == nil && !gc.IsStale() {
- intentMap.Notify(gc.GID, keys...)
+ intentMap.NotifyClose(gc.GID, keys...)
return ch
}
gid = gc.GID
if gid == 0 {
if cache.Get(&gc, altKeys...) == nil && !gc.IsStale() {
- intentMap.Notify(gc.GID, keys...)
+ intentMap.NotifyClose(gc.GID, keys...)
return ch
}
gid = gc.GID
cache.Delete(altKeys...)
}
- intentMap.Notify(gid, keys...)
+ intentMap.NotifyClose(gid, keys...)
}()
return ch
}
type intentMapStruct struct {
sync.Mutex
+ intentLock sync.Mutex // used by the Intent function
+
m map[string]*intentStruct
}
}
// 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
func (m *intentMapStruct) Intent(ch chan Cacheable, keys ...cacheKey) bool {
key := cachePath(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 ...cacheKey) *intentStruct {
m.Lock()
defer m.Unlock()
- s, ok := m.m[key]
+
+ return m._lockIntent(keys...)
+}
+
+func (m *intentMapStruct) _lockIntent(keys ...cacheKey) *intentStruct {
+ s, ok := m.m[cachePath(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 ...cacheKey) {
+ m.Lock()
+ defer m.Unlock()
+
+ m._free(is, keys...)
+}
+func (m *intentMapStruct) _free(is *intentStruct, keys ...cacheKey) {
+ // deletes the key before unlocking, Intent needs to recheck key status
+ delete(m.m, cachePath(keys...))
+ // better than unlocking then deleting -- could delete a "brand new" entry
+ 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 Cacheable, keys ...cacheKey) {
+ m.Lock()
+ defer m.Unlock()
+
+ is := m._lockIntent(keys...)
+ defer m._free(is, keys...)
+
+ 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 ...cacheKey) {
+ m.Lock()
+ defer m.Unlock()
+
+ is := m._lockIntent(keys...)
+ defer m._free(is, keys...)
+
+ 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 Cacheable, keys ...cacheKey) {
+ m.Lock()
+ defer m.Unlock()
+
+ is := m._lockIntent(keys...)
+ defer is.Unlock()
+
+ is.Notify(v)
+}
+
+// NOTE: does not lock the stuct
+func (s *intentStruct) Notify(v Cacheable) {
+ for _, ch := range s.chs {
+ ch <- v
+ }
+}
+
+// NOTE: does not lock the struct
+func (s *intentStruct) Close() {
for _, ch := range s.chs {
- go func(c chan Cacheable) { c <- v }(ch)
+ close(ch)
}
+ s.chs = nil
+}
- delete(m.m, key)
+// NOTE: does not lock the struct
+func (s *intentStruct) NotifyClose(v Cacheable) {
+ for _, ch := range s.chs {
+ ch <- v
+ close(ch)
+ }
+ s.chs = nil
}