Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <h1>Constraining the flow of requests into the server</h1> <p>It's possible to prevent overload of the built-in <code>Server</code> and its HTTP/HTTPS variants by setting the <code>maxConnections</code> property on the instance. Setting this property will cause node to stop <code>accept()</code>ing connections and force the operating system to drop requests when the <code>listen()</code> backlog is full and the application is already handling <code>maxConnections</code> requests.</p> <h1>Throttling outgoing requests</h1> <p>Sometimes, it's necessary to throttle outgoing requests, as in the example script from the question. </p> <h2>Using node directly or using a generic pool</h2> <p>As the question demonstrates, unchecked use of the node network subsystem directly can result in out of memory errors. Something like <code>node-pool</code> makes the active pool management attractive, but it doesn't solve the fundamental problem of unconstrained queuing. The reason for this is that <code>node-pool</code> doesn't provide any feedback about the state of the client pool.</p> <p><strong>UPDATE</strong>: As of v1.0.7 node-pool includes a patch inspired by this post to add a boolean return value to <code>acquire()</code>. The code in the following section is no longer necessary and the example with the streams pattern is working code with node-pool.</p> <h2>Cracking open the abstraction</h2> <p>As demonstrated by <a href="https://stackoverflow.com/questions/6623683/node-js-process-out-of-memory-in-http-request-loop/6624173#6624173">Andrey Sidorov</a>, a solution can be reached by tracking the queue size explicitly and mingling the queuing code with the requesting code:</p> <pre class="lang-js prettyprint-override"><code>var useExplicitThrottling = function () { var active = 0 var remaining = 10 var queueRequests = function () { while(active &lt; 2 &amp;&amp; --remaining &gt;= 0) { active++; pool.acquire(function (err, client) { if (err) { console.log("Error acquiring from pool") if (--active &lt; 2) queueRequests() return } console.log("Handling request with client " + client) setTimeout(function () { pool.release(client) if(--active &lt; 2) { queueRequests() } }, 1000) }) } } queueRequests(10) console.log("Finished!") } </code></pre> <h2>Borrowing the streams pattern</h2> <p>The <a href="http://nodejs.org/docs/v0.4.12/api/streams.html#streams" rel="nofollow noreferrer">streams</a> pattern is a solution which is idiomatic in node. Streams have a <code>write</code> operation which returns <code>false</code> when the stream cannot buffer more data. The same pattern can be applied to a pool object with <code>acquire()</code> returning <code>false</code> when the maximum number of clients have been acquired. A <code>drain</code> event is emitted when the number of active clients drops below the maximum. The pool abstraction is closed again and it's possible to omit explicit references to the pool size.</p> <pre class="lang-js prettyprint-override"><code>var useStreams = function () { var queueRequests = function (remaining) { var full = false pool.once('drain', function() { if (remaining) queueRequests(remaining) }) while(!full &amp;&amp; --remaining &gt;= 0) { console.log("Sending request...") full = !pool.acquire(function (err, client) { if (err) { console.log("Error acquiring from pool") return } console.log("Handling request with client " + client) setTimeout(pool.release, 1000, client) }) } } queueRequests(10) console.log("Finished!") } </code></pre> <h2>Fibers</h2> <p>An alternative solution can be obtained by providing a blocking abstraction on top of the queue. The <a href="https://github.com/laverdet/node-fibers" rel="nofollow noreferrer"><code>fibers</code></a> module exposes <a href="https://secure.wikimedia.org/wikipedia/en/wiki/Coroutine" rel="nofollow noreferrer">coroutines</a> that are implemented in C++. By using fibers, it's possible to block an execution context without blocking the node event loop. While I find this approach to be quite elegant, it is often overlooked in the node community because of a curious aversion to all things synchronous-looking. Notice that, excluding the <code>callcc</code> utility, the actual loop logic is wonderfully concise.</p> <pre class="lang-js prettyprint-override"><code>/* This is the call-with-current-continuation found in Scheme and other * Lisps. It captures the current call context and passes a callback to * resume it as an argument to the function. Here, I've modified it to fit * JavaScript and node.js paradigms by making it a method on Function * objects and using function (err, result) style callbacks. */ Function.prototype.callcc = function(context /* args... */) { var that = this, caller = Fiber.current, fiber = Fiber(function () { that.apply(context, Array.prototype.slice.call(arguments, 1).concat( function (err, result) { if (err) caller.throwInto(err) else caller.run(result) } )) }) process.nextTick(fiber.run.bind(fiber)) return Fiber.yield() } var useFibers = function () { var remaining = 10 while(--remaining &gt;= 0) { console.log("Sending request...") try { client = pool.acquire.callcc(this) console.log("Handling request with client " + client); setTimeout(pool.release, 1000, client) } catch (x) { console.log("Error acquiring from pool") } } console.log("Finished!") } </code></pre> <h1>Conclusion</h1> <p>There are a number of correct ways to approach the problem. However, for library authors or applications that require a single pool to be shared in many contexts it is best to properly encapsulate the pool. Doing so helps prevent errors and produces cleaner, more modular code. Preventing unconstrained queuing then becomes an evented dance or a coroutine pattern. I hope this answer dispels a lot of FUD and confusion around blocking-style code and asynchronous behavior and encourages you to write code which makes you happy.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload