Skip to content

Commit

Permalink
array.cache.list`
Browse files Browse the repository at this point in the history
  • Loading branch information
evantahler committed Feb 24, 2016
1 parent 692fde7 commit 719a00e
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 4 deletions.
19 changes: 19 additions & 0 deletions initializers/cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,25 @@ module.exports = {
});
};

api.cache.push = function(key, item, callback){
var object = JSON.stringify({data: item});
api.redis.client.rpush(api.cache.redisPrefix + key, object, function(err){
if(typeof next === 'function'){ process.nextTick(function(){ next(err); }); }
});
};

api.cache.pop = function(key, callback){
api.redis.client.lpop(api.cache.redisPrefix + key, function(err, object){
if(err){ return callback(err); }
var item = JSON.parse(object);
return callback(null, item.data);
});
};

api.cache.listLength = function(key, callback){
api.redis.client.llen(api.cache.redisPrefix + key, callback);
};

api.cache.lock = function(key, expireTimeMS, next){
if(typeof expireTimeMS === 'function' && next === null){
expireTimeMS = expireTimeMS;
Expand Down
28 changes: 24 additions & 4 deletions site/source/includes/docs/core/cache.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ The cache's redis server is defined by `api.config.redis`. Note that if `api.co
### api.cache.save

* Invoke: `api.cache.save(key, value, expireTimeMS, next)`
* `expireTimeMS` can be null if you never want the object to expire
* `expireTimeMS` can be null if you never want the object to expire
* Callback: `next(error, new)`
* `error` will be null unless the object can't be saved (perhaps out of ram or a bad object type).
* overwriting an existing object will return `new = true`

`api.cache.save` is used to both create new entires or update existing cache entires. If you don't define an expireTimeMS, `null` will be assumed, and using `null` will cause this cached item to never expire. Expired cache objects will be periodically swept away (but not necessarily exactly when they expire)

### api.cache.load
Expand All @@ -34,9 +34,29 @@ The cache's redis server is defined by `api.config.redis`. Note that if `api.co
* Callback: `next(error, destroyed)`
* will be false if the object cannot be found, and true if destroyed

## List methods

api.cache implements a distributed shared list. 3 simple functions are provided to interact with this list, `push`, `pop`, and `listLength`. These lists are stored in Redis, and cannot be locked. That said, a `push` and `pop` operation will guarantee that one-and-only-one copy of your data is returned to whichever application acted first (when popping) or an error will be returned (when pushing).

### api.cache.push
* Invoke: `api.cache.push(key, data, next)`
* data must be serializable via JSON.stringify
* Callback: `next(error)`

### api.cache.pop
* Invoke: `api.cache.pop(key, next)`
* Callback: `next(error, data)`
* data will be returned in the object form it was saved (array, object, string)

### api.cache.listLength
* Invoke: `api.cache.listLength(key, next)`
* Callback: `next(error, length)`
* length will be an integer.
* if the list does not exist, `0` will be returned

## Lock Methods

You may optionally implement locking methods along with your cache objects. This will allow one actionhero server to obtain a lock on an object and prevent modification of it by another member of the cluster. For example you may want to first `api.cache.lock` a key, and then save it to prevent other nodes from modifying the object.
You may optionally implement locking methods along with your cache objects. This will allow one actionhero server to obtain a lock on an object and prevent modification of it by another member of the cluster. For example you may want to first `api.cache.lock` a key, and then save it to prevent other nodes from modifying the object.

### api.cache.lock

Expand Down Expand Up @@ -66,7 +86,7 @@ You may optionally implement locking methods along with your cache objects. Thi
* Callback: `next(error, locks)`
* `locks` is an array of all currently active locks


You can see an example of using the cache within an action in [actions/cacheTest.js](https://github.com/evantahler/actionhero/blob/master/actions/cacheTest.js)

## Redis
Expand Down
56 changes: 56 additions & 0 deletions test/core/cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,62 @@ describe('Core: Cache', function(){
});
});

describe('lists', function(){
it('can push and pop from an array', function(done){
var jobs = [];

jobs.push(function(next){ api.cache.push('testListKey', 'a string', next); });
jobs.push(function(next){ api.cache.push('testListKey', ['an array'], next); });
jobs.push(function(next){ api.cache.push('testListKey', {what: 'an aobject'}, next); });
async.parallel(jobs, function(error){
should.not.exist(error);
jobs = [];

jobs.push(function(next){
api.cache.pop('testListKey', function(error, data){
data.should.equal('a string');
next();
});
});
jobs.push(function(next){
api.cache.pop('testListKey', function(error, data){
data.should.deepEqual(['an array']);
next();
});
});
jobs.push(function(next){
api.cache.pop('testListKey', function(error, data){
data.should.deepEqual({what: 'an aobject'});
next();
});
});

async.series(jobs, function(error){
should.not.exist(error);
done();
});
});
});

it('can get the length of an array when full', function(done){
api.cache.push('testListKey2', 'a string', function(){
api.cache.listLength('testListKey2', function(error, l){
should.not.exist(error);
l.should.equal(1);
done();
});
});
});

it('will return 0 length when the key does not exist', function(done){
api.cache.listLength('testListKey3', function(error, l){
should.not.exist(error);
l.should.equal(0);
done();
});
});
});

describe('locks', function(){

var key = 'testKey';
Expand Down

0 comments on commit 719a00e

Please sign in to comment.