Change config on the fly, without stooping a long running process moin.

In the current system, we have to stop a long running server each time we want to change a configuration option or add a wiki instance. This is no a big problem, just little annoying when you try to configure a wiki, and change a settting, restart the server, change more, restart the server... and so on. We should look into this later if we think its important to have this.

First try

<!> should be updated with the module-less code

In my fix branch, while refactoring multiconfig, I came up with this simple solution:

   1 _timestamp = {}
   2 def importConfig(name):
   3     """ Import a config file, reloading when file changed
   4 
   5     Each time a config file is imported, a timestamp is saved. If the
   6     file modification time changed, it is reloaded.
   7 
   8     This will make it possible to changed a config file without stooping
   9     a long running process. The next request will reload the config and
  10     use the new settings.
  11 
  12     return a tuple (module, changed). The interface is different from the
  13     built in import, but this makes the calling code much simpler. 
  14     """
  15     # Get timestamp or raise error if there is no such file
  16     try:
  17         timestamp = os.path.getmtime(os.path.join(os.getcwd(), name + '.py'))
  18     except OSError, why:
  19         raise ImportError(why)
  20 
  21     # If the file did not change, return module from sys.modules
  22     module = sys.modules.get(name)
  23     if module and _timestamp.get(name) == timestamp:
  24         return module, 0
  25         
  26     # Reload module and save timestamp
  27     # Using locks make it thread safe (require Python 2.3)
  28     try:
  29         if hasattr(imp, 'acquire_lock'):
  30             imp.acquire_lock()
  31         fp, pathname, description = imp.find_module(name)   
  32         module = imp.load_module(name, fp, pathname, description)
  33         _timestamp[name] = timestamp
  34     finally:
  35         if fp:
  36             fp.close()
  37         if hasattr(imp, 'release_lock'):
  38             imp.release_lock()
  39     return module, 1

This code works nicely in my branch, I can play with the config while Twisted is serving pages, and the next requests just catch the new settings. But this solution have few problems:

Reload problem

There is one big problem with this solution: when you reload a module, old names in the module dict are not remved. This can cause hard to find bugs. See the python docs here: http://docs.python.org/lib/built-in-funcs.html

This small test demonstrate this problem: oldnames.py

module-less solution

We can use a system similar to Twisted tac configuration files. A tac file is a fragment of python code. It is executed using exec, with a dict, so names from the code get into the dict. Then you get the names you want from the dict.

Our config file can be the same file we use today, probably with another extension, to make it mode clear that its not a module, like moin.pcf for "python config file".

   1 def readNameFromFile(path, Name):
   2     """ Read name from file without importing a module
   3 
   4     Using this method, we can change the file at path in runtime, and read
   5     the file again and again, without the "reload issues" of modules.
   6     
   7     Idea stolen from twisted.persisted.sob, maintained by Moshe Zadka.   
   8     
   9     @param path: python source containing name.
  10     @param name: name to get from file
  11     @rtype: object
  12     @return name from file, or None
  13     """
  14     f = file(path)
  15     globals = {}
  16     locals = {}
  17     exec f in globals, locals
  18     f.close()
  19     return locals.get(name, None)

If we use similar system, we can execute our config code each time a config file changes.

MoinMoin: OnTheFlyConfig (last edited 2007-10-29 19:10:58 by localhost)