changeset 254:fa2c3671bcd9

Fixes for running in `mod_wsgi`. * No more `wsgiutil` for now, since it's easy enough to write the script. * Added a "context" to wiki parameters to communicate if we're running in normal mode, initialization mode (creating a new wiki), or background task mode. * Mercurial source-control uses this context to decide how to setup (or not) a command server. * Mercurial source-control is now sharing its command server per process, and tries to correctly shut it down on exit.
author Ludovic Chabant <ludovic@chabant.com>
date Wed, 02 Apr 2014 08:13:12 -0700
parents bef755e567a3
children c27317412100
files wikked/commands/manage.py wikked/scm/mercurial.py wikked/tasks.py wikked/web.py wikked/wiki.py wikked/wsgiutil.py
diffstat 6 files changed, 73 insertions(+), 59 deletions(-) [+]
line wrap: on
line diff
--- a/wikked/commands/manage.py	Tue Apr 01 00:14:42 2014 -0700
+++ b/wikked/commands/manage.py	Wed Apr 02 08:13:12 2014 -0700
@@ -3,6 +3,7 @@
 import shutil
 import logging
 from wikked.commands.base import WikkedCommand, register_command
+from wikked.wiki import Wiki, WikiParameters, INIT_CONTEXT
 
 
 logger = logging.getLogger(__name__)
@@ -40,8 +41,8 @@
 
         logger.info("Initializing new wiki at: %s" % path)
         from wikked.wiki import WikiParameters, Wiki
-        parameters = WikiParameters(path)
-        wiki = Wiki(parameters, for_init=True)
+        parameters = WikiParameters(path, ctx=INIT_CONTEXT)
+        wiki = Wiki(parameters)
         wiki.init()
 
         if not ctx.args.bare:
--- a/wikked/scm/mercurial.py	Tue Apr 01 00:14:42 2014 -0700
+++ b/wikked/scm/mercurial.py	Wed Apr 02 08:13:12 2014 -0700
@@ -179,11 +179,14 @@
         return subprocess.check_output(exe)
 
 
+hg_client = None
+cl_lock = threading.Lock()
+
+
 class MercurialCommandServerSourceControl(MercurialBaseSourceControl):
     def __init__(self, root, client=None):
         MercurialBaseSourceControl.__init__(self, root)
         self.client = client
-        self.cl_lock = threading.Lock()
 
     def _initRepo(self, root):
         exe = ['hg', 'init', root]
@@ -192,23 +195,54 @@
 
     def _doStart(self):
         if self.client is None:
-            import hglib
-            self.client = hglib.open(self.root)
+            if hg_client is None:
+                with cl_lock:
+                    if hg_client is None:
+                        self._createServer()
+                self.client = hg_client
+            else:
+                logger.debug("Re-using existing Mercurial command server.")
+                self.client = hg_client
+
+    def _createServer(self):
+        logger.debug("Spawning Mercurial command server.")
+        import hglib
+        global hg_client
+        hg_client = hglib.open(self.root)
+
+        def shutdown_commandserver(num, frame):
+            global hg_client
+            if hg_client is not None:
+                with cl_lock:
+                    if hg_client is not None:
+                        logger.debug("Shutting down Mercurial command server.")
+                        hg_client.close()
+                        hg_client = None
+        import atexit
+        atexit.register(shutdown_commandserver, None, None)
+        try:
+            import signal
+            signal.signal(signal.SIGTERM, shutdown_commandserver)
+        except:
+            # `mod_wsgi` prevents adding stuff to `SIGTERM`
+            # so let's not make a big deal if this doesn't
+            # go through.
+            pass
 
     def getHistory(self, path=None, limit=10):
         if path is not None:
-            with self.cl_lock:
+            with cl_lock:
                 status = self.client.status(include=[path])
             if len(status) > 0 and status[0] == '?':
                 return []
 
         needs_files = False
         if path is not None:
-            with self.cl_lock:
+            with cl_lock:
                 repo_revs = self.client.log(files=[path], follow=True, limit=limit)
         else:
             needs_files = True
-            with self.cl_lock:
+            with cl_lock:
                 repo_revs = self.client.log(follow=True, limit=limit)
         revisions = []
         for rev in repo_revs:
@@ -218,7 +252,7 @@
             r.timestamp = time.mktime(rev.date.timetuple())
             r.description = unicode(rev.desc)
             if needs_files:
