comparison mercurial/hgweb/hgweb_mod.py @ 26134:e0a6908f066f

hgweb: establish class for holding per request context Currently, hgweb applications have many instance variables holding mutated state. This is somewhat problematic because multiple threads may race accessing or changing this state. This patch starts a series that will add more thread safety to hgweb applications. It will do this by moving mutated state out of hgweb and into per-request instances of the newly established "requestcontext" class. Our new class currently behaves like a proxy to hgweb instances. This should change once all state is captured in it instead of hgweb. The effectiveness of this proxy is demonstrated by passing instances of it - not hgweb instances/self - to various functions.
author Gregory Szorc <gregory.szorc@gmail.com>
date Sat, 22 Aug 2015 14:59:36 -0700
parents 44ed220ef26f
children edfb4d3b9672
comparison
equal deleted inserted replaced
26133:44ed220ef26f 26134:e0a6908f066f
58 break 58 break
59 breadcrumb.append({'url': urlel, 'name': pathel}) 59 breadcrumb.append({'url': urlel, 'name': pathel})
60 urlel = os.path.dirname(urlel) 60 urlel = os.path.dirname(urlel)
61 return reversed(breadcrumb) 61 return reversed(breadcrumb)
62 62
63
64 class requestcontext(object):
65 """Holds state/context for an individual request.
66
67 Servers can be multi-threaded. Holding state on the WSGI application
68 is prone to race conditions. Instances of this class exist to hold
69 mutable and race-free state for requests.
70 """
71 def __init__(self, app):
72 object.__setattr__(self, 'app', app)
73 object.__setattr__(self, 'repo', app.repo)
74
75 # Proxy unknown reads and writes to the application instance
76 # until everything is moved to us.
77 def __getattr__(self, name):
78 return getattr(self.app, name)
79
80 def __setattr__(self, name, value):
81 return setattr(self.app, name, value)
63 82
64 class hgweb(object): 83 class hgweb(object):
65 """HTTP server for individual repositories. 84 """HTTP server for individual repositories.
66 85
67 Instances of this class serve HTTP responses for a particular 86 Instances of this class serve HTTP responses for a particular
191 210
192 This is typically only called by Mercurial. External consumers 211 This is typically only called by Mercurial. External consumers
193 should be using instances of this class as the WSGI application. 212 should be using instances of this class as the WSGI application.
194 """ 213 """
195 self.refresh(req) 214 self.refresh(req)
215 rctx = requestcontext(self)
196 216
197 # work with CGI variables to create coherent structure 217 # work with CGI variables to create coherent structure
198 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME 218 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
199 219
200 req.url = req.env['SCRIPT_NAME'] 220 req.url = req.env['SCRIPT_NAME']
221 if protocol.iscmd(cmd): 241 if protocol.iscmd(cmd):
222 try: 242 try:
223 if query: 243 if query:
224 raise ErrorResponse(HTTP_NOT_FOUND) 244 raise ErrorResponse(HTTP_NOT_FOUND)
225 if cmd in perms: 245 if cmd in perms:
226 self.check_perm(req, perms[cmd]) 246 self.check_perm(rctx, req, perms[cmd])
227 return protocol.call(self.repo, req, cmd) 247 return protocol.call(self.repo, req, cmd)
228 except ErrorResponse as inst: 248 except ErrorResponse as inst:
229 # A client that sends unbundle without 100-continue will 249 # A client that sends unbundle without 100-continue will
230 # break if we respond early. 250 # break if we respond early.
231 if (cmd == 'unbundle' and 251 if (cmd == 'unbundle' and
282 ctype = tmpl('mimetype', encoding=encoding.encoding) 302 ctype = tmpl('mimetype', encoding=encoding.encoding)
283 ctype = templater.stringify(ctype) 303 ctype = templater.stringify(ctype)
284 304
285 # check read permissions non-static content 305 # check read permissions non-static content
286 if cmd != 'static': 306 if cmd != 'static':
287 self.check_perm(req, None) 307 self.check_perm(rctx, req, None)
288 308
289 if cmd == '': 309 if cmd == '':
290 req.form['cmd'] = [tmpl.cache['default']] 310 req.form['cmd'] = [tmpl.cache['default']]
291 cmd = req.form['cmd'][0] 311 cmd = req.form['cmd'][0]
292 312
295 if cmd not in webcommands.__all__: 315 if cmd not in webcommands.__all__:
296 msg = 'no such method: %s' % cmd 316 msg = 'no such method: %s' % cmd
297 raise ErrorResponse(HTTP_BAD_REQUEST, msg) 317 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
298 elif cmd == 'file' and 'raw' in req.form.get('style', []): 318 elif cmd == 'file' and 'raw' in req.form.get('style', []):
299 self.ctype = ctype 319 self.ctype = ctype
300 content = webcommands.rawfile(self, req, tmpl) 320 content = webcommands.rawfile(rctx, req, tmpl)
301 else: 321 else:
302 content = getattr(webcommands, cmd)(self, req, tmpl) 322 content = getattr(webcommands, cmd)(rctx, req, tmpl)
303 req.respond(HTTP_OK, ctype) 323 req.respond(HTTP_OK, ctype)
304 324
305 return content 325 return content
306 326
307 except (error.LookupError, error.RepoLookupError) as err: 327 except (error.LookupError, error.RepoLookupError) as err:
440 'bz2': ('application/x-bzip2', 'tbz2', '.tar.bz2', None), 460 'bz2': ('application/x-bzip2', 'tbz2', '.tar.bz2', None),
441 'gz': ('application/x-gzip', 'tgz', '.tar.gz', None), 461 'gz': ('application/x-gzip', 'tgz', '.tar.gz', None),
442 'zip': ('application/zip', 'zip', '.zip', None), 462 'zip': ('application/zip', 'zip', '.zip', None),
443 } 463 }
444 464
445 def check_perm(self, req, op): 465 def check_perm(self, rctx, req, op):
446 for permhook in permhooks: 466 for permhook in permhooks:
447 permhook(self, req, op) 467 permhook(rctx, req, op)