pixl-server-storage 3.0.5 → 3.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -846,7 +846,7 @@ storage.getStream( 'test1.gif', function(err, readStream) {
846
846
  } );
847
847
  ```
848
848
 
849
- Please note that not all the storage engines support streams natively, so the content may actually be loaded into RAM in the background. Namely, as of this writing, the Couchbase and Redis APIs does not support streams, so they are currently simulated in those engines. Streams *are* supported natively in both the Filesystem and Amazon S3 engines.
849
+ Please note that not all the storage engines support streams natively, so the content may actually be loaded into RAM in the background. Namely, as of this writing, the Couchbase and Redis APIs do not support streams, so they are currently simulated in those engines. Streams *are* supported natively in both the Filesystem and Amazon S3 engines.
850
850
 
851
851
  # Expiring Data
852
852
 
package/docs/API.md CHANGED
@@ -18,6 +18,7 @@ var storage = server.Storage;
18
18
  * [get](#get)
19
19
  * [getMulti](#getmulti)
20
20
  * [getStream](#getstream)
21
+ * [getStreamRange](#getstreamrange)
21
22
  * [head](#head)
22
23
  * [headMulti](#headmulti)
23
24
  * [delete](#delete)
@@ -126,6 +127,8 @@ storage.putMulti( records, function(err) {
126
127
  } );
127
128
  ```
128
129
 
130
+ Note that if any of the individual put operations fail, the entire `putMulti()` function is aborted, and the first error is passed to your callback. At this point the operation may have been partially successful, with some records written, and others not. Due to this uncertainty, you may want to use this method inside of a [transaction](./Transactions.md), which can be safely rolled back upon error.
131
+
129
132
  ## putStream
130
133
 
131
134
  ```javascript
@@ -142,6 +145,8 @@ storage.putStream( 'test1.gif', stream, function(err) {
142
145
  } );
143
146
  ```
144
147
 
148
+ Please note that as of this writing, the `Couchbase`, `Redis` and `RedisCluster` engines have no native stream API, so the `putStream()` method has to load the entire record into memory.
149
+
145
150
  ## get
146
151
 
147
152
  ```javascript
@@ -173,19 +178,46 @@ storage.getMulti( ['test1', 'test2', 'test3'], function(err, values) {
173
178
  } );
174
179
  ```
175
180
 
181
+ Note that if *any* of the records fail, the entire operation fails, and the first error is passed to your callback.
182
+
176
183
  ## getStream
177
184
 
178
185
  ```javascript
179
186
  storage.getStream( KEY, CALLBACK );
180
187
  ```
181
188
 
