Skip to content

Commit

Permalink
refactor cache/store, expose Lookup for servers
Browse files Browse the repository at this point in the history
  • Loading branch information
tjfontaine committed Dec 28, 2012
1 parent e19bf1c commit 7328b43
Show file tree
Hide file tree
Showing 9 changed files with 342 additions and 151 deletions.
10 changes: 7 additions & 3 deletions dns.js
Expand Up @@ -101,7 +101,11 @@ exports.Question = function (opts) {
};
exports.Request = client.Request;

var cache = require('./lib/cache');
exports.Cache = require('./lib/cache');
exports.MemoryStore = require('./lib/memory').MemoryStore;

exports.Cache = cache.Cache;
exports.MemoryStore = cache.MemoryStore;
var utils = require('./lib/utils');

exports.Lookup = utils.Lookup
exports.is_absolute = utils.is_absolute;
exports.ensure_absolute = utils.ensure_absolute;
263 changes: 126 additions & 137 deletions lib/cache.js
Expand Up @@ -20,176 +20,165 @@

'use strict';

var consts = require('./consts'),
util = require('util'),
Heap = require('binaryheap');
var MemoryStore = require('./memory').MemoryStore;
var utils = require('./utils');
var Lookup = utils.Lookup;
var util = require('util');
var Heap = require('binaryheap');

var CNAME = consts.NAME_TO_QTYPE.CNAME;

