Skip to content

Commit 5ff6fac

Browse files
authored
Remove fast-future (Level/leveldown#638) (facebook#133)
1 parent 2815ef3 commit 5ff6fac

File tree

4 files changed

+131
-14
lines changed

4 files changed

+131
-14
lines changed

binding.cc

+5
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,8 @@ struct Iterator {
702702

703703
bool IteratorNext (std::vector<std::pair<std::string, std::string> >& result) {
704704
size_t size = 0;
705+
uint32_t cacheSize = 0;
706+
705707
while (true) {
706708
std::string key, value;
707709
bool ok = Read(key, value);
@@ -717,6 +719,9 @@ struct Iterator {
717719
size = size + key.size() + value.size();
718720
if (size > highWaterMark_) return true;
719721

722+
// Limit the size of the cache to prevent starving the event loop
723+
// in JS-land while we're recursively calling process.nextTick().
724+
if (++cacheSize >= 1000) return true;
720725
} else {
721726
return false;
722727
}

iterator.js

+2-13
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
const util = require('util')
22
const AbstractIterator = require('abstract-leveldown').AbstractIterator
3-
const fastFuture = require('fast-future')
43
const binding = require('./binding')
54

65
function Iterator (db, options) {
@@ -9,7 +8,6 @@ function Iterator (db, options) {
98
this.context = binding.iterator_init(db.context, options)
109
this.cache = null
1110
this.finished = false
12-
this.fastFuture = fastFuture()
1311
}
1412

1513
util.inherits(Iterator, AbstractIterator)
@@ -26,20 +24,11 @@ Iterator.prototype._seek = function (target) {
2624

2725
Iterator.prototype._next = function (callback) {
2826
var that = this
29-
var key
30-
var value
3127

3228
if (this.cache && this.cache.length) {
33-
key = this.cache.pop()
34-
value = this.cache.pop()
35-
36-
this.fastFuture(function () {
37-
callback(null, key, value)
38-
})
29+
process.nextTick(callback, null, this.cache.pop(), this.cache.pop())
3930
} else if (this.finished) {
40-
this.fastFuture(function () {
41-
callback()
42-
})
31+
process.nextTick(callback)
4332
} else {
4433
binding.iterator_next(this.context, function (err, array, finished) {
4534
if (err) return callback(err)

package.json

-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
},
2121
"dependencies": {
2222
"abstract-leveldown": "~6.0.3",
23-
"fast-future": "~1.0.2",
2423
"napi-macros": "~2.0.0",
2524
"node-gyp-build": "~4.1.0"
2625
},

test/iterator-starvation-test.js

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
'use strict'
2+
3+
const test = require('tape')
4+
const testCommon = require('./common')
5+
const sourceData = []
6+
7+
// For this test the number of records in the db must be a multiple of
8+
// the hardcoded fast-future limit (1000) or a cache size limit in C++.
9+
for (let i = 0; i < 1e4; i++) {
10+
sourceData.push({
11+
type: 'put',
12+
key: i.toString(),
13+
value: ''
14+
})
15+
}
16+
17+
test('setUp', testCommon.setUp)
18+
19+
test('iterator does not starve event loop', function (t) {
20+
t.plan(6)
21+
22+
const db = testCommon.factory()
23+
24+
db.open(function (err) {
25+
t.ifError(err, 'no open error')
26+
27+
// Insert test data
28+
db.batch(sourceData.slice(), function (err) {
29+
t.ifError(err, 'no batch error')
30+
31+
// Set a high highWaterMark to fill up the cache entirely
32+
const it = db.iterator({ highWaterMark: Math.pow(1024, 3) })
33+
34+
let breaths = 0
35+
let entries = 0
36+
let scheduled = false
37+
38+
// Iterate continuously while also scheduling work with setImmediate(),
39+
// which should be given a chance to run because we limit the tick depth.
40+
const next = function () {
41+
it.next(function (err, key, value) {
42+
if (err || (key === undefined && value === undefined)) {
43+
t.ifError(err, 'no next error')
44+
t.is(entries, sourceData.length, 'got all data')
45+
t.is(breaths, sourceData.length / 1000, 'breathed while iterating')
46+
47+
return db.close(function (err) {
48+
t.ifError(err, 'no close error')
49+
})
50+
}
51+
52+
entries++
53+
54+
if (!scheduled) {
55+
scheduled = true
56+
setImmediate(function () {
57+
breaths++
58+
scheduled = false
59+
})
60+
}
61+
62+
next()
63+
})
64+
}
65+
66+
next()
67+
})
68+
})
69+
})
70+
71+
test('iterator with seeks does not starve event loop', function (t) {
72+
t.plan(6)
73+
74+
const db = testCommon.factory()
75+
76+
db.open(function (err) {
77+
t.ifError(err, 'no open error')
78+
79+
db.batch(sourceData.slice(), function (err) {
80+
t.ifError(err, 'no batch error')
81+
82+
const it = db.iterator({ highWaterMark: Math.pow(1024, 3), limit: sourceData.length })
83+
84+
let breaths = 0
85+
let entries = 0
86+
let scheduled = false
87+
88+
const next = function () {
89+
it.next(function (err, key, value) {
90+
if (err || (key === undefined && value === undefined)) {
91+
t.ifError(err, 'no next error')
92+
t.is(entries, sourceData.length, 'got all data')
93+
t.is(breaths, sourceData.length, 'breathed while iterating')
94+
95+
return db.close(function (err) {
96+
t.ifError(err, 'no close error')
97+
})
98+
}
99+
100+
entries++
101+
102+
if (!scheduled) {
103+
// Seeking clears the cache, which should only have a positive
104+
// effect because it means the cache must be refilled, which
105+
// again gives us time to breathe. This is a smoke test, really.
106+
it.seek(sourceData[0].key)
107+
108+
scheduled = true
109+
setImmediate(function () {
110+
breaths++
111+
scheduled = false
112+
})
113+
}
114+
115+
next()
116+
})
117+
}
118+
119+
next()
120+
})
121+
})
122+
})
123+
124+
test('tearDown', testCommon.tearDown)

0 commit comments

Comments
 (0)