182
- The `getStream()` method retrieves a [readable stream](https://nodejs.org/api/stream.html#stream_class_stream_readable) to a given record's data, so it can be read or piped to a writable stream. This is for very large records, so nothing is loaded into memory. Example of spooling to a local file:
189
+ The `getStream()` method retrieves a [readable stream](https://nodejs.org/api/stream.html#class-streamreadable) to a given record's data, so it can be read or piped to a writable stream. This is for very large records, so nothing is loaded into memory. Example of spooling to a local file:
190
+
191
+ ```javascript
192
+ var fs = require('fs');
193
+ var writeStream = fs.createWriteStream('/var/tmp/downloaded.gif');
194
+
195
+ storage.getStream( 'test1.gif', function(err, readStream, info) {
196
+ if (err) throw err;
197
+ writeStream.on('finish', function() {
198
+ // data is completely written
199
+ } );
200
+ readStream.pipe( writeStream );
201
+ } );
202
+ ```
203
+
204
+ As you can see above, your callback is also passed a 3rd argument, which is an object containing the record's full byte length and modification date. These properties match those returned when you call [head()](#head) (i.e. `len` and `mod`).
205
+
206
+ Please note that as of this writing, the `Couchbase`, `Redis` and `RedisCluster` engines have no native stream API, so the `getStream()` method has to load the entire record into memory.
207
+
208
+ ## getStreamRange
209
+
210
+ ```javascript
211
+ storage.getStreamRange( KEY, START, END, CALLBACK );
212
+ ```
213
+
214
+ The `getStreamRange()` method retrieves a [readable stream](https://nodejs.org/api/stream.html#class-streamreadable) to a specific slice of a record's data, so it can be read or piped to a writable stream. The `start` and `end` arguments should be set to the starting and ending byte offset of the slice you want. Both values are *inclusive*. Example of spooling bytes `0-99` of a record to a local file:
183
215
 
184
216
  ```javascript
185
217
  var fs = require('fs');
186
218
  var writeStream = fs.createWriteStream('/var/tmp/downloaded.gif');
187
219
 
188
- storage.getStream( 'test1.gif', function(err, readStream) {
220
+ storage.getStreamRange( 'test1.gif', 0, 99, function(err, readStream, info) {
189
221
  if (err) throw err;
190
222
  writeStream.on('finish', function() {
191
223
  // data is completely written
@@ -194,6 +226,10 @@ storage.getStream( 'test1.gif', function(err, readStream) {
194
226
  } );
195
227
  ```
196
228
 
229
+ As you can see above, your callback is also passed a 3rd argument, which is an object containing the record's full byte length and modification date. These properties match those returned when you call [head()](#head) (i.e. `len` and `mod`). The `len` is not affected by the `start` and `end` range -- it is always the full byte length of the record.
230
+
231
+ Please note that as of this writing, the `Couchbase`, `Redis` and `RedisCluster` engines have no native stream API, so the `getStreamRange()` method has to load the entire record into memory.
232
+
197
233
  ## head
198
234
 
199
235
  ```javascript
@@ -217,7 +253,7 @@ storage.head( 'test1', function(err, data) {
217
253
  } );
218
254
  ```
219
255
 
220
- Please note that as of this writing, the `Couchbase` engine has no native API, so the `head()` method has to load the entire record. It does return the record size in to the `len` property, but there is no way to retrieve the last modified date.
256
+ Please note that as of this writing, the `Couchbase`, `Redis` and `RedisCluster` engines have no native head API, so the `head()` method has to load the entire record. It does return the record size in to the `len` property, but there is no way to retrieve the last modified date.
221
257
 
222
258
  ## headMulti
223
259
 
@@ -224,7 +224,33 @@ module.exports = Class.create({
224
224
  }
225
225
 
226
226
  var stream = new BufferStream(buf);
227
- callback(null, stream);
227
+ callback(null, stream, { mod: 1, len: buf.length });
228
+ } );
229
+ },
230
+
231
+ getStreamRange: function(key, start, end, callback) {
232
+ // get readable stream to record value given key and range
233
+ var self = this;
234
+
235
+ // The Couchbase Node.JS 2.0 API has no stream support.
236
+ // So, we have to do this the RAM-hard way...
237
+ this.get( key, function(err, buf) {
238
+ if (err && (err.code != CouchbaseAPI.errors.keyNotFound)) {
239
+ // some other error
240
+ err.message = "Failed to fetch key: " + key + ": " + err.message;
241
+ self.logError('couchbase', err.message);
242
+ return callback(err);
243
+ }
244
+ else if (!buf) {
245
+ // record not found
246
+ var err = new Error("Failed to fetch key: " + key + ": Not found");
247
+ err.code = "NoSuchKey";
248
+ return callback( err, null );
249
+ }
250
+
251
+ var range = buf.slice(start, end + 1);
252
+ var stream = new BufferStream(range);
253
+ callback(null, stream, { mod: 1, len: buf.length });
228
254
  } );
229
255
  },
230
256
 
@@ -449,7 +449,43 @@ module.exports = Class.create({
449
449
  // create read stream
450
450
  var inp = fs.createReadStream( file );
451
451
 
452
- callback( null, inp );
452
+ callback( null, inp, {
453
+ mod: Math.floor(stats.mtime.getTime() / 1000),
454
+ len: stats.size
455
+ } );
456
+ } );
457
+ },
458
+
459
+ getStreamRange: function(key, start, end, callback) {
460
+ // get readable stream to record value given key and byte range
461
+ var self = this;
462
+ var file = this.getFilePath(key);
463
+
464
+ this.logDebug(9, "Fetching ranged binary stream: " + key, { file, start, end } );
465
+
466
+ // make sure record exists
467
+ fs.stat(file, function(err, stats) {
468
+ if (err) {
469
+ if (err.message.match(/ENOENT/)) {
470
+ err.message = "File not found";
471
+ err.code = "NoSuchKey";
472
+ }
473
+ else {
474
+ // log fs errors that aren't simple missing files (i.e. I/O errors)
475
+ self.logError('file', "Failed to stat file: " + key + ": " + file + ": " + err.message);
476
+ }
477
+
478
+ err.message = "Failed to head key: " + key + ": " + err.message;
479
+ return callback( err, null );
480
+ }
481
+
482
+ // create read stream
483
+ var inp = fs.createReadStream( file, { start, end } );
484
+
485
+ callback( null, inp, {
486
+ mod: Math.floor(stats.mtime.getTime() / 1000),
487
+ len: stats.size
488
+ } );
453
489
  } );
454
490
  },
