From af6e04e6bca19a7c934b1377e45731622b7e16e6 Mon Sep 17 00:00:00 2001 From: Mathieu Cartoixa Date: Thu, 3 Mar 2016 17:03:29 +0100 Subject: [PATCH] Retry strategy, designed for Sql Azure --- lib/connect-tedious.js | 306 ++++++++++++++++++++++++----------------- package.json | 3 +- 2 files changed, 178 insertions(+), 131 deletions(-) diff --git a/lib/connect-tedious.js b/lib/connect-tedious.js index 14e28c1..674cdbc 100644 --- a/lib/connect-tedious.js +++ b/lib/connect-tedious.js @@ -9,6 +9,7 @@ var tedious=require('tedious'); var tediousPool=require('tedious-connection-pool'); +var retry=require('retry'); var ncsBuilder=require('node-connection-string-builder'); var debug={ connection: require('debug')('connect-tedious:connection'), @@ -129,6 +130,12 @@ module.exports = function(connect){ this.sidColumnName=options.sidColumnName || '[Sid]'; this.sessColumnName=options.sessColumnName || '[Sess]'; this.expiresColumnName=options.expiresColumnName || '[Expires]'; + + this.retryOptions={ + retries: 3, + minTimeout: 200, + maxTimeout: 1000 + }; this.pool=new tediousPool({ name: 'connect-tedious', @@ -143,7 +150,9 @@ module.exports = function(connect){ }); dbCleanup(this, function (err) { - if (err) console.log('ERROR ' + err); + if (err) { + console.log('ERROR ' + err); + } }); } @@ -163,42 +172,48 @@ module.exports = function(connect){ TediousStore.prototype.get = function(sid, fn) { var self=this; - self.pool.acquire(function(err, db) { - if (err) - return fn(err); - - var r = new tedious.Request( - 'SELECT s.' + self.expiresColumnName + ', s.' + self.sessColumnName + ' FROM ' + self.tableName + ' s WHERE s.' + self.sidColumnName + '=@sid AND s.' + self.expiresColumnName + '>=SYSUTCDATETIME()', - function(err, rowCount) { - debug.sql('Executed SELECT'); - db.release(); - if (err) - return fn(err); - if (!rowCount || rowCount!==1) - return fn(); - } - ); - r.on('row', function(columns) { - if (!columns || columns.length!==2) - return fn(); - - var expires = columns[0].value; - var sess = columns[1].value; - if (!expires || !sess) - return fn(); - - var dExpires = new Date(expires).toISOString(); - var oSess = JSON.parse(sess); - oSess.cookie.expires = dExpires; - - debug.sql('Returning ' + oSess); - return fn(null, oSess); + var operation=retry.operation(self.retryOptions); + operation.attempt(function() { + self.pool.acquire(function(err, db) { + if (operation.retry(err) || err) { + return fn(err); + } + + var r = new tedious.Request( + 'SELECT s.' + self.expiresColumnName + ', s.' + self.sessColumnName + ' FROM ' + self.tableName + ' s WHERE s.' + self.sidColumnName + '=@sid AND s.' + self.expiresColumnName + '>=SYSUTCDATETIME()', + function(err, rowCount) { + debug.sql('Executed SELECT'); + db.release(); + if (operation.retry(err) || err) { + return fn(err); + } + if (!rowCount || rowCount!==1) + return fn(); + } + ); + r.on('row', function(columns) { + if (!columns || columns.length!==2) + return fn(); + + var expires = columns[0].value; + var sess = columns[1].value; + + if (!expires || !sess) + return fn(); + + var dExpires = new Date(expires).toISOString(); + var oSess = JSON.parse(sess); + oSess.cookie.expires = dExpires; + + debug.sql('Returning ' + oSess); + return fn(null, oSess); + }); + r.addParameter('sid', tedious.TYPES.VarChar, sid); + + debugSql(r); + db.execSql(r); }); - r.addParameter('sid', tedious.TYPES.VarChar, sid); - - debugSql(r); - db.execSql(r); }); }; @@ -213,28 +228,36 @@ module.exports = function(connect){ TediousStore.prototype.set = function(sid, sess, fn){ var self=this; - self.pool.acquire(function(err, db) { - if (err) - return fn(err); - var duration = sess.cookie.maxAge || oneDay; - var r = new tedious.Request( - 'MERGE INTO ' + self.tableName + ' WITH (HOLDLOCK) s' + - ' USING (VALUES(@sid, @sess)) ns(' + self.sidColumnName + ', ' + self.sessColumnName + ') ON (s.' + self.sidColumnName + '=ns.' + self.sidColumnName + ')' + - ' WHEN MATCHED THEN UPDATE SET s.' + self.sessColumnName + '=@sess, s.' + self.expiresColumnName + '=DATEADD(ms, @duration, SYSUTCDATETIME())' + - ' WHEN NOT MATCHED THEN INSERT (' + self.sidColumnName + ', ' + self.sessColumnName + ', ' + self.expiresColumnName + ') VALUES (@sid, @sess, DATEADD(ms, @duration, SYSUTCDATETIME()));', - function(err) { - debug.sql('Executed MERGE'); - db.release(); - fn.apply(self, arguments); - } - ); - r.addParameter('sid', tedious.TYPES.VarChar, sid); - r.addParameter('sess', tedious.TYPES.NVarChar, JSON.stringify(sess)); - r.addParameter('duration', tedious.TYPES.Int, duration); - - debugSql(r); - db.execSql(r); + var operation=retry.operation(self.retryOptions); + operation.attempt(function() { + self.pool.acquire(function(err, db) { + if (operation.retry(err) || err) { + return fn(err); + } + + var duration = sess.cookie.maxAge || oneDay; + var r = new tedious.Request( + 'MERGE INTO ' + self.tableName + ' WITH (HOLDLOCK) s' + + ' USING (VALUES(@sid, @sess)) ns(' + self.sidColumnName + ', ' + self.sessColumnName + ') ON (s.' + self.sidColumnName + '=ns.' + self.sidColumnName + ')' + + ' WHEN MATCHED THEN UPDATE SET s.' + self.sessColumnName + '=@sess, s.' + self.expiresColumnName + '=DATEADD(ms, @duration, SYSUTCDATETIME())' + + ' WHEN NOT MATCHED THEN INSERT (' + self.sidColumnName + ', ' + self.sessColumnName + ', ' + self.expiresColumnName + ') VALUES (@sid, @sess, DATEADD(ms, @duration, SYSUTCDATETIME()));', + function(err) { + debug.sql('Executed MERGE'); + db.release(); + if (operation.retry(err) || err) { + return fn(err); + } + fn.apply(self, arguments); + } + ); + r.addParameter('sid', tedious.TYPES.VarChar, sid); + r.addParameter('sess', tedious.TYPES.NVarChar, JSON.stringify(sess)); + r.addParameter('duration', tedious.TYPES.Int, duration); + + debugSql(r); + db.execSql(r); + }); }); }; @@ -247,24 +270,30 @@ module.exports = function(connect){ TediousStore.prototype.destroy = function(sid, fn){ var self=this; - self.pool.acquire(function(err, db) { - if (err) - return fn(err); - var r = new tedious.Request( - 'DELETE s FROM ' + self.tableName + ' s WHERE s.' + self.sidColumnName + '=@sid', - function(err) { - debug.sql('Executed DELETE'); - db.release(); - if (err) - return fn(err); - return fn(err, true); - } - ); - r.addParameter('sid', tedious.TYPES.VarChar, sid); - - debugSql(r); - db.execSql(r); + var operation=retry.operation(self.retryOptions); + operation.attempt(function() { + self.pool.acquire(function(err, db) { + if (operation.retry(err) || err) { + return fn(err); + } + + var r = new tedious.Request( + 'DELETE s FROM ' + self.tableName + ' s WHERE s.' + self.sidColumnName + '=@sid', + function(err) { + debug.sql('Executed DELETE'); + db.release(); + if (operation.retry(err) || err) { + return fn(err); + } + return fn(null, true); + } + ); + r.addParameter('sid', tedious.TYPES.VarChar, sid); + + debugSql(r); + db.execSql(r); + }); }); }; @@ -277,30 +306,35 @@ module.exports = function(connect){ TediousStore.prototype.length = function(fn){ var self=this; - self.pool.acquire(function(err, db) { - if (err) - return fn(err); - var r = new tedious.Request( - 'SELECT @count=COUNT(*) FROM ' + self.tableName, - function(err, rowCount) { - debug.sql('Executed SELECT'); - db.release(); + var operation=retry.operation(self.retryOptions); + operation.attempt(function() { + self.pool.acquire(function(err, db) { if (err) - return fn(err); - if (!rowCount || rowCount!==1) - return fn(); - } - ); - r.on('returnValue', function(parameterName, value, metadata) { - if (!value) - return fn(); - return fn(null, value); + return fn(err); + + var r = new tedious.Request( + 'SELECT @count=COUNT(*) FROM ' + self.tableName, + function(err, rowCount) { + debug.sql('Executed SELECT'); + db.release(); + if (operation.retry(err) || err) { + return fn(err); + } + if (!rowCount || rowCount!==1) + return fn(); + } + ); + r.on('returnValue', function(parameterName, value, metadata) { + if (!value) + return fn(); + return fn(null, value); + }); + r.addOutputParameter('count', tedious.TYPES.Int); + + debugSql(r); + db.execSql(r); }); - r.addOutputParameter('count', tedious.TYPES.Int); - - debugSql(r); - db.execSql(r); }); }; @@ -314,23 +348,29 @@ module.exports = function(connect){ TediousStore.prototype.clear = function(fn){ var self=this; - self.pool.acquire(function(err, db) { - if (err) - return fn(err); - var r = new tedious.Request( - 'TRUNCATE TABLE ' + self.tableName, - function(err) { - debug.sql('Executed TRUNCATE'); - db.release(); - if (err) - return fn(err); - fn(null, true); - } - ); - - debugSql(r); - db.execSql(r); + var operation=retry.operation(self.retryOptions); + operation.attempt(function() { + self.pool.acquire(function(err, db) { + if (operation.retry(err) || err) { + return fn(err); + } + + var r = new tedious.Request( + 'TRUNCATE TABLE ' + self.tableName, + function(err) { + debug.sql('Executed TRUNCATE'); + db.release(); + if (operation.retry(err) || err) { + return fn(err); + } + fn(null, true); + } + ); + + debugSql(r); + db.execSql(r); + }); }); }; @@ -345,27 +385,33 @@ module.exports = function(connect){ */ TediousStore.prototype.touch = function (sid, sess, fn) { var self = this; - self.pool.acquire(function (err, db) { - if (err) - return fn(err); - - var duration = sess.cookie.maxAge || oneDay; - var r = new tedious.Request( - 'UPDATE ' + self.tableName + ' SET ' + self.expiresColumnName + '=DATEADD(ms, @duration, SYSUTCDATETIME()) WHERE ' + self.sidColumnName + '=@sid', - function (err) { - debug.sql('Executed UPDATE'); - db.release(); - if (err) - return fn(err); - fn(null, true); - } - ); - r.addParameter('duration', tedious.TYPES.Int, duration); - r.addParameter('sid', tedious.TYPES.VarChar, sid); - - debugSql(r); - db.execSql(r); + var operation=retry.operation(self.retryOptions); + operation.attempt(function() { + self.pool.acquire(function (err, db) { + if (operation.retry(err) || err) { + return fn(err); + } + + var duration = sess.cookie.maxAge || oneDay; + + var r = new tedious.Request( + 'UPDATE ' + self.tableName + ' SET ' + self.expiresColumnName + '=DATEADD(ms, @duration, SYSUTCDATETIME()) WHERE ' + self.sidColumnName + '=@sid', + function (err) { + debug.sql('Executed UPDATE'); + db.release(); + if (operation.retry(err) || err) { + return fn(err); + } + fn(null, true); + } + ); + r.addParameter('duration', tedious.TYPES.Int, duration); + r.addParameter('sid', tedious.TYPES.VarChar, sid); + + debugSql(r); + db.execSql(r); + }); }); }; diff --git a/package.json b/package.json index d36eee9..11a2136 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,13 @@ { "name": "connect-tedious", "description": "Connect session store for SQL Server, using Tedious.", - "version": "1.0.0", + "version": "1.1.0", "author": "Mathieu Cartoixa", "main": "lib/connect-tedious", "keywords": ["sql-server", "tds", "connection", "session", "store"], "dependencies": { "node-connection-string-builder": "^0.0.1", + "retry": "^0.9.0", "tedious": "^1.13.2", "tedious-connection-pool": "^0.3.9", "debug": "^2.2.0"