Skip to content

Commit 395af3f

Browse files
Merge #714: add commit cache (feature #622)
1 parent 10187bb commit 395af3f

File tree

2 files changed

+145
-6
lines changed

2 files changed

+145
-6
lines changed

src/indexeddb.js

+86-1
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,95 @@
5959

6060
this.getsRunning = 0;
6161
this.putsRunning = 0;
62+
63+
/**
64+
* Property: changesQueued
65+
*
66+
* Given a node for which uncommitted changes exist, this cache
67+
* stores either the entire uncommitted node, or false for a deletion.
68+
* The node's path is used as the key.
69+
*
70+
* changesQueued stores changes for which no IndexedDB transaction has
71+
* been started yet.
72+
*/
73+
this.changesQueued = {};
74+
75+
/**
76+
* Property: changesRunning
77+
*
78+
* Given a node for which uncommitted changes exist, this cache
79+
* stores either the entire uncommitted node, or false for a deletion.
80+
* The node's path is used as the key.
81+
*
82+
* At any time there is at most one IndexedDB transaction running.
83+
* changesRunning stores the changes that are included in that currently
84+
* running IndexedDB transaction, or if none is running, of the last one
85+
* that ran.
86+
*/
87+
this.changesRunning = {};
6288
};
6389

6490
RS.IndexedDB.prototype = {
6591
getNodes: function(paths) {
92+
var misses = [], fromCache = {};
93+
for (var i=0; i<paths.length; i++) {
94+
if (this.changesQueued[paths[i]] !== undefined) {
95+
fromCache[paths[i]] = this._getInternals().deepClone(this.changesQueued[paths[i]] || undefined);
96+
} else if(this.changesRunning[paths[i]] !== undefined) {
97+
fromCache[paths[i]] = this._getInternals().deepClone(this.changesRunning[paths[i]] || undefined);
98+
} else {
99+
misses.push(paths[i]);
100+
}
101+
}
102+
if (misses.length > 0) {
103+
return this.getNodesFromDb(misses).then(function(nodes) {
104+
for (var i in fromCache) {
105+
nodes[i] = fromCache[i];
106+
}
107+
return nodes;
108+
});
109+
} else {
110+
promise = promising();
111+
promise.fulfill(fromCache);
112+
return promise;
113+
}
114+
},
115+
116+
setNodes: function(nodes) {
117+
var promise = promising();
118+
for (var i in nodes) {
119+
this.changesQueued[i] = nodes[i] || false;
120+
}
121+
this.maybeFlush();
122+
promise.fulfill();
123+
return promise;
124+
},
125+
126+
maybeFlush: function() {
127+
if (this.putsRunning === 0) {
128+
this.flushChangesQueued();
129+
} else {
130+
if (!this.commitSlownessWarning) {
131+
this.commitSlownessWarning = setInterval(function() {
132+
console.log('WARNING: waited more than 10 seconds for previous commit to finish');
133+
}, 10000);
134+
}
135+
}
136+
},
137+
138+
flushChangesQueued: function() {
139+
if (this.commitSlownessWarning) {
140+
clearInterval(this.commitSlownessWarning);
141+
this.commitSlownessWarning = null;
142+
}
143+
if (Object.keys(this.changesQueued).length > 0) {
144+
this.changesRunning = this.changesQueued;
145+
this.changesQueued = {};
146+
this.setNodesInDb(this.changesRunning).then(this.flushChangesQueued.bind(this));
147+
}
148+
},
149+
150+
getNodesFromDb: function(paths) {
66151
var promise = promising();
67152
var transaction = this.db.transaction(['nodes'], 'readonly');
68153
var nodes = transaction.objectStore('nodes');
@@ -93,7 +178,7 @@
93178
return promise;
94179
},
95180

96-
setNodes: function(nodes) {
181+
setNodesInDb: function(nodes) {
97182
var promise = promising();
98183
var transaction = this.db.transaction(['nodes'], 'readwrite');
99184
var nodesStore = transaction.objectStore('nodes');

test/unit/indexeddb-suite.js

+59-5
Original file line numberDiff line numberDiff line change
@@ -89,23 +89,77 @@ define(['requirejs'], function(requirejs) {
8989
desc: "fireInitial fires change event with 'local' origin for initial cache content",
9090
timeout: 250,
9191
run: function(env, test) {
92+
env.idb.putsRunning = 0;
9293
env.idb.put('/foo/bla', 'basdf', 'text/plain').then(function() {
9394
env.idb.on('change', function(event) {
94-
test.assert(event.origin, 'local');
95+
test.assertAnd(event.origin, 'local');
96+
setTimeout(function() {
97+
test.done();
98+
}, 50);
9599
});
96100
//the mock is just an in-memory object; need to explicitly set its .length and its .key() function now:
97101
env.idb.fireInitial();
98102
}, function(e) {
99103
test.result(false, e);
100104
});
101-
setTimeout(function() {
102-
env._puts[0].onsuccess();
103-
env._transactions[1].oncomplete();
104-
}, 100);
105105
env._gets[0].onsuccess({ target: {}});
106106
env._transactions[0].oncomplete();
107107
}
108108
},
109+
110+
{
111+
desc: "setNodes calls setNodesInDb when putsRunning is 0",
112+
run: function(env, test) {
113+
var setNodesInDb = env.idb.setNodesInDb,
114+
getNodesFromDb = env.idb.getNodesFromDb;
115+
env.idb.setNodesInDb = function(nodes) {
116+
var promise = promising();
117+
test.assertAnd(nodes, {foo: {path: 'foo'}});
118+
setTimeout(function() {
119+
env.idb.setNodesInDb = setNodesInDb;
120+
env.idb.getNodesFromDb = getNodesFromDb;
121+
test.done();
122+
}, 10);
123+
promise.fulfill();
124+
return promise;
125+
};
126+
env.idb.putsRunning = 0;
127+
env.idb.setNodes({foo: {path: 'foo'}});
128+
}
129+
},
130+
131+
{
132+
desc: "setNodes doesn't call setNodesInDb when putsRunning is 1, but will flush later",
133+
run: function(env, test) {
134+
var setNodesInDb = env.idb.setNodesInDb,
135+
getNodesFromDb = env.idb.getNodesFromDb;
136+
env.idb.changesQueued = {};
137+
env.idb.changesRunning = {};
138+
env.idb.setNodesInDb = function(nodes) {
139+
test.result(false, 'should not have called this function');
140+
};
141+
env.idb.putsRunning = 1;
142+
env.idb.setNodes({foo: {path: 'foo'}});
143+
test.assertAnd(env.idb.changesQueued, {foo: {path: 'foo'}});
144+
test.assertAnd(env.idb.changesRunning, {});
145+
146+
env.idb.setNodesInDb = function(nodes) {
147+
var promise = promising();
148+
test.assertAnd(nodes, {foo: {path: 'foo'}});
149+
setTimeout(function() {
150+
env.idb.setNodesInDb = setNodesInDb;
151+
env.idb.getNodesFromDb = getNodesFromDb;
152+
test.done();
153+
}, 10);
154+
promise.fulfill();
155+
return promise;
156+
};
157+
env.idb.putsRunning = 0;
158+
env.idb.maybeFlush();
159+
test.assertAnd(env.idb.changesQueued, {});
160+
test.assertAnd(env.idb.changesRunning, {foo: {path: 'foo'}});
161+
}
162+
}
109163
/* TODO: mock indexeddb with some nodejs library
110164
{
111165
desc: "getNodes, setNodes",

0 commit comments

Comments
 (0)