Permalink
Browse files

Avoid smashing the stack if too many no-op transactions occur in a row

  • Loading branch information...
1 parent ce02674 commit 8e11237a9781ebb61505e773d24d0cffc7d15d39 @jhs jhs committed Apr 7, 2012
Showing with 53 additions and 1 deletion.
  1. +4 −1 lib/txn.js
  2. +49 −0 test/txn.js
View
5 lib/txn.js
@@ -87,7 +87,10 @@ function couch_doc_txn(fetch_req, operation, callback) {
})
txn.on('done', function(doc) {
- return callback(null, doc, txn);
+ // A huge string of no-op changes can push the stack too deeply.
+ process.nextTick(function() {
+ callback(null, doc, txn);
+ })
})
txn.start();
View
49 test/txn.js
@@ -536,4 +536,53 @@ function couch_errors(done) {
}
},
+{'timeout': 30*1000},
+function avoid_smashing_the_stack(done) {
+ var depth_limit
+ find_limit(1)
+
+ function find_limit(depth) {
+ if(depth > 25000)
+ throw new Error('Cannot find stack depth limit after 25,000 function calls')
+
+ try { find_limit(depth + 1) }
+ catch (er) {
+ depth_limit = depth * 5
+ setTimeout(find_txn_limit, 100)
+ }
+ }
+
+ function find_txn_limit() {
+ assert.ok(depth_limit, 'Depth limit found')
+
+ // The idea is to produce zero i/o so Txn always calls the callback immediately, never allowing the stack to unwind.
+ , no_change = function(doc, to_txn) { return to_txn() }
+ , error = null
+
+ function opts() { return {'couch':COUCH, 'db':DB, 'doc':{'_id':'txn_limit_doc'}, 'timeout':60*1000} }
+
+ //console.log('depth_limit = %d', depth_limit)
+ txn_depth(1)
+ function txn_depth(depth) {
+ if(depth >= depth_limit)
+ return check_results()
+
+ txn(opts(), no_change, function(er) {
+ if(er) throw er;
+ try { txn_depth(depth + 1) } // Try one level deeper.
+ catch (er) {
+ error = er
+ process.nextTick(check_results)
+ }
+ })
+ }
+
+ function check_results() {
+ //if(error) console.error('Error: %s', error.stack)
+ assert.ok(error === null, 'Txn called back deeper than the stack allows')
+ done()
+ }
+ }
+},
+
] // TESTS

0 comments on commit 8e11237

Please sign in to comment.