From ca80b455949be91a8774de1b4e791b8c087dd89d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 7 Apr 2007 18:33:19 +0200 Subject: [PATCH] [svn] fixed severe jinja memcaching bug --HG-- branch : trunk --- jinja/loaders.py | 108 +++++++++++++++++++----------------- jinja/translators/python.py | 68 ++++++++++++----------- 2 files changed, 93 insertions(+), 83 deletions(-) diff --git a/jinja/loaders.py b/jinja/loaders.py index b55fb0e..fb904a1 100644 --- a/jinja/loaders.py +++ b/jinja/loaders.py @@ -155,65 +155,71 @@ class CachedLoaderMixin(object): try: # caching is only possible for the python translator. skip # all other translators - if translator is PythonTranslator: - tmpl = None - - # auto reload enabled? check for the last change of - # the template - if self.__auto_reload: - last_change = self.check_source_changed(environment, name) - else: - last_change = None - - # check if we have something in the memory cache and the - # memory cache is enabled. - if self.__memcache is not None and name in self.__memcache: + if translator is not PythonTranslator: + return super(CachedLoaderMixin, self).load( + environment, name, translator) + + tmpl = None + save_to_disk = False + push_to_memory = False + + # auto reload enabled? check for the last change of + # the template + if self.__auto_reload: + last_change = self.check_source_changed(environment, name) + else: + last_change = None + + # check if we have something in the memory cache and the + # memory cache is enabled. + if self.__memcache is not None: + if name in self.__memcache: tmpl = self.__memcache[name] + # if auto reload is enabled check if the template changed if last_change is not None and \ last_change > self.__times[name]: tmpl = None + push_to_memory = True + else: + push_to_memory = True - # if diskcache is enabled look for an already compiled - # template. - if self.__cache_folder is not None: - cache_fn = get_cachename(self.__cache_folder, name) - - # there is an up to date compiled template - if tmpl is not None and last_change is None: - try: - cache_time = path.getmtime(cache_fn) - except OSError: - cache_time = -1 - if cache_time == -1 or last_change >= cache_time: - f = file(cache_fn, 'rb') - try: - tmpl = Template.load(environment, f) - finally: - f.close() - - # no template so far, parse, translate and compile it - elif tmpl is None: - tmpl = super(CachedLoaderMixin, self).load( - environment, name, translator) - - # save the compiled template - f = file(cache_fn, 'wb') + # mem cache disabled or not cached by now + # try to load if from the disk cache + if tmpl is None and self.__cache_folder is not None: + cache_fn = get_cachename(self.__cache_folder, name) + if last_change is not None: try: - tmpl.dump(f) + cache_time = path.getmtime(cache_fn) + except OSError: + cache_time = -1 + if last_change is None or last_change <= cache_time: + f = file(cache_fn, 'rb') + try: + tmpl = Template.load(environment, f) finally: f.close() - - # if memcaching is enabled push the template - if tmpl is not None: - if self.__memcache is not None: - self.__times[name] = time.time() - self.__memcache[name] = tmpl - return tmpl - - # if we reach this point we don't have caching enabled or translate - # to something else than python - return super(CachedLoaderMixin, self).load( - environment, name, translator) + else: + save_to_disk = True + + # if we still have no template we load, parse and translate it. + if tmpl is None: + tmpl = super(CachedLoaderMixin, self).load( + environment, name, translator) + + # save the compiled template on the disk if enabled + if save_to_disk: + f = file(cache_fn, 'wb') + try: + tmpl.dump(f) + finally: + f.close() + + # if memcaching is enabled and the template not loaded + # we add that there. + if push_to_memory: + self.__times[name] = time.time() + self.__memcache[name] = tmpl + return tmpl finally: self.__lock.release() diff --git a/jinja/translators/python.py b/jinja/translators/python.py index 67c2d9a..a75a6e9 100644 --- a/jinja/translators/python.py +++ b/jinja/translators/python.py @@ -32,16 +32,16 @@ from jinja.utils import translate_exception, capture_generator, \ RUNTIME_EXCEPTION_OFFSET +#: regular expression for the debug symbols +_debug_re = re.compile(r'^\s*\# DEBUG\(filename=(?P.*?), ' + r'lineno=(?P\d+)\)$') + + try: GeneratorExit except NameError: class GeneratorExit(Exception): - pass - - -#: regular expression for the debug symbols -_debug_re = re.compile(r'^\s*\# DEBUG\(filename=(?P.*?), ' - r'lineno=(?P\d+)\)$') + """For python2.3/python2.4 compatibility""" def _to_tuple(args): @@ -91,47 +91,51 @@ class Template(object): def render(self, *args, **kwargs): """Render a template.""" - return capture_generator(self._generate(*args, **kwargs)) + ctx = self._prepare(*args, **kwargs) + try: + return capture_generator(self.generate_func(ctx)) + except: + self._debug(ctx, *sys.exc_info()) def stream(self, *args, **kwargs): """Render a template as stream.""" - return TemplateStream(self._generate(*args, **kwargs)) - - def _generate(self, *args, **kwargs): - """Template generation helper""" + def proxy(ctx): + try: + for item in self.generate_func(ctx): + yield item + except GeneratorExit: + return + except: + self._debug(ctx, *sys.exc_info()) + return TemplateStream(proxy(self._prepare(*args, **kwargs))) + + def _prepare(self, *args, **kwargs): + """Prepare the template execution.""" # if there is no generation function we execute the code # in a new namespace and save the generation function and # debug information. + env = self.environment if self.generate_func is None: - ns = {'environment': self.environment} + ns = {'environment': env} exec self.code in ns self.generate_func = ns['generate'] self._debug_info = ns['debug_info'] - ctx = self.environment.context_class(self.environment, *args, **kwargs) - try: - for item in self.generate_func(ctx): - yield item - except: - if not self.environment.friendly_traceback: - raise - # debugging system: - # on any exception we first get the current exception information - # and skip the internal frames (currently either one (python2.5) - # or two (python2.4 and lower)). After that we call a function - # that creates a new traceback that is easier to debug. - exc_type, exc_value, traceback = sys.exc_info() - - # if an exception is a GeneratorExit we just reraise it. If we - # run on top of python2.3 or python2.4 a fake GeneratorExit - # class is added for this module so that we don't get a NameError - if exc_type is GeneratorExit: - raise + return env.context_class(env, *args, **kwargs) + def _debug(self, ctx, exc_type, exc_value, traceback): + """Debugging Helper""" + # just modify traceback if we have that feature enabled + if self.environment.friendly_traceback: + # debugging system: + # on any exception we first skip the internal frames (currently + # either one (python2.5) or two (python2.4 and lower)). After that + # we call a function that creates a new traceback that is easier + # to debug. for _ in xrange(RUNTIME_EXCEPTION_OFFSET): traceback = traceback.tb_next traceback = translate_exception(self, exc_type, exc_value, traceback, ctx) - raise exc_type, exc_value, traceback + raise exc_type, exc_value, traceback class PythonTranslator(Translator): -- 2.26.2