pixl-server-storage 4.1.0 → 4.2.0

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/test/test.js CHANGED
@@ -67,44 +67,54 @@ module.exports = {
67
67
  // save ref to storage
68
68
  self.storage = server.Storage;
69
69
 
70
- // build our test array dynamically
71
- // we have some repeating tests with different configuration options
72
- self.tests = self.tests.concat(
73
- listTests.tests,
70
+ var finishStartup = function() {
71
+ // build our test array dynamically
72
+ // we have some repeating tests with different configuration options
73
+ self.tests = self.tests.concat(
74
+ listTests.tests,
75
+
76
+ (self.args.splice || self.args.all || self.args.comprehensive) ?
77
+ listTests.generateSpliceTests() : [],
78
+
79
+ hashTests.tests
80
+ );
74
81
 
75
- (self.args.splice || self.args.all || self.args.comprehensive) ?
76
- listTests.generateSpliceTests() : [],
82
+ // now add transaction tests, which enable and init transactions
83
+ self.tests = self.tests.concat( transactionTests.tests );
77
84
 
78
- hashTests.tests
79
- );
80
-
81
- // now add transaction tests, which enable and init transactions
82
- self.tests = self.tests.concat( transactionTests.tests );
83
-
84
- // now repeat list and hash tests, with transactions enabled
85
- // augment test names for clarity
86
- [].concat( listTests.tests, hashTests.tests ).forEach( function(func) {
87
- var wrapper = function(test) { func.apply(this, [test]); };
88
- wrapper.testName = 'Transaction_' + func.name;
89
- self.tests.push( wrapper );
90
- } );
91
-
92
- // finally add indexer tests
93
- self.tests = self.tests.concat( indexerTests.tests );
94
-
95
- // log memory usage at end
96
- self.tests.push( function(test) {
97
- if (global.gc) { global.gc(); global.gc(); }
98
- test.ok( true );
99
- setTimeout( function() {
100
- self.storage.logDebug(9, "MEMORY USAGE: " + Tools.getTextFromBytes( process.memoryUsage.rss() ), process.memoryUsage() );
101
- test.done();
102
- }, 1 );
103
- } );
85
+ // now repeat list and hash tests, with transactions enabled
86
+ // augment test names for clarity
87
+ [].concat( listTests.tests, hashTests.tests ).forEach( function(func) {
88
+ var wrapper = function(test) { func.apply(this, [test]); };
89
+ wrapper.testName = 'Transaction_' + func.name;
90
+ self.tests.push( wrapper );
91
+ } );
92
+
93
+ // finally add indexer tests
94
+ self.tests = self.tests.concat( indexerTests.tests );
95
+
96
+ // log memory usage at end
97
+ self.tests.push( function(test) {
98
+ if (global.gc) { global.gc(); global.gc(); }
99
+ test.ok( true );
100
+ setTimeout( function() {
101
+ self.storage.logDebug(9, "MEMORY USAGE: " + Tools.getTextFromBytes( process.memoryUsage.rss() ), process.memoryUsage() );
102
+ test.done();
103
+ }, 1 );
104
+ } );
105
+
106
+ // startup complete
107
+ // delay this by 1ms so the log is in the correct order (pre-start is async)
108
+ setTimeout( function() { callback(); }, 1 );
109
+ };
104
110
 
105
- // startup complete
106
- // delay this by 1ms so the log is in the correct order (pre-start is async)
107
- setTimeout( function() { callback(); }, 1 );
111
+ if (self.storage.engine.unitTestCleanup) {
112
+ self.storage.engine.unitTestCleanup( function(err) {
113
+ if (err) return callback(err);
114
+ finishStartup();
115
+ } );
116
+ }
117
+ else finishStartup();
108
118
  } ); // startup
109
119
  } ); // delete
110
120
  },
