From 0f86ad1bed392506c440ce0f719c3dc9bf07f90c Mon Sep 17 00:00:00 2001 From: jpellizzari Date: Mon, 20 Mar 2017 12:30:32 -0700 Subject: [PATCH] Prevent client polling after shutdown --- client/app/scripts/actions/app-actions.js | 19 +++--- client/app/scripts/components/app.js | 9 +-- client/app/scripts/constants/action-types.js | 1 + client/app/scripts/reducers/root.js | 5 ++ client/app/scripts/utils/web-api-utils.js | 69 ++++++++++++-------- 5 files changed, 62 insertions(+), 41 deletions(-) diff --git a/client/app/scripts/actions/app-actions.js b/client/app/scripts/actions/app-actions.js index 5bb0a74dc8..2199797349 100644 --- a/client/app/scripts/actions/app-actions.js +++ b/client/app/scripts/actions/app-actions.js @@ -17,7 +17,7 @@ import { getNodeDetails, getTopologies, deletePipe, - stopTopologyPolling, + stopPolling, teardownWebsockets, } from '../utils/web-api-utils'; import { getCurrentTopologyUrl } from '../utils/topology-utils'; @@ -720,20 +720,17 @@ export function toggleTroubleshootingMenu(ev) { export function changeInstance() { return (dispatch, getState) => { - dispatch({ type: ActionTypes.CHANGE_INSTANCE }); + dispatch({ + type: ActionTypes.CHANGE_INSTANCE + }); updateRoute(getState); - const state = getState(); - getTopologies(activeTopologyOptionsSelector(state), dispatch); - getNodesDelta( - getCurrentTopologyUrl(state), - activeTopologyOptionsSelector(state), - dispatch, - true // forces websocket teardown and reconnect to new instance - ); }; } export function shutdown() { - stopTopologyPolling(); + stopPolling(); teardownWebsockets(); + return { + type: ActionTypes.SHUTDOWN + }; } diff --git a/client/app/scripts/components/app.js b/client/app/scripts/components/app.js index adc1251f48..19d7d92454 100644 --- a/client/app/scripts/components/app.js +++ b/client/app/scripts/components/app.js @@ -42,9 +42,10 @@ class App extends React.Component { window.addEventListener('keyup', this.onKeyUp); getRouter(this.props.dispatch, this.props.urlState).start({hashbang: true}); - if (!this.props.routeSet) { - // dont request topologies when already done via router - getTopologies(this.props.activeTopologyOptions, this.props.dispatch); + if (!this.props.routeSet || process.env.WEAVE_CLOUD) { + // dont request topologies when already done via router. + // If running as a component, always request topologies when the app mounts. + getTopologies(this.props.activeTopologyOptions, this.props.dispatch, true); } getApiDetails(this.props.dispatch); } @@ -52,7 +53,7 @@ class App extends React.Component { componentWillUnmount() { window.removeEventListener('keypress', this.onKeyPress); window.removeEventListener('keyup', this.onKeyUp); - shutdown(); + this.props.dispatch(shutdown()); } onKeyUp(ev) { diff --git a/client/app/scripts/constants/action-types.js b/client/app/scripts/constants/action-types.js index d18818c6c7..fbf0a45f60 100644 --- a/client/app/scripts/constants/action-types.js +++ b/client/app/scripts/constants/action-types.js @@ -62,6 +62,7 @@ const ACTION_TYPES = [ 'SET_GRID_MODE', 'CHANGE_INSTANCE', 'TOGGLE_CONTRAST_MODE', + 'SHUTDOWN' ]; export default zipObject(ACTION_TYPES, ACTION_TYPES); diff --git a/client/app/scripts/reducers/root.js b/client/app/scripts/reducers/root.js index 49fe78d123..b2bf96441a 100644 --- a/client/app/scripts/reducers/root.js +++ b/client/app/scripts/reducers/root.js @@ -744,6 +744,11 @@ export function rootReducer(state = initialState, action) { return state.set('contrastMode', action.enabled); } + case ActionTypes.SHUTDOWN: { + state = clearNodes(state); + return state.set('nodesLoaded', false); + } + default: { return state; } diff --git a/client/app/scripts/utils/web-api-utils.js b/client/app/scripts/utils/web-api-utils.js index 90423118c6..6fa2331c02 100644 --- a/client/app/scripts/utils/web-api-utils.js +++ b/client/app/scripts/utils/web-api-utils.js @@ -40,6 +40,7 @@ let apiDetailsTimer = 0; let controlErrorTimer = 0; let createWebsocketAt = 0; let firstMessageOnWebsocketAt = 0; +let continuePolling = true; export function buildOptionsQuery(options) { if (options) { @@ -111,9 +112,11 @@ function createWebsocket(topologyUrl, optionsQuery, dispatch) { socket = null; dispatch(closeWebsocket()); - reconnectTimer = setTimeout(() => { - createWebsocket(topologyUrl, optionsQuery, dispatch); - }, reconnectTimerInterval); + if (continuePolling) { + reconnectTimer = setTimeout(() => { + createWebsocket(topologyUrl, optionsQuery, dispatch); + }, reconnectTimerInterval); + } }; socket.onerror = () => { @@ -172,35 +175,44 @@ export function getAllNodes(getState, dispatch) { Promise.resolve()); } -export function getTopologies(options, dispatch) { +export function getTopologies(options, dispatch, initialPoll) { + // Used to resume polling when navigating between pages in Weave Cloud. + continuePolling = initialPoll === true ? true : continuePolling; clearTimeout(topologyTimer); const optionsQuery = buildOptionsQuery(options); const url = `${getApiPath()}/api/topology?${optionsQuery}`; doRequest({ url, success: (res) => { - dispatch(receiveTopologies(res)); - topologyTimer = setTimeout(() => { - getTopologies(options, dispatch); - }, TOPOLOGY_INTERVAL); + if (continuePolling) { + dispatch(receiveTopologies(res)); + topologyTimer = setTimeout(() => { + getTopologies(options, dispatch); + }, TOPOLOGY_INTERVAL); + } }, - error: (err) => { - log(`Error in topology request: ${err.responseText}`); + error: (req) => { + log(`Error in topology request: ${req.responseText}`); dispatch(receiveError(url)); - topologyTimer = setTimeout(() => { - getTopologies(options, dispatch); - }, TOPOLOGY_INTERVAL); + // Only retry in stand-alone mode + if (continuePolling) { + topologyTimer = setTimeout(() => { + getTopologies(options, dispatch); + }, TOPOLOGY_INTERVAL); + } } }); } -export function getNodesDelta(topologyUrl, options, dispatch, forceReload) { +export function getNodesDelta(topologyUrl, options, dispatch) { const optionsQuery = buildOptionsQuery(options); // Only recreate websocket if url changed or if forced (weave cloud instance reload); // Check for truthy options and that options have changed. const isNewOptions = currentOptions && currentOptions !== optionsQuery; - const isNewUrl = topologyUrl && (topologyUrl !== currentUrl || isNewOptions); - if (forceReload || isNewUrl) { + const isNewUrl = topologyUrl !== currentUrl || isNewOptions; + // `topologyUrl` can be undefined initially, so only create a socket if it is truthy + // and no socket exists, or if we get a new url. + if ((topologyUrl && !socket) || (topologyUrl && isNewUrl)) { createWebsocket(topologyUrl, optionsQuery, dispatch); currentUrl = topologyUrl; currentOptions = optionsQuery; @@ -250,16 +262,20 @@ export function getApiDetails(dispatch) { url, success: (res) => { dispatch(receiveApiDetails(res)); - apiDetailsTimer = setTimeout(() => { - getApiDetails(dispatch); - }, API_INTERVAL); + if (continuePolling) { + apiDetailsTimer = setTimeout(() => { + getApiDetails(dispatch); + }, API_INTERVAL); + } }, - error: (err) => { - log(`Error in api details request: ${err.responseText}`); + error: (req) => { + log(`Error in api details request: ${req.responseText}`); receiveError(url); - apiDetailsTimer = setTimeout(() => { - getApiDetails(dispatch); - }, API_INTERVAL / 2); + if (continuePolling) { + apiDetailsTimer = setTimeout(() => { + getApiDetails(dispatch); + }, API_INTERVAL / 2); + } } }); } @@ -353,9 +369,10 @@ export function getPipeStatus(pipeId, dispatch) { }); } -export function stopTopologyPolling() { +export function stopPolling() { + clearTimeout(apiDetailsTimer); clearTimeout(topologyTimer); - topologyTimer = 0; + continuePolling = false; } export function teardownWebsockets() {