455
491
 
package/engines/Hybrid.js CHANGED
@@ -79,6 +79,12 @@ module.exports = Class.create({
79
79
  this.binaryEngine.getStream( key, callback );
80
80
  },
81
81
 
82
+ getStreamRange: function(key, start, end, callback) {
83
+ // get readable stream to record value given key and range
84
+ // streams are binary only!
85
+ this.binaryEngine.getStreamRange( key, start, end, callback );
86
+ },
87
+
82
88
  delete: function(key, callback) {
83
89
  // delete hybrid key given key
84
90
  if (this.storage.isBinaryKey(key)) {
package/engines/Redis.js CHANGED
@@ -227,7 +227,34 @@ module.exports = Class.create({
227
227
  }
228
228
 
229
229
  var stream = new BufferStream(buf);
230
- callback(null, stream);
230
+ callback(null, stream, { mod: 1, len: buf.length });
231
+ } );
232
+ },
233
+
234
+ getStreamRange: function(key, start, end, callback) {
235
+ // get readable stream to record value given key and range
236
+ var self = this;
237
+
238
+ // The Redis API has no stream support.
239
+ // So, we have to do this the RAM-hard way...
240
+
241
+ this.get( key, function(err, buf) {
242
+ if (err) {
243
+ // an actual error
244
+ err.message = "Failed to fetch key: " + key + ": " + err;
245
+ self.logError('redis', ''+err);
246
+ return callback(err);
247
+ }
248
+ else if (!buf) {
249
+ // record not found
250
+ var err = new Error("Failed to fetch key: " + key + ": Not found");
251
+ err.code = "NoSuchKey";
252
+ return callback( err, null );
253
+ }
254
+
255
+ var range = buf.slice(start, end + 1);
256
+ var stream = new BufferStream(range);
257
+ callback(null, stream, { mod: 1, len: buf.length });
231
258
  } );
232
259
  },
233
260
 
@@ -244,7 +244,34 @@ module.exports = Class.create({
244
244
  }
245
245
 
246
246
  var stream = new BufferStream(buf);
247
- callback(null, stream);
247
+ callback(null, stream, { mod: 1, len: buf.length });
248
+ } );
249
+ },
250
+
251
+ getStreamRange: function(key, start, end, callback) {
252
+ // get readable stream to record value given key and range
253
+ var self = this;
254
+
255
+ // The Redis API has no stream support.
256
+ // So, we have to do this the RAM-hard way...
257
+
258
+ this.get( key, function(err, buf) {
259
+ if (err) {
260
+ // an actual error
261
+ err.message = "Failed to fetch key: " + key + ": " + err;
262
+ self.logError('redis', ''+err);
263
+ return callback(err);
264
+ }
265
+ else if (!buf) {
266
+ // record not found
267
+ var err = new Error("Failed to fetch key: " + key + ": Not found");
268
+ err.code = "NoSuchKey";
269
+ return callback( err, null );
270
+ }
271
+
272
+ var range = buf.slice(start, end + 1);
273
+ var stream = new BufferStream(range);
274
+ callback(null, stream, { mod: 1, len: buf.length });
248
275
  } );
249
276
  },
250
277
 
