[svn] fixed severe jinja memcaching bug
authorArmin Ronacher <armin.ronacher@active-4.com>
Sat, 7 Apr 2007 16:33:19 +0000 (18:33 +0200)
committerArmin Ronacher <armin.ronacher@active-4.com>
Sat, 7 Apr 2007 16:33:19 +0000 (18:33 +0200)
--HG--
branch : trunk

jinja/loaders.py
jinja/translators/python.py

index b55fb0e5b74deca392ead6428ebc11f09dc16322..fb904a1ff8077dca280f1cf2f0e08d831d2faeca 100644 (file)
@@ -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()
 
index 67c2d9ac4dee02e74a37c91b6cd72f832e73614d..a75a6e9891a9c45ea8e5daa93911605e2a32fd9f 100644 (file)
@@ -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<filename>.*?), '
+                       r'lineno=(?P<lineno>\d+)\)$')
+
+
 try:
     GeneratorExit
 except NameError:
     class GeneratorExit(Exception):
-        pass
-
-
-#: regular expression for the debug symbols
-_debug_re = re.compile(r'^\s*\# DEBUG\(filename=(?P<filename>.*?), '
-                       r'lineno=(?P<lineno>\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):