Use an RLock for session locking.
[wrw.git] / wrw / session.py
CommitLineData
b409a338 1import threading, time, pickle, random, os
1f61bf31 2import cookie, env
b409a338
FT
3
4__all__ = ["db", "get"]
5
6def hexencode(str):
7 ret = ""
8 for byte in str:
9 ret += "%02X" % (ord(byte),)
10 return ret
11
12def gennonce(length):
13 nonce = ""
14 for i in xrange(length):
15 nonce += chr(random.randint(0, 255))
16 return nonce
17
18class session(object):
19 def __init__(self, expire = 86400 * 7):
20 self.id = hexencode(gennonce(16))
21 self.dict = {}
d5156ebe 22 self.lock = threading.RLock()
b409a338
FT
23 self.ctime = self.atime = self.mtime = int(time.time())
24 self.expire = expire
25 self.dctl = set()
26 self.dirtyp = False
27
28 def dirty(self):
29 for d in self.dctl:
30 if d.sessdirty():
31 return True
32 return self.dirtyp
33
34 def frozen(self):
35 for d in self.dctl:
36 d.sessfrozen()
37 self.dirtyp = False
38
39 def __getitem__(self, key):
40 return self.dict[key]
41
42 def get(self, key, default = None):
43 return self.dict.get(key, default)
44
45 def __setitem__(self, key, value):
46 self.dict[key] = value
47 if hasattr(value, "sessdirty"):
48 self.dctl.add(value)
49 else:
50 self.dirtyp = True
51
52 def __delitem__(self, key):
53 old = self.dict.pop(key)
54 if old in self.dctl:
55 self.dctl.remove(old)
56 self.dirtyp = True
57
58 def __contains__(self, key):
59 return key in self.dict
60
61 def __getstate__(self):
62 ret = []
63 for k, v in self.__dict__.items():
64 if k == "lock": continue
65 ret.append((k, v))
66 return ret
67
68 def __setstate__(self, st):
69 for k, v in st:
70 self.__dict__[k] = v
71 self.lock = threading.Lock()
72
73class db(object):
f84a3f10 74 def __init__(self, backdb = None, cookiename = "wrwsess", path = "/"):
b409a338
FT
75 self.live = {}
76 self.cookiename = cookiename
77 self.path = path
78 self.lock = threading.Lock()
b409a338
FT
79 self.cthread = None
80 self.freezetime = 3600
f84a3f10 81 self.backdb = backdb
b409a338
FT
82
83 def clean(self):
84 now = int(time.time())
85 with self.lock:
86 dlist = []
87 for sess in self.live.itervalues():
88 if sess.atime + self.freezetime < now:
89 try:
90 if sess.dirty():
91 self.freeze(sess)
92 except:
93 if sess.atime + sess.expire < now:
94 dlist.append(sess)
95 else:
96 dlist.append(sess)
97 for sess in dlist:
98 del self.live[sess.id]
99
100 def cleanloop(self):
101 try:
188da534 102 while True:
b409a338
FT
103 time.sleep(300)
104 self.clean()
188da534
FT
105 if len(self.live) == 0:
106 break
b409a338
FT
107 finally:
108 with self.lock:
109 self.cthread = None
110
111 def fetch(self, req):
112 now = int(time.time())
b409a338 113 sessid = cookie.get(req, self.cookiename)
e70341b2 114 new = False
b409a338
FT
115 with self.lock:
116 if self.cthread is None:
117 self.cthread = threading.Thread(target = self.cleanloop)
118 self.cthread.setDaemon(True)
119 self.cthread.start()
120 try:
121 if sessid is None:
122 raise KeyError()
123 elif sessid in self.live:
124 sess = self.live[sessid]
125 else:
126 sess = self.thaw(sessid)
127 self.live[sessid] = sess
128 if sess.atime + sess.expire < now:
129 raise KeyError()
130 sess.atime = now
131 except KeyError:
132 sess = session()
e70341b2
FT
133 new = True
134
135 def ckfreeze(req):
136 if sess.dirty():
bce33109
FT
137 if new:
138 cookie.add(req, self.cookiename, sess.id, self.path)
139 with self.lock:
140 self.live[sess.id] = sess
e70341b2 141 try:
e70341b2
FT
142 self.freeze(sess)
143 except:
144 pass
145 req.oncommit(ckfreeze)
b409a338
FT
146 return sess
147
b409a338 148 def thaw(self, sessid):
f84a3f10
FT
149 if self.backdb is None:
150 raise KeyError()
b409a338
FT
151 data = self.backdb[sessid]
152 try:
153 return pickle.loads(data)
154 except Exception, e:
155 raise KeyError()
156
157 def freeze(self, sess):
f84a3f10
FT
158 if self.backdb is None:
159 raise TypeError()
f2e2dc9e 160 self.backdb[sess.id] = pickle.dumps(sess, -1)
b409a338
FT
161 sess.frozen()
162
f84a3f10
FT
163 def get(self, req):
164 return req.item(self.fetch)
165
b409a338
FT
166class dirback(object):
167 def __init__(self, path):
168 self.path = path
169
170 def __getitem__(self, key):
171 try:
172 with open(os.path.join(self.path, key)) as inf:
173 return inf.read()
174 except IOError:
175 raise KeyError(key)
176
177 def __setitem__(self, key, value):
178 if not os.path.exists(self.path):
179 os.makedirs(self.path)
180 with open(os.path.join(self.path, key), "w") as out:
181 out.write(value)
182
1f61bf31 183default = env.var(db(backdb = dirback(os.path.join("/tmp", "wrwsess-" + str(os.getuid())))))
b409a338
FT
184
185def get(req):
1f61bf31 186 return default.val.get(req)