Browse Source

maint: make HttpHandler threadsafe

wangweimin 5 years ago
parent
commit
8d0cd2be06
1 changed files with 28 additions and 9 deletions
  1. 28 9
      pywebio/platform/httpbased.py

+ 28 - 9
pywebio/platform/httpbased.py

@@ -83,22 +83,31 @@ _event_loop = None
 
 # todo: use lock to avoid thread race condition
 class HttpHandler:
+    """基于HTTP的后端Handler实现
+
+    .. note::
+        对 HttpHandler._webio_sessions 的访问不需要加锁, See:
+            https://stackoverflow.com/questions/1312331/using-a-global-dictionary-with-threads-in-python
+
+    """
     # type: Dict[str, Session]
     _webio_sessions = {}  # WebIOSessionID -> WebIOSession()
     _webio_expire = LRUDict()  # WebIOSessionID -> last active timestamp。按照最后活跃时间递增排列
+    _webio_expire_lock = threading.Lock()
 
     _last_check_session_expire_ts = 0  # 上次检查session有效期的时间戳
 
-    DEFAULT_SESSION_EXPIRE_SECONDS = 60  # 超过60s会话不活跃则视为会话过期
-    SESSIONS_CLEANUP_INTERVAL = 20  # 清理过期会话间隔(秒)
     WAIT_MS_ON_POST = 100  # 在处理完POST请求时,等待WAIT_MS_ON_POST毫秒再读取返回数据。Task的command可以立即返回
 
+    DEFAULT_SESSION_EXPIRE_SECONDS = 60  # 默认会话过期时间
+    DEFAULT_SESSIONS_CLEANUP_INTERVAL = 20  # 默认清理过期会话间隔(秒)
+
     @classmethod
     def _remove_expired_sessions(cls, session_expire_seconds):
-        logger.debug("removing expired sessions")
         """清除当前会话列表中的过期会话"""
+        logger.debug("removing expired sessions")
         while cls._webio_expire:
-            sid, active_ts = cls._webio_expire.popitem(last=False)
+            sid, active_ts = cls._webio_expire.popitem(last=False)  # 弹出最不活跃的session info
 
             if time.time() - active_ts < session_expire_seconds:
                 # 当前session未过期
@@ -128,6 +137,18 @@ class HttpHandler:
             context.set_header('Access-Control-Expose-Headers', 'webio-session-id')
             context.set_header('Access-Control-Max-Age', str(1440 * 60))
 
+    def interval_cleaning(self):
+        # clean up at intervals
+        cls = type(self)
+        need_clean = False
+        with cls._webio_expire_lock:
+            if time.time() - cls._last_check_session_expire_ts > self.session_cleanup_interval:
+                cls._last_check_session_expire_ts = time.time()
+                need_clean = True
+
+        if need_clean:
+            cls._remove_expired_sessions(self.session_expire_seconds)
+
     def handle_request(self, context: HttpContext):
         """处理请求"""
         cls = type(self)
@@ -189,10 +210,8 @@ class HttpHandler:
             pass
 
         cls._webio_expire[webio_session_id] = time.time()
-        # clean up at intervals
-        if time.time() - cls._last_check_session_expire_ts > self.session_cleanup_interval:
-            cls._last_check_session_expire_ts = time.time()
-            self._remove_expired_sessions(self.session_expire_seconds)
+
+        self.interval_cleaning()
 
         context.set_content(webio_session.get_task_commands(), json_type=True)
 
@@ -227,7 +246,7 @@ class HttpHandler:
         self.applications = make_applications(applications)
         self.check_origin = check_origin
         self.session_expire_seconds = session_expire_seconds or cls.DEFAULT_SESSION_EXPIRE_SECONDS
-        self.session_cleanup_interval = session_cleanup_interval or cls.SESSIONS_CLEANUP_INTERVAL
+        self.session_cleanup_interval = session_cleanup_interval or cls.DEFAULT_SESSIONS_CLEANUP_INTERVAL
 
         for target in self.applications.values():
             register_session_implement_for_target(target)