Skip to content
This repository

"pooled" method to perform function decoration for pooled methods #36

Merged
merged 4 commits into from almost 2 years ago

2 participants

Thomas Dimson James Cooper
Thomas Dimson

Hi there,

I've created a branch that introduces a method on object pools called pooled(). This acts as a python-style function decorator, that wraps methods in code that will auto-acquire on call, and auto-release on callback. The general idea is described in the README.md of my branch, and in my blog post here: http://blog.argteam.com/coding/connection-pooling-with-node-postgres/ (the version in my branch is much more robust than the blog post).

This pattern has greatly simplified re-using methods within transactions and has cleaned up my code a lot. Let me know what you think.

James Cooper coopernurse merged commit df2579e into from June 17, 2012
James Cooper coopernurse closed this June 17, 2012
James Cooper
Owner

Thanks for the contribution! changes merged, README updated, and new version published to npm.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
31  README.md
Source Rendered
@@ -110,7 +110,7 @@
110 110
 ## Priority Queueing
111 111
 
112 112
 The pool now supports optional priority queueing.  This becomes relevant when no resources 
113  
-are available and the caller has to wait. acquire() accepts an optional priority int which 
  113
+are available and the caller has to wait. `acquire()` accepts an optional priority int which 
114 114
 specifies the caller's relative position in the queue.
115 115
 
116 116
      // create pool with priorityRange of 3
@@ -158,6 +158,35 @@ with `drain()`:
158 158
 One side-effect of calling `drain()` is that subsequent calls to `acquire()`
159 159
 will throw an Error.
160 160
 
  161
+## Pooled function decoration
  162
+
  163
+To transparently handle object acquisition for a function, 
  164
+one can use `pooled()`:
  165
+
  166
+    var privateFn, publicFn;
  167
+    publicFn = pool.pooled(privateFn = function(client, arg, cb) {
  168
+        // Do something with the client and arg. Client is auto-released when cb is called
  169
+        cb(null, arg);
  170
+    });
  171
+
  172
+Keeping both private and public versions of each function allows for pooled 
  173
+functions to call other pooled functions with the same member. This is a handy
  174
+pattern for database transactions:
  175
+
  176
+    var privateTop, privateBottom, publicTop, publicBottom;
  177
+    publicBottom = pool.pooled(privateBottom = function(client, arg, cb) {
  178
+        //Use client, assumed auto-release 
  179
+    });
  180
+
  181
+    publicTop = pool.pooled(privateTop = function(client, cb) {
  182
+        // e.g., open a database transaction
  183
+        privateBottom(client, "arg", function(err, retVal) {
  184
+            if(err) { return cb(err); }
  185
+            // e.g., close a transaction
  186
+            cb();
  187
+        });
  188
+    });
  189
+
161 190
 ## Pool info
162 191
 
163 192
 The following functions will let you get information about the pool:
39  lib/generic-pool.js
@@ -348,6 +348,44 @@ exports.Pool = function (factory) {
348 348
     }
349 349
   };
350 350
 
  351
+  /**
  352
+   * Decorates a function to use a acquired client from the object pool when called.
  353
+   *
  354
+   * @param {Function} decorated
  355
+   *   The decorated function, accepting a client as the first argument and 
  356
+   *   (optionally) a callback as the final argument.
  357
+   *
  358
+   * @param {Number} priority
  359
+   *   Optional.  Integer between 0 and (priorityRange - 1).  Specifies the priority
  360
+   *   of the caller if there are no available resources.  Lower numbers mean higher
  361
+   *   priority.
  362
+   */
  363
+  me.pooled = function(decorated, priority) {
  364
+    return function() {
  365
+      var callerArgs = arguments;
  366
+      var callerCallback = callerArgs[callerArgs.length - 1];
  367
+      var callerHasCallback = typeof callerCallback === 'function';
  368
+      me.acquire(function(err, client) {
  369
+        if(err) {
  370
+          if(callerHasCallback) {
  371
+            callerCallback(err);
  372
+          }
  373
+          return;
  374
+        }
  375
+
  376
+        var args = [client].concat(Array.prototype.slice.call(callerArgs, 0, callerHasCallback ? -1 : undefined));
  377
+        args.push(function() {
  378
+          me.release(client);
  379
+          if(callerHasCallback) {
  380
+            callerCallback.apply(null, arguments);
  381
+          }
  382
+        });
  383
+        
  384
+        decorated.apply(null, args);
  385
+      }, priority);
  386
+    };
  387
+  };
  388
+
351 389
   me.getPoolSize = function() {
352 390
     return count;
353 391
   };
@@ -364,5 +402,6 @@ exports.Pool = function (factory) {
364 402
     return waitingClients.size();
365 403
   };