package/engines/S3.js CHANGED
@@ -125,7 +125,11 @@ module.exports = Class.create({
125
125
  params.ContentType = 'application/json';
126
126
  }
127
127
 
128
+ // double-callback protection (bug in aws-sdk)
129
+ var done = false;
128
130
  this.s3.putObject( params, function(err, data) {
131
+ if (done) return; else done = true;
132
+
129
133
  if (err) {
130
134
  self.logError('s3', "Failed to store object: " + key + ": " + err.message);
131
135
  }
@@ -152,7 +156,11 @@ module.exports = Class.create({
152
156
 
153
157
  this.logDebug(9, "Storing S3 Binary Stream: " + key);
154
158
 
159
+ // double-callback protection (bug in aws-sdk)
160
+ var done = false;
155
161
  this.s3.upload(params, function(err, data) {
162
+ if (done) return; else done = true;
163
+
156
164
  if (err) {
157
165
  self.logError('s3', "Failed to store stream: " + key + ": " + err.message);
158
166
  }
@@ -183,7 +191,11 @@ module.exports = Class.create({
183
191
  return;
184
192
  } // cache
185
193
 
194
+ // double-callback protection (bug in aws-sdk)
195
+ var done = false;
186
196
  this.s3.headObject( { Key: this.extKey(key, orig_key) }, function(err, data) {
197
+ if (done) return; else done = true;
198
+
187
199
  if (err) {
188
200
  if (err.code != 'NoSuchKey') {
189
201
  self.logError('s3', "Failed to head key: " + key + ": " + err.message);
@@ -227,7 +239,11 @@ module.exports = Class.create({
227
239
  return;
228
240
  } // cache
229
241
 
242
+ // double-callback protection (bug in aws-sdk)
243
+ var done = false;
230
244
  this.s3.getObject( { Key: this.extKey(key, orig_key) }, function(err, data) {
245
+ if (done) return; else done = true;
246
+
231
247
  if (err) {
232
248
  if (err.code == 'NoSuchKey') {
233
249
  // key not found, special case, don't log an error
@@ -278,29 +294,82 @@ module.exports = Class.create({
278
294
  this.logDebug(9, "Fetching S3 Stream: " + key);
279
295
 
280
296
  var params = { Key: this.extKey(key, orig_key) };
297
+ var download = self.s3.getObject(params).createReadStream();
298
+ var proceed = false;
299
+
300
+ download.on('error', function(err) {
301
+ if (proceed) self.logError('s3', "Failed to download key: " + key + ": " + err.message);
302
+ });
303
+ download.once('end', function() {
304
+ self.logDebug(9, "S3 stream download complete: " + key);
305
+ } );
306
+ download.once('close', function() {
307
+ self.logDebug(9, "S3 stream download closed: " + key);
308
+ } );
281
309
 
282
- this.s3.headObject( params, function(err) {
310
+ // double-callback protection (bug in aws-sdk)
311
+ var done = false;
312
+ this.s3.headObject( params, function(err, data) {
313
+ if (done) return; else done = true;
314
+
283
315
  if (err) {
284
316
  if (err.code != 'NoSuchKey') {
285
317
  self.logError('s3', "Failed to head key: " + key + ": " + err.message);
286
318
  }
319
+ download.destroy();
287
320
  callback( err, null );
288
321
  return;
289
322
  }
290
323
 
291
- var download = self.s3.getObject(params).createReadStream();
292
-
293
- download.on('error', function(err) {
294
- self.logError('s3', "Failed to download key: " + key + ": " + err.message);
295
- });
296
- download.on('end', function() {
297
- self.logDebug(9, "S3 stream download complete: " + key);
298
- } );
299
- download.on('close', function() {
300
- self.logDebug(9, "S3 stream download closed: " + key);
324
+ proceed = true;
325
+ callback( null, download, {
326
+ mod: Math.floor((new Date(data.LastModified)).getTime() / 1000),
327
+ len: data.ContentLength
301
328
  } );
329
+ }); // headObject
330
+ },
331
+
332
+ getStreamRange: function(key, start, end, callback) {
333
+ // get readable stream to record value given key and range
334
+ var self = this;
335
+ var orig_key = key;
336
+ key = this.prepKey(key);
337
+
338
+ this.logDebug(9, "Fetching ranged S3 stream: " + key, { start, end });
339
+
340
+ var params = { Key: this.extKey(key, orig_key) };
341
+ var download = self.s3.getObject( Tools.mergeHashes(params, { Range: "bytes=" + start + "-" + end }) ).createReadStream();
342
+ var proceed = false;
343
+
344
+ download.on('error', function(err) {
345
+ if (proceed) self.logError('s3', "Failed to download key: " + key + ": " + err.message);
346
+ });
347
+ download.once('end', function() {
348
+ self.logDebug(9, "S3 stream download complete: " + key);
349
+ } );
350
+ download.once('close', function() {
351
+ self.logDebug(9, "S3 stream download closed: " + key);
352
+ } );
353
+
354
+ // double-callback protection (bug in aws-sdk)
355
+ var done = false;
356
+ this.s3.headObject( params, function(err, data) {
357
+ if (done) return; else done = true;
302
358
 
303
- callback( null, download );
359
+ if (err) {
360
+ if (err.code != 'NoSuchKey') {
361
+ self.logError('s3', "Failed to head key: " + key + ": " + err.message);
362
+ }
363
+ download.destroy();
364
+ callback( err, null );
365
+ return;
366
+ }
367
+
368
+ proceed = true;
369
+ callback( null, download, {
370
+ mod: Math.floor((new Date(data.LastModified)).getTime() / 1000),
371
+ len: data.ContentLength
372
+ } );
304
373
  }); // headObject
305
374
  },
306
375
 
@@ -312,7 +381,11 @@ module.exports = Class.create({
312
381
 
313
382
  this.logDebug(9, "Deleting S3 Object: " + key);
314
383
 
384
+ // double-callback protection (bug in aws-sdk)
385
+ var done = false;
315
386
  this.s3.deleteObject( { Key: this.extKey(key, orig_key) }, function(err, data) {
387
+ if (done) return; else done = true;
388
+
316
389
  if (err) {
317
390
  self.logError('s3', "Failed to delete object: " + key + ": " + err.message);
318
391
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixl-server-storage",
3
- "version": "3.0.5",
3
+ "version": "3.0.9",
4
4
  "description": "A key/value/list storage component for the pixl-server framework.",
5
5
  "author": "Joseph Huckaby <jhuckaby@gmail.com>",
6
6
  "homepage": "https://github.com/jhuckaby/pixl-server-storage",
package/storage.js CHANGED
@@ -354,6 +354,21 @@ module.exports = Class.create({
354
354
  this.engine.getStream( key, callback );
355
355
  },
356
356
 
357
+ getStreamRange: function(key, start, end, callback) {
358
+ // fetch value via stream pipe and range
359
+ // start and end are both inclusive
360
+ var self = this;
361
+ if (!this.started) return callback( new Error("Storage has not completed startup.") );
362
+
363
+ key = this.normalizeKey( key );
364
+
365
+ if (!this.isBinaryKey(key)) {
366
+ return callback( new Error("Stream values are only allowed with keys containing file extensions, e.g. " + key + ".bin") );
367
+ }
368
+
369
+ this.engine.getStreamRange( key, start, end, callback );
370
+ },
371
+
357
372
  getMulti: function(keys, callback) {
358
373
  // fetch multiple records at once, given array of keys
359
374
  // callback is provided an array of values in matching order to keys
package/test/test-main.js CHANGED
@@ -375,7 +375,7 @@ module.exports = {
375
375
  },
376
376
 
377
377
  function testStream(test) {
378
- test.expect(11);
378
+ test.expect(14);
379
379
  var self = this;
380
380
 
381
381
  var key = 'spacer-stream.gif';
@@ -395,10 +395,13 @@ module.exports = {
395
395
  var tempFile = __dirname + '/' + filename + '.streamtemp';
396
396
  var outStream = fs.createWriteStream( tempFile );
397
397
 
398
- self.storage.getStream( key, function(err, storageStream) {
398
+ self.storage.getStream( key, function(err, storageStream, streamInfo) {
399
399
  test.ok( !err, "No error fetching stream: " + err );
400
400
  test.ok( !!storageStream, "Got storage stream as 2nd arg");
401
401
  test.ok( !!storageStream.pipe, "Storage stream has a pipe");
402
+ test.ok( !!streamInfo, "Info was provided as the 3rd arg");
403
+ test.ok( streamInfo.len == 43, "Info has correct data length");
404
+ test.ok( streamInfo.mod > 0, "Info has a non-zero mod date");
402
405
 
403
406
  outStream.on('finish', function() {
404
407
  var newSpacerBuf = fs.readFileSync( tempFile );
@@ -420,6 +423,52 @@ module.exports = {
420
423
  } ); // putStream
421
424
  },
422
425
 
426
+ function testStreamRange(test) {
427
+ test.expect(14);
428
+ var self = this;
429
+
430
+ var key = 'spacer-stream.gif';
431
+ var filename = 'spacer.gif';
432
+ var spacerBuf = fs.readFileSync( __dirname + '/' + filename );
433
+ var spacerStream = fs.createReadStream( __dirname + '/' + filename );
434
+
435
+ test.ok( !!spacerBuf, "Got buffer from file" );
436
+ test.ok( typeof(spacerBuf) == 'object', "Buffer is an object" );
437
+ test.ok( spacerBuf.length > 0, "Buffer has size" );
438
+ test.ok( !!spacerStream, "Got read stream" );
439
+
440
+ this.storage.putStream( key, spacerStream, function(err) {
441
+ test.ok( !err, "No error creating stream: " + err );
442
+
443
+ var tempFile = __dirname + '/' + filename + '.streamtemp';
444
+ var outStream = fs.createWriteStream( tempFile );
445
+
446
+ self.storage.getStreamRange( key, 0, 5, function(err, storageStream, streamInfo) {
447
+ test.ok( !err, "No error fetching stream: " + err );
448
+ test.ok( !!storageStream, "Got storage stream as 2nd arg");
449
+ test.ok( !!storageStream.pipe, "Storage stream has a pipe");
450
+ test.ok( !!streamInfo, "Info was provided as the 3rd arg");
451
+ test.ok( streamInfo.len == 43, "Info has correct data length");
452
+ test.ok( streamInfo.mod > 0, "Info has a non-zero mod date");
453
+
454
+ outStream.on('finish', function() {
455
+ var newSpacerBuf = fs.readFileSync( tempFile );
456
+ test.ok( newSpacerBuf.length == 6, "Stream length is correct" );
457
+ test.ok( newSpacerBuf.toString() == "GIF89a", "Range buffer content is correct" );
458
+
459
+ self.storage.delete( key, function(err) {
460
+ test.ok( !err, "No error deleting stream key: " + err );
461
+ fs.unlinkSync( tempFile );
462
+ test.done();
463
+ } ); // delete
464
+ } ); // stream finish
465
+
466
+ storageStream.pipe( outStream );
467
+
468
+ } ); // getStream
469
+ } ); // putStream
470
+ },
471
+
423
472
  function testPutMulti(test) {
424
473
  // test storing multiple keys at once
425
474
  test.expect(1);
package/transaction.js CHANGED
@@ -27,14 +27,6 @@ var TransStorageFunctions = {
27
27
  this.tempFileCounter = 1;
28
28
  },
29
29
 
30
- getFilePath: function(key) {
31
- // get local path to file given storage key
32
- var trans = this.transactions[ this.currentTransactionPath ];
33
- var key_id = Tools.digestHex( key );
34
- var file = Path.join( this.transDir, "data", trans.id + '-' + key_id + '.json' );
35
- return file;
36
- },
37
-
38
30
  put: function(key, value, callback) {
39
31
  // store key+value pair in transaction
40
32
  var self = this;
@@ -645,35 +637,16 @@ module.exports = Class.create({
645
637
  self.logError('rollback', "Unable to delete rollback log: " + trans.log + ": " + err);
646
638
  }
647
639
 
648
- // also delete any files written to temp area
649
- if (!trans.keys) trans.keys = {};
640
+ // complete, unlock and remove transaction from memory
641
+ self.unlock( 'C|'+path );
642
+ self._transUnlock(path);
643
+ self.transactions[path].keys = {}; // release memory
644
+ self.transactions[path].values = {}; // release memory
645
+ self.transactions[path].queue = []; // release memory
646
+ delete self.transactions[path];
650
647
 
651
- async.forEachOfLimit( trans.keys, self.concurrency,
652
- function(value, key, callback) {
653
- // delete temp key file
654
- var key_id = Tools.digestHex( key );
655
- var file = Path.join( self.transDir, "data", trans.id + '-' + key_id + '.json' );
656
-
657
- fs.unlink( file, function(err) {
658
- if (err && !err.message.match(/ENOENT/)) {
659
- self.logError('rollback', "Unable to delete transaction file: " + file + ": " + err);
660
- }
661
- callback();
662
- });
663
- },
664
- function(err) {
665
- // complete, unlock and remove transaction from memory
666
- self.unlock( 'C|'+path );
667
- self._transUnlock(path);
668
- self.transactions[path].keys = {}; // release memory
669
- self.transactions[path].values = {}; // release memory
670
- self.transactions[path].queue = []; // release memory
671
- delete self.transactions[path];
672
-
673
- self.logDebug(3, "Transaction rollback complete: " + trans.id, { path: path });
674
- callback();
675
- }
676
- ); // forEachOfLimit
648
+ self.logDebug(3, "Transaction rollback complete: " + trans.id, { path: path });
649
+ callback();
677
650
  }); // fs.unlink
678
651
  } // done with log
679
652
  ); // fileEachLine