-
Notifications
You must be signed in to change notification settings - Fork 24
Proposed API for a generic keyed cache #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: development
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| # swiftclient | ||
|
|
||
| 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 | ||
| 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; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess we'd require a resource be "held" when calling MarkDirty()... |
||
| // (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) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So no "iterate through"? |
||
| // | ||
| // 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 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? |
||
| ) | ||
|
|
||
| // 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. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What are |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be
yeah?