diff --git a/docs/en_US/images/server_advanced.png b/docs/en_US/images/server_advanced.png old mode 100755 new mode 100644 index d7e9919e8..83a7ad178 Binary files a/docs/en_US/images/server_advanced.png and b/docs/en_US/images/server_advanced.png differ diff --git a/docs/en_US/release_notes_4_16.rst b/docs/en_US/release_notes_4_16.rst index a38977148..a6349feb1 100644 --- a/docs/en_US/release_notes_4_16.rst +++ b/docs/en_US/release_notes_4_16.rst @@ -21,10 +21,14 @@ Bug fixes | `Issue #3538 `_ - Fix issue where the Reset button does not get enabled till all the mandatory fields are provided in the dialog. | `Issue #4659 `_ - Updated documentation for default privileges to clarify more on the grantor. +| `Issue #4724 `_ - Fix network disconnect issue while establishing the connection via SSH Tunnel and it impossible to expand the Servers node. | `Issue #4792 `_ - Ensure that the superuser should be able to create database, as the superuser overrides all the access restrictions. +| `Issue #4818 `_ - Fix server connection drops out issue in query tool. | `Issue #4836 `_ - Updated the json file name from 'servers.json' to 'pgadmin4/servers.json' in the container deployment section of the documentation. | `Issue #4878 `_ - Ensure that the superuser should be able to create role, as the superuser overrides all the access restrictions. | `Issue #4925 `_ - Shown some text on process watcher till the initial logs are loaded. +| `Issue #4926 `_ - Fix VPN network disconnect issue where pgAdmin4 hangs on expanding the Servers node. | `Issue #4930 `_ - Fix main window tab navigation accessibility issue. +| `Issue #4933 `_ - Ensure that the Servers collection node should expand independently of server connections. | `Issue #4934 `_ - Fix the help button link on the User Management dialog. | `Issue #4935 `_ - Fix accessibility issues. diff --git a/docs/en_US/server_dialog.rst b/docs/en_US/server_dialog.rst index 4016ba6fd..bd57e68b0 100644 --- a/docs/en_US/server_dialog.rst +++ b/docs/en_US/server_dialog.rst @@ -170,7 +170,8 @@ Use the fields in the *Advanced* tab to configure a connection: `Section 33.15 of the Postgres documentation `_. * Use the *Connection timeout* field to specify the maximum wait for connection, in seconds. Zero or not specified means wait indefinitely. It is not - recommended to use a timeout of less than 2 seconds. + recommended to use a timeout of less than 2 seconds. By default it is set to + 10 seconds. .. note:: The password file option is only supported when pgAdmin is using libpq v10.0 or later to connect to the server. diff --git a/web/migrations/versions/aff1436e3c8c_.py b/web/migrations/versions/aff1436e3c8c_.py new file mode 100644 index 000000000..7a7c3017d --- /dev/null +++ b/web/migrations/versions/aff1436e3c8c_.py @@ -0,0 +1,27 @@ + +""" Update the default timeout to 10 seconds instead on 0. +0 indicates wait indefinitely which causes trouble when network connection +to server is lost. + +Revision ID: aff1436e3c8c +Revises: a77a0932a568 +Create Date: 2019-10-28 12:47:36.828709 + +""" +from pgadmin.model import db + +# revision identifiers, used by Alembic. +revision = 'aff1436e3c8c' +down_revision = 'a77a0932a568' +branch_labels = None +depends_on = None + + +def upgrade(): + db.engine.execute( + 'UPDATE server SET connect_timeout=10 WHERE connect_timeout=0 OR connect_timeout IS NULL' + ) + + +def downgrade(): + pass diff --git a/web/pgadmin/browser/server_groups/servers/__init__.py b/web/pgadmin/browser/server_groups/servers/__init__.py index dc39a1393..a813f7a7c 100644 --- a/web/pgadmin/browser/server_groups/servers/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/__init__.py @@ -28,6 +28,7 @@ from pgadmin.model import db, Server, ServerGroup, User from pgadmin.utils.driver import get_driver from pgadmin.utils.master_password import get_crypt_key from pgadmin.utils.exception import CryptKeyMissing +from psycopg2 import Error as psycopg2_Error, OperationalError def has_any(data, keys): @@ -58,7 +59,7 @@ def recovery_state(connection, postgres_version): else: in_recovery = None wal_paused = None - return in_recovery, wal_paused + return status, result, in_recovery, wal_paused def server_icon_and_background(is_connected, manager, server): @@ -121,19 +122,21 @@ class ServerModule(sg.ServerGroupPluginModule): for server in servers: connected = False manager = None + errmsg = None + was_connected = False + in_recovery = None + wal_paused = None try: manager = driver.connection_manager(server.id) conn = manager.connection() - connected = conn.connected() + was_connected = conn.wasConnected except CryptKeyMissing: # show the nodes at least even if not able to connect. pass + except psycopg2_Error as e: + current_app.logger.exception(e) + errmsg = str(e) - in_recovery = None - wal_paused = None - - if connected: - in_recovery, wal_paused = recovery_state(conn, manager.version) yield self.generate_browser_node( "%d" % (server.id), gid, @@ -151,7 +154,9 @@ class ServerModule(sg.ServerGroupPluginModule): is_password_saved=True if server.password is not None else False, is_tunnel_password_saved=True - if server.tunnel_password is not None else False + if server.tunnel_password is not None else False, + was_connected=was_connected, + errmsg=errmsg ) @property @@ -352,12 +357,16 @@ class ServerNode(PGChildNodeView): manager = driver.connection_manager(server.id) conn = manager.connection() connected = conn.connected() - + errmsg = None + in_recovery = None + wal_paused = None if connected: - in_recovery, wal_paused = recovery_state(conn, manager.version) - else: - in_recovery = None - wal_paused = None + status, result, in_recovery, wal_paused =\ + recovery_state(conn, manager.version) + if not status: + connected = False + manager.release() + errmsg = "{0} : {1}".format(server.name, result) res.append( self.blueprint.generate_browser_node( @@ -377,7 +386,8 @@ class ServerNode(PGChildNodeView): is_password_saved=True if server.password is not None else False, is_tunnel_password_saved=True - if server.tunnel_password is not None else False + if server.tunnel_password is not None else False, + errmsg=errmsg ) ) @@ -409,12 +419,16 @@ class ServerNode(PGChildNodeView): manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(server.id) conn = manager.connection() connected = conn.connected() - + errmsg = None + in_recovery = None + wal_paused = None if connected: - in_recovery, wal_paused = recovery_state(conn, manager.version) - else: - in_recovery = None - wal_paused = None + status, result, in_recovery, wal_paused =\ + recovery_state(conn, manager.version) + if not status: + connected = False + manager.release() + errmsg = "{0} : {1}".format(server.name, result) return make_json_response( result=self.blueprint.generate_browser_node( @@ -434,8 +448,9 @@ class ServerNode(PGChildNodeView): is_password_saved=True if server.password is not None else False, is_tunnel_password_saved=True - if server.tunnel_password is not None else False - ) + if server.tunnel_password is not None else False, + errmsg=errmsg + ), ) @login_required @@ -949,19 +964,33 @@ class ServerNode(PGChildNodeView): def connect_status(self, gid, sid): """Check and return the connection status.""" + server = Server.query.filter_by(id=sid).first() manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid) conn = manager.connection() - res = conn.connected() + connected = conn.connected() + in_recovery = None + wal_paused = None + errmsg = None + if connected: + status, result, in_recovery, wal_paused =\ + recovery_state(conn, manager.version) - if res: - from pgadmin.utils.exception import ConnectionLost, \ - SSHTunnelConnectionLost - try: - conn.execute_scalar('SELECT 1') - except (ConnectionLost, SSHTunnelConnectionLost): - res = False + if not status: + connected = False + manager.release() + errmsg = "{0} : {1}".format(server.name, result) - return make_json_response(data={'connected': res}) + return make_json_response( + data={ + 'icon': server_icon_and_background(connected, manager, server), + 'connected': connected, + 'in_recovery': in_recovery, + 'wal_pause': wal_paused, + 'server_type': manager.server_type if connected else "pg", + 'user': manager.user_info if connected else None, + 'errmsg': errmsg + } + ) def connect(self, gid, sid): """ @@ -1078,6 +1107,8 @@ class ServerNode(PGChildNodeView): tunnel_password=tunnel_password, server_types=ServerType.types() ) + except OperationalError as e: + return internal_server_error(errormsg=str(e)) except Exception as e: current_app.logger.exception(e) return self.get_response_for_password( @@ -1088,7 +1119,7 @@ class ServerNode(PGChildNodeView): errmsg = errmsg.decode('utf-8') current_app.logger.error( - "Could not connected to server(#{0}) - '{1}'.\nError: {2}" + "Could not connect to server(#{0}) - '{1}'.\nError: {2}" .format(server.id, server.name, errmsg) ) return self.get_response_for_password(server, 401, True, @@ -1125,7 +1156,8 @@ class ServerNode(PGChildNodeView): %s - %s' % (server.id, server.name)) # Update the recovery and wal pause option for the server # if connected successfully - in_recovery, wal_paused = recovery_state(conn, manager.version) + _, _, in_recovery, wal_paused =\ + recovery_state(conn, manager.version) return make_json_response( success=1, diff --git a/web/pgadmin/browser/server_groups/servers/static/js/server.js b/web/pgadmin/browser/server_groups/servers/static/js/server.js index 99fed949e..843794d53 100644 --- a/web/pgadmin/browser/server_groups/servers/static/js/server.js +++ b/web/pgadmin/browser/server_groups/servers/static/js/server.js @@ -292,6 +292,10 @@ define('pgadmin.node.server', [ // Call added method of node.js pgAdmin.Browser.Node.callbacks.added.apply(this, arguments); + + if(data.was_connected) { + fetch_connection_status(this, data, pgBrowser.tree, item); + } return true; }, /* Reload configuration */ @@ -732,7 +736,7 @@ define('pgadmin.node.server', [ tunnel_password: undefined, tunnel_authentication: 0, save_tunnel_password: false, - connect_timeout: 0, + connect_timeout: 10, }, // Default values! initialize: function(attrs, args) { @@ -1273,7 +1277,14 @@ define('pgadmin.node.server', [ } }; + /* Wait till the existing request completes */ + if(data.is_connecting) { + return; + } data.is_connecting = true; + tree.setLeaf(item); + tree.removeIcon(item); + tree.addIcon(item, {icon: 'icon-server-connecting'}); var url = obj.generate_url(item, 'connect', data, true); $.post(url) .done(function(res) { @@ -1287,6 +1298,40 @@ define('pgadmin.node.server', [ return onFailure( xhr, status, error, obj, data, tree, item, wasConnected ); + }) + .always(function(){ + data.is_connecting = false; + }); + }; + var fetch_connection_status = function(obj, data, tree, item) { + var url = obj.generate_url(item, 'connect', data, true); + + tree.setLeaf(item); + tree.removeIcon(item); + tree.addIcon(item, {icon: 'icon-server-connecting'}); + $.get(url) + .done(function(res) { + tree.setInode(item); + if (res && res.data) { + if (typeof res.data.icon == 'string') { + tree.removeIcon(item); + data.icon = res.data.icon; + tree.addIcon(item, {icon: data.icon}); + } + _.extend(data, res.data); + + var serverInfo = pgBrowser.serverInfo = pgBrowser.serverInfo || {}; + serverInfo[data._id] = _.extend({}, data); + + if(data.errmsg) { + Alertify.error(data.errmsg); + } + } + }) + .fail(function(xhr, status, error) { + tree.setInode(item); + tree.addIcon(item, {icon: 'icon-server-not-connected'}); + Alertify.pgRespErrorNotify(xhr, error); }); }; } diff --git a/web/pgadmin/browser/utils.py b/web/pgadmin/browser/utils.py index 2a17b3a2d..67dce0942 100644 --- a/web/pgadmin/browser/utils.py +++ b/web/pgadmin/browser/utils.py @@ -17,7 +17,8 @@ from flask.views import View, MethodViewType, with_metaclass from flask_babelex import gettext from config import PG_DEFAULT_DRIVER -from pgadmin.utils.ajax import make_json_response, precondition_required +from pgadmin.utils.ajax import make_json_response, precondition_required,\ + internal_server_error from pgadmin.utils.exception import ConnectionLost, SSHTunnelConnectionLost,\ CryptKeyMissing @@ -377,11 +378,7 @@ class PGChildNodeView(NodeView): if not conn.connected(): status, msg = conn.connect() if not status: - return precondition_required( - gettext( - "Connection to the server has been lost." - ) - ) + return internal_server_error(errormsg=msg) except (ConnectionLost, SSHTunnelConnectionLost, CryptKeyMissing): raise except Exception as e: diff --git a/web/pgadmin/dashboard/static/js/dashboard.js b/web/pgadmin/dashboard/static/js/dashboard.js index 42d2eca4b..6e466052a 100644 --- a/web/pgadmin/dashboard/static/js/dashboard.js +++ b/web/pgadmin/dashboard/static/js/dashboard.js @@ -543,6 +543,7 @@ define('pgadmin.dashboard', [ ); const WAIT_COUNTER = 3; let last_poll_wait_counter = 0; + let resp_not_received_counter = 0; /* Stop if running, only one poller lives */ self.stopChartsPoller(); @@ -563,7 +564,7 @@ define('pgadmin.dashboard', [ /* If none of the chart wants data, don't trouble * If response not received from prev poll, don't trouble !! */ - if(chart_names_to_get.length == 0 || last_poll_wait_counter > 0) { + if(chart_names_to_get.length == 0 || last_poll_wait_counter > 0 || resp_not_received_counter >= WAIT_COUNTER) { /* reduce the number of tries, request should be sent if last_poll_wait_counter * completes WAIT_COUNTER times.*/ last_poll_wait_counter--; @@ -571,12 +572,12 @@ define('pgadmin.dashboard', [ } var path = self.getStatsUrl(sid, did, chart_names_to_get); + resp_not_received_counter++; $.ajax({ url: path, type: 'GET', }) .done(function(resp) { - last_poll_wait_counter = 0; for(let chart_name in resp) { let chart_obj = chart_store[chart_name].chart_obj; $(chart_obj.getContainer()).removeClass('graph-error'); @@ -584,7 +585,6 @@ define('pgadmin.dashboard', [ } }) .fail(function(xhr) { - last_poll_wait_counter = 0; let err = ''; let msg = ''; let cls = 'info'; @@ -613,6 +613,10 @@ define('pgadmin.dashboard', [ '' ); } + }) + .always(function() { + last_poll_wait_counter = 0; + resp_not_received_counter--; }); last_poll_wait_counter = WAIT_COUNTER; }; diff --git a/web/pgadmin/model/__init__.py b/web/pgadmin/model/__init__.py index 629e98f37..b22868fbb 100644 --- a/web/pgadmin/model/__init__.py +++ b/web/pgadmin/model/__init__.py @@ -29,7 +29,7 @@ from flask_sqlalchemy import SQLAlchemy # ########################################################################## -SCHEMA_VERSION = 23 +SCHEMA_VERSION = 24 ########################################################################## # diff --git a/web/pgadmin/static/js/sqleditor/execute_query.js b/web/pgadmin/static/js/sqleditor/execute_query.js index f714c2b50..63ad7721b 100644 --- a/web/pgadmin/static/js/sqleditor/execute_query.js +++ b/web/pgadmin/static/js/sqleditor/execute_query.js @@ -12,6 +12,7 @@ import $ from 'jquery'; import url_for from '../url_for'; import axios from 'axios'; import * as httpErrorHandler from './query_tool_http_error_handler'; +import * as queryTxnStatus from 'sources/sqleditor/query_txn_status_constants'; class LoadingScreen { constructor(sqlEditor) { @@ -83,7 +84,8 @@ class ExecuteQuery { self.loadingScreen.hide(); self.enableSQLEditorButtons(); // Enable/Disable commit and rollback button. - if (result.data.data.transaction_status == 2 || result.data.data.transaction_status == 3) { + if (result.data.data.transaction_status == queryTxnStatus.TRANSACTION_STATUS_INTRANS + || result.data.data.transaction_status == queryTxnStatus.TRANSACTION_STATUS_INERROR) { self.enableTransactionButtons(); } else { self.disableTransactionButtons(); @@ -123,7 +125,8 @@ class ExecuteQuery { self.updateSqlEditorLastTransactionStatus(httpMessage.data.data.transaction_status); // Enable/Disable commit and rollback button. - if (httpMessage.data.data.transaction_status == 2 || httpMessage.data.data.transaction_status == 3) { + if (httpMessage.data.data.transaction_status == queryTxnStatus.TRANSACTION_STATUS_INTRANS + || httpMessage.data.data.transaction_status == queryTxnStatus.TRANSACTION_STATUS_INERROR) { self.enableTransactionButtons(); } else { self.disableTransactionButtons(); @@ -131,7 +134,7 @@ class ExecuteQuery { if (ExecuteQuery.isQueryFinished(httpMessage)) { if (this.sqlServerObject.close_on_idle_transaction && - httpMessage.data.data.transaction_status == 0) + httpMessage.data.data.transaction_status == queryTxnStatus.TRANSACTION_STATUS_IDLE) this.sqlServerObject.check_needed_confirmations_before_closing_panel(); self.loadingScreen.setMessage('Loading data from the database server and rendering...'); diff --git a/web/pgadmin/static/js/sqleditor/query_txn_status_constants.js b/web/pgadmin/static/js/sqleditor/query_txn_status_constants.js new file mode 100644 index 000000000..075264296 --- /dev/null +++ b/web/pgadmin/static/js/sqleditor/query_txn_status_constants.js @@ -0,0 +1,11 @@ +/* psycopg2 transaction status constants + * http://initd.org/psycopg/docs/extensions.html#transaction-status-constants + */ + +module.exports = { + TRANSACTION_STATUS_IDLE: 0, + TRANSACTION_STATUS_ACTIVE: 1, + TRANSACTION_STATUS_INTRANS: 2, + TRANSACTION_STATUS_INERROR: 3, + TRANSACTION_STATUS_UNKNOWN: 5, +}; diff --git a/web/pgadmin/static/js/tree/tree.js b/web/pgadmin/static/js/tree/tree.js index 33397e80d..e317aeb81 100644 --- a/web/pgadmin/static/js/tree/tree.js +++ b/web/pgadmin/static/js/tree/tree.js @@ -9,6 +9,7 @@ import {isValidData} from 'sources/utils'; import $ from 'jquery'; +import Alertify from 'pgadmin.alertifyjs'; export class TreeNode { constructor(id, data, domNode, parent) { @@ -261,6 +262,9 @@ export class Tree { const parentId = this.translateTreeNodeIdFromACITree(api.parent(item)); this.addNewNode(id, data, item, parentId); + if(data.errmsg) { + Alertify.error(data.errmsg); + } } } }.bind(this)); diff --git a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js index 9d9ab5c84..8f5897f3f 100644 --- a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js +++ b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js @@ -36,6 +36,7 @@ define('tools.querytool', [ 'sources/sqleditor/calculate_query_run_time', 'sources/sqleditor/call_render_after_poll', 'sources/sqleditor/query_tool_preferences', + 'sources/sqleditor/query_txn_status_constants', 'sources/csrf', 'tools/datagrid/static/js/datagrid_panel_title', 'sources/window', @@ -52,7 +53,7 @@ define('tools.querytool', [ XCellSelectionModel, setStagedRows, SqlEditorUtils, ExecuteQuery, httpErrorHandler, FilterHandler, GeometryViewer, historyColl, queryHist, querySources, keyboardShortcuts, queryToolActions, queryToolNotifications, Datagrid, - modifyAnimation, calculateQueryRunTime, callRenderAfterPoll, queryToolPref, csrfToken, panelTitleFunc, + modifyAnimation, calculateQueryRunTime, callRenderAfterPoll, queryToolPref, queryTxnStatus, csrfToken, panelTitleFunc, pgWindow) { /* Return back, this has been called more than once */ if (pgAdmin.SqlEditor) @@ -4182,8 +4183,9 @@ define('tools.querytool', [ self.unsaved_changes_user_confirmation(msg, false); } // If a transaction is currently ongoing else if (self.preferences.prompt_commit_transaction - && self.last_transaction_status > 0) { // 0 -> idle (no transaction) - var is_commit_disabled = self.last_transaction_status == 3; // 3 -> Failed transaction + && (self.last_transaction_status === queryTxnStatus.TRANSACTION_STATUS_INTRANS + || self.last_transaction_status === queryTxnStatus.TRANSACTION_STATUS_INERROR)) { + var is_commit_disabled = self.last_transaction_status == queryTxnStatus.TRANSACTION_STATUS_INERROR; self.uncommitted_transaction_user_confirmation(is_commit_disabled); } else { diff --git a/web/pgadmin/utils/driver/psycopg2/__init__.py b/web/pgadmin/utils/driver/psycopg2/__init__.py index 4b3e2204b..94dc2976c 100644 --- a/web/pgadmin/utils/driver/psycopg2/__init__.py +++ b/web/pgadmin/utils/driver/psycopg2/__init__.py @@ -14,19 +14,21 @@ object. """ import datetime -from flask import session, request +from flask import session from flask_login import current_user -from flask_babelex import gettext import psycopg2 from psycopg2.extensions import adapt +from threading import Lock import config -from pgadmin.model import Server, User +from pgadmin.model import Server from .keywords import ScanKeyword from ..abstract import BaseDriver from .connection import Connection from .server_manager import ServerManager +connection_restore_lock = Lock() + class Driver(BaseDriver): """ @@ -80,21 +82,30 @@ class Driver(BaseDriver): return None if session.sid not in self.managers: - self.managers[session.sid] = managers = dict() - if '__pgsql_server_managers' in session: - session_managers = session['__pgsql_server_managers'].copy() + with connection_restore_lock: + # The wait is over but the object might have been loaded + # by some other thread check again + if session.sid not in self.managers: + self.managers[session.sid] = managers = dict() + if '__pgsql_server_managers' in session: + session_managers =\ + session['__pgsql_server_managers'].copy() + for server in \ + Server.query.filter_by( + user_id=current_user.id): + manager = managers[str(server.id)] =\ + ServerManager(server) + if server.id in session_managers: + manager._restore(session_managers[server.id]) + manager.update_session() - for server in Server.query.filter_by(user_id=current_user.id): - manager = managers[str(server.id)] = ServerManager(server) - if server.id in session_managers: - manager._restore(session_managers[server.id]) - manager.update_session() else: managers = self.managers[session.sid] if str(sid) in managers: manager = managers[str(sid)] - manager._restore_connections() - manager.update_session() + with connection_restore_lock: + manager._restore_connections() + manager.update_session() managers['pinged'] = datetime.datetime.now() if str(sid) not in managers: diff --git a/web/pgadmin/utils/driver/psycopg2/server_manager.py b/web/pgadmin/utils/driver/psycopg2/server_manager.py index 8ae46c2ee..1676f7e38 100644 --- a/web/pgadmin/utils/driver/psycopg2/server_manager.py +++ b/web/pgadmin/utils/driver/psycopg2/server_manager.py @@ -25,13 +25,10 @@ from pgadmin.model import Server, User from pgadmin.utils.exception import ConnectionLost, SSHTunnelConnectionLost,\ CryptKeyMissing from pgadmin.utils.master_password import get_crypt_key -from threading import Lock if config.SUPPORT_SSH_TUNNEL: from sshtunnel import SSHTunnelForwarder, BaseSSHTunnelForwarderError -connection_restore_lock = Lock() - class ServerManager(object): """ @@ -292,80 +289,79 @@ WHERE db.oid = {0}""".format(did)) connections = data['connections'] - with connection_restore_lock: - for conn_id in connections: - conn_info = connections[conn_id] - if conn_info['conn_id'] in self.connections: - conn = self.connections[conn_info['conn_id']] - else: - conn = self.connections[conn_info['conn_id']] = Connection( - self, conn_info['conn_id'], conn_info['database'], - conn_info['auto_reconnect'], conn_info['async_'], - use_binary_placeholder=conn_info[ - 'use_binary_placeholder'], - array_to_string=conn_info['array_to_string'] + for conn_id in connections: + conn_info = connections[conn_id] + if conn_info['conn_id'] in self.connections: + conn = self.connections[conn_info['conn_id']] + else: + conn = self.connections[conn_info['conn_id']] = Connection( + self, conn_info['conn_id'], conn_info['database'], + conn_info['auto_reconnect'], conn_info['async_'], + use_binary_placeholder=conn_info[ + 'use_binary_placeholder'], + array_to_string=conn_info['array_to_string'] + ) + + # only try to reconnect if connection was connected previously + # and auto_reconnect is true. + if conn_info['wasConnected'] and conn_info['auto_reconnect']: + try: + # Check SSH Tunnel needs to be created + if self.use_ssh_tunnel == 1 and \ + not self.tunnel_created: + status, error = self.create_ssh_tunnel( + data['tunnel_password']) + + # Check SSH Tunnel is alive or not. + self.check_ssh_tunnel_alive() + + conn.connect( + password=data['password'], + server_types=ServerType.types() ) - - # only try to reconnect if connection was connected previously - # and auto_reconnect is true. - if conn_info['wasConnected'] and conn_info['auto_reconnect']: - try: - # Check SSH Tunnel needs to be created - if self.use_ssh_tunnel == 1 and \ - not self.tunnel_created: - status, error = self.create_ssh_tunnel( - data['tunnel_password']) - - # Check SSH Tunnel is alive or not. - self.check_ssh_tunnel_alive() - - conn.connect( - password=data['password'], - server_types=ServerType.types() - ) - # This will also update wasConnected flag in - # connection so no need to update the flag manually. - except CryptKeyMissing: - # maintain the status as this will help to restore once - # the key is available - conn.wasConnected = conn_info['wasConnected'] - conn.auto_reconnect = conn_info['auto_reconnect'] - except Exception as e: - current_app.logger.exception(e) - self.connections.pop(conn_info['conn_id']) - raise + # This will also update wasConnected flag in + # connection so no need to update the flag manually. + except CryptKeyMissing: + # maintain the status as this will help to restore once + # the key is available + conn.wasConnected = conn_info['wasConnected'] + conn.auto_reconnect = conn_info['auto_reconnect'] + except Exception as e: + current_app.logger.exception(e) + self.connections.pop(conn_info['conn_id']) + raise def _restore_connections(self): - with connection_restore_lock: - for conn_id in self.connections: - conn = self.connections[conn_id] - # only try to reconnect if connection was connected previously - # and auto_reconnect is true. - wasConnected = conn.wasConnected - auto_reconnect = conn.auto_reconnect - if conn.wasConnected and conn.auto_reconnect: - try: - # Check SSH Tunnel needs to be created - if self.use_ssh_tunnel == 1 and \ - not self.tunnel_created: - status, error = self.create_ssh_tunnel( - self.tunnel_password - ) + for conn_id in self.connections: + conn = self.connections[conn_id] + # only try to reconnect if connection was connected previously + # and auto_reconnect is true. + wasConnected = conn.wasConnected + auto_reconnect = conn.auto_reconnect + if conn.wasConnected and conn.auto_reconnect: + try: + # Check SSH Tunnel needs to be created + if self.use_ssh_tunnel == 1 and \ + not self.tunnel_created: + status, error = self.create_ssh_tunnel( + self.tunnel_password + ) - # Check SSH Tunnel is alive or not. - self.check_ssh_tunnel_alive() + # Check SSH Tunnel is alive or not. + self.check_ssh_tunnel_alive() - conn.connect() - # This will also update wasConnected flag in - # connection so no need to update the flag manually. - except CryptKeyMissing: - # maintain the status as this will help to restore once - # the key is available - conn.wasConnected = wasConnected - conn.auto_reconnect = auto_reconnect - except Exception as e: - current_app.logger.exception(e) - raise + conn.connect() + # This will also update wasConnected flag in + # connection so no need to update the flag manually. + except CryptKeyMissing: + # maintain the status as this will help to restore once + # the key is available + conn.wasConnected = wasConnected + conn.auto_reconnect = auto_reconnect + except Exception as e: + self.connections.pop(conn_id) + current_app.logger.exception(e) + raise def release(self, database=None, conn_id=None, did=None): # Stop the SSH tunnel if release() function calls without