366 404
 
  405
+
367 406
   return me;
368 407
 };
111  test/generic-pool.test.js
@@ -195,6 +195,117 @@ module.exports = {
195 195
         });
196 196
     },
197 197
 
  198
+    'pooled decorator should acquire and release' : function (beforeExit) {
  199
+        var assertion_count = 0;
  200
+        var destroyed_count = 0;
  201
+        var pool = poolModule.Pool({
  202
+            name     : 'test1',
  203
+            create   : function(callback) { callback({id: Math.floor(Math.random()*1000)}); },
  204
+            destroy  : function(client) { destroyed_count += 1; },
  205
+            max : 1,
  206
+            idleTimeoutMillis : 100
  207
+        });
  208
+
  209
+        var pooledFn = pool.pooled(function(client, cb) {
  210
+          assert.equal(typeof client.id, 'number');
  211
+          assert.equal(pool.getPoolSize(), 1);
  212
+          assertion_count += 2;
  213
+          cb();
  214
+        });
  215
+
  216
+        assert.equal(pool.getPoolSize(), 0);
  217
+        assertion_count += 1;
  218
+
  219
+        pooledFn(function(err) {
  220
+          if (err) { throw err; }
  221
+          assert.ok(true);
  222
+          assertion_count += 1;
  223
+        });
  224
+
  225
+        beforeExit(function() {
  226
+          assert.equal(assertion_count, 4);
  227
+          assert.equal(destroyed_count, 1); 
  228
+        });
  229
+    },
  230
+    
  231
+    'pooled decorator should pass arguments and return values' : function(beforeExit) {
  232
+        var assertion_count = 0;
  233
+        var pool = poolModule.Pool({
  234
+            name     : 'test1',
  235
+            create   : function(callback) { callback({id: Math.floor(Math.random()*1000)}); },
  236
+            destroy  : function(client) { },
  237
+            max : 1,
  238
+            idleTimeoutMillis : 100
  239
+        });
  240
+
  241
+        var pooledFn = pool.pooled(function(client, arg1, arg2, cb) {
  242
+          assert.equal(arg1, "First argument");
  243
+          assert.equal(arg2, "Second argument");
  244
+          assertion_count += 2;
  245
+          cb(null, "First return", "Second return");
  246
+        });
  247
+
  248
+        pooledFn("First argument", "Second argument", function(err, retVal1, retVal2) {
  249
+          if(err) { throw err; }
  250
+          assert.equal(retVal1, "First return");
  251
+          assert.equal(retVal2, "Second return");
  252
+          assertion_count += 2;
  253
+        });
  254
+
  255
+        beforeExit(function() {
  256
+          assert.equal(assertion_count, 4);
  257
+        });
  258
+    },
  259
+
  260
+    'pooled decorator should allow undefined callback' : function(beforeExit) {
  261
+        var assertion_count = 0;
  262
+        var pool = poolModule.Pool({
  263
+            name     : 'test1',
  264
+            create   : function(callback) { callback({id: Math.floor(Math.random()*1000)}); },
  265
+            destroy  : function(client) { },
  266
+            max : 1,
  267
+            idleTimeoutMillis : 100
  268
+        });
  269
+
  270
+        var pooledFn = pool.pooled(function(client, arg, cb) {
  271
+          assert.equal(arg, "Arg!");
  272
+          assertion_count += 1;
  273
+          cb();
  274
+        });
  275
+
  276
+        pooledFn("Arg!");
  277
+
  278
+        beforeExit(function() {
  279
+          assert.equal(pool.getPoolSize(), 0);
  280
+          assert.equal(assertion_count, 1);
  281
+        });
  282
+
  283
+    },
  284
+
  285
+    'pooled decorator should forward pool errors' : function(beforeExit) {
  286
+        var assertion_count = 0;
  287
+        var pool = poolModule.Pool({
  288
+            name     : 'test1',
  289
+            create   : function(callback) { callback(new Error('Pool error')); },
  290
+            destroy  : function(client) { },
  291
+            max : 1,
  292
+            idleTimeoutMillis : 100
  293
+        });
  294
+
  295
+        var pooledFn = pool.pooled(function(cb) {
  296
+          assert.ok(false, "Pooled function shouldn't be called due to a pool error");
  297
+        });
  298
+
  299
+        pooledFn(function(err, obj) {
  300
+          assert.equal(err.message, 'Pool error');
  301
+          assertion_count += 1;
  302
+        });
  303
+
  304
+        beforeExit(function() {
  305
+          assert.equal(assertion_count, 1);
  306
+        });
  307
+    },
  308
+
198 309
     'getPoolSize' : function (beforeExit) {
199 310
         var assertion_count = 0;
200 311
         var pool = poolModule.Pool({
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.