-                with self.cl_lock:
+                with cl_lock:
                     rev_statuses = self.client.status(change=rev.node)
                 for rev_status in rev_statuses:
                     r.files.append({
@@ -229,7 +263,7 @@
         return revisions
 
     def getState(self, path):
-        with self.cl_lock:
+        with cl_lock:
             statuses = self.client.status(include=[path])
         if len(statuses) == 0:
             return STATE_COMMITTED
@@ -241,11 +275,11 @@
         raise Exception("Unsupported status: %s" % status)
 
     def getRevision(self, path, rev):
-        with self.cl_lock:
+        with cl_lock:
             return self.client.cat([path], rev=rev)
 
     def diff(self, path, rev1, rev2):
-        with self.cl_lock:
+        with cl_lock:
             if rev2 is None:
                 return self.client.diff(files=[path], change=rev1, git=True)
             return self.client.diff(files=[path], revs=[rev1, rev2], git=True)
@@ -264,13 +298,13 @@
             args = cmdbuilder('commit', *paths,
                     debug=True, m=op_meta['message'], A=True,
                     **kwargs)
-            with self.cl_lock:
+            with cl_lock:
                 self.client.rawcommand(args)
         except CommandError as e:
             raise SourceControlError('commit', e.out, e.args, e.out)
 
     def revert(self, paths=None):
-        with self.cl_lock:
+        with cl_lock:
             if paths is not None:
                 self.client.revert(files=paths, nobackup=True)
             else:
--- a/wikked/tasks.py	Tue Apr 01 00:14:42 2014 -0700
+++ b/wikked/tasks.py	Wed Apr 02 08:13:12 2014 -0700
@@ -1,6 +1,6 @@
 import logging
 from celery import Celery
-from wikked.wiki import Wiki, WikiParameters
+from wikked.wiki import Wiki, WikiParameters, BACKGROUND_CONTEXT
 
 
 logger = logging.getLogger(__name__)
@@ -16,7 +16,7 @@
         self.wiki = None
 
     def __enter__(self):
-        params = WikiParameters(root=self.wiki_root)
+        params = WikiParameters(self.wiki_root, ctx=BACKGROUND_CONTEXT)
         self.wiki = Wiki(params)
         self.wiki.start(False)
         return self.wiki
--- a/wikked/web.py	Tue Apr 01 00:14:42 2014 -0700
+++ b/wikked/web.py	Wed Apr 02 08:13:12 2014 -0700
@@ -2,7 +2,7 @@
 import os.path
 import logging
 from flask import Flask, abort, g
-from wikked.wiki import Wiki
+from wikked.wiki import Wiki, WikiParameters
 
 
 # Create the main app.
@@ -67,6 +67,7 @@
 
 app.set_wiki_params = set_app_wiki_params
 app.wiki_updater = None
+app.set_wiki_params(WikiParameters(wiki_root))
 
 
 # Set the wiki as a request global, and open/close the database.
--- a/wikked/wiki.py	Tue Apr 01 00:14:42 2014 -0700
+++ b/wikked/wiki.py	Wed Apr 02 08:13:12 2014 -0700
@@ -25,13 +25,19 @@
     pass
 
 
+NORMAL_CONTEXT = 0
+INIT_CONTEXT = 1
+BACKGROUND_CONTEXT = 2
+
+
 class WikiParameters(object):
     """ An object that defines how a wiki gets initialized.
     """
-    def __init__(self, root=None):
+    def __init__(self, root=None, ctx=NORMAL_CONTEXT):
         if root is None:
             root = os.getcwd()
         self.root = root
+        self.context = ctx
         self.formatters = self.getFormatters()
         self.wiki_updater = self.getWikiUpdater()
         self._config = None
@@ -55,7 +61,7 @@
         from wikked.db.sql import SQLDatabase
         return SQLDatabase(self.config)
 
-    def scm_factory(self, for_init=False):
+    def scm_factory(self):
         self._ensureScmFactory()
         return self._scm_factory()
 
@@ -136,22 +142,19 @@
                     # Default to Mercurial. Yes. I just decided that myself.
                     scm_type = 'hg'
 
-            if scm_type == 'hg':
-                logger.debug("Creating Mercurial command server.")
-                import hglib
-                client = hglib.open()
+            if self.context != NORMAL_CONTEXT and scm_type == 'hg':
+                # Quick workaround for when we're creating a new repo,
+                # or running background tasks.
+                # We'll be using the `hg` process instead of the command
+                # server, since there's no repo there yet, or we just don't
+                # want to spawn a new process unless we want to.
+                logger.debug("Forcing `hgexe` source-control for new repo.")
+                scm_type = 'hgexe'
 
-                def shutdown_commandserver(num, frame):
-                    logger.debug("Shutting down Mercurial command server.")
-                    client.close()
-                import atexit
-                atexit.register(shutdown_commandserver, None, None)
-                import signal
-                signal.signal(signal.SIGTERM, shutdown_commandserver)
-
+            if scm_type == 'hg':
                 def impl():
                     from wikked.scm.mercurial import MercurialCommandServerSourceControl
-                    return MercurialCommandServerSourceControl(self.root, client)
+                    return MercurialCommandServerSourceControl(self.root)
                 self._scm_factory = impl
 
             elif scm_type == 'hgexe':
@@ -180,7 +183,7 @@
 class Wiki(object):
     """ The wiki class! This is where the magic happens.
     """
-    def __init__(self, parameters, for_init=False):
+    def __init__(self, parameters):
         """ Creates a new wiki instance. It won't be fully functional
             until you call `start`, which does the actual initialization.
             This gives you a chance to customize a few more things before
@@ -203,7 +206,7 @@
         self.fs = parameters.fs_factory()
         self.index = parameters.index_factory()
         self.db = parameters.db_factory()
-        self.scm = parameters.scm_factory(for_init)
+        self.scm = parameters.scm_factory()
         self.auth = parameters.auth_factory()
 
         self._wiki_updater = parameters.wiki_updater
--- a/wikked/wsgiutil.py	Tue Apr 01 00:14:42 2014 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,25 +0,0 @@
-import os
-import sys
-import logging
-import logging.handlers
-from wikked.wiki import WikiParameters
-
-
-def get_wsgi_app(wiki_root, log_file=None, async_update=True):
-    os.chdir(wiki_root)
-    logging.basicConfig(stream=sys.stderr)
-
-    if async_update:
-        import wikked.settings
-        wikked.settings.WIKI_ASYNC_UPDATE = True
-
-    from wikked.web import app
-    app.set_wiki_params(WikiParameters(wiki_root))
-
-    if log_file is not None:
-        h = logging.handlers.RotatingFileHandler(log_file, maxBytes=4096)
-        h.setLevel(logging.WARNING)
-        app.logger.addHandler(h)
-
-    return app
-