diff --git a/superset/assets/src/SqlLab/components/QueryAutoRefresh.jsx b/superset/assets/src/SqlLab/components/QueryAutoRefresh.jsx index 892b7caf5..55e06cc14 100644 --- a/superset/assets/src/SqlLab/components/QueryAutoRefresh.jsx +++ b/superset/assets/src/SqlLab/components/QueryAutoRefresh.jsx @@ -8,6 +8,7 @@ const $ = require('jquery'); const QUERY_UPDATE_FREQ = 2000; const QUERY_UPDATE_BUFFER_MS = 5000; +const MAX_QUERY_AGE_TO_POLL = 21600000; class QueryAutoRefresh extends React.PureComponent { componentWillMount() { @@ -19,10 +20,12 @@ class QueryAutoRefresh extends React.PureComponent { shouldCheckForQueries() { // if there are started or running queries, this method should return true const { queries } = this.props; - const queryKeys = Object.keys(queries); - const queriesAsArray = queryKeys.map(key => queries[key]); - return queriesAsArray.some(q => - ['running', 'started', 'pending', 'fetching'].indexOf(q.state) >= 0); + const now = new Date().getTime(); + return Object.values(queries) + .some( + q => ['running', 'started', 'pending', 'fetching'].indexOf(q.state) >= 0 && + now - q.startDttm < MAX_QUERY_AGE_TO_POLL, + ); } startTimer() { if (!(this.timer)) { diff --git a/superset/assets/src/SqlLab/reducers.js b/superset/assets/src/SqlLab/reducers.js index f01f2c3bb..9c67f3822 100644 --- a/superset/assets/src/SqlLab/reducers.js +++ b/superset/assets/src/SqlLab/reducers.js @@ -162,9 +162,6 @@ export const sqlLabReducer = function (state, action) { return alterInObject(state, 'queries', action.query, { state: 'fetching' }); }, [actions.QUERY_SUCCESS]() { - if (action.query.state === 'stopped') { - return state; - } let rows; if (action.results.data) { rows = action.results.data.length; @@ -174,7 +171,7 @@ export const sqlLabReducer = function (state, action) { progress: 100, results: action.results, rows, - state: 'success', + state: action.query.state, errorMessage: null, cached: false, }; diff --git a/superset/db_engine_specs.py b/superset/db_engine_specs.py index 9d2c65db9..a718a0d62 100644 --- a/superset/db_engine_specs.py +++ b/superset/db_engine_specs.py @@ -675,7 +675,7 @@ class PrestoEngineSpec(BaseEngineSpec): stats = polled.get('stats', {}) query = session.query(type(query)).filter_by(id=query.id).one() - if query.status == QueryStatus.STOPPED: + if query.status in [QueryStatus.STOPPED, QueryStatus.TIMED_OUT]: cursor.cancel() break diff --git a/superset/views/core.py b/superset/views/core.py index 0359cae73..d290ecc9c 100755 --- a/superset/views/core.py +++ b/superset/views/core.py @@ -26,7 +26,7 @@ import pandas as pd import simplejson as json from six import text_type import sqlalchemy as sqla -from sqlalchemy import create_engine +from sqlalchemy import and_, create_engine, update from sqlalchemy.engine.url import make_url from sqlalchemy.exc import IntegrityError from unidecode import unidecode @@ -2549,6 +2549,35 @@ class Superset(BaseSupersetView): .all() ) dict_queries = {q.client_id: q.to_dict() for q in sql_queries} + + now = int(round(time.time() * 1000)) + + unfinished_states = [ + utils.QueryStatus.PENDING, + utils.QueryStatus.RUNNING, + ] + + queries_to_timeout = [ + client_id for client_id, query_dict in dict_queries.items() + if ( + query_dict['state'] in unfinished_states and ( + now - query_dict['startDttm'] > + config.get('SQLLAB_ASYNC_TIME_LIMIT_SEC') * 1000 + ) + ) + ] + + if queries_to_timeout: + update(Query).where( + and_( + Query.user_id == g.user.get_id(), + Query.client_id in queries_to_timeout, + ), + ).values(state=utils.QueryStatus.TIMED_OUT) + + for client_id in queries_to_timeout: + dict_queries[client_id]['status'] = utils.QueryStatus.TIMED_OUT + return json_success( json.dumps(dict_queries, default=utils.json_int_dttm_ser))