[Pkg-javascript-commits] [node-generic-pool] 08/13: Imported Upstream version 2.0.3

Jérémy Lal kapouer at alioth.debian.org
Mon Sep 2 21:54:25 UTC 2013


This is an automated email from the git hooks/post-receive script.

kapouer pushed a commit to branch master
in repository node-generic-pool.

commit 599a837cbe11a673194d437544d43178813a093f
Author: Jérémy Lal <kapouer at melix.org>
Date:   Mon Sep 2 23:47:43 2013 +0200

    Imported Upstream version 2.0.3
---
 .gitignore                |    1 +
 .travis.yml               |    4 +
 Makefile                  |    4 +
 README.md                 |  141 ++++++++++++++++++--
 lib/generic-pool.js       |  195 ++++++++++++++++++++-------
 package.json              |   13 +-
 test/generic-pool.test.js |  325 ++++++++++++++++++++++++++++++++++++++++++---
 7 files changed, 610 insertions(+), 73 deletions(-)

diff --git a/.gitignore b/.gitignore
index 06bf63f..fcb718b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
 fabfile.pyc
 node-pool.iml
 node-pool.tmproj
+node_modules
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..8111245
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,4 @@
+language: node_js
+node_js:
+  - 0.6
+  - 0.8
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..669888d
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,4 @@
+all:
+
+check:
+	npm test
diff --git a/README.md b/README.md
index 4d1a6d1..15d1a40 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,18 @@
+[![build status](https://secure.travis-ci.org/coopernurse/node-pool.png)](http://travis-ci.org/coopernurse/node-pool)
 
 # About
 
   Generic resource pool.  Can be used to reuse or throttle expensive resources such as
   database connections.
+  
+## 2.0 Release Warning
+
+The 2.0.0 release removed support for variable argument callbacks.  When you acquire
+a resource from the pool, your callback *must* accept two arguments: (err, obj)
+
+Previously this library attempted to determine the arity of the callback, but this resulted
+in a variety of issues.  This change eliminates these issues, and makes the acquire callback
+parameter order consistent with the factory.create callback.
 
 ## Installation
 
@@ -10,6 +20,37 @@
     
 ## History
 
+    2.0.3 - January 16 2013
+       - Merged #56/#57 - Add optional refreshIdle flag. If false, idle resources at the pool minimum will not be
+         destroyed/re-created. (contributed by wshaver)
+       - Merged #54 - Factory can be asked to validate pooled objects (contributed by tikonen)
+
+    2.0.2 - October 22 2012
+       - Fix #51, #48 - createResource() should check for null clientCb in err case (contributed by pooyasencha)
+       - Merged #52 - fix bug of infinite wait when create object aync error (contributed by windyrobin)
+       - Merged #53 - change the position of dispense and callback to ensure the time order (contributed by windyrobin)
+    
+    2.0.1 - August 29 2012
+       - Fix #44 - leak of 'err' and 'obj' in createResource()
+       - Add devDependencies block to package.json
+       - Add travis-ci.org integration
+       
+    2.0.0 - July 31 2012
+       - Non-backwards compatible change: remove adjustCallback
+          - acquire() callback must accept two params: (err, obj)
+       - Add optional 'min' param to factory object that specifies minimum number of
+         resources to keep in pool
+       - Merged #38 (package.json/Makefile changes - contributed by strk)
+
+    1.0.12 - June 27 2012
+       - Merged #37 (Clear remove idle timer after destroyAllNow - contributed by dougwilson)
+
+    1.0.11 - June 17 2012
+       - Merged #36 ("pooled" method to perform function decoration for pooled methods - contributed by cosbynator)
+
+    1.0.10 - May 3 2012
+       - Merged #35 (Remove client from availbleObjects on destroy(client) - contributed by blax)
+
     1.0.9 - Dec 18 2011
        - Merged #25 (add getName() - contributed by BryanDonovan)
        - Merged #27 (remove sys import - contributed by botker)
@@ -50,8 +91,10 @@
 
 ## Example
 
+### Step 1 - Create pool using a factory object
+
     // Create a MySQL connection pool with
-    // a max of 10 connections and a 30 second max idle time
+    // a max of 10 connections, a min of 2, and a 30 second max idle time
     var poolModule = require('generic-pool');
     var pool = poolModule.Pool({
         name     : 'mysql',
@@ -69,19 +112,57 @@
         },
         destroy  : function(client) { client.end(); },
         max      : 10,
+        // optional. if you set this, make sure to drain() (see step 3)
+        min      : 2, 
+        // specifies how long a resource can stay idle in pool before being removed
         idleTimeoutMillis : 30000,
-        log : true
+         // if true, logs via console.log - can also be a function
+        log : true 
     });
+    
+### Step 2 - Use pool in your code to acquire/release resources
 
     // acquire connection - callback function is called
     // once a resource becomes available
     pool.acquire(function(err, client) {
-        client.query("select * from foo", [], function() {
-            // return object back to pool
-            pool.release(client);
-        });
+        if (err) {
+            // handle error - this is generally the err from your
+            // factory.create function  
+        }
+        else {
+            client.query("select * from foo", [], function() {
+                // return object back to pool
+                pool.release(client);
+            });
+        }
+    });
+    
+### Step 3 - Drain pool during shutdown (optional)
+    
+If you are shutting down a long-lived process, you may notice
+that node fails to exit for 30 seconds or so.  This is a side
+effect of the idleTimeoutMillis behavior -- the pool has a 
+setTimeout() call registered that is in the event loop queue, so
+node won't terminate until all resources have timed out, and the pool
+stops trying to manage them.  
+
+This behavior will be more problematic when you set factory.min > 0,
+as the pool will never become empty, and the setTimeout calls will
+never end.  
+
+In these cases, use the pool.drain() function.  This sets the pool
+into a "draining" state which will gracefully wait until all 
+idle resources have timed out.  For example, you can call:
+    
+    // Only call this once in your application -- at the point you want
+    // to shutdown and stop using this pool.
+    pool.drain(function() {
+	    pool.destroyAllNow();
     });
     
+If you do this, your node process will exit gracefully.
+    
+    
 ## Documentation
 
     Pool() accepts an object with these slots:
@@ -91,12 +172,23 @@
                            should call callback() with the created resource
                destroy : function that accepts a resource and destroys it
                    max : maximum number of resources to create at any given time
+                         optional (default=1)
+                   min : minimum number of resources to keep in pool at any given time
+                         if this is set > max, the pool will silently set the min
+                         to factory.max - 1
+                         optional (default=0)
+           refreshIdle : boolean that specifies whether idle resources at or below the min threshold
+                         should be destroyed/re-created.  optional (default=true)
      idleTimeoutMillis : max milliseconds a resource can go unused before it should be destroyed
                          (default 30000)
     reapIntervalMillis : frequency to check for idle resources (default 1000),
          priorityRange : int between 1 and x - if set, borrowers can specify their
                          relative priority in the queue if no resources are available.
                          see example.  (default 1)
+              validate : function that accepts a pooled resource and returns true if the resource
+                         is OK to use, or false if the object is invalid.  Invalid objects will be destroyed.
+                         This function is called in acquire() before returning a resource from the pool. 
+                         Optional.  Default function always returns true.
                    log : true/false or function -
                            If a log is a function, it will be called with two parameters:
                                                     - log string
@@ -107,7 +199,7 @@
 ## Priority Queueing
 
 The pool now supports optional priority queueing.  This becomes relevant when no resources 
-are available and the caller has to wait. acquire() accepts an optional priority int which 
+are available and the caller has to wait. `acquire()` accepts an optional priority int which 
 specifies the caller's relative position in the queue.
 
      // create pool with priorityRange of 3
@@ -144,8 +236,8 @@ specifies the caller's relative position in the queue.
 
 ## Draining
 
-If you know would like to terminate all the resources in your queue before
-their timeouts have been reached, you can use `shutdownNow()` in conjunction
+If you know would like to terminate all the resources in your pool before
+their timeouts have been reached, you can use `destroyAllNow()` in conjunction
 with `drain()`:
 
     pool.drain(function() {
@@ -155,6 +247,35 @@ with `drain()`:
 One side-effect of calling `drain()` is that subsequent calls to `acquire()`
 will throw an Error.
 
+## Pooled function decoration
+
+To transparently handle object acquisition for a function, 
+one can use `pooled()`:
+
+    var privateFn, publicFn;
+    publicFn = pool.pooled(privateFn = function(client, arg, cb) {
+        // Do something with the client and arg. Client is auto-released when cb is called
+        cb(null, arg);
+    });
+
+Keeping both private and public versions of each function allows for pooled 
+functions to call other pooled functions with the same member. This is a handy
+pattern for database transactions:
+
+    var privateTop, privateBottom, publicTop, publicBottom;
+    publicBottom = pool.pooled(privateBottom = function(client, arg, cb) {
+        //Use client, assumed auto-release 
+    });
+
+    publicTop = pool.pooled(privateTop = function(client, cb) {
+        // e.g., open a database transaction
+        privateBottom(client, "arg", function(err, retVal) {
+            if(err) { return cb(err); }
+            // e.g., close a transaction
+            cb();
+        });
+    });
+
 ## Pool info
 
 The following functions will let you get information about the pool:
@@ -182,7 +303,7 @@ The following functions will let you get information about the pool:
 
 (The MIT License)
 
-Copyright (c) 2010-2011 James Cooper <james at bitmechanic.com>
+Copyright (c) 2010-2013 James Cooper <james at bitmechanic.com>
 
 Permission is hereby granted, free of charge, to any person obtaining
 a copy of this software and associated documentation files (the
diff --git a/lib/generic-pool.js b/lib/generic-pool.js
index a1b7cb6..6bd3aa9 100644
--- a/lib/generic-pool.js
+++ b/lib/generic-pool.js
@@ -70,9 +70,18 @@ var PriorityQueue = function(size) {
  * @param {Function} factory.destroy
  *   Should gently close any resources that the item is using.
  *   Called before the items is destroyed.
+ * @param {Function} factory.validate
+ *   Should return true if connection is still valid and false
+ *   If it should be removed from pool. Called before item is
+ *   acquired from pool.
  * @param {Number} factory.max
- *   Maximum numnber of items that can exist at the same time.
+ *   Maximum number of items that can exist at the same time.  Default: 1.
  *   Any further acquire requests will be pushed to the waiting list.
+ * @param {Number} factory.min
+ *   Minimum number of items in pool (including in-use). Default: 0.
+ *   When the pool is created, or a resource destroyed, this minimum will
+ *   be checked. If the pool resource count is below the minimum, a new
+ *   resource will be created and added to the pool.
  * @param {Number} factory.idleTimeoutMillis
  *   Delay in milliseconds after the idle items in the pool will be destroyed.
  *   And idle item is that is not acquired yet. Waiting items doesn't count here.
@@ -83,7 +92,8 @@ var PriorityQueue = function(size) {
  *   that will be used instead. The function expects the arguments msg, loglevel
  * @param {Number} factory.priorityRange
  *   The range from 1 to be treated as a valid priority
- *
+ * @param {RefreshIdle} factory.refreshIdle
+ *   Should idle resources be destroyed and recreated every idleTimeoutMillis? Default: true.
  * @returns {Object} An Object pool that works with the supplied `factory`.
  */
 exports.Pool = function (factory) {
@@ -91,66 +101,84 @@ exports.Pool = function (factory) {
 
       idleTimeoutMillis = factory.idleTimeoutMillis || 30000,
       reapInterval = factory.reapIntervalMillis || 1000,
-
+      refreshIdle = ('refreshIdle' in factory) ? factory.refreshIdle : true,
       availableObjects = [],
       waitingClients = new PriorityQueue(factory.priorityRange || 1),
       count = 0,
       removeIdleScheduled = false,
+      removeIdleTimer = null,
       draining = false,
 
       // Prepare a logger function.
       log = factory.log ?
         (function (str, level) {
-           typeof factory.log === 'function' ?
-               factory.log(str, level) :
-               console.log(level.toUpperCase() + " pool " + factory.name + " - " + str);
+           if (typeof factory.log === 'function') {
+             factory.log(str, level);
+           }
+           else {
+             console.log(level.toUpperCase() + " pool " + factory.name + " - " + str);
+           }
          }
         ) :
         function () {};
 
-
-  factory.max = Math.max(factory.max, 1);
+  factory.validate = factory.validate || function() { return true; };
+        
+  factory.max = parseInt(factory.max, 10);
+  factory.min = parseInt(factory.min, 10);
+  
+  factory.max = Math.max(isNaN(factory.max) ? 1 : factory.max, 1);
+  factory.min = Math.min(isNaN(factory.min) ? 0 : factory.min, factory.max-1);
+  
+  ///////////////
 
   /**
    * Request the client to be destroyed. The factory's destroy handler
    * will also be called.
    *
+   * This should be called within an acquire() block as an alternative to release().
+   *
    * @param {Object} obj
    *   The acquired item to be destoyed.
    */
   me.destroy = function(obj) {
     count -= 1;
+    availableObjects = availableObjects.filter(function(objWithTimeout) {
+              return (objWithTimeout.obj !== obj);
+    });
     factory.destroy(obj);
+    
+    ensureMinimum();
   };
 
   /**
    * Checks and removes the available (idle) clients that have timed out.
    */
   function removeIdle() {
-    var toKeep = [],
+    var toRemove = [],
         now = new Date().getTime(),
         i,
-        al,
+        al, tr,
         timeout;
 
     removeIdleScheduled = false;
 
     // Go through the available (idle) items,
     // check if they have timed out
-    for (i = 0, al = availableObjects.length; i < al; i += 1) {
+    for (i = 0, al = availableObjects.length; i < al && (refreshIdle || (count - factory.min)) > toRemove.length ; i += 1) {
       timeout = availableObjects[i].timeout;
-      if (now < timeout) {
-        // Client hasn't timed out, so keep it.
-        toKeep.push(availableObjects[i]);
-      } else {
-        // The client timed out, call its destroyer.
+      if (now >= timeout) {
+        // Client timed out, so destroy it.
         log("removeIdle() destroying obj - now:" + now + " timeout:" + timeout, 'verbose');
-        me.destroy(availableObjects[i].obj);
-      }
+        toRemove.push(availableObjects[i].obj);
+      } 
+    }
+
+    for (i = 0, tr = toRemove.length; i < tr; i += 1) {
+      me.destroy(toRemove[i]);
     }
 
     // Replace the available items with the ones to keep.
-    availableObjects = toKeep;
     al = availableObjects.length;
 
     if (al > 0) {
@@ -170,7 +198,7 @@ exports.Pool = function (factory) {
   function scheduleRemoveIdle() {
     if (!removeIdleScheduled) {
       removeIdleScheduled = true;
-      setTimeout(removeIdle, reapInterval);
+      removeIdleTimer = setTimeout(removeIdle, reapInterval);
     }
   }
 
@@ -202,37 +230,65 @@ exports.Pool = function (factory) {
     var obj = null,
         objWithTimeout = null,
         err = null,
+        clientCb = null,
         waitingCount = waitingClients.size();
+        
     log("dispense() clients=" + waitingCount + " available=" + availableObjects.length, 'info');
     if (waitingCount > 0) {
-      if (availableObjects.length > 0) {
+      while (availableObjects.length > 0) {
         log("dispense() - reusing obj", 'verbose');
-        objWithTimeout = availableObjects.shift();
-        adjustCallback(waitingClients.dequeue(), err, objWithTimeout.obj);
+        objWithTimeout = availableObjects[0];
+        if (!factory.validate(objWithTimeout.obj)) {
+          me.destroy(objWithTimeout.obj);
+          continue;
+        }
+        availableObjects.shift();
+        clientCb = waitingClients.dequeue();
+        return clientCb(err, objWithTimeout.obj);
       }
-      else if (count < factory.max) {
-        count += 1;
-        log("dispense() - creating obj - count=" + count, 'verbose');
-        factory.create(function () {
-          var cb = waitingClients.dequeue();
-          if (arguments.length > 1) {
-            err = arguments[0];
-            obj = arguments[1];
-          } else {
-            err = (arguments[0] instanceof Error) ? arguments[0] : null;
-            obj = (arguments[0] instanceof Error) ? null : arguments[0];
-          }
-          if (err) {
-            count -= 1;
-            adjustCallback(cb, err, obj);
-          } else {
-            if (cb) {
-              adjustCallback(cb, err, obj);
-            } else {
-              me.release(obj);
-            }
-          }
+      if (count < factory.max) {
+        createResource();
+      }
+    }
+  }
+  
+  function createResource() {
+    count += 1;
+    log("createResource() - creating obj - count=" + count + " min=" + factory.min + " max=" + factory.max, 'verbose');
+    factory.create(function () {
+      var err, obj;
+      var clientCb = waitingClients.dequeue();
+      if (arguments.length > 1) {
+        err = arguments[0];
+        obj = arguments[1];
+      } else {
+        err = (arguments[0] instanceof Error) ? arguments[0] : null;
+        obj = (arguments[0] instanceof Error) ? null : arguments[0];
+      }
+      if (err) {
+        count -= 1;
+        if (clientCb) {
+          clientCb(err, obj);
+        }
+        process.nextTick(function(){
+          dispense();
         });
+      } else {
+        if (clientCb) {
+          clientCb(err, obj);
+        } else {
+          me.release(obj);
+        }
+      }
+    });
+  }
+  
+  function ensureMinimum() {
+    var i, diff;
+    if (!draining && (count < factory.min)) {
+      diff = factory.min - count;
+      for (i = 0; i < diff; i++) {
+        createResource();
       }
     }
   }
@@ -325,6 +381,11 @@ exports.Pool = function (factory) {
    * invoked as part of a drain.  Does not prevent the creation of new
    * clients as a result of subsequent calls to acquire.
    *
+   * Note that if factory.min > 0, the pool will destroy all idle resources
+   * in the pool, but replace them with newly created resources up to the
+   * specified factory.min value.  If this is not desired, set factory.min
+   * to zero before calling destroyAllNow()
+   *
    * @param {Function} callback
    *   Optional. Callback invoked after all existing clients are destroyed.
    */
@@ -337,11 +398,51 @@ exports.Pool = function (factory) {
       me.destroy(obj.obj);
       obj = willDie.shift();
     }
+    removeIdleScheduled = false;
+    clearTimeout(removeIdleTimer);
     if (callback) {
       callback();
     }
   };
 
+  /**
+   * Decorates a function to use a acquired client from the object pool when called.
+   *
+   * @param {Function} decorated
+   *   The decorated function, accepting a client as the first argument and 
+   *   (optionally) a callback as the final argument.
+   *
+   * @param {Number} priority
+   *   Optional.  Integer between 0 and (priorityRange - 1).  Specifies the priority
+   *   of the caller if there are no available resources.  Lower numbers mean higher
+   *   priority.
+   */
+  me.pooled = function(decorated, priority) {
+    return function() {
+      var callerArgs = arguments;
+      var callerCallback = callerArgs[callerArgs.length - 1];
+      var callerHasCallback = typeof callerCallback === 'function';
+      me.acquire(function(err, client) {
+        if(err) {
+          if(callerHasCallback) {
+            callerCallback(err);
+          }
+          return;
+        }
+
+        var args = [client].concat(Array.prototype.slice.call(callerArgs, 0, callerHasCallback ? -1 : undefined));
+        args.push(function() {
+          me.release(client);
+          if(callerHasCallback) {
+            callerCallback.apply(null, arguments);
+          }
+        });
+        
+        decorated.apply(null, args);
+      }, priority);
+    };
+  };
+
   me.getPoolSize = function() {
     return count;
   };
@@ -358,5 +459,9 @@ exports.Pool = function (factory) {
     return waitingClients.size();
   };
 
+
+  // create initial resources (if factory.min > 0)
+  ensureMinimum();
+
   return me;
 };
diff --git a/package.json b/package.json
index 539c40b..f144731 100644
--- a/package.json
+++ b/package.json
@@ -1,13 +1,14 @@
 {
   "name": "generic-pool",
   "description": "Generic resource pooling for Node.JS",
-  "version": "1.0.9",
+  "version": "2.0.3",
   "author": "James Cooper <james at bitmechanic.com>",
   "contributors": [
     { "name": "James Cooper", "email": "james at bitmechanic.com" },
     { "name": "Peter Galiba", "email": "poetro at poetro.hu", "url": "http://poetro.hu/" },
     { "name": "Gary Dusbabek" },
-    { "name": "Tom MacWright", "url" : "http://www.developmentseed.org/" }
+    { "name": "Tom MacWright", "url" : "http://www.developmentseed.org/" },
+    { "name": "Douglas Christopher Wilson", "email": "doug at somethingdoug.com", "url" : "http://somethingdoug.com/" }
   ],
   "keywords": ["pool", "pooling", "throttle"],
   "main": "lib/generic-pool.js",
@@ -15,5 +16,11 @@
     "type": "git",
     "url": "http://github.com/coopernurse/node-pool.git"
   },
-  "engines": { "node": ">= 0.2.0" }
+  "devDependencies": {
+      "expresso": ">0.0.0"
+  },
+  "engines": { "node": ">= 0.2.0" },
+  "scripts": {
+     "test": "expresso -I lib test/*.js"
+  }
 }
diff --git a/test/generic-pool.test.js b/test/generic-pool.test.js
index d59162f..d7ac17a 100644
--- a/test/generic-pool.test.js
+++ b/test/generic-pool.test.js
@@ -7,8 +7,8 @@ module.exports = {
         var createCount  = 0;
         var destroyCount = 0;
         var borrowCount  = 0;
-
-        var pool = poolModule.Pool({
+        
+        var factory = {
             name     : 'test1',
             create   : function(callback) {
                 callback(null, { count: ++createCount });
@@ -16,7 +16,9 @@ module.exports = {
             destroy  : function(client) { destroyCount++; },
             max : 2,
             idleTimeoutMillis : 100
-        });
+        };
+
+        var pool = poolModule.Pool(factory);
 
         for (var i = 0; i < 10; i++) {
             var full = !pool.acquire(function(err, obj) {
@@ -32,11 +34,86 @@ module.exports = {
         }
 
         beforeExit(function() {
+            assert.equal(0, factory.min);
             assert.equal(2, createCount);
             assert.equal(2, destroyCount);
             assert.equal(10, borrowCount);
         });
     },
+    
+    'respects min limit' : function (beforeExit) {
+        var createCount  = 0;
+        var destroyCount = 0;
+        var borrowCount  = 0;
+
+        var pool = poolModule.Pool({
+            name     : 'test-min',
+            create   : function(callback) {
+                callback(null, { count: ++createCount });
+            },
+            destroy  : function(client) { destroyCount++; },
+            min : 1,
+            max : 2,
+            idleTimeoutMillis : 100
+        });
+        pool.drain();
+
+        beforeExit(function() {
+            assert.equal(0, pool.availableObjectsCount());
+            assert.equal(1, createCount);
+            assert.equal(1, destroyCount);
+        });
+    },
+    
+    'min and max limit defaults' : function (beforeExit) {
+      var factory = {
+        name    : "test-limit-defaults",
+        create  : function(callback) { callback(null, {}); },
+        destroy : function(client) { },
+        idleTimeoutMillis: 100
+      };
+      var pool = poolModule.Pool(factory);
+      
+      beforeExit(function() {
+        assert.equal(1, factory.max);
+        assert.equal(0, factory.min);
+      });
+    },
+    
+    'malformed min and max limits are ignored' : function (beforeExit) {
+      var factory = {
+        name    : "test-limit-defaults2",
+        create  : function(callback) { callback(null, {}); },
+        destroy : function(client) { },
+        idleTimeoutMillis: 100,
+        min : "asf",
+        max : [ ]
+      };
+      var pool = poolModule.Pool(factory);
+      
+      beforeExit(function() {
+        assert.equal(1, factory.max);
+        assert.equal(0, factory.min);
+      });
+    },
+    
+    'min greater than max sets to max minus one' : function (beforeExit) {
+      var factory = {
+        name    : "test-limit-defaults3",
+        create  : function(callback) { callback(null, {}); },
+        destroy : function(client) { },
+        idleTimeoutMillis: 100,
+        min : 5,
+        max : 3
+      };
+      var pool = poolModule.Pool(factory);
+      pool.drain();
+      
+      beforeExit(function() {
+        assert.equal(3, factory.max);
+        assert.equal(2, factory.min);
+      });
+    },
 
     'supports priority on borrow' : function(beforeExit) {
         var borrowTimeLow  = 0;
@@ -152,29 +229,55 @@ module.exports = {
         }, Error);
     },
 
-    'supports single arg callbacks' : function (beforeExit) {
+    'handle creation errors' : function (beforeExit) {
+        var created = 0;
         var pool = poolModule.Pool({
-            name     : 'test5',
-            create   : function(callback) { callback({ id : 1 }); },
-            destroy  : function(client) { destroyed.push(client.id); },
-            max : 2,
-            idleTimeoutMillis : 100
+            name     : 'test6',
+            create   : function(callback) {
+                if (created < 5) {
+                    callback(new Error('Error occurred.'));
+                } else {
+                    callback({ id : created });
+                }
+                created++;
+            },
+            destroy  : function(client) { },
+            max : 1,
+            idleTimeoutMillis : 1000
         });
+        // ensure that creation errors do not populate the pool.
+        for (var i = 0; i < 5; i++) {
+            pool.acquire(function(err, client) {
+                assert.ok(err instanceof Error);
+                assert.ok(client === null);
+            });
+        }
 
-        pool.acquire(function(client) {
-            assert.equal(client.id, 1);
+        var called = false;
+        pool.acquire(function(err, client) {
+            assert.ok(err === null);
+            assert.equal(typeof client.id, 'number');
+            called = true;
+        });
+        beforeExit(function() {
+            assert.ok(called);
+            assert.equal(pool.waitingClientsCount(), 0);
         });
     },
 
-    'handle creation errors' : function (beforeExit) {
+    'handle creation errors for delayed creates' : function (beforeExit) {
         var created = 0;
         var pool = poolModule.Pool({
             name     : 'test6',
             create   : function(callback) {
                 if (created < 5) {
-                    callback(new Error('Error occurred.'));
+                    setTimeout(function() {
+                        callback(new Error('Error occurred.'));
+                    }, 0);
                 } else {
-                    callback({ id : created });
+                    setTimeout(function() {
+                        callback({ id : created });
+                    }, 0);
                 }
                 created++;
             },
@@ -189,9 +292,126 @@ module.exports = {
                 assert.ok(client === null);
             });
         }
+        var called = false;
         pool.acquire(function(err, client) {
             assert.ok(err === null);
             assert.equal(typeof client.id, 'number');
+            called = true;
+        });
+        beforeExit(function() {
+            assert.ok(called);
+            assert.equal(pool.waitingClientsCount(), 0);
+        });
+    },
+
+    'pooled decorator should acquire and release' : function (beforeExit) {
+        var assertion_count = 0;
+        var destroyed_count = 0;
+        var pool = poolModule.Pool({
+            name     : 'test1',
+            create   : function(callback) { callback({id: Math.floor(Math.random()*1000)}); },
+            destroy  : function(client) { destroyed_count += 1; },
+            max : 1,
+            idleTimeoutMillis : 100
+        });
+
+        var pooledFn = pool.pooled(function(client, cb) {
+          assert.equal(typeof client.id, 'number');
+          assert.equal(pool.getPoolSize(), 1);
+          assertion_count += 2;
+          cb();
+        });
+
+        assert.equal(pool.getPoolSize(), 0);
+        assertion_count += 1;
+
+        pooledFn(function(err) {
+          if (err) { throw err; }
+          assert.ok(true);
+          assertion_count += 1;
+        });
+
+        beforeExit(function() {
+          assert.equal(assertion_count, 4);
+          assert.equal(destroyed_count, 1); 
+        });
+    },
+    
+    'pooled decorator should pass arguments and return values' : function(beforeExit) {
+        var assertion_count = 0;
+        var pool = poolModule.Pool({
+            name     : 'test1',
+            create   : function(callback) { callback({id: Math.floor(Math.random()*1000)}); },
+            destroy  : function(client) { },
+            max : 1,
+            idleTimeoutMillis : 100
+        });
+
+        var pooledFn = pool.pooled(function(client, arg1, arg2, cb) {
+          assert.equal(arg1, "First argument");
+          assert.equal(arg2, "Second argument");
+          assertion_count += 2;
+          cb(null, "First return", "Second return");
+        });
+
+        pooledFn("First argument", "Second argument", function(err, retVal1, retVal2) {
+          if(err) { throw err; }
+          assert.equal(retVal1, "First return");
+          assert.equal(retVal2, "Second return");
+          assertion_count += 2;
+        });
+
+        beforeExit(function() {
+          assert.equal(assertion_count, 4);
+        });
+    },
+
+    'pooled decorator should allow undefined callback' : function(beforeExit) {
+        var assertion_count = 0;
+        var pool = poolModule.Pool({
+            name     : 'test1',
+            create   : function(callback) { callback({id: Math.floor(Math.random()*1000)}); },
+            destroy  : function(client) { },
+            max : 1,
+            idleTimeoutMillis : 100
+        });
+
+        var pooledFn = pool.pooled(function(client, arg, cb) {
+          assert.equal(arg, "Arg!");
+          assertion_count += 1;
+          cb();
+        });
+
+        pooledFn("Arg!");
+
+        beforeExit(function() {
+          assert.equal(pool.getPoolSize(), 0);
+          assert.equal(assertion_count, 1);
+        });
+
+    },
+
+    'pooled decorator should forward pool errors' : function(beforeExit) {
+        var assertion_count = 0;
+        var pool = poolModule.Pool({
+            name     : 'test1',
+            create   : function(callback) { callback(new Error('Pool error')); },
+            destroy  : function(client) { },
+            max : 1,
+            idleTimeoutMillis : 100
+        });
+
+        var pooledFn = pool.pooled(function(cb) {
+          assert.ok(false, "Pooled function shouldn't be called due to a pool error");
+        });
+
+        pooledFn(function(err, obj) {
+          assert.equal(err.message, 'Pool error');
+          assertion_count += 1;
+        });
+
+        beforeExit(function() {
+          assert.equal(assertion_count, 1);
         });
     },
 
@@ -309,7 +529,7 @@ module.exports = {
 
         pool.acquire(function(err, obj){
           if (err) {throw err;}
-          assert.equal(logmessages.verbose[0], 'dispense() - creating obj - count=1');
+          assert.equal(logmessages.verbose[0], 'createResource() - creating obj - count=1 min=0 max=2');
           assert.equal(logmessages.info[0], 'dispense() clients=1 available=0');
           logmessages.info = [];
           logmessages.verbose = [];
@@ -319,6 +539,81 @@ module.exports = {
             assert.equal(logmessages.warn.length, 0);
           });
         });
+    },
+    
+    'removes from available objects on destroy': function(beforeExit){
+        var destroyCalled = false;
+        var factory = {
+            name: 'test',
+            create: function(callback) {callback(null, {}); },
+            destroy: function(client) {destroyCalled = true; },
+            max: 2,
+            idleTimeoutMillis: 100
+        };
+
+        var pool = poolModule.Pool(factory);
+        pool.acquire(function(err, obj){
+            pool.destroy(obj);            
+        });
+        assert.equal(destroyCalled, true);
+        assert.equal(pool.availableObjectsCount(), 0);        
+    },
+
+    'removes from available objects on validation failure': function(beforeExit){
+        var destroyCalled = false,
+            validateCalled = false,
+            count = 0;
+        var factory = {
+            name: 'test',
+            create: function(callback) {callback(null, {count: count++}); },
+            destroy: function(client) {destroyCalled = client.count; },
+            validate: function(client) {validateCalled = true; return client.count != 0;},
+            max: 2,
+            idleTimeoutMillis: 100
+        };
+
+        var pool = poolModule.Pool(factory);
+        pool.acquire(function(err, obj){
+            pool.release(obj);
+            assert.equal(obj.count, 0);
+
+            pool.acquire(function(err, obj){
+                pool.release(obj);
+                assert.equal(obj.count, 1);
+            });
+        });
+        assert.equal(validateCalled, true);
+        assert.equal(destroyCalled, 0);
+        assert.equal(pool.availableObjectsCount(), 1);
+    },
+
+    'do schedule again if error occured when creating new Objects async': function(beforeExit){
+        var factory = {
+            name: 'test',
+            create: function(callback) {
+              process.nextTick(function(){
+                var err = new Error('Create Error');
+                callback(err); 
+              })
+            },
+            destroy: function(client) {},
+            max: 1,
+            idleTimeoutMillis: 100
+        };
+
+        var getFlag = 0;
+        var pool = poolModule.Pool(factory);
+        pool.acquire(function(){});
+        pool.acquire(function(err, obj){
+           getFlag = 1;
+           assert(err);
+           assert.equal(pool.availableObjectsCount(), 0);        
+       });
+
+       beforeExit(function() {
+         assert.equal(getFlag, 1);   
+       });
     }
 
+
 };

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/collab-maint/node-generic-pool.git



More information about the Pkg-javascript-commits mailing list