From: Max I. <ma...@ma...> - 2002-09-25 13:38:20
|
Could someone point me to the webware code which handles automagic reloading of servlet code? I want to try to implement it for my custom modules... -- Bst rgrds, M.A.X.: Mechanical Artificial Xenomorph. |
From: Jason H. <ja...@pe...> - 2002-09-25 17:57:37
|
On Wed, 2002-09-25 at 08:35, Max Ischenko wrote: > Could someone point me to the webware code > which handles automagic reloading of servlet code? Reloading servlets is a nontrivial problem, since one must also check all modules imported from that servlet. AFAIK, no one has implemented a full-scale solution to this problem. For alternatives, see http://webware.colorstudy.net/twiki/bin/view/Webware/OneShot -- Jason D. Hildebrand ja...@pe... |
From: Tavis R. <ta...@re...> - 2002-09-25 19:00:38
Attachments:
MultiServer.py
|
On September 25, 2002 10:56 am, Jason Hildebrand wrote: > On Wed, 2002-09-25 at 08:35, Max Ischenko wrote: > > Could someone point me to the webware code > > which handles automagic reloading of servlet code? > > Reloading servlets is a nontrivial problem, since one must also > check all modules imported from that servlet. AFAIK, no one has > implemented a full-scale solution to this problem. > > For alternatives, see > http://webware.colorstudy.net/twiki/bin/view/Webware/OneShot Actually, I have implemented this in expWebware and it should be possible= for=20 someone to implement in Webware. Unfortunately, I don't have time to do = it=20 myself. It works like this: * the class that controls everything (AppServer in Webware's case, MultiS= erver=20 in expWebware) spawns an extra thread that maintains a list of all module= =20 files and their mtimes. It sits in a loop and each cycle it checks to se= e if=20 any of the module files have been modified. If it finds any modification= s it=20 sets the flag self._shouldRestart. * the main threadLoop (also managed by AppServer in Webware) keeps an eye= on=20 the self._shouldRestart flag and calls self._restart() if it is set to tr= ue. =20 NB: this method may only be called by the main thread. * self._restart() shuts down the AppServer and all applications, flushe s= tdout=20 and stderr, then uses os.execve to restart the WebKit process with the sa= me=20 arguments it was started with. The PID remains the same. This approach is significantly simpler and faster than oneshot and less p= rone=20 to strange errors as the AppServer operates normally unless a module has=20 changed. =20 Here are some of difficulties/differences that might be encountered in=20 implementing this in Webware: * WebKit.ThreadedAppServer.py is not cleanly object-oriented as several=20 critical parts of this module are handled via module-level functions (run= (),=20 main(), and shutDown()) and global variables rather than being handled by= the=20 ThreadedAppServer class. The code I've attached belongs to a single clas= s in=20 expWebware, but in Webware it might need to be spread between=20 ThreadedAppServer and the module-level functions. * The startup sequence for Webware is significantly different to that in=20 expWebware so I'm not certain that this os.execve approach will work. Cheers, Tavis ------------------- def restart(self): """Tell the main thread to restart the server.""" self._shouldRestart =3D True def _restart(self): """Restart the server by completely reinitializing the process wi= th os.execve(). This method can only be called by the main thread. = If a worker thread calls it, the process will freeze up.""" self.shutdown() sys.stdout.flush() sys.stderr.flush() L =3D [sys.executable] # shutdown has already completed so 'start' not 'restart' L.extend([i.replace('restart','start') for i in sys.argv]) os.execve(L[0], L, os.environ) def mainThreadWaitLoop(self): """The main thread waits here while the server is running and the= =20 worker threads are doing their thing. This function is called at the en= d of the start sequence.""" while self.running: time.sleep(0.2) #@@TR: softcode this later if self._shouldRestart: self._restart() def activateAutoReload(self): self._autoReload =3D True ## @@ Temporary self._fileMonitorThread =3D t =3D=20 Thread(target=3Dself._fileMonitorThreadLoop) t.start() def deactivateAutoReload(self): self._autoReload =3D False try: self._fileMonitorThread.join() except: pass =20 def _fileMonitorThreadLoop(self, getmtime=3Dos.path.getmtime): monitoredFiles =3D self._monitoredFiles monitoredModules =3D self._monitoredModules while self._autoReload: time.sleep(1) #@@TR: softcode this later for mod in sys.modules.values(): if not mod or mod in monitoredModules: continue monitoredModules.append(mod) f2 =3D getattr(mod, '__orig_file__', False) # @@TR might = rename=20 __orig_file__, this is used for cheetah and psp mods f =3D getattr(mod, '__file__', False) if f2 and f2 not in monitoredFiles.keys(): try: monitoredFiles[f2] =3D getmtime(f2) except OSError: pass elif f and f not in monitoredFiles.keys(): try: monitoredFiles[f] =3D getmtime(f) except OSError: pass for f, mtime in monitoredFiles.items(): try: if mtime < getmtime(f): print '*** The file', f, 'has changed. The serve= r is=20 restarting now.' sys.stdout.flush() sys.stderr.flush() self._autoReload =3D False return self.restart() except OSError: print '*** The file', f, 'is no longer accesible The= =20 server is restarting now.' sys.stdout.flush() sys.stderr.flush() self._autoReload =3D False return self.restart() |
From: Ian B. <ia...@co...> - 2002-09-25 19:11:50
|
On Wed, 2002-09-25 at 14:00, Tavis Rudd wrote: > This approach is significantly simpler and faster than oneshot and less prone > to strange errors as the AppServer operates normally unless a module has > changed. This is important, because if you just reload the changed module you won't change the class behavior of instances that have already been created. Webware has a lot of instances floating around, so it just doesn't work to do reloads. Also, if some module uses "from X import Y" and X gets reloaded, that module will still be looking at the old Y. Again, Webware does this a lot. Ian |
From: Jason H. <ja...@pe...> - 2002-09-25 19:56:42
|
On Wed, 2002-09-25 at 14:00, Tavis Rudd wrote: > On September 25, 2002 10:56 am, Jason Hildebrand wrote: > > On Wed, 2002-09-25 at 08:35, Max Ischenko wrote: > > > Could someone point me to the webware code > > > which handles automagic reloading of servlet code? > > > > Reloading servlets is a nontrivial problem, since one must also > > check all modules imported from that servlet. AFAIK, no one has > > implemented a full-scale solution to this problem. > > > > For alternatives, see > > http://webware.colorstudy.net/twiki/bin/view/Webware/OneShot > > Actually, I have implemented this in expWebware and it should be possible for > someone to implement in Webware. Unfortunately, I don't have time to do it > myself. > > It works like this: <snip> Cool! It's basically the same idea that I had (see http://webware.colorstudy.net/twiki/bin/view/Webware/OneShot), but it's the application server that restarts itself, not some external entity, which makes it much cleaner and more general (don't have to worry about which user the appserver runs under, because it stays the same). I may take a crack an integrating this into mainline Webware. peace, Jason |
From: <ir...@ms...> - 2002-09-26 00:44:09
|
On Wed, Sep 25, 2002 at 12:00:26PM -0700, Tavis Rudd wrote: > Actually, I have implemented this in expWebware and it should be possible for > someone to implement in Webware. What happens to transactions that are in the middle of processing or haven't started yet when the server is restarted? Do they just time out? -- -Mike (Iron) Orr, ir...@ms... (if mail problems: ms...@oz...) http://iron.cx/ English * Esperanto * Russkiy * Deutsch * Espan~ol |
From: Tavis R. <ta...@re...> - 2002-09-26 00:56:53
|
That's something for the administrator of the site to think about. This s= ort=20 of reload behaviour should be turned off by default, and only enable duri= ng=20 dev/testing. But, this shouldn't affected processing of transactions if = the=20 site uses reliable persistance mechanisms. All active requests are finis= hed=20 before the restart happens and the shutdown is clean (sessions stored to=20 disk, etc.) Tavis On September 25, 2002 05:44 pm, Mike Orr wrote: > On Wed, Sep 25, 2002 at 12:00:26PM -0700, Tavis Rudd wrote: > > Actually, I have implemented this in expWebware and it should be poss= ible > > for someone to implement in Webware. > > What happens to transactions that are in the middle of processing > or haven't started yet when the server is restarted? Do they just > time out? |
From: Tavis R. <ta...@re...> - 2002-09-25 20:02:15
|
On September 25, 2002 12:56 pm, Jason Hildebrand wrote: > Cool! It's basically the same idea that I had > (see http://webware.colorstudy.net/twiki/bin/view/Webware/OneShot), but > it's the application server that restarts itself, not some external > entity, which makes it much cleaner and more general (don't have to > worry about which user the appserver runs under, because it stays the > same). Another big difference is that it will find changes in any module importe= d by=20 Webware or your application, rather than just changes in your application= =20 path subtree. > I may take a crack an integrating this into mainline Webware. Excellent! Tavis |
From: Jason H. <ja...@pe...> - 2002-09-25 21:04:29
|
On Wed, 2002-09-25 at 15:02, Tavis Rudd wrote: > On September 25, 2002 12:56 pm, Jason Hildebrand wrote: > > Cool! It's basically the same idea that I had > > (see http://webware.colorstudy.net/twiki/bin/view/Webware/OneShot), but > > it's the application server that restarts itself, not some external > > entity, which makes it much cleaner and more general (don't have to > > worry about which user the appserver runs under, because it stays the > > same). > > Another big difference is that it will find changes in any module imported by > Webware or your application, rather than just changes in your application > path subtree. Exactly -- it doesn't require any configuration (of application paths), which is another win. Here are a couple of ideas for improving the implementation, though. One procedure I do fairly often is to 'cvs update' my application then restart the server. I'm not sure if your implementation will work flawlessly in the situation where multiple files change -- there is a race condition which I see: 1) File A is updated 2) Appserver notices that A has changed, shuts down and begins to restart 3) In restarting, Appserver imports file B at time t. 4) File B is updated at time t+1. 5) The file monitor thread notices that File B has been imported, records the mtime as t+1. So the appserver is running using an out-of-date File B. One way to fix this would be to hook into Python's import routine to record the mtime somewhere before the file is actually loaded/initialized. A search just turned up the standard module ihooks.py, which looks promising. Also, in the situtation where multiple files are changing, it might be useful for the server _not_ to restart immediately, but for it to wait until the files stop changing (think again of a 'cvs update' which updates 100 source files over a one-minute period -- we'd want to prevent the app server from restarting continuously during that minute, and just do it once at the end). This might be look like: (take note of the need_restart and saw_changed_file flags) def _fileMonitorThreadLoop(self, getmtime=os.path.getmtime): monitoredFiles = self._monitoredFiles monitoredModules = self._monitoredModules need_restart = 0 while self._autoReload: time.sleep(1) #@@TR: softcode this later for mod in sys.modules.values(): if not mod or mod in monitoredModules: continue monitoredModules.append(mod) f2 = getattr(mod, '__orig_file__', False) # @@TR might rename __orig_file__, this is used for cheetah and psp mods f = getattr(mod, '__file__', False) if f2 and f2 not in monitoredFiles.keys(): try: monitoredFiles[f2] = getmtime(f2) except OSError: pass elif f and f not in monitoredFiles.keys(): try: monitoredFiles[f] = getmtime(f) except OSError: pass saw_changed_file = 0 for f, mtime in monitoredFiles.items(): try: if mtime < getmtime(f): print '*** The file', f, 'has changed. The server is restarting now.' need_restart = 1 saw_changed_file = 1 except OSError: print '*** The file', f, 'is no longer accessible The server is restarting now.' need_restart = 1 saw_changed_file = 1 if need_restart and not saw_changed_file: sys.stdout.flush() sys.stderr.flush() self._autoReload = False return self.restart() Increasing the 1-second sleep time would improve this aspect, too, but is a trade-off against response time. I'm for making it configurable, and letting the user decide (a 1-second default is probably fine). I'll try merging your code into "stock" Webware and keep you posted. -- Jason D. Hildebrand ja...@pe... |
From: Tavis R. <ta...@re...> - 2002-09-25 22:17:41
|
On September 25, 2002 02:04 pm, Jason Hildebrand wrote: > Here are a couple of ideas for improving the implementation, though. > > One procedure I do fairly often is to 'cvs update' my application then > restart the server. I'm not sure if your implementation will work > flawlessly in the situation where multiple files change -- there is a > race condition which I see: > > 1) File A is updated > 2) Appserver notices that A has changed, shuts down and begins to > restart > 3) In restarting, Appserver imports file B at time t. > 4) File B is updated at time t+1. > 5) The file monitor thread notices that File B has been imported, > records the mtime as t+1. > > So the appserver is running using an out-of-date File B. I don't think there's a graceful way around that: if a module is changed= =20 after the appserver starts, but before the application actually imports i= t. The implementation could be extended so that you can manually add files t= o the=20 dicts self._monitoredFiles and self._monitoredModules, but a simpler solu= tion=20 in this restricted case of a cvs update would be to issue a restart comma= nd=20 along with the 'cvs update': cvs update; webkit stop; webkit start or in expWebware: cvs update; webkit restart |
From: Jason H. <ja...@pe...> - 2002-09-26 17:09:05
|
On Wed, 2002-09-25 at 17:17, Tavis Rudd wrote: > I don't think there's a graceful way around that: if a module is changed > after the appserver starts, but before the application actually imports it. Well there is a way: you can install a hook which gets called when the python "import" statement is called. At that point, you could stat the file and set a __MTIME__ variable in the module, which would be checked later by the file monitor thread. I haven't implemented this yet, though. I've applied your implementation to Webware 0.7 (which is what I currently run) and it's working well so far. I didn't have to change much of your code, just a few small things. I did add the lines marked + below, since if python uses an existing .pyc file, we still want to monitor the corresponding .py. Did you not run into this? f2 = getattr(mod, '__orig_file__', 0) f = getattr(mod, '__file__', 0) if f2 and f2 not in monitoredFiles.keys(): try: monitoredFiles[f2] = getmtime(f2) except OSError: pass elif f and f not in monitoredFiles.keys(): + if f[-4:] == '.pyc': + f = f[:-1] try: monitoredFiles[f] = getmtime(f) except OSError: pass Also, I'm assuming that you modified the PSP code to put the __orig_file__ into each generated servlet. This will need to be added to stock webware, too, for this to work properly. I don't use cheetah, so I can't check that. -- Jason D. Hildebrand ja...@pe... |