commit 20ffa78b6fdd51701cb383a349ae81532ab69f7d Author: Florian Best Date: Wed Oct 28 21:03:53 2020 +0100 Bug #52272: cleanup all sessions with one timer don't call N_SESSION timer functions every second but only one function. diff --git management/univention-management-console/src/univention/management/console/protocol/server.py management/univention-management-console/src/univention/management/console/protocol/server.py index 1d7541adc5..76adca9b57 100644 --- management/univention-management-console/src/univention/management/console/protocol/server.py +++ management/univention-management-console/src/univention/management/console/protocol/server.py @@ -38,6 +38,7 @@ Defines the basic class for an UMC server. import os import errno import fcntl +import time import socket import resource import traceback @@ -70,6 +71,7 @@ class MagicBucket(object): def __init__(self): self.__states = {} + notifier.timer_add(1000, self._timeout_connections) def new(self, client, socket): """Is called by the Server object to announce a new incoming @@ -83,19 +85,15 @@ class MagicBucket(object): state.session.signal_connect('success', notifier.Callback(self._response, state)) self.__states[socket] = state notifier.socket_add(socket, self._receive) - self._timeout_connection(state) - def _timeout_connection(self, state): + def _timeout_connections(self): """Closes the connection after a specified timeout""" - state.time_remaining -= 1 + timed_out = [sock for sock, state in self.__states.items() if state.timed_out()] + for sock in timed_out: + CORE.process('Session timed out. (sock=%r)' % (sock,)) + self._cleanup(sock) - if state.time_remaining <= 0 and not state.requests and not state.session.has_active_module_processes(): - CORE.process('Session timed out.') - self._cleanup(state.socket) - else: - # count down the timer second-wise (in order to avoid problems when - # changing the system time, e.g. via rdate) - notifier.timer_add(1000, lambda: self._timeout_connection(state)) + return True def exit(self): '''Closes all open connections.''' @@ -513,7 +511,7 @@ class State(object): :param fd socket: file descriptor or socket object """ - __slots__ = ('client', 'socket', 'buffer', 'requests', 'resend_queue', 'session', 'time_remaining') + __slots__ = ('client', 'socket', 'buffer', 'requests', 'resend_queue', 'session', 'session_end_time') def __init__(self, client, socket): self.client = client @@ -525,7 +523,14 @@ class State(object): self.reset_connection_timeout() def reset_connection_timeout(self): - self.time_remaining = SERVER_CONNECTION_TIMEOUT + self.session_end_time = int(time.time() + SERVER_CONNECTION_TIMEOUT) + + def timed_out(self): + return not self.requests and not self.session.has_active_module_processes() and self.time_remaining <= 0 + + @property + def time_remaining(self): + return int(self.session_end_time - time.time()) def __repr__(self): return '' % (self.client, self.socket, len(self.buffer), len(self.requests), self.time_remaining) commit 2c778585265d083a9fb9070a3c4b6892ca5faee5 Author: Florian Best Date: Wed Oct 28 11:30:56 2020 +0100 Bug #52272: UMC-Webserver: cleanup all sessions with one timer The UMC-Web-Server creates a timer which counts down the session second wise for each session to check if it can be destroyed. When 1000 uses are logged in currently, every second 1000 timer-callbacks are called. We should register one timer which iterates over all sessions instead. diff --git management/univention-management-console/univention-management-console-web-server management/univention-management-console/univention-management-console-web-server index 9fe5c7be2d..97dae6eee8 100755 --- management/univention-management-console/univention-management-console-web-server +++ management/univention-management-console/univention-management-console-web-server @@ -304,6 +304,17 @@ class UMCP_Dispatcher(object): except KeyError: CORE.info('Session %r not found' % (sessionid,)) + @classmethod + def session_timeout_timer(cls): + now = time.time() + for sessionid, user in list(Ressource.sessions.items()): + session = UMCP_Dispatcher.sessions.get(sessionid) + if (not session or not session._requestid2response_queue) and user.timed_out(now): + CORE.info('session %r timed out' % (sessionid,)) + Ressource.sessions.pop(user.sessionid, None) + user.on_logout() + return True # execute again! + class UploadManager(dict): @@ -359,35 +370,26 @@ class QueueRequest(object): class User(object): - __slots__ = ('sessionid', 'username', 'password', 'saml', '_time_remaining') + __slots__ = ('sessionid', 'username', 'password', 'saml', '_timeout') def __init__(self, sessionid, username, password, saml=None): self.sessionid = sessionid self.username = username self.password = password self.saml = saml - self._time_remaining = _session_timeout self.reset_timeout() def reset_timeout(self): - self._time_remaining = self.session_validity + self._timeout = time.time() + _session_timeout - @property - def session_validity(self): - if self.saml is not None: - return self.time_remaining - return _session_timeout + def timed_out(self, now): + return self.session_end_time < now @property - def time_remaining(self): - remaining = [] - if self.saml is not None: - remaining.append(self.saml.time_remaining) - remaining.append(self._time_remaining) - try: - return min(remaining) - except ValueError: # no SAML, no client - return 0 + def session_end_time(self): + if self.is_saml_user() and self.saml.not_on_or_after: + return self.saml.not_on_or_after + return self._timeout def is_saml_user(self): # self.saml indicates that it was originally a @@ -412,9 +414,6 @@ class User(object): else: return None - def timed_out(self): - return self.saml.timed_out() - def __repr__(self): return '' % (self.username, self.sessionid, self.saml is not None) @@ -429,17 +428,6 @@ class SAMLUser(object): self.message = message self.username = u''.join(response.ava['uid']) - @property - def time_remaining(self): - if self.not_on_or_after == 0: - return 0 - return int(self.not_on_or_after - time.time()) - - def timed_out(self): - if self.not_on_or_after == 0: - return False - return self.time_remaining < 0 - traceback_pattern = re.compile(r'(Traceback.*most recent call|File.*line.*in.*\d)') @@ -668,7 +656,7 @@ class Ressource(object): def check_saml_session_validity(self): user = self.get_user() - if user and user.saml is not None and user.time_remaining < 1: + if user and user.saml is not None and user.timed_out(time.time()): raise UMC_HTTPError(UNAUTHORIZED) def set_cookies(self, *cookies, **kwargs): @@ -695,8 +683,6 @@ class Ressource(object): olduser = self.get_user() user = User(sessionid, username, password, saml or olduser and olduser.saml) - self._session_timeout_timer(user) - self.sessions[sessionid] = user self.set_cookies(('UMCSessionId', sessionid), ('UMCUsername', username)) return user @@ -715,29 +701,10 @@ class Ressource(object): if not value or value not in self.sessions: return user = self.sessions[value] - if user.time_remaining <= 0: + if user.timed_out(time.time()): return return user - def _session_timeout_timer(self, user): - """In order to avoid problems when the system time is changed (e.g., - via rdate), we register a timer event that counts down the session - timeout second-wise.""" - - # count down the remaining time - user._time_remaining -= 1 - - session = UMCP_Dispatcher.sessions.get(user.sessionid) - if user._time_remaining <= 0 and (not session or not session._requestid2response_queue): - self._log('info', 'session timed out') - self.sessions.pop(user.sessionid, None) - user.on_logout() - return - - # count down the timer second-wise (in order to avoid problems when - # changing the system time, e.g. via rdate) - notifier.timer_add(1000, lambda: self._session_timeout_timer(user)) - class CPgeneric(Ressource): @@ -902,8 +869,7 @@ class CPGet(CPgeneric): raise UMC_HTTPError(UNAUTHORIZED) info['username'] = user.username info['auth_type'] = user.saml and 'SAML' - info['remaining'] = user.time_remaining - info['validity'] = user.session_validity + info['remaining'] = int(user.session_end_time - time.time()) return json.dumps({"status": 200, "result": info, "message": ""}).encode('ASCII') @cherrypy.expose @@ -1035,7 +1001,7 @@ class CPAuth(CPgeneric): CORE.info('CPAuth/auth/sso: got new auth request (%s:%s <=> %s)' % (get_ip_address(), remote.port, remote.name)) user = self.get_user() - if not user or not user.saml or user.timed_out(): + if not user or not user.saml or user.timed_out(time.time()): # redirect user to login page in case he's not authenticated or his session timed out raise HTTPRedirect('/univention/saml/') @@ -1638,6 +1604,7 @@ class UMC_HTTP_Daemon(DaemonRunner): notifier.init(notifier.GENERIC) notifier.dispatch.MIN_TIMER = get_int('umc/http/dispatch-interval', notifier.dispatch.MIN_TIMER) notifier.dispatcher_add(UMCP_Dispatcher.check_queue) + notifier.timer_add(1000, UMCP_Dispatcher.session_timeout_timer) notifier.loop() except (SystemExit, KeyboardInterrupt) as exc: # stop the web server