Browse files

fixed error handling

  • Loading branch information...
1 parent 358ff1f commit 2b8ae2fe399534638bf55cbbc36352013027b629 @rjrodger committed Jan 23, 2011
Showing with 90 additions and 23 deletions.
  1. +37 −12 lib/simpledb.js
  2. +53 −11 test/simpledb.test.js
View
49 lib/simpledb.js
@@ -19,11 +19,12 @@ var MARK = 'simpledb: '
* randomdelay: random multiplier to the delay, default=true
*
* Callbacks:
- * fn(stop,tryI,delay)
+ * fn(stop,tryI,last,delay)
* stop: callback to halt retries
* done: true if done, so stop
* err: err for statuscb, if any
* tryI: try index, starts from 1
+ * last: true if this is the last try
* delay: delay before this try
*
* statuscb(done,tryI,last,delay,err)
@@ -48,9 +49,9 @@ exports.expbackoff = function(fn,statuscb,maxtry,expbase,delaymin,delayscale,ran
delayscale = null == delayscale ? 100 : delayscale
randomdelay = null == randomdelay ? true : randomdelay
- function retry(tryI,delay) {
+ function retry(tryI,last,delay) {
try {
- fn(stop,tryI,delay)
+ fn(stop,tryI,last,delay)
}
catch( err ) {
stop(false,err)
@@ -65,13 +66,15 @@ exports.expbackoff = function(fn,statuscb,maxtry,expbase,delaymin,delayscale,ran
var nextdelay = delaymin + (delayscale * (randomdelay?Math.random():1) * Math.pow(expbase,tryI))
setTimeout(function(){
- retry(tryI+1,nextdelay)
+ var nextTryI = tryI+1
+ var nextLast = maxtry <= nextTryI
+ retry(nextTryI,nextLast,nextdelay)
}, nextdelay)
}
}
}
- retry(1,0)
+ retry(1,false,0)
}
@@ -117,17 +120,26 @@ exports.SimpleDB = function(opts,logger) {
self.client = aws.createSimpleDBClient(opts.keyid, opts.secret, awsopts)
- self.handle = function(start,act,q,tryI,res,stop,callback){
- log('handle',start,act,q,tryI,res)
+ self.handle = function(start,act,q,tryI,last,res,stop,callback){
+ log('handle',start,act,q,tryI,last,res)
var time = new Date().getTime()
var err = null
var meta = {action:act,query:q,result:res,time:time,duration:time-start,trycount:tryI}
+ var retry = false
+
if( res.Errors ) {
var error = arrayify(res.Errors.Error)[0]
err = {Code:error.Code,Message:error.Message}
meta.RequestId = res.RequestId
+
+ // retry only server errors, as per SimpleDB dev guide
+ retry = !!({
+ 'InternalError':true,
+ 'ServiceUnavailable':true
+ }[err.Code])
+ util.debug('RETRY:'+retry+' Code:'+err.Code)
}
else {
meta.RequestId = res.ResponseMetadata.RequestId
@@ -139,8 +151,11 @@ exports.SimpleDB = function(opts,logger) {
callback(err,res,meta)
}
else {
- log('error',err,res,meta)
- stop(false)
+ log('error',start,act,q,tryI,last,retry,err,res,meta)
+ stop(!retry,err)
+ if( !retry || last ) {
+ callback(err,null,meta)
+ }
}
}
@@ -161,13 +176,13 @@ exports.SimpleDB = function(opts,logger) {
log('request',start,act,q)
exports.expbackoff(
- function(stop,tryI,delay) {
+ function(stop,tryI,last,delay) {
// remove previous Signature from previous attempts
delete q.Signature
self.client.call(act,q,function(res){
- self.handle(start,act,q,tryI,res,stop,handler)
+ self.handle(start,act,q,tryI,last,res,stop,handler)
})
},
@@ -442,11 +457,21 @@ exports.SimpleDB = function(opts,logger) {
}
- self.select = function(query, override, callback ) {
+ self.select = function(query, args, override, callback ) {
+ if( !Array.isArray(args) ) {
+ callback = override
+ override = args
+ args = []
+ }
callback = getcallback(override,callback)
if( badargs(callback ))return
if( empty('query',query,callback ))return
+ args.forEach(function(arg){
+ arg = arg.replace(/'/g,"''")
+ query = query.replace(/\?/,arg)
+ })
+
var asarrays = getoverride(override).$AsArrays
var act = 'Select'
var q = {SelectExpression:query,ConsistentRead:''+opts.consistent}
View
64 test/simpledb.test.js
@@ -14,8 +14,8 @@ module.exports = {
expbackoff: function() {
var action_calls = []
- function action(stop,tryI,delay) {
- action_calls.push(tryI+':'+delay)
+ function action(stop,tryI,last,delay) {
+ action_calls.push(tryI+':'+last+':'+delay)
stop(2 < tryI)
}
@@ -58,26 +58,28 @@ module.exports = {
status_calls_expect = s
}
+ // NOTE: assertions errors are appended to expectation arrays by statuscb above
+
var testI = 0
var tests = [
// happy path
function() {
- expect( [ '1:0', '2:20', '3:40' ],
+ expect( [ '1:false:0', '2:false:20', '3:false:40' ],
[ 'false:1:false:0:null', 'false:2:false:20:null', 'true:3:true:40:null' ] )
simpledb.expbackoff(action,status,4,2,0,10,false)
},
// maxtry ends it
function() {
- expect( [ '1:0', '2:20' ],
+ expect( [ '1:false:0', '2:true:20' ],
[ 'false:1:false:0:null', 'false:2:true:20:null' ] )
simpledb.expbackoff(action,status,2,2,0,10,false)
},
// exponent base
function() {
- expect( [ '1:0', '2:40', '3:160' ],
+ expect( [ '1:false:0', '2:false:40', '3:false:160' ],
[ 'false:1:false:0:null', 'false:2:false:40:null', 'true:3:true:160:null' ] )
simpledb.expbackoff(action,status,4,4,0,10,false)
},
@@ -93,6 +95,7 @@ module.exports = {
expect( 3,3 )
simpledb.expbackoff(action,status)
},
+
]
tests[testI]()
@@ -163,18 +166,31 @@ module.exports = {
// overrides
sdb = new simpledb.SimpleDB({keyid:'foo',secret:'bar'},simpledb.debuglogger)
- sdb.handle = function(op,q,start,tryI,res,stop,callback){ assert.ok(!q.ConsistentRead) }
+ sdb.handle = function(op,q,start,tryI,last,res,stop,callback){ assert.ok(!q.ConsistentRead) }
sdb.getItem('domain','itemname',function(){
- sdb.handle = function(op,q,start,tryI,res,stop,callback){ assert.equal('false',q.ConsistentRead) }
+ sdb.handle = function(op,q,start,tryI,last,res,stop,callback){ assert.equal('false',q.ConsistentRead) }
sdb.getItem('domain','itemname',{ConsistentRead:'false'},function(){})
})
+
+ // errors
+ sdb = new simpledb.SimpleDB({keyid:'foo',secret:'bar'},simpledb.debuglogger)
+ sdb.getItem('domain','itemname',function(err,res,meta){
+ eyes.inspect(err)
+ eyes.inspect(meta)
+ assert.isNotNull(err)
+ assert.equal('InvalidClientTokenId',err.Code)
+ assert.equal(1, meta.trycount) // do not retry client errors
+ })
+
+
+ // the real deal
sdb = new simpledb.SimpleDB({keyid:keys.id,secret:keys.secret},simpledb.debuglogger)
//eyes.inspect(sdb)
var orighandle = sdb.handle
- var againhandle = function(op,q,start,tryI,res,stop,callback){
+ var againhandle = function(op,q,start,tryI,last,res,stop,callback){
if( 1 == tryI ) {
res = {
Errors:{
@@ -186,7 +202,7 @@ module.exports = {
RequestID:'81abaa80-7309-e39e-2644-b33b2c3acb57'
}
}
- orighandle(op,q,start,tryI,res,stop,callback)
+ orighandle(op,q,start,tryI,last,res,stop,callback)
}
@@ -206,13 +222,16 @@ module.exports = {
assert.ok( 2 <= meta.trycount )
assert.ok( Array.isArray(res) )
assert.ok( 1 <= res.length )
+ assert.equal(2, meta.trycount) // retry server errors
+
sdb.handle = orighandle
;sdb.putItem('simpledbtest','item1',
{
foo:1,
bar:'BAR',
- woz:['one','two']
+ woz:['one','two'],
+ quote:"'n"
},function(err,res,meta){
debugres(err,res,meta)
assert.isNull(err)
@@ -229,6 +248,7 @@ module.exports = {
assert.equal(1,parseInt(res.foo,10))
assert.equal('BAR',res.bar)
assert.equal('one,two',res.woz)
+ assert.equal("'n",res.quote)
;sdb.getItem('simpledbtest','item1',{$AsArrays:true},function(err,res,meta){
debugres(err,res,meta)
@@ -238,15 +258,23 @@ module.exports = {
assert.equal('BAR',res.bar[0])
assert.equal('one',res.woz[0])
assert.equal('two',res.woz[1])
+ assert.equal("'n",res.quote[0])
;sdb.request("GetAttributes",
{
DomainName:'simpledbtest',
ItemName:'item1',
+ ConsistentRead:'true'
},
function(err,res,meta){
debugres(err,res,meta)
assert.isNull(err)
+ assert.equal( 5, res.GetAttributesResult.Attribute.length )
+
+ ;sdb.select("not a select expression at all at all",function(err,res,meta){
+ debugres(err,res,meta)
+ assert.isNotNull(err)
+ assert.equal( 'InvalidQueryExpression', err.Code )
;sdb.select("select * from simpledbtest where bar = 'BAR'",function(err,res,meta){
debugres(err,res,meta)
@@ -255,6 +283,20 @@ module.exports = {
assert.equal('item1',res[0].$ItemName)
assert.equal( 'BAR', res[0].bar )
+ ;sdb.select("select * from simpledbtest where bar = '?'",['BAR'],function(err,res,meta){
+ debugres(err,res,meta)
+ assert.isNull(err)
+ assert.ok( 1 == res.length )
+ assert.equal('item1',res[0].$ItemName)
+ assert.equal( 'BAR', res[0].bar )
+
+ ;sdb.select("select * from simpledbtest where bar = '?' and quote = '?'",['BAR',"'n"],function(err,res,meta){
+ debugres(err,res,meta)
+ assert.isNull(err)
+ assert.ok( 1 == res.length )
+ assert.equal('item1',res[0].$ItemName)
+ assert.equal( 'BAR', res[0].bar )
+
;sdb.batchPutItem('simpledbtest',
[
{ $ItemName:'b1', batch:'yes', field:'one'},
@@ -289,7 +331,7 @@ module.exports = {
debugres(err,res,meta)
assert.isNotNull(err)
- }) }) }) }) }) }) }) }) }) }) }) }) }) })
+ }) }) }) }) }) }) }) }) }) }) }) }) }) }) }) }) })
},
example: function() {

0 comments on commit 2b8ae2f

Please sign in to comment.