@@ -127,9 +137,20 @@ module.exports = {
127
137
 
128
138
  tearDown: function (callback) {
129
139
  // clean up
130
- this.server.shutdown( function() {
131
- cp.exec("rm -rf transactions " + base_data_dir, callback);
132
- } );
140
+ var self = this;
141
+
142
+ var finishShutdown = function(cleanup_err) {
143
+ self.server.shutdown( function(shutdown_err) {
144
+ cp.exec("rm -rf transactions " + base_data_dir, function(rm_err) {
145
+ callback( cleanup_err || shutdown_err || rm_err );
146
+ } );
147
+ } );
148
+ };
149
+
150
+ if (this.storage && this.storage.engine && this.storage.engine.unitTestCleanup) {
151
+ this.storage.engine.unitTestCleanup( finishShutdown );
152
+ }
153
+ else finishShutdown();
133
154
  },
134
155
 
135
156
  multiCheck: function(map, storage, callback) {
package/transaction.js CHANGED
@@ -80,10 +80,10 @@ var TransStorageFunctions = {
80
80
  if (trans.keys[key] == 'W') {
81
81
  // we've written the key, so fetch our version
82
82
  this.logDebug(9, "Pinging Object from transaction: " + key);
83
+ var value = trans.values[key];
83
84
 
84
85
  setImmediate( function() {
85
86
  self.logDebug(9, "Head complete: " + key);
86
- var value = trans.values[key];
87
87
  callback( null, {
88
88
  mod: value.mod,
89
89
  len: value.len
@@ -121,11 +121,12 @@ var TransStorageFunctions = {
121
121
  if (trans.keys[key] == 'W') {
122
122
  // we've written the key, so fetch our version
123
123
  this.logDebug(9, "Fetching Object in transaction: " + key);
124
+ var value = trans.values[key];
124
125
 
125
126
  setImmediate( function() {
126
- var data = trans.values[key].data;
127
+ var data = value.data;
127
128
  self.logDebug(9, "JSON fetch complete: " + key, self.debugLevel(10) ? data : null);
128
- callback( err, Tools.copyHash(data, true) );
129
+ callback( null, Tools.copyHash(data, true) );
129
130
  } );
130
131
  }
131
132
  else if (trans.keys[key] == 'D') {
@@ -700,6 +701,107 @@ module.exports = Class.create({
700
701
  var num_bytes = 0;
701
702
  var pf = this.perf.begin('commit');
702
703
 
704
+ if (this.engine.commitTransaction) {
705
+ // native engine transaction support replaces our entire rollback-log commit system.
706
+ // The engine is responsible for applying all record changes atomically, using the
707
+ // full transaction object. We still own the outer locks, events, logs and cleanup.
708
+ async.waterfall(
709
+ [
710
+ function(callback) {
711
+ // acquire commit lock
712
+ self.lock( 'C|'+path, true, function() { callback(); } );
713
+ },
714
+ function(callback) {
715
+ // notify listeners that the commit is starting
716
+ self.emit('commitStart', trans);
717
+
718
+ // ask the engine to atomically commit all actions
719
+ self.engine.commitTransaction( trans, callback );
720
+ }
721
+ ],
722
+ function(err) {
723
+ // commit complete
724
+ var elapsed = pf.end();
725
+
726
+ if (err) {
727
+ var msg = "Failed to commit transaction: " + path + ": " + err.message;
728
+ self.logError('commit', msg, { id: trans.id });
729
+
730
+ if (err.fatal) {
731
+ // Native engine detected an unsafe transaction outcome. Do not fall
732
+ // through to abortTransaction(), as there is no rollback log to replay.
733
+ var fatal_err = new Error(msg);
734
+ fatal_err.fatal = true;
735
+ fatal_err.originalError = err;
736
+ return self.transFatalError(fatal_err);
737
+ }
738
+
739
+ // Note: in this case unlocking happens in abortTransaction
740
+ return callback( new Error(msg) );
741
+ }
742
+
743
+ // emulate the normal per-record put/delete side effects, since native
744
+ // engine commits bypass self.put() and self.delete() for atomicity.
745
+ Object.keys(trans.keys).forEach( function(key) {
746
+ var record_state = trans.keys[key];
747
+ var norm_key = self.normalizeKey(key);
748
+
749
+ if (record_state == 'W') {
750
+ var value = trans.values[key];
751
+ num_bytes += value.len;
752
+
753
+ // update the Storage-level RAM cache, if enabled for this key
754
+ if (self.cacheKeyRegex && norm_key.match(self.cacheKeyRegex)) {
755
+ self.cache[norm_key] = value.data;
756
+ }
757
+
758
+ self.logTransaction('put', norm_key, {
759
+ elapsed_ms: 0
760
+ });
761
+ self.emit('put', norm_key, value.data);
762
+ }
763
+ else if (record_state == 'D') {
764
+ // update the Storage-level RAM cache, if enabled for this key
765
+ if (self.cacheKeyRegex && norm_key.match(self.cacheKeyRegex) && (norm_key in self.cache)) {
766
+ delete self.cache[norm_key];
767
+ }
768
+
769
+ self.logTransaction('delete', norm_key, {
770
+ elapsed_ms: 0
771
+ });
772
+ self.emit('delete', norm_key);
773
+ }
774
+ });
775
+
776
+ self.logDebug(5, "Transaction committed successfully: " + trans.id, { path: path, actions: num_actions });
777
+ self.logTransaction('commit', path, {
778
+ id: trans.id,
779
+ elapsed_ms: elapsed,
780
+ actions: num_actions,
781
+ bytes_written: num_bytes
782
+ });
783
+
784
+ // transaction is complete
785
+ delete trans.values; // release memory
786
+ delete self.transactions[path];
787
+
788
+ // enqueue any pending tasks that got added during the transaction
789
+ if (trans.queue.length) {
790
+ trans.queue.forEach( self.enqueue.bind(self) );
791
+ trans.queue = []; // release memory
792
+ }
793
+
794
+ delete trans.keys; // release memory
795
+
796
+ self.emit('commitEnd', trans);
797
+ self.unlock( 'C|'+path );
798
+ self._transUnlock(path);
799
+ callback();
800
+ }
801
+ ); // native engine commit
802
+ return;
803
+ }
804
+
703
805
  async.waterfall(
704
806
  [
705
807
  function(callback) {