var debug = function() {
//var args = Array.prototype.slice.call(arguments);
//console.log.apply(this, ['cache', Date.now().toString()].concat(args));
var MemoryStoreExpire = function (store, zone, opts) {
opts = opts || {};
this._store = store;
this._zone = zone;
this._max_keys = opts.max_keys;
this._ttl = new Heap(true);
};

var extend = util._extend;

if (!extend) {
// shamelessly grabbed from joyent/node/lib/util.js
extend = function(origin, add) {
// Don't do anything if add isn't an object
if (!add || typeof add !== 'object') return origin;

var keys = Object.keys(add);
var i = keys.length;
while (i--) {
origin[keys[i]] = add[keys[i]];
MemoryStoreExpire.prototype.get = function (domain, key, cb) {
var self = this;
this._store.get(domain, key, function (err, results) {
var i, j, type, record;
var nresults = {};
var now = Date.now();

for (i in results) {
type = results[i];
for (j in type) {
record = type[j];
record.ttl = Math.round((record._ttl_expires - now) / 1000)
if (record.ttl > 0) {
if (!nresults[i]) {
nresults[i] = [];
}
nresults[i].push(record);
} else {
self._ttl.remove(record);
self._store.delete(self._zone, record.name, record.type, function () {});
}
}
}
return origin;
};
}

var MemoryStore = exports.MemoryStore = function(max, range) {
this._max_keys = max || 1000;
this._range = range || 50;
this._index = {};
this._ttl = new Heap(true);
};

MemoryStore.prototype._delete = function(name, type) {
var entry = this._index[name + type];
this._ttl.remove(entry);
delete this._index[name + type];
cb(err, nresults);
});
};

MemoryStore.prototype._trim = function() {
MemoryStoreExpire.prototype.set = function (domain, key, data, cb) {
var i, j, type, record, expires;
var self = this;
var remove_count = this._ttl.length - this._max_keys;
var remove;

if (remove_count > 0) {
debug('memory store trim to remove', remove_count);
while (remove_count > 0) {
remove = this._ttl.pop();
debug('memory store trim', remove);
delete this._index[remove.name + remove.type];
remove_count -= 1;
var now = Date.now();

for (i in data) {
type = data[i];
for (j in type) {
record = type[j];
expires = (record.ttl * 1000) + now;
record._ttl_expires = expires;
self._ttl.insert(record, expires);
}
}
};

MemoryStore.prototype.purge = function() {
this._index = {};
this._ttl = new Heap();
};

MemoryStore.prototype.get = function(name, type, cb) {
var key = name + type;
var value = this._index[key];
var results;

debug('memory store get', name, type);

if (value) {
if (Date.now() < value.expires || !value.expires) {
results = value.values;
} else {
this._delete(name, type);
}
while (this._ttl.length > this._max_keys) {
var record = this._ttl.pop();
this._store.delete(this._zone, record.name, record.type);
}

process.nextTick(function() {
cb(results);
this._store.set(domain, key, data, function (err, results) {
if (cb)
cb(err, results);
});
};

MemoryStore.prototype.set = function(rr) {
var name = rr.name,
type = rr.type;

var expires;

if (rr.ttl !== false) {
expires = Date.now() + (rr.ttl * 1000);
} else {
expires = false;
MemoryStoreExpire.prototype.delete = function (domain, key, type, cb) {
if (!cb) {
cb = type;
type = undefined;
}

var key = name + type;
var value = this._index[key];

debug('memory store set', name, type, expires);
var self = this;

if (!value) {
value = this._index[key] = {
values: []
};
this._store.get(domain, utils.ensure_absolute(key), function (gerr, gresults) {
var i, j, ktype, record;

this._ttl.insert({
name: name,
type: type,
ttl: expires
}, expires);
for (i in gresults) {
ktype = gresults[i];
for (j in ktype) {
record = ktype[j];
self._ttl.remove(record);
}
}

this._trim();
}
if (!gresults) {
if (cb)
cb(gerr, gresults);
return;
}

value.expires = expires;
value.values.push(extend({}, rr));
self._store.delete(domain, key, type, function (err, results) {
if (cb)
cb(err, results);
});
});
};

var Cache = exports.Cache = function(opts) {
var Cache = module.exports = function (opts) {
opts = opts || {};
this._store = opts.store || new MemoryStore();
this._zone = '.' || opts.zone;
this._store = undefined;
this.purge = function () {
this._store = new MemoryStoreExpire(opts.store || new MemoryStore(), this._zone, opts);
}
this.purge();
};

var send = function(type, results, cb) {
var i, packet;
Cache.prototype.store = function (packet) {
var self = this;
var c = {};

function each(record) {
var r = c[record.name.toLowerCase()];
var t;

if (!r)
r = c[record.name.toLowerCase()] = {};

t = r[record.type];

if (!t)
t = r[record.type] = [];

if (results.length) {
cb(results)
} else {
cb(null);
t.push(record);
}

packet.answer.forEach(each);
packet.authority.forEach(each);
packet.additional.forEach(each);

Object.keys(c).forEach(function (key) {
self._store.set(self._zone, utils.ensure_absolute(key), c[key]);
});
};

Cache.prototype.lookup = function(question, cb) {
Cache.prototype.lookup = function (question, cb) {
var self = this;
var results = [];
var name = question.name.toLowerCase();
var type = question.type;

debug('cache lookup', name);

var found_cname = function(result) {
if (!result) {
send(type, results, cb);
} else {
debug('cache found cname', name);
results.push(result[0]);
name = result[0].data;
self._store.get(name, type, found_exact);
Lookup(this._store, this._zone, question, function (err, results) {
var i, record, found = false;

for (i in results) {
record = results[i];
if (record.type == question.type) {
found = true;
break;
}
}
};

var found_exact = function(result) {
if (!result) {
self._store.get(name, CNAME, found_cname);
} else {
debug('cache found', name, type);
results = results.concat(result);
send(type, results, cb);
}
};

self._store.get(name, type, found_exact);
};

Cache.prototype.store = function(packet) {
packet.answer.forEach(this._store.set.bind(this._store));
packet.authority.forEach(this._store.set.bind(this._store));
packet.additional.forEach(this._store.set.bind(this._store));
};
if (results && !found) {
self._store.delete(self._zone, utils.ensure_absolute(question.name));
results.forEach(function (rr) {
self._store.delete(self._zone, utils.ensure_absolute(rr.name));
});
results = null;
}

Cache.prototype.purge = function() {
this._store.purge();
cb(results);
});
};
4 changes: 3 additions & 1 deletion lib/client.js
Expand Up @@ -554,11 +554,13 @@ Lookup.prototype.start = function() {

platform.hosts.lookup(this.question, function(results) {
var packet;
if (results) {
if (results && results.length) {
debug('Lookup in hosts', results);
packet = new Packet();
packet.answer = results.slice();
self._emit(null, packet);
} else {
debug('Lookup not in hosts');
Resolve.prototype.start.call(self);
}
});
Expand Down

0 comments on commit 7328b43

Please sign in to comment.