Mercurial > libervia-backend
comparison src/memory/memory.py @ 1447:b003dbd2b4e9
core (memory): Sessions fixes:
- avoid memory leak with forgotten _purgeSession in case of conflict: an exception is now raised instead
- _purgeSession now cancel the timer if it was not done before
- KeyError in profileGet is now explicitly raised throught a failure.Failure
- use iterator to iterate session in ProfileSessions
author | Goffi <goffi@goffi.org> |
---|---|
date | Sat, 15 Aug 2015 22:13:27 +0200 |
parents | 3265a2639182 |
children | c7fd121a6180 |
comparison
equal
deleted
inserted
replaced
1446:e8c8e467964b | 1447:b003dbd2b4e9 |
---|---|
25 import os.path | 25 import os.path |
26 import copy | 26 import copy |
27 from collections import namedtuple | 27 from collections import namedtuple |
28 from ConfigParser import SafeConfigParser, NoOptionError, NoSectionError | 28 from ConfigParser import SafeConfigParser, NoOptionError, NoSectionError |
29 from uuid import uuid4 | 29 from uuid import uuid4 |
30 from twisted.internet import defer, reactor | 30 from twisted.python import failure |
31 from twisted.internet import defer, reactor, error | |
31 from twisted.words.protocols.jabber import jid | 32 from twisted.words.protocols.jabber import jid |
32 from sat.core import exceptions | 33 from sat.core import exceptions |
33 from sat.core.constants import Const as C | 34 from sat.core.constants import Const as C |
34 from sat.memory.sqlite import SqliteStorage | 35 from sat.memory.sqlite import SqliteStorage |
35 from sat.memory.persistent import PersistentDict | 36 from sat.memory.persistent import PersistentDict |
38 from sat.memory.crypto import BlockCipher | 39 from sat.memory.crypto import BlockCipher |
39 from sat.tools import config as tools_config | 40 from sat.tools import config as tools_config |
40 | 41 |
41 | 42 |
42 PresenceTuple = namedtuple("PresenceTuple", ('show', 'priority', 'statuses')) | 43 PresenceTuple = namedtuple("PresenceTuple", ('show', 'priority', 'statuses')) |
44 MSG_NO_SESSION = "Session id doesn't exist or is finished" | |
43 | 45 |
44 class Sessions(object): | 46 class Sessions(object): |
45 """Sessions are data associated to key used for a temporary moment, with optional profile checking.""" | 47 """Sessions are data associated to key used for a temporary moment, with optional profile checking.""" |
46 DEFAULT_TIMEOUT = 600 | 48 DEFAULT_TIMEOUT = 600 |
47 | 49 |
53 self._sessions = dict() | 55 self._sessions = dict() |
54 self.timeout = timeout or Sessions.DEFAULT_TIMEOUT | 56 self.timeout = timeout or Sessions.DEFAULT_TIMEOUT |
55 self.resettable_timeout = resettable_timeout | 57 self.resettable_timeout = resettable_timeout |
56 | 58 |
57 def newSession(self, session_data=None, session_id=None, profile=None): | 59 def newSession(self, session_data=None, session_id=None, profile=None): |
58 """ Create a new session | 60 """Create a new session |
61 | |
59 @param session_data: mutable data to use, default to a dict | 62 @param session_data: mutable data to use, default to a dict |
60 @param session_id (str): force the session_id to the given string | 63 @param session_id (str): force the session_id to the given string |
61 @param profile: if set, the session is owned by the profile, | 64 @param profile: if set, the session is owned by the profile, |
62 and profileGet must be used instead of __getitem__ | 65 and profileGet must be used instead of __getitem__ |
63 @return: session_id, session_data | 66 @return: session_id, session_data |
64 """ | 67 """ |
65 if session_id is None: | 68 if session_id is None: |
66 session_id = str(uuid4()) | 69 session_id = str(uuid4()) |
67 elif session_id in self._sessions: | 70 elif session_id in self._sessions: |
68 self._sessions[session_id][0].cancel() | 71 raise exceptions.ConflictError(u"Session id {} is already used".format(session_id)) |
69 log.warning(u"Session [{id}] is going to be re-initialised".format(id=session_id)) | |
70 timer = reactor.callLater(self.timeout, self._purgeSession, session_id) | 72 timer = reactor.callLater(self.timeout, self._purgeSession, session_id) |
71 if session_data is None: | 73 if session_data is None: |
72 session_data = {} | 74 session_data = {} |
73 self._sessions[session_id] = (timer, session_data) if profile is None else (timer, session_data, profile) | 75 self._sessions[session_id] = (timer, session_data) if profile is None else (timer, session_data, profile) |
74 return session_id, session_data | 76 return session_id, session_data |
75 | 77 |
76 def _purgeSession(self, session_id): | 78 def _purgeSession(self, session_id): |
79 try: | |
80 timer, session_data, profile = self._sessions[session_id] | |
81 except ValueError: | |
82 timer, session_data = self._sessions[session_id] | |
83 profile = None | |
84 try: | |
85 timer.cancel() | |
86 except error.AlreadyCalled: | |
87 # if the session is time-outed, the timer has been called | |
88 pass | |
77 del self._sessions[session_id] | 89 del self._sessions[session_id] |
78 log.debug(u"Session [%s] purged" % session_id) | 90 log.debug(u"Session {} purged{}".format(session_id, u' (profile {})'.format(profile) if profile is not None else u'')) |
79 | 91 |
80 def __len__(self): | 92 def __len__(self): |
81 return len(self._sessions) | 93 return len(self._sessions) |
82 | 94 |
83 def __contains__(self, session_id): | 95 def __contains__(self, session_id): |
86 def profileGet(self, session_id, profile): | 98 def profileGet(self, session_id, profile): |
87 try: | 99 try: |
88 timer, session_data, profile_set = self._sessions[session_id] | 100 timer, session_data, profile_set = self._sessions[session_id] |
89 except ValueError: | 101 except ValueError: |
90 raise exceptions.InternalError("You need to use __getitem__ when profile is not set") | 102 raise exceptions.InternalError("You need to use __getitem__ when profile is not set") |
103 except KeyError: | |
104 raise failure.Failure(KeyError(MSG_NO_SESSION)) | |
91 if profile_set != profile: | 105 if profile_set != profile: |
92 raise exceptions.InternalError("current profile differ from set profile !") | 106 raise exceptions.InternalError("current profile differ from set profile !") |
93 if self.resettable_timeout: | 107 if self.resettable_timeout: |
94 timer.reset(self.timeout) | 108 timer.reset(self.timeout) |
95 return session_data | 109 return session_data |
97 def __getitem__(self, session_id): | 111 def __getitem__(self, session_id): |
98 try: | 112 try: |
99 timer, session_data = self._sessions[session_id] | 113 timer, session_data = self._sessions[session_id] |
100 except ValueError: | 114 except ValueError: |
101 raise exceptions.InternalError("You need to use profileGet instead of __getitem__ when profile is set") | 115 raise exceptions.InternalError("You need to use profileGet instead of __getitem__ when profile is set") |
116 except KeyError: | |
117 raise failure.Failure(KeyError(MSG_NO_SESSION)) | |
102 if self.resettable_timeout: | 118 if self.resettable_timeout: |
103 timer.reset(self.timeout) | 119 timer.reset(self.timeout) |
104 return session_data | 120 return session_data |
105 | 121 |
106 def __setitem__(self, key, value): | 122 def __setitem__(self, key, value): |
107 raise NotImplementedError("You need do use newSession to create a session") | 123 raise NotImplementedError("You need do use newSession to create a session") |
108 | 124 |
109 def __delitem__(self, session_id): | 125 def __delitem__(self, session_id): |
110 """ Cancel the timer, then actually delete the session data """ | 126 """ Cancel the timer, then actually delete the session data """ |
111 try: | 127 timer = self._sessions[session_id][0] |
112 timer = self._sessions[session_id][0] | 128 timer.cancel() |
113 timer.cancel() | 129 self._purgeSession(session_id) |
114 self._purgeSession(session_id) | |
115 except KeyError: | |
116 log.debug(u"Session [%s] doesn't exists, timeout expired?" % session_id) | |
117 | 130 |
118 def keys(self): | 131 def keys(self): |
119 return self._sessions.keys() | 132 return self._sessions.keys() |
120 | 133 |
121 def iterkeys(self): | 134 def iterkeys(self): |
132 | 145 |
133 @param profile: %(doc_profile)s | 146 @param profile: %(doc_profile)s |
134 @return: a list containing the sessions ids | 147 @return: a list containing the sessions ids |
135 """ | 148 """ |
136 ret = [] | 149 ret = [] |
137 for session_id in self._sessions: | 150 for session_id in self._sessions.iterkeys(): |
138 try: | 151 try: |
139 timer, session_data, profile_set = self._sessions[session_id] | 152 timer, session_data, profile_set = self._sessions[session_id] |
140 except ValueError: | 153 except ValueError: |
141 continue | 154 continue |
142 if profile == profile_set: | 155 if profile == profile_set: |