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: