Merge pull request #1756 from strongloop/fix/migrate-errors
fix: report errors from automigrate/autoupdate
This commit is contained in:
commit
0c2bb81dac
|
@ -136,6 +136,7 @@ function DataSource(name, settings, modelBuilder) {
|
||||||
this.models = this.modelBuilder.models;
|
this.models = this.modelBuilder.models;
|
||||||
this.definitions = this.modelBuilder.definitions;
|
this.definitions = this.modelBuilder.definitions;
|
||||||
this.juggler = juggler;
|
this.juggler = juggler;
|
||||||
|
this._queuedInvocations = 0;
|
||||||
|
|
||||||
// operation metadata
|
// operation metadata
|
||||||
// Initialize it before calling setup as the connector might register operations
|
// Initialize it before calling setup as the connector might register operations
|
||||||
|
@ -477,19 +478,23 @@ DataSource.prototype.setup = function(dsName, settings) {
|
||||||
if (this.connected) {
|
if (this.connected) {
|
||||||
debug('DataSource %s is now connected to %s', this.name, this.connector.name);
|
debug('DataSource %s is now connected to %s', this.name, this.connector.name);
|
||||||
this.emit('connected');
|
this.emit('connected');
|
||||||
} else {
|
} else if (err) {
|
||||||
// The connection fails, let's report it and hope it will be recovered in the next call
|
// The connection fails, let's report it and hope it will be recovered in the next call
|
||||||
if (err) {
|
|
||||||
// Reset the connecting to `false`
|
// Reset the connecting to `false`
|
||||||
this.connecting = false;
|
this.connecting = false;
|
||||||
|
if (this._queuedInvocations) {
|
||||||
|
// Another operation is already waiting for connect() result,
|
||||||
|
// let them handle the connection error.
|
||||||
|
debug('Connection fails: %s\nIt will be retried for the next request.', err);
|
||||||
|
} else {
|
||||||
g.error('Connection fails: %s\nIt will be retried for the next request.', err);
|
g.error('Connection fails: %s\nIt will be retried for the next request.', err);
|
||||||
this.emit('error', err);
|
this.emit('error', err);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Either lazyConnect or connector initialize() defers the connection
|
// Either lazyConnect or connector initialize() defers the connection
|
||||||
debug('DataSource %s will be connected to connector %s', this.name,
|
debug('DataSource %s will be connected to connector %s', this.name,
|
||||||
this.connector.name);
|
this.connector.name);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -1053,6 +1058,14 @@ DataSource.prototype.automigrate = function(models, cb) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const args = [models, cb];
|
||||||
|
args.callee = this.automigrate;
|
||||||
|
const queued = this.ready(this, args);
|
||||||
|
if (queued) {
|
||||||
|
// waiting to connect
|
||||||
|
return cb.promise;
|
||||||
|
}
|
||||||
|
|
||||||
this.connector.automigrate(models, cb);
|
this.connector.automigrate(models, cb);
|
||||||
return cb.promise;
|
return cb.promise;
|
||||||
};
|
};
|
||||||
|
@ -1114,6 +1127,14 @@ DataSource.prototype.autoupdate = function(models, cb) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const args = [models, cb];
|
||||||
|
args.callee = this.automigrate;
|
||||||
|
const queued = this.ready(this, args);
|
||||||
|
if (queued) {
|
||||||
|
// waiting to connect
|
||||||
|
return cb.promise;
|
||||||
|
}
|
||||||
|
|
||||||
this.connector.autoupdate(models, cb);
|
this.connector.autoupdate(models, cb);
|
||||||
return cb.promise;
|
return cb.promise;
|
||||||
};
|
};
|
||||||
|
@ -2514,12 +2535,15 @@ function(obj, args) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._queuedInvocations++;
|
||||||
|
|
||||||
const method = args.callee;
|
const method = args.callee;
|
||||||
// Set up a callback after the connection is established to continue the method call
|
// Set up a callback after the connection is established to continue the method call
|
||||||
|
|
||||||
let onConnected = null, onError = null, timeoutHandle = null;
|
let onConnected = null, onError = null, timeoutHandle = null;
|
||||||
onConnected = function() {
|
onConnected = function() {
|
||||||
debug('Datasource %s is now connected - executing method %s', self.name, method.name);
|
debug('Datasource %s is now connected - executing method %s', self.name, method.name);
|
||||||
|
this._queuedInvocations--;
|
||||||
// Remove the error handler
|
// Remove the error handler
|
||||||
self.removeListener('error', onError);
|
self.removeListener('error', onError);
|
||||||
if (timeoutHandle) {
|
if (timeoutHandle) {
|
||||||
|
@ -2542,6 +2566,7 @@ function(obj, args) {
|
||||||
};
|
};
|
||||||
onError = function(err) {
|
onError = function(err) {
|
||||||
debug('Datasource %s fails to connect - aborting method %s', self.name, method.name);
|
debug('Datasource %s fails to connect - aborting method %s', self.name, method.name);
|
||||||
|
this._queuedInvocations--;
|
||||||
// Remove the connected listener
|
// Remove the connected listener
|
||||||
self.removeListener('connected', onConnected);
|
self.removeListener('connected', onConnected);
|
||||||
if (timeoutHandle) {
|
if (timeoutHandle) {
|
||||||
|
@ -2563,6 +2588,7 @@ function(obj, args) {
|
||||||
timeoutHandle = setTimeout(function() {
|
timeoutHandle = setTimeout(function() {
|
||||||
debug('Datasource %s fails to connect due to timeout - aborting method %s',
|
debug('Datasource %s fails to connect due to timeout - aborting method %s',
|
||||||
self.name, method.name);
|
self.name, method.name);
|
||||||
|
this._queuedInvocations--;
|
||||||
self.connecting = false;
|
self.connecting = false;
|
||||||
self.removeListener('error', onError);
|
self.removeListener('error', onError);
|
||||||
self.removeListener('connected', onConnected);
|
self.removeListener('connected', onConnected);
|
||||||
|
@ -2575,7 +2601,11 @@ function(obj, args) {
|
||||||
|
|
||||||
if (!this.connecting) {
|
if (!this.connecting) {
|
||||||
debug('Connecting datasource %s to connector %s', this.name, this.connector.name);
|
debug('Connecting datasource %s to connector %s', this.name, this.connector.name);
|
||||||
this.connect();
|
// When no callback is provided to `connect()`, it returns a Promise.
|
||||||
|
// We are not handling that promise and thus UnhandledPromiseRejection
|
||||||
|
// warning is triggered when the connection could not be established.
|
||||||
|
// We are avoiding this problem by providing a no-op callback.
|
||||||
|
this.connect(() => {});
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
|
@ -457,4 +457,80 @@ describe('DataSource', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('automigrate', () => {
|
||||||
|
it('reports connection errors (immediate connect)', async () => {
|
||||||
|
const dataSource = new DataSource({
|
||||||
|
connector: givenConnectorFailingOnConnect(),
|
||||||
|
});
|
||||||
|
dataSource.define('MyModel');
|
||||||
|
await dataSource.automigrate().should.be.rejectedWith(/test failure/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reports connection errors (lazy connect)', () => {
|
||||||
|
const dataSource = new DataSource({
|
||||||
|
connector: givenConnectorFailingOnConnect(),
|
||||||
|
lazyConnect: true,
|
||||||
|
});
|
||||||
|
dataSource.define('MyModel');
|
||||||
|
return dataSource.automigrate().should.be.rejectedWith(/test failure/);
|
||||||
|
});
|
||||||
|
|
||||||
|
function givenConnectorFailingOnConnect() {
|
||||||
|
return givenMockConnector({
|
||||||
|
connect: function(cb) {
|
||||||
|
process.nextTick(() => cb(new Error('test failure')));
|
||||||
|
},
|
||||||
|
automigrate: function(models, cb) {
|
||||||
|
cb(new Error('automigrate should not have been called'));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('autoupdate', () => {
|
||||||
|
it('reports connection errors (immediate connect)', async () => {
|
||||||
|
const dataSource = new DataSource({
|
||||||
|
connector: givenConnectorFailingOnConnect(),
|
||||||
|
});
|
||||||
|
dataSource.define('MyModel');
|
||||||
|
await dataSource.autoupdate().should.be.rejectedWith(/test failure/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reports connection errors (lazy connect)', () => {
|
||||||
|
const dataSource = new DataSource({
|
||||||
|
connector: givenConnectorFailingOnConnect(),
|
||||||
|
lazyConnect: true,
|
||||||
|
});
|
||||||
|
dataSource.define('MyModel');
|
||||||
|
return dataSource.autoupdate().should.be.rejectedWith(/test failure/);
|
||||||
|
});
|
||||||
|
|
||||||
|
function givenConnectorFailingOnConnect() {
|
||||||
|
return givenMockConnector({
|
||||||
|
connect: function(cb) {
|
||||||
|
process.nextTick(() => cb(new Error('test failure')));
|
||||||
|
},
|
||||||
|
autoupdate: function(models, cb) {
|
||||||
|
cb(new Error('autoupdate should not have been called'));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function givenMockConnector(props) {
|
||||||
|
const connector = {
|
||||||
|
name: 'loopback-connector-mock',
|
||||||
|
initialize: function(ds, cb) {
|
||||||
|
ds.connector = connector;
|
||||||
|
if (ds.settings.lazyConnect) {
|
||||||
|
cb(null, false);
|
||||||
|
} else {
|
||||||
|
connector.connect(cb);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
...props,
|
||||||
|
};
|
||||||
|
return connector;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue