view roundup/cgi/engine_zopetal.py @ 6565:2c2dbfc332ba

Try to handle multiple connections better. The session database is a hot spot. When multiple requests (e.g. 20) come in at the same time session database contention can get great. The original code didn't retry session database access when the open failed. This resulted in errors at the client. The second pass delayed 0.01 seconds and retried. It was better but we still had multiple second stalls. I think the first request got in, everybody else backed up and then retried at the same time. Again they stepped on each other. With logging I would see many counters go all the way to low single digits or to -1 indicating falure. This pass uses randomint to generate delays from 0-.125 seconds in 5ms increments. This performs better in testing. I rarely saw a counter less than 13 (2 failed retries). Current logging starts after 6 failures and counts down until success or failure.
author John Rouillard <rouilj@ieee.org>
date Thu, 16 Dec 2021 20:02:00 -0500
parents 808f7a8ed2b6
children 408fd477761f
line wrap: on
line source

"""Templating engine adapter for the legacy TAL implementation ported from
Zope.
"""
__docformat__ = 'restructuredtext'

import errno
import mimetypes
import os
import os.path

from roundup.cgi.templating import StringIO, context, translationService, TALLoaderBase
from roundup.cgi.PageTemplates import PageTemplate, GlobalTranslationService
from roundup.cgi.PageTemplates.Expressions import getEngine
from roundup.cgi.TAL import TALInterpreter

GlobalTranslationService.setGlobalTranslationService(translationService)


class Loader(TALLoaderBase):
    templates = {}

    def __init__(self, dir):
        self.dir = dir

    def load(self, tplname):
        # find the source
        src, filename = self._find(tplname)

        # has it changed?
        try:
            stime = os.stat(src)[os.path.stat.ST_MTIME]
        except os.error as error:
            if error.errno != errno.ENOENT:
                raise

        if src in self.templates and \
                stime <= self.templates[src].mtime:
            # compiled template is up to date
            return self.templates[src]

        # compile the template
        pt = RoundupPageTemplate()
        # use pt_edit so we can pass the content_type guess too
        content_type = mimetypes.guess_type(filename)[0] or 'text/html'
        with open(src) as srcd:
            pt.pt_edit(srcd.read(), content_type)
        pt.id = filename
        pt.mtime = stime
        # Add it to the cache.  We cannot do this until the template
        # is fully initialized, as we could otherwise have a race
        # condition when running with multiple threads:
        #
        # 1. Thread A notices the template is not in the cache,
        #    adds it, but has not yet set "mtime".
        #
        # 2. Thread B notices the template is in the cache, checks
        #    "mtime" (above) and crashes.
        #
        # Since Python dictionary access is atomic, as long as we
        # insert "pt" only after it is fully initialized, we avoid
        # this race condition.  It's possible that two separate
        # threads will both do the work of initializing the template,
        # but the risk of wasted work is offset by avoiding a lock.
        self.templates[src] = pt
        return pt


class RoundupPageTemplate(PageTemplate.PageTemplate):
    """A Roundup-specific PageTemplate.

    Interrogate the client to set up Roundup-specific template variables
    to be available.  See 'context' function for the list of variables.

    """

    def render(self, client, classname, request, **options):
        """Render this Page Template"""

        if not self._v_cooked:
            self._cook()

        __traceback_supplement__ = (PageTemplate.PageTemplateTracebackSupplement, self)

        if self._v_errors:
            raise PageTemplate.PTRuntimeError('Page Template %s has errors.' %
                                              self.id)

        # figure the context
        c = context(client, self, classname, request)
        c.update({'options': options})

        # and go
        output = StringIO()
        TALInterpreter.TALInterpreter(self._v_program, self.macros,
                                      getEngine().getContext(c), output,
                                      tal=1, strictinsert=0)()
        return output.getvalue()

Roundup Issue Tracker: http://roundup-tracker.org/