Commit | Line | Data |
---|---|---|
b409a338 | 1 | import threading, time, pickle, random, os |
b6f62b89 | 2 | <<<<<<< HEAD |
1f61bf31 | 3 | import cookie, env |
b6f62b89 | 4 | ======= |
c4b97e16 | 5 | from . import cookie, env, proto |
b6f62b89 | 6 | >>>>>>> master |
b409a338 FT |
7 | |
8 | __all__ = ["db", "get"] | |
9 | ||
b409a338 | 10 | def gennonce(length): |
691f278c | 11 | return os.urandom(length) |
b409a338 FT |
12 | |
13 | class session(object): | |
9bc70dab | 14 | def __init__(self, lock, expire=86400 * 7): |
c4b97e16 | 15 | self.id = proto.enhex(gennonce(16)) |
b409a338 | 16 | self.dict = {} |
b65f311b | 17 | self.lock = lock |
b409a338 FT |
18 | self.ctime = self.atime = self.mtime = int(time.time()) |
19 | self.expire = expire | |
20 | self.dctl = set() | |
21 | self.dirtyp = False | |
22 | ||
23 | def dirty(self): | |
24 | for d in self.dctl: | |
25 | if d.sessdirty(): | |
26 | return True | |
27 | return self.dirtyp | |
28 | ||
29 | def frozen(self): | |
30 | for d in self.dctl: | |
31 | d.sessfrozen() | |
32 | self.dirtyp = False | |
33 | ||
34 | def __getitem__(self, key): | |
35 | return self.dict[key] | |
36 | ||
9bc70dab | 37 | def get(self, key, default=None): |
b409a338 FT |
38 | return self.dict.get(key, default) |
39 | ||
40 | def __setitem__(self, key, value): | |
41 | self.dict[key] = value | |
42 | if hasattr(value, "sessdirty"): | |
43 | self.dctl.add(value) | |
44 | else: | |
45 | self.dirtyp = True | |
46 | ||
47 | def __delitem__(self, key): | |
48 | old = self.dict.pop(key) | |
49 | if old in self.dctl: | |
50 | self.dctl.remove(old) | |
51 | self.dirtyp = True | |
52 | ||
53 | def __contains__(self, key): | |
54 | return key in self.dict | |
55 | ||
56 | def __getstate__(self): | |
57 | ret = [] | |
58 | for k, v in self.__dict__.items(): | |
59 | if k == "lock": continue | |
60 | ret.append((k, v)) | |
61 | return ret | |
62 | ||
63 | def __setstate__(self, st): | |
64 | for k, v in st: | |
65 | self.__dict__[k] = v | |
b65f311b | 66 | # The proper lock is set by the thawer |
b409a338 | 67 | |
b9e22c33 FT |
68 | def __repr__(self): |
69 | return "<session %s>" % self.id | |
70 | ||
b409a338 | 71 | class db(object): |
9bc70dab | 72 | def __init__(self, backdb=None, cookiename="wrwsess", path="/"): |
b409a338 FT |
73 | self.live = {} |
74 | self.cookiename = cookiename | |
75 | self.path = path | |
76 | self.lock = threading.Lock() | |
b409a338 FT |
77 | self.cthread = None |
78 | self.freezetime = 3600 | |
f84a3f10 | 79 | self.backdb = backdb |
b409a338 FT |
80 | |
81 | def clean(self): | |
82 | now = int(time.time()) | |
83 | with self.lock: | |
b65f311b FT |
84 | clist = self.live.keys() |
85 | for sessid in clist: | |
86 | with self.lock: | |
87 | try: | |
88 | entry = self.live[sessid] | |
89 | except KeyError: | |
90 | continue | |
91 | with entry[0]: | |
92 | rm = False | |
93 | if entry[1] == "retired": | |
94 | pass | |
95 | elif entry[1] is None: | |
96 | pass | |
97 | else: | |
98 | sess = entry[1] | |
99 | if sess.atime + self.freezetime < now: | |
100 | try: | |
101 | if sess.dirty(): | |
102 | self.freeze(sess) | |
103 | except: | |
104 | if sess.atime + sess.expire < now: | |
105 | rm = True | |
106 | else: | |
107 | rm = True | |
108 | if rm: | |
109 | entry[1] = "retired" | |
110 | with self.lock: | |
111 | del self.live[sessid] | |
b409a338 FT |
112 | |
113 | def cleanloop(self): | |
114 | try: | |
188da534 | 115 | while True: |
b409a338 FT |
116 | time.sleep(300) |
117 | self.clean() | |
188da534 FT |
118 | if len(self.live) == 0: |
119 | break | |
b409a338 FT |
120 | finally: |
121 | with self.lock: | |
122 | self.cthread = None | |
123 | ||
b65f311b FT |
124 | def _fetch(self, sessid): |
125 | while True: | |
126 | now = int(time.time()) | |
127 | with self.lock: | |
128 | if sessid in self.live: | |
129 | entry = self.live[sessid] | |
130 | else: | |
131 | entry = self.live[sessid] = [threading.RLock(), None] | |
132 | with entry[0]: | |
133 | if isinstance(entry[1], session): | |
134 | entry[1].atime = now | |
135 | return entry[1] | |
136 | elif entry[1] == "retired": | |
137 | continue | |
138 | elif entry[1] is None: | |
139 | try: | |
140 | thawed = self.thaw(sessid) | |
141 | if thawed.atime + thawed.expire < now: | |
142 | raise KeyError() | |
143 | thawed.lock = entry[0] | |
144 | thawed.atime = now | |
145 | entry[1] = thawed | |
146 | return thawed | |
147 | finally: | |
148 | if entry[1] is None: | |
149 | entry[1] = "retired" | |
150 | with self.lock: | |
151 | del self.live[sessid] | |
152 | else: | |
153 | raise Exception("Illegal session entry: " + repr(entry[1])) | |
154 | ||
dc7155d6 | 155 | def checkclean(self): |
b409a338 FT |
156 | with self.lock: |
157 | if self.cthread is None: | |
158 | self.cthread = threading.Thread(target = self.cleanloop) | |
159 | self.cthread.setDaemon(True) | |
160 | self.cthread.start() | |
dc7155d6 | 161 | |
afd93253 FT |
162 | def mksession(self, req): |
163 | return session(threading.RLock()) | |
164 | ||
165 | def mkcookie(self, req, sess): | |
c6e56d74 FT |
166 | cookie.add(req, self.cookiename, sess.id, |
167 | path=self.path, | |
168 | expires=cookie.cdate(time.time() + sess.expire)) | |
afd93253 | 169 | |
dc7155d6 FT |
170 | def fetch(self, req): |
171 | now = int(time.time()) | |
172 | sessid = cookie.get(req, self.cookiename) | |
173 | new = False | |
b65f311b FT |
174 | try: |
175 | if sessid is None: | |
176 | raise KeyError() | |
177 | sess = self._fetch(sessid) | |
178 | except KeyError: | |
afd93253 | 179 | sess = self.mksession(req) |
b65f311b | 180 | new = True |
e70341b2 FT |
181 | |
182 | def ckfreeze(req): | |
183 | if sess.dirty(): | |
bce33109 | 184 | if new: |
afd93253 | 185 | self.mkcookie(req, sess) |
bce33109 | 186 | with self.lock: |
b65f311b | 187 | self.live[sess.id] = [sess.lock, sess] |
e70341b2 | 188 | try: |
e70341b2 FT |
189 | self.freeze(sess) |
190 | except: | |
191 | pass | |
dc7155d6 | 192 | self.checkclean() |
e70341b2 | 193 | req.oncommit(ckfreeze) |
b409a338 FT |
194 | return sess |
195 | ||
b409a338 | 196 | def thaw(self, sessid): |
f84a3f10 FT |
197 | if self.backdb is None: |
198 | raise KeyError() | |
b409a338 FT |
199 | data = self.backdb[sessid] |
200 | try: | |
201 | return pickle.loads(data) | |
202 | except Exception, e: | |
203 | raise KeyError() | |
204 | ||
205 | def freeze(self, sess): | |
f84a3f10 FT |
206 | if self.backdb is None: |
207 | raise TypeError() | |
b65f311b FT |
208 | with sess.lock: |
209 | data = pickle.dumps(sess, -1) | |
210 | self.backdb[sess.id] = data | |
b409a338 FT |
211 | sess.frozen() |
212 | ||
f84a3f10 FT |
213 | def get(self, req): |
214 | return req.item(self.fetch) | |
215 | ||
b409a338 FT |
216 | class dirback(object): |
217 | def __init__(self, path): | |
218 | self.path = path | |
219 | ||
220 | def __getitem__(self, key): | |
221 | try: | |
222 | with open(os.path.join(self.path, key)) as inf: | |
223 | return inf.read() | |
224 | except IOError: | |
225 | raise KeyError(key) | |
226 | ||
227 | def __setitem__(self, key, value): | |
228 | if not os.path.exists(self.path): | |
229 | os.makedirs(self.path) | |
230 | with open(os.path.join(self.path, key), "w") as out: | |
231 | out.write(value) | |
232 | ||
9bc70dab | 233 | default = env.var(db(backdb=dirback(os.path.join("/tmp", "wrwsess-" + str(os.getuid()))))) |
b409a338 FT |
234 | |
235 | def get(req): | |
1f61bf31 | 236 | return default.val.get(req) |