changeset 6747:d32d43e4a5ba

wsgi can cache tracker instance enabled by feature flag. Patch by Marcus Priesch caches a loaded tracker instance and reuse it for future client sessions. It is enabled by a feature flag in wsgi.py since it arrived during the 2.2.0 beta period. The provided wsgi.py is modified to enable it. Testing is run with flag enabled and disabled. Ralf Schlatterbeck and Marcus tested it on one of their larger more complex trackers and it sped up the response time to a client request by a factor of 3 (270ms down to about 80-85ms).
author John Rouillard <rouilj@ieee.org>
date Sat, 02 Jul 2022 14:04:00 -0400
parents efa203f3c696
children 647f806d54b8
files CHANGES.txt doc/upgrading.txt frontends/wsgi.py roundup/cgi/wsgi_handler.py test/test_liveserver.py
diffstat 5 files changed, 70 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/CHANGES.txt	Sat Jul 02 13:52:52 2022 -0400
+++ b/CHANGES.txt	Sat Jul 02 14:04:00 2022 -0400
@@ -176,6 +176,9 @@
   i18n object is now also correctly set for the mail interface:
   previously the 'language' setting in the [mailgw] section seems to
   have been ignored. Thanks to Marcus Priesch for the patch.
+- issue2551212 - speed up wsgi interface by caching the tracker
+  instance. Hidden behind a feature flag. See upgrading.txt for
+  details. (Marcus Priesch with feature flag by John Rouillard)
 
 2021-07-13 2.1.0
 
--- a/doc/upgrading.txt	Sat Jul 02 13:52:52 2022 -0400
+++ b/doc/upgrading.txt	Sat Jul 02 14:04:00 2022 -0400
@@ -209,6 +209,25 @@
 of ps or shell history. The new password will be encrypted using the
 default encryption method (usually pbkdf2).
 
+Enable performance improvement for wsgi mode (optional)
+-------------------------------------------------------
+
+There is an experimental wsgi performance improvement mode that caches
+the loaded roundup instance. This eliminates disk reads that are
+incurred on each connection. In one report it improves speed by a
+factor of 2 to 3 times. To enable this you should add a feature flag
+to your Roundup wsgi wrapper (see the file
+``.../share/frontends/wsgi.py``) so it looks like::
+
+   feature_flags = { "cache_tracker": "" }
+   app =  RequestDispatcher(tracker_home, feature_flags=feature_flags)
+
+to enable this mode. Note that this is experimental and was added
+during the 2.2.0 beta period, so it is enabled using a feature flag.
+If you use this and it works for you please followup with an email to
+the roundup-users at lists.sourceforge.net mailing list so we can
+enable it by default in a future release.
+
 Migrating from 2.0.0 to 2.1.0
 =============================
 
--- a/frontends/wsgi.py	Sat Jul 02 13:52:52 2022 -0400
+++ b/frontends/wsgi.py	Sat Jul 02 14:04:00 2022 -0400
@@ -12,5 +12,10 @@
 # Set the path to tracker home.
 tracker_home = '/path/to/tracker'
 
+# Enable the feature flag to speed up wsgi response by caching the
+#   Roundup tracker instance on startup. See upgrading.txt for
+#   more info.
+feature_flags =  { "cache_tracker": "" }
+
 # Definition signature for app: app(environ, start_response):
-app =  RequestDispatcher(tracker_home)
+app =  RequestDispatcher(tracker_home= feature_flags=feature_flags)
--- a/roundup/cgi/wsgi_handler.py	Sat Jul 02 13:52:52 2022 -0400
+++ b/roundup/cgi/wsgi_handler.py	Sat Jul 02 14:04:00 2022 -0400
@@ -74,17 +74,24 @@
 
 
 class RequestDispatcher(object):
-    def __init__(self, home, debug=False, timing=False, lang=None):
+    def __init__(self, home, debug=False, timing=False, lang=None,
+                 feature_flags=None):
         assert os.path.isdir(home), '%r is not a directory' % (home,)
         self.home = home
         self.debug = debug
         self.timing = timing
+        self.feature_flags= feature_flags or {}
+        self.tracker = None
         if lang:
             self.translator = TranslationService.get_translation(lang,
                 tracker_home=home)
         else:
             self.translator = None
-        self.preload()
+
+        if "cache_tracker" in self.feature_flags:
+            self.tracker = roundup.instance.open(self.home, not self.debug)
+        else:
+            self.preload()
 
     def __call__(self, environ, start_response):
         """Initialize with `apache.Request` object"""
@@ -116,8 +123,8 @@
         else:
             form = BinaryFieldStorage(fp=environ['wsgi.input'], environ=environ)
 
-        with self.get_tracker() as tracker:
-            client = tracker.Client(tracker, request, environ, form,
+        if "cache_tracker" in self.feature_flags:
+            client = self.tracker.Client(self.tracker, request, environ, form,
                                     self.translator)
             try:
                 client.main()
@@ -125,6 +132,16 @@
                 request.start_response([('Content-Type', 'text/html')], 404)
                 request.wfile.write(s2b('Not found: %s' % 
                                         html_escape(client.path)))
+        else:
+            with self.get_tracker() as tracker:
+                client = tracker.Client(tracker, request, environ, form,
+                                        self.translator)
+                try:
+                    client.main()
+                except roundup.cgi.client.NotFound:
+                    request.start_response([('Content-Type', 'text/html')], 404)
+                    request.wfile.write(s2b('Not found: %s' % 
+                                            html_escape(client.path)))
 
         # all body data has been written using wfile
         return []
--- a/test/test_liveserver.py	Sat Jul 02 13:52:52 2022 -0400
+++ b/test/test_liveserver.py	Sat Jul 02 14:04:00 2022 -0400
@@ -38,7 +38,7 @@
 _py3 = sys.version_info[0] > 2
 
 @skip_requests
-class SimpleTest(LiveServerTestCase):
+class WsgiSetup(LiveServerTestCase):
     # have chicken and egg issue here. Need to encode the base_url
     # in the config file but we don't know it until after
     # the server is started and has read the config.ini.
@@ -103,7 +103,8 @@
         i18n.DOMAIN = cls.backup_domain
 
     def create_app(self):
-        '''The wsgi app to start'''
+        '''The wsgi app to start - no feature_flags set.'''
+
         if _py3:
             return validator(RequestDispatcher(self.dirname))
         else:
@@ -112,6 +113,11 @@
             return RequestDispatcher(self.dirname)
 
 
+class BaseTestCases(WsgiSetup):
+    """Class with all tests to run against wsgi server. Is reused when
+       wsgi server is started with various feature flags
+    """
+
     def test_start_page(self):
         """ simple test that verifies that the server can serve a start page.
         """
@@ -973,4 +979,16 @@
         self.assertEqual(r.status_code, 201)
         print(r.status_code)
 
-
+class TestFeatureFlagCacheTrackerOn(BaseTestCases, WsgiSetup):
+    """Class to run all test in BaseTestCases with the cache_tracker
+       feature flag enabled when starting the wsgi server
+    """
+    def create_app(self):
+        '''The wsgi app to start with feature flag enabled'''
+        ff = { "cache_tracker": "" }
+        if _py3:
+            return validator(RequestDispatcher(self.dirname, feature_flags=ff))
+        else:
+            # wsgiref/validator.py InputWrapper::readline is broke and
+            # doesn't support the max bytes to read argument.
+            return RequestDispatcher(self.dirname, feature_flags=ff)

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