Fix datasource state management
Use case: 1. Configure a datasource with lazyConnect = true 2. Do NOT start the DB 3. Start the app 4. Send first request and it fails to connnect to the DB 5. Start the DB 5. Requests are now served correctly
This commit is contained in:
parent
dd30054138
commit
5a66f9ad72
|
@ -314,8 +314,6 @@ DataSource.prototype.connect = function(callback) {
|
|||
|
||||
// The connect is already in progress
|
||||
if (this.connecting) return callback.promise;
|
||||
// Set connecting flag to be true
|
||||
this.connecting = true;
|
||||
this.connector.connect(function(err, result) {
|
||||
self.connecting = false;
|
||||
if (!err) self.connected = true;
|
||||
|
@ -340,6 +338,11 @@ DataSource.prototype.connect = function(callback) {
|
|||
if (err) throw err; // It should not happen
|
||||
});
|
||||
});
|
||||
|
||||
// Set connecting flag to be `true` so that the connector knows there is
|
||||
// a connect in progress. The change of `connecting` should happen immediately
|
||||
// after the connect request is sent
|
||||
this.connecting = true;
|
||||
return callback.promise;
|
||||
};
|
||||
|
||||
|
@ -416,6 +419,7 @@ DataSource.prototype.setup = function(dsName, settings) {
|
|||
// Disconnected by default
|
||||
this.connected = false;
|
||||
this.connecting = false;
|
||||
this.initialized = false;
|
||||
|
||||
this.name = dsName || (typeof this.settings.name === 'string' && this.settings.name);
|
||||
|
||||
|
@ -453,20 +457,38 @@ DataSource.prototype.setup = function(dsName, settings) {
|
|||
throw new Error(g.f('Connector is not defined correctly: ' +
|
||||
'it should create `{{connector}}` member of dataSource'));
|
||||
}
|
||||
this.connected = !err; // Connected now
|
||||
if (!err) {
|
||||
this.initialized = true;
|
||||
this.emit('initialized');
|
||||
}
|
||||
debug('Connector is initialized for dataSource %s', this.name);
|
||||
// If `result` is set to `false` explicitly, the connection will be
|
||||
// lazily established
|
||||
if (!this.settings.lazyConnect) {
|
||||
this.connected = (!err) && (result !== false); // Connected now
|
||||
}
|
||||
if (this.connected) {
|
||||
debug('DataSource %s is now connected to %s', this.name, this.connector.name);
|
||||
this.emit('connected');
|
||||
} else {
|
||||
// The connection fails, let's report it and hope it will be recovered in the next call
|
||||
g.error('Connection fails: %s\nIt will be retried for the next request.', err);
|
||||
this.emit('error', err);
|
||||
this.connecting = false;
|
||||
if (err) {
|
||||
// Reset the connecting to `false`
|
||||
this.connecting = false;
|
||||
g.error('Connection fails: %s\nIt will be retried for the next request.', err);
|
||||
this.emit('error', err);
|
||||
} else {
|
||||
// Either lazyConnect or connector initialize() defers the connection
|
||||
debug('DataSource %s will be connected to connector %s', this.name,
|
||||
this.connector.name);
|
||||
}
|
||||
}
|
||||
}.bind(this);
|
||||
|
||||
try {
|
||||
if ('function' === typeof connector.initialize) {
|
||||
// Call the async initialize method
|
||||
debug('Initializing connector %s', connector.name);
|
||||
connector.initialize(this, postInit);
|
||||
} else if ('function' === typeof connector) {
|
||||
// Use the connector constructor directly
|
||||
|
@ -2464,13 +2486,21 @@ DataSource.prototype.isRelational = function() {
|
|||
};
|
||||
|
||||
/**
|
||||
* Check if the data source is ready.
|
||||
* Returns a Boolean value.
|
||||
* @param {Object} obj Deferred method call if the connector is not fully connected yet
|
||||
* @param {Object} args argument passing to method call.
|
||||
* Check if the data source is connected. If not, the method invocation will be
|
||||
* deferred and queued.
|
||||
*
|
||||
*
|
||||
* @param {Object} obj Receiver for the method call
|
||||
* @param {Object} args Arguments passing to the method call
|
||||
* @returns - a Boolean value to indicate if the method invocation is deferred.
|
||||
* false: The datasource is already connected
|
||||
* - true: The datasource is yet to be connected
|
||||
*/
|
||||
DataSource.prototype.ready = function(obj, args) {
|
||||
DataSource.prototype.queueInvocation = DataSource.prototype.ready =
|
||||
function(obj, args) {
|
||||
var self = this;
|
||||
debug('Datasource %s: connected=%s connecting=%s', this.name,
|
||||
this.connected, this.connecting);
|
||||
if (this.connected) {
|
||||
// Connected
|
||||
return false;
|
||||
|
@ -2481,6 +2511,7 @@ DataSource.prototype.ready = function(obj, args) {
|
|||
|
||||
var onConnected = null, onError = null, timeoutHandle = null;
|
||||
onConnected = function() {
|
||||
debug('Datasource %s is now connected - executing method %s', self.name, method.name);
|
||||
// Remove the error handler
|
||||
self.removeListener('error', onError);
|
||||
if (timeoutHandle) {
|
||||
|
@ -2502,6 +2533,7 @@ DataSource.prototype.ready = function(obj, args) {
|
|||
}
|
||||
};
|
||||
onError = function(err) {
|
||||
debug('Datasource %s fails to connect - aborting method %s', self.name, method.name);
|
||||
// Remove the connected listener
|
||||
self.removeListener('connected', onConnected);
|
||||
if (timeoutHandle) {
|
||||
|
@ -2521,6 +2553,9 @@ DataSource.prototype.ready = function(obj, args) {
|
|||
// Set up a timeout to cancel the invocation
|
||||
var timeout = this.settings.connectionTimeout || 5000;
|
||||
timeoutHandle = setTimeout(function() {
|
||||
debug('Datasource %s fails to connect due to timeout - aborting method %s',
|
||||
self.name, method.name);
|
||||
self.connecting = false;
|
||||
self.removeListener('error', onError);
|
||||
self.removeListener('connected', onConnected);
|
||||
var params = [].slice.call(args);
|
||||
|
@ -2531,6 +2566,7 @@ DataSource.prototype.ready = function(obj, args) {
|
|||
}, timeout);
|
||||
|
||||
if (!this.connecting) {
|
||||
debug('Connecting datasource %s to connector %s', this.name, this.connector.name);
|
||||
this.connect();
|
||||
}
|
||||
return true;
|
||||
|
|
|
@ -155,6 +155,172 @@ describe('DataSource', function() {
|
|||
dataSource.connector.should.equal(mockConnector);
|
||||
});
|
||||
|
||||
it('should set states correctly with eager connect', function(done) {
|
||||
var mockConnector = {
|
||||
name: 'loopback-connector-mock',
|
||||
initialize: function(ds, cb) {
|
||||
ds.connector = mockConnector;
|
||||
this.connect(cb);
|
||||
},
|
||||
|
||||
connect: function(cb) {
|
||||
process.nextTick(function() {
|
||||
cb(null);
|
||||
});
|
||||
},
|
||||
};
|
||||
var dataSource = new DataSource(mockConnector);
|
||||
// DataSource is instantiated
|
||||
// connected: false, connecting: false, initialized: false
|
||||
dataSource.connected.should.be.false();
|
||||
dataSource.connecting.should.be.false();
|
||||
dataSource.initialized.should.be.false();
|
||||
|
||||
dataSource.on('initialized', function() {
|
||||
// DataSource is initialized with lazyConnect
|
||||
// connected: false, connecting: false, initialized: true
|
||||
dataSource.connected.should.be.false();
|
||||
dataSource.connecting.should.be.false();
|
||||
dataSource.initialized.should.be.true();
|
||||
});
|
||||
|
||||
dataSource.on('connected', function() {
|
||||
// DataSource is now connected
|
||||
// connected: true, connecting: false
|
||||
dataSource.connected.should.be.true();
|
||||
dataSource.connecting.should.be.false();
|
||||
});
|
||||
|
||||
// Call connect() in next tick so that we'll receive initialized event
|
||||
// first
|
||||
process.nextTick(function() {
|
||||
// At this point, the datasource is already connected by
|
||||
// connector's (mockConnector) initialize function
|
||||
dataSource.connect(function() {
|
||||
// DataSource is now connected
|
||||
// connected: true, connecting: false
|
||||
dataSource.connected.should.be.true();
|
||||
dataSource.connecting.should.be.false();
|
||||
done();
|
||||
});
|
||||
// As the datasource is already connected, no connecting will happen
|
||||
// connected: true, connecting: false
|
||||
dataSource.connected.should.be.true();
|
||||
dataSource.connecting.should.be.false();
|
||||
});
|
||||
});
|
||||
|
||||
it('should set states correctly with deferred connect', function(done) {
|
||||
var mockConnector = {
|
||||
name: 'loopback-connector-mock',
|
||||
initialize: function(ds, cb) {
|
||||
ds.connector = mockConnector;
|
||||
// Explicitly call back with false to denote connection is not ready
|
||||
process.nextTick(function() {
|
||||
cb(null, false);
|
||||
});
|
||||
},
|
||||
|
||||
connect: function(cb) {
|
||||
process.nextTick(function() {
|
||||
cb(null);
|
||||
});
|
||||
},
|
||||
};
|
||||
var dataSource = new DataSource(mockConnector);
|
||||
// DataSource is instantiated
|
||||
// connected: false, connecting: false, initialized: false
|
||||
dataSource.connected.should.be.false();
|
||||
dataSource.connecting.should.be.false();
|
||||
dataSource.initialized.should.be.false();
|
||||
|
||||
dataSource.on('initialized', function() {
|
||||
// DataSource is initialized with lazyConnect
|
||||
// connected: false, connecting: false, initialized: true
|
||||
dataSource.connected.should.be.false();
|
||||
dataSource.connecting.should.be.false();
|
||||
dataSource.initialized.should.be.true();
|
||||
});
|
||||
|
||||
dataSource.on('connected', function() {
|
||||
// DataSource is now connected
|
||||
// connected: true, connecting: false
|
||||
dataSource.connected.should.be.true();
|
||||
dataSource.connecting.should.be.false();
|
||||
});
|
||||
|
||||
// Call connect() in next tick so that we'll receive initialized event
|
||||
// first
|
||||
process.nextTick(function() {
|
||||
dataSource.connect(function() {
|
||||
// DataSource is now connected
|
||||
// connected: true, connecting: false
|
||||
dataSource.connected.should.be.true();
|
||||
dataSource.connecting.should.be.false();
|
||||
done();
|
||||
});
|
||||
// As the datasource is not connected, connecting will happen
|
||||
// connected: false, connecting: true
|
||||
dataSource.connected.should.be.false();
|
||||
dataSource.connecting.should.be.true();
|
||||
});
|
||||
});
|
||||
|
||||
it('should set states correctly with lazyConnect = true', function(done) {
|
||||
var mockConnector = {
|
||||
name: 'loopback-connector-mock',
|
||||
initialize: function(ds, cb) {
|
||||
ds.connector = mockConnector;
|
||||
process.nextTick(function() {
|
||||
cb(null);
|
||||
});
|
||||
},
|
||||
|
||||
connect: function(cb) {
|
||||
process.nextTick(function() {
|
||||
cb(null);
|
||||
});
|
||||
},
|
||||
};
|
||||
var dataSource = new DataSource(mockConnector, {lazyConnect: true});
|
||||
// DataSource is instantiated
|
||||
// connected: false, connecting: false, initialized: false
|
||||
dataSource.connected.should.be.false();
|
||||
dataSource.connecting.should.be.false();
|
||||
dataSource.initialized.should.be.false();
|
||||
|
||||
dataSource.on('initialized', function() {
|
||||
// DataSource is initialized with lazyConnect
|
||||
// connected: false, connecting: false, initialized: true
|
||||
dataSource.connected.should.be.false();
|
||||
dataSource.connecting.should.be.false();
|
||||
dataSource.initialized.should.be.true();
|
||||
});
|
||||
|
||||
dataSource.on('connected', function() {
|
||||
// DataSource is now connected
|
||||
// connected: true, connecting: false
|
||||
dataSource.connected.should.be.true();
|
||||
dataSource.connecting.should.be.false();
|
||||
});
|
||||
|
||||
// Call connect() in next tick so that we'll receive initialized event
|
||||
// first
|
||||
process.nextTick(function() {
|
||||
dataSource.connect(function() {
|
||||
// DataSource is now connected
|
||||
// connected: true, connecting: false
|
||||
dataSource.connected.should.be.true();
|
||||
dataSource.connecting.should.be.false();
|
||||
done();
|
||||
});
|
||||
// DataSource is now connecting
|
||||
// connected: false, connecting: true
|
||||
dataSource.connected.should.be.false();
|
||||
dataSource.connecting.should.be.true();
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteModelByName()', () => {
|
||||
it('removes the model from ModelBuilder registry', () => {
|
||||
const ds = new DataSource('ds', {connector: 'memory'});
|
||||
|
|
|
@ -76,6 +76,7 @@ export declare class DataSource {
|
|||
name: string;
|
||||
settings: Options;
|
||||
|
||||
initialized?: boolean;
|
||||
connected?: boolean;
|
||||
connecting?: boolean;
|
||||
|
||||
|
|
Loading…
Reference in New Issue