Mike Gerwitz

Activist for User Freedom

aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMike Gerwitz <gerwitzm@lovullo.com>2017-02-13 13:36:31 -0500
committerMike Gerwitz <gerwitzm@lovullo.com>2017-02-16 12:37:46 -0500
commit71024bd389205492c88263ef0b8d126fca14ef67 (patch)
tree887da21d383cabf0f19eee8305b4ee4125019b74
parent9225924e7e3d2b872f63986fcfc2d6e438de94c7 (diff)
downloadliza-71024bd389205492c88263ef0b8d126fca14ef67.tar.gz
liza-71024bd389205492c88263ef0b8d126fca14ef67.tar.bz2
liza-71024bd389205492c88263ef0b8d126fca14ef67.zip
DataValidator: Queue validations when incomplete
* src/validate/DataValidator.js (validate): If a validation is ongoing, queue requests. * test/validate/DataValidatorTest.js: Add test. DEV-2299
-rw-r--r--src/validate/DataValidator.js28
-rw-r--r--test/validate/DataValidatorTest.js65
2 files changed, 91 insertions, 2 deletions
diff --git a/src/validate/DataValidator.js b/src/validate/DataValidator.js
index 3690ac5..1a7a90b 100644
--- a/src/validate/DataValidator.js
+++ b/src/validate/DataValidator.js
@@ -61,6 +61,12 @@ module.exports = Class( 'DataValidator',
*/
'private _stores': {},
+ /**
+ * Pending validation
+ * @type {Promise}
+ */
+ 'private _pending': null,
+
/**
* Initialize validator
@@ -103,6 +109,10 @@ module.exports = Class( 'DataValidator',
/**
* Validate diff and update field monitor
*
+ * If a validation is pending completion, all further `#validate`
+ * requests will be queued to prevent unexpected/inconsistent system
+ * states and race conditions.
+ *
* The external validator `validatef` is a kluge while the system
* undergoes refactoring.
*
@@ -116,6 +126,16 @@ module.exports = Class( 'DataValidator',
{
const _self = this;
+ // queue requests if we're already processing
+ if ( this._pending )
+ {
+ this._pending.then(
+ () => this.validate( diff, classes, validatef )
+ );
+
+ return this._pending;
+ }
+
let failures = {};
if ( diff !== undefined )
@@ -132,8 +152,12 @@ module.exports = Class( 'DataValidator',
}
// XXX: this assumes that the above is synchronous
- return this._populateStore( classes, this._stores.cstore, 'indexes' )
- .then( () => this.updateFailures( diff, failures ) );
+ return this._pending =
+ this._populateStore(
+ classes, this._stores.cstore, 'indexes'
+ )
+ .then( () => this.updateFailures( diff, failures ) )
+ .then( () => this._pending = null );
},
diff --git a/test/validate/DataValidatorTest.js b/test/validate/DataValidatorTest.js
index e2364d3..272aab0 100644
--- a/test/validate/DataValidatorTest.js
+++ b/test/validate/DataValidatorTest.js
@@ -251,6 +251,71 @@ describe( 'DataValidator', () =>
expect( cleared.b && cleared.c ).to.be.true
);
} ) );
+
+
+ // otherwise system might get into an unexpected state
+ it( 'queues concurrent validations', () =>
+ {
+ const expected_failure = {};
+
+ let vcalled = 0;
+
+ const bvalidator = createMockBucketValidator( function( _, __, ___ )
+ {
+ vcalled++;
+ } );
+
+ const vmonitor = sinon.createStubInstance( ValidStateMonitor );
+ const dep_factory = createMockDependencyFactory();
+ const getStore = createStubStore();
+
+ const diff_a = { foo: [ 'a', 'b', 'c' ] };
+ const diff_b = { foo: [ 'd' ] };
+
+ const validatef = ( diff, failures ) =>
+ {
+ // not a real failure; just used to transfer state to stub
+ // (see below)
+ failures.failedon = diff;
+ };
+
+ return new Promise( ( accept, reject ) =>
+ {
+ // by the time it gets to this the second time, store could
+ // be in any sort of state depending on what callbacks were
+ // invoked first (implementation details)
+ vmonitor.update = ( _, failures ) =>
+ {
+ const orig_diff = failures.failedon;
+
+ // if the external validator was called twice, then they
+ // didn't wait for us to finish
+ if ( ( orig_diff === diff_a ) && ( vcalled !== 1 ) )
+ {
+ reject( Error( "Request not queued" ) );
+ }
+
+ // if this key doesn't exist, then the store has been
+ // cleared (which happens before it's re-populated with
+ // the new diff)
+ return expect( getStore().bstore.get( 'foo' ) )
+ .to.eventually.deep.equal( orig_diff.foo )
+ .then( () => {
+ // the second test, after which we're done
+ if ( orig_diff === diff_b )
+ {
+ accept();
+ }
+ } )
+ .catch( e => reject( e ) );
+ };
+
+ const sut = Sut( bvalidator, vmonitor, dep_factory, getStore );
+
+ sut.validate( diff_a, {}, validatef );
+ sut.validate( diff_b, {}, validatef );
+ } );
+ } );
} );