blob: fb4f8ea86a2154fdd5b6087d5799cbbf60d8285d [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package principal
import (
"reflect"
"sync"
"time"
"v.io/v23/security"
)
// BlessingsCacheEntry is an entry in the blessing cache.
type BlessingsCacheEntry struct {
refCount uint32 // Keep track of uses so JS knows when to clean up.
blessings security.Blessings
dirty bool // True iff modified since last GC.
}
// BlessingsCache is a cache for blessings.
// This cache is synced with a corresponding cache in Javascript with the goal
// of reducing the number of times that blessings objects are sent in their
// entirety.
// BlessingsCache provides an id for a blessing after a Put() call that is
// understood by Javascript.
// After a new blessings cache is created, recently unused blessings will be
// cleaned up periodically.
type BlessingsCache struct {
lock sync.Mutex
nextId BlessingsId
entries map[BlessingsId]*BlessingsCacheEntry
notifier func([]BlessingsCacheMessage)
inactiveGc bool
}
func NewBlessingsCache(notifier func([]BlessingsCacheMessage), gcPolicy *BlessingsCacheGCPolicy) *BlessingsCache {
bc := &BlessingsCache{
entries: map[BlessingsId]*BlessingsCacheEntry{},
notifier: notifier,
}
go bc.gcLoop(gcPolicy)
return bc
}
// Put a blessing in the blessing cache and get an id for it that will
// be understood by the Javascript cache.
// If the blessings object is already in the cache, a new entry won't be created
// and the id for the blessings from a previous put will be returned.
func (bc *BlessingsCache) Put(blessings security.Blessings) BlessingsId {
if blessings.IsZero() {
return 0
}
bc.lock.Lock()
defer bc.lock.Unlock()
for id, entry := range bc.entries {
if reflect.DeepEqual(blessings, entry.blessings) {
entry.refCount++
entry.dirty = true
return id
}
}
bc.nextId++
id := bc.nextId
bc.entries[id] = &BlessingsCacheEntry{
refCount: 1,
blessings: blessings,
dirty: true,
}
addMessage := BlessingsCacheAddMessage{
CacheId: id,
Blessings: blessings,
}
bc.notifier([]BlessingsCacheMessage{
BlessingsCacheMessageAdd{
Value: addMessage,
},
})
return id
}
// BlessingsCacheGCPolicy specifies when GC will occur and provides a function
// to pass notifications of deletions (used to notify Javascript to delete
// the cache items).
type BlessingsCacheGCPolicy struct {
nextTrigger func() <-chan time.Time
}
// PeriodicGcPolicy periodically performs gc at the specified interval.
func PeriodicGcPolicy(interval time.Duration) *BlessingsCacheGCPolicy {
return &BlessingsCacheGCPolicy{
nextTrigger: func() <-chan time.Time { return time.After(interval) },
}
}
// Stop stops the GC loop and frees up resources.
func (bc *BlessingsCache) Stop() {
bc.lock.Lock()
bc.inactiveGc = true
bc.lock.Unlock()
}
func (bc *BlessingsCache) gcLoop(policy *BlessingsCacheGCPolicy) {
for {
bc.lock.Lock()
if bc.inactiveGc {
return
}
bc.lock.Unlock()
<-policy.nextTrigger()
bc.gc(policy)
}
}
// Perform Garbage Collection.
// All blessings that weren't referenced since the last GC will be removed from
// the cache.
func (bc *BlessingsCache) gc(policy *BlessingsCacheGCPolicy) {
bc.lock.Lock()
defer bc.lock.Unlock()
var toDelete []BlessingsCacheMessage
for id, entry := range bc.entries {
if !entry.dirty {
deleteEntry := BlessingsCacheDeleteMessage{
CacheId: id,
DeleteAfter: entry.refCount,
}
toDelete = append(toDelete, BlessingsCacheMessageDelete{Value: deleteEntry})
} else {
entry.dirty = false
}
}
for _, deleteEntry := range toDelete {
delete(bc.entries, deleteEntry.(BlessingsCacheMessageDelete).Value.CacheId)
}
if len(toDelete) > 0 {
bc.notifier(toDelete)
}
}