Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions keyedcache/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# swiftclient
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be

# keyedcache

yeah?


A Go package providing a thread-safe Keyed Cache for inodes, buffers, DLM locks, and anything else that might need it.

## Synopsis

Provides a thread-safe cache for storing objects which are identified by a key.
The maximum size of the cache is bounded and old objects are discarded to make
space for new ones. The client must provide read() and write() callbacks to
support instantiation of new objects and flushing of dirty objects. The cache
interlocks object instatiation and initialization to insure only one instance of
a particular key is present and that it is initialized (read) before any thread
can see its content. Reference counting is used to insure referenced objects
are kept around as long as they're needed, so the client must be careful to
release each hold that it acquires.

## Motivation

We need a thread-safe map with a bounded size to 1) cache recently accessed
objects and 2) cache dirty objects such tha multiple modifications can be made
to the object before it is flushed.

## Installation

TBD

## API Reference

TBD

## Tests

TBD

## Contributors

* charmer@swiftstack.com

## License

TBD
153 changes: 153 additions & 0 deletions keyedcache/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// Package keyedcache provides a thread-safe cache to hold clean or dirty
// objects identified by opaque keys in a bounded amount of memory.

package keyedcache

import (
"sync"
)

// Just ignore this for the time being, other then noting that a pointer to one
// of these gets passed through requests into the keyed cache and down into the
// callbacks.
//
type ReqContext interface {
}

// Objects in the cache are identified by a key, which is unique for each
// object. The key must support the following operations:
//
// o Equal() -- returns true if the two keys refer to the same object
// o Hash() -- return a hash value based on the key (two keys that are equal
// must have the same hash value)
// o SizeOf() -- return memory used by the key (to track memory consumption);
// all keys are assumed to be the same size.
//
type Key interface {
Equal(key *Key) bool
Hash() uint64
SizeOf() uint64
}

// Objects in the cache are "allocated" by calling a NewObject function supplied
// by the client when it created the cache. New objects do not have an
// indentity until they are assigned one by a lookup in the cache.
//
// Objects must support the following operations. These methods are used
// exclusively by the keyedcache; clients who get an object from the cache
// should not call them, instead they should call methods provided by the cache
// to flush or invalidate objects:
//
// o Read() -- called to initialize an object when its assigned an identity;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a "Load()" method? Wouldn't we need to have a callback mechanism to enable the generic cache to be able to perform the type-specific "load" operation?

// it can block arbitrarity but cannot call into this instance of the
// cache.
// o Write() -- called to flush the contents of a dirty object; it can
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer this be named, uh, Flush() :-)

// block arbitrarity but cannot call into this instance of the cache.
// o Inval() -- called to destroy the identity of an object before
// assigning it a new identity; it can block indefinitely but cannot
// call back into this instance of the cache (key is the old identity
// of the object)
// o Free() -- called when the cache is no longer referencing the object;
// it cannot call back into this instance of the cache (I'm not sure that
// this method makes sense in Go).
// o SizeOf() -- return memory used by the object (to track memory consumption);
// all objects are assumed to be the same size.
//
// Note: perhaps the Read() and Write() operations should be asynchronous.
//
type Object interface {
Read(cntxt *ReqContext, key *Key) (err error)
Write(cntxt *ReqContext, key *Key) (err error)
Inval(cntxt *ReqContext, key *Key)
Free(cntxt *ReqContext)
SizeOf() uint64
}

// CachedObject holds the information that a keyedcache uses to manage an object
// stored in the cache. An object managed by the cache must include an embedded
// pointer to this interface, which it must initializ with a pointer passed to
// the NewObject function supplied by the client of the cache (so when an object
// is created by NewObject() this interface is initialized).
//
// CachedObject supports the following methods. These may be invoked by a client
// that has a hold on the object. Successful lookup of an object in the cache
// returns a pointer to a held object.
//
// o Hold() -- get an additional hold on the object
// o Release() -- release a hold on the object; once all holds on an object
// are released the object may be invalidated
// o MarkDirty() -- mark a held object dirty; dirty objects are cleaned
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we'd require a resource be "held" when calling MarkDirty()...
An alternative might be for the call to Release() to pass back an indication of "dirtiness", but this would mean others wouldn't know it was already dirty until potentially log after it was. Plus, the caller to Release() would be required to "buffer" the knowledge of dirtiness until it was ready to Release() it. I'm fine either way though.

// (written) by the cache before invalidation unless a forced invalidation
// is done
// o IsDirty() -- returns true if the object is dirty
// o Flush() -- cause a dirty object to be flushed via a call to Write();
// panics if the object is not dirty
//
// If Flush() fails because the corresponding Write() fails then the object is
// marked dirty.
//
type CachedObject interface {
Hold(cntxt *ReqContext)
Release(cntxt *ReqContext)
MarkDirty(cntxt *ReqContext)
IsDirty(cntxt *ReqContext) bool
Flush(cntxt *ReqContext) (err error)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, ok, here is the "flush" upcall/callback... now I see...

}

// Cache caches objects identified by keys, upto a specified maximum amount of
// memory. Objects in the cache are accessed by calling Lookup() with the
// desired key and Lookup() returns a pointer to an object with a hold on it.
// When the caller is done with the object it must release the hold.
//
// If the object is not currently cached, Cache will create a new object
// corresponding to the key or reuse an existing one. It is initialized by a
// call to Object.Read() before return. Cache locks the object duing the call
// to Object.Read() so only one object with a given key is present in the cache
// and no thread can access it until it is initialized.
//
// There is no provision for otherwise locking the object. In particular, an
// object can be marked dirty or flushed while multiple threads have a hold on
// the object.
//
// A Cache provides the following methods:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So no "iterate through"?
I could imagine a desire to "flush everything" wanting an iterator.
Indeed, it might be interesting to iterate just over the dirty entries or just over the clean entries...

//
// o Lookup() -- lookup an object by key, creating it if necessary; may return
// an error if Read() fails; blocks if the cache is full
// o Flush() -- flush all objects matched by the function predicate(); return
// an error if any of the Object.Write() fails
// o Inval() -- invalidate all objects matched by the function predicate();
// InvalFlush determines whether dirty objects are flushed before they are
// invalidated; Inval() will block until a held object is released; Inval()
// will return an error if flush failed
//
type Cache interface {
Lookup(cntxt *ReqContext, key Key) (err error)
Flush(cntxt *ReqContext, predicate func(cntxt *ReqContext, key *Key) bool) (err error)
Inval(cntxt *ReqContext, predicate func(cntxt *ReqContext, key *Key) bool,
flushIfDirty InvalFlush) (err error)
Free(cntxt *ReqContext)
}

type InvalFlush int

const (
INVAL_DIRTY InvalFlush = iota
FLUSH_DIRTY InvalFlush = iota
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to assume you want INVAL_DIRTY and FLUSH_DIRTY to have the same (zero) value here?
If not, I think you wanted to remove the second "= iota"... thus emulating an "enum" sequence that Go otherwise lacks.

)

// Create a new keyed cache whiche consumes a maximum of cacheSz bytes of memory
// (not including hash chains and some other data). The caller supplies the
// size of keys and objects so we can compute the maximum number of objects to
// cache.
//
// The caller also supplies newObject(), which is called by the cache when it
// wants to create a new object. Objects must embed a pointer to a CachedObject
// and initialize that pointer with the value passed in.
//
// The *ReqContext is passed through without using it, however the cntxt passed
// to newObject() will be the cntxt passed to the call to Lookup() that
// triggered object creation.
Copy link
Collaborator

@edmc-ss edmc-ss Jun 8, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there are more callbacks (e.g. hash and equals) that might be useful here...

//
func NewCache(cntxt *ReqContext,
newObject func(cntxt *ReqContext, cachedObj *CachedObject) (object Object),
cacheSz uint64, objectSz uint64, keySz uint64) (cache Cache)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are objectSz and keySz? How are they related to Object.SizeOf and Key.SizeOf?