From 013ad7446fdfa7367b4d580d9e9d53dfd98d0316 Mon Sep 17 00:00:00 2001 From: Harshal Dhumal Date: Wed, 22 Aug 2018 11:55:39 +0530 Subject: [PATCH] Make the session thread safe. As sessions in pgAdmin4 are filesystem based session, they need locking for avoiding the access from multiple threads, specially running as an WSGI application. Fixes #3547 --- docs/en_US/release_notes_3_3.rst | 1 + web/pgadmin/utils/session.py | 76 ++++++++++++++++++-------------- 2 files changed, 44 insertions(+), 33 deletions(-) diff --git a/docs/en_US/release_notes_3_3.rst b/docs/en_US/release_notes_3_3.rst index 531df4707..4b2be3027 100644 --- a/docs/en_US/release_notes_3_3.rst +++ b/docs/en_US/release_notes_3_3.rst @@ -20,5 +20,6 @@ Bug fixes | `Bug #3407 `_ - Fix keyboard shortcuts layout in the preferences panel. | `Bug #3461 `_ - Ensure that refreshing a node also updates the Property list. | `Bug #3528 `_ - Handle connection errors properly in the query tool. +| `Bug #3547 `_ - Make session implementation thread safe | `Bug #3558 `_ - Fix sort/filter dialog editing issue. | `Bug #3578 `_ - Ensure sql for Role should be visible in SQL panel for GPDB. diff --git a/web/pgadmin/utils/session.py b/web/pgadmin/utils/session.py index fa313e0ad..cdf39ce5e 100644 --- a/web/pgadmin/utils/session.py +++ b/web/pgadmin/utils/session.py @@ -24,6 +24,7 @@ import random import string import time from uuid import uuid4 +from threading import Lock from flask import current_app, request, flash, redirect from flask_login import login_url from pgadmin.utils.ajax import make_json_response @@ -50,6 +51,9 @@ def _calc_hmac(body, secret): ).decode() +sess_lock = Lock() + + class ManagedSession(CallbackDict, SessionMixin): def __init__(self, initial=None, sid=None, new=False, randval=None, hmac_digest=None): @@ -111,8 +115,9 @@ class CachingSessionManager(SessionManager): def _normalize(self): if len(self._cache) > self.num_to_store: # Flush 20% of the cache - while len(self._cache) > (self.num_to_store * 0.8): - self._cache.popitem(False) + with sess_lock: + while len(self._cache) > (self.num_to_store * 0.8): + self._cache.popitem(False) def new_session(self): session = self.parent.new_session() @@ -122,59 +127,64 @@ class CachingSessionManager(SessionManager): if request.path.startswith(sp): return session - self._cache[session.sid] = session + with sess_lock: + self._cache[session.sid] = session self._normalize() return session def remove(self, sid): - self.parent.remove(sid) - if sid in self._cache: - del self._cache[sid] + with sess_lock: + self.parent.remove(sid) + if sid in self._cache: + del self._cache[sid] def exists(self, sid): - if sid in self._cache: - return True - return self.parent.exists(sid) + with sess_lock: + if sid in self._cache: + return True + return self.parent.exists(sid) def get(self, sid, digest): session = None - if sid in self._cache: - session = self._cache[sid] - if session.hmac_digest != digest: - session = None + with sess_lock: + if sid in self._cache: + session = self._cache[sid] + if session.hmac_digest != digest: + session = None - # reset order in Dict - del self._cache[sid] + # reset order in Dict + del self._cache[sid] - if not session: - session = self.parent.get(sid, digest) + if not session: + session = self.parent.get(sid, digest) - # Do not store the session if skip paths - for sp in self.skip_paths: - if request.path.startswith(sp): - return session + # Do not store the session if skip paths + for sp in self.skip_paths: + if request.path.startswith(sp): + return session - self._cache[sid] = session + self._cache[sid] = session self._normalize() return session def put(self, session): - self.parent.put(session) + with sess_lock: + self.parent.put(session) - # Do not store the session if skip paths - for sp in self.skip_paths: - if request.path.startswith(sp): - return + # Do not store the session if skip paths + for sp in self.skip_paths: + if request.path.startswith(sp): + return - if session.sid in self._cache: - try: - del self._cache[session.sid] - except Exception: - pass + if session.sid in self._cache: + try: + del self._cache[session.sid] + except Exception: + pass - self._cache[session.sid] = session + self._cache[session.sid] = session self._normalize()