ui: Reduce reconnection attempts on disconnection (#8481)

* ui: Reduce reconnection attempts on disconnection

The UI will attempt to reconnect/retry a blocking query to Consul after
a disconnection in certain circumstances.

1. On receipt of a 5xx error (used for keeping blocking queries running
through reverse proxies that have lowertimeouts than consul itself)
2. When a user switches to a different tab and back again)
3. When the connection to Consul is dropped entirely (when Consul itself
has exited)

In the last case the retry attempts where not using a 3 second interval
between attempts like the first case is.

This commit changes the last case to use the same 3 second pause as the
last case.
This commit is contained in:
John Cowen 2020-08-11 18:47:15 +01:00 committed by GitHub
parent a686de0414
commit 43ec04f073
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 17 additions and 9 deletions

View File

@ -21,7 +21,7 @@ export default Service.extend({
this._listeners.add(this.dom.document(), { this._listeners.add(this.dom.document(), {
visibilitychange: e => { visibilitychange: e => {
if (e.target.hidden) { if (e.target.hidden) {
this.purge(); this.purge(-1);
} }
}, },
}); });

View File

@ -15,7 +15,9 @@ export const restartWhenAvailable = function(client) {
// setup the aborted connection restarting // setup the aborted connection restarting
// this should happen here to avoid cache deletion // this should happen here to avoid cache deletion
const status = get(e, 'errors.firstObject.status'); const status = get(e, 'errors.firstObject.status');
if (status === '0') { // TODO: Reconsider a proper custom HTTP code
// -1 is a UI only error code for 'user switched tab'
if (status === '-1') {
// Any '0' errors (abort) should possibly try again, depending upon the circumstances // Any '0' errors (abort) should possibly try again, depending upon the circumstances
// whenAvailable returns a Promise that resolves when the client is available // whenAvailable returns a Promise that resolves when the client is available
// again // again

View File

@ -2,7 +2,9 @@ import { get } from '@ember/object';
const pause = 2000; const pause = 2000;
// native EventSource retry is ~3s wait // native EventSource retry is ~3s wait
export const create5xxBackoff = function(ms = 3000, P = Promise, wait = setTimeout) { // any specified errors here will mean that the blocking query will attempt
// a reconnection every 3s until it reconnects to Consul
export const createErrorBackoff = function(ms = 3000, P = Promise, wait = setTimeout) {
// This expects an ember-data like error // This expects an ember-data like error
return function(err) { return function(err) {
const status = get(err, 'errors.firstObject.status'); const status = get(err, 'errors.firstObject.status');
@ -10,6 +12,11 @@ export const create5xxBackoff = function(ms = 3000, P = Promise, wait = setTimeo
switch (true) { switch (true) {
// Any '5xx' (not 500) errors should back off and try again // Any '5xx' (not 500) errors should back off and try again
case status.indexOf('5') === 0 && status.length === 3 && status !== '500': case status.indexOf('5') === 0 && status.length === 3 && status !== '500':
// fallsthrough
case status === '0':
// TODO: Move this to the view layer so we can show a connection error
// and reconnection success to the user
// Any 0 aborted connections should back off and try again
return new P(function(resolve) { return new P(function(resolve) {
wait(function() { wait(function() {
resolve(err); resolve(err);
@ -51,9 +58,9 @@ const defaultCreateEvent = function(result, configuration) {
* Wraps an EventSource with functionality to add native EventSource-like functionality * Wraps an EventSource with functionality to add native EventSource-like functionality
* *
* @param {Class} [CallableEventSource] - CallableEventSource Class * @param {Class} [CallableEventSource] - CallableEventSource Class
* @param {Function} [backoff] - Default backoff function for all instances, defaults to create5xxBackoff * @param {Function} [backoff] - Default backoff function for all instances, defaults to createErrorBackoff
*/ */
export default function(EventSource, backoff = create5xxBackoff()) { export default function(EventSource, backoff = createErrorBackoff()) {
/** /**
* An EventSource implementation to add native EventSource-like functionality with just callbacks (`cursor` and 5xx backoff) * An EventSource implementation to add native EventSource-like functionality with just callbacks (`cursor` and 5xx backoff)
* *

View File

@ -1,6 +1,6 @@
import domEventSourceBlocking, { import domEventSourceBlocking, {
validateCursor, validateCursor,
create5xxBackoff, createErrorBackoff,
} from 'consul-ui/utils/dom/event-source/blocking'; } from 'consul-ui/utils/dom/event-source/blocking';
import { module } from 'qunit'; import { module } from 'qunit';
import test from 'ember-sinon-qunit/test-support/test'; import test from 'ember-sinon-qunit/test-support/test';
@ -46,13 +46,12 @@ module('Unit | Utility | dom/event-source/blocking', function() {
assert.ok(source instanceof EventSource); assert.ok(source instanceof EventSource);
}); });
test("the 5xx backoff continues to throw when it's not a 5xx", function(assert) { test("the 5xx backoff continues to throw when it's not a 5xx", function(assert) {
const backoff = create5xxBackoff(); const backoff = createErrorBackoff();
[ [
undefined, undefined,
null, null,
new Error(), new Error(),
{ errors: [] }, { errors: [] },
{ errors: [{ status: '0' }] },
{ errors: [{ status: 501 }] }, { errors: [{ status: 501 }] },
{ errors: [{ status: '401' }] }, { errors: [{ status: '401' }] },
{ errors: [{ status: '500' }] }, { errors: [{ status: '500' }] },
@ -76,7 +75,7 @@ module('Unit | Utility | dom/event-source/blocking', function() {
const timeout = this.stub().callsArg(0); const timeout = this.stub().callsArg(0);
const resolve = this.stub().withArgs(item); const resolve = this.stub().withArgs(item);
const Promise = createPromise(resolve); const Promise = createPromise(resolve);
const backoff = create5xxBackoff(undefined, Promise, timeout); const backoff = createErrorBackoff(undefined, Promise, timeout);
const promise = backoff(item); const promise = backoff(item);
assert.ok(promise instanceof Promise, 'a promise was returned'); assert.ok(promise instanceof Promise, 'a promise was returned');
assert.ok(resolve.calledOnce, 'the promise was resolved with the correct arguments'); assert.ok(resolve.calledOnce, 'the promise was resolved with the correct arguments');