Merge lp:~achipa/web2py/cron into lp:~mdipierro/web2py/devel

Proposed by Attila Csipa
Status: Needs review
Proposed branch: lp:~achipa/web2py/cron
Merge into: lp:~mdipierro/web2py/devel
Diff against target: None lines
To merge this branch: bzr merge lp:~achipa/web2py/cron
Reviewer Review Type Date Requested Status
Massimo Pending
Review via email: mp+4193@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Attila Csipa (achipa) wrote :

cross-crontype locking to avoid race contitions if several crontypes are activated at the same time (most often soft+external in wsgi mode)

lp:~achipa/web2py/cron updated
578. By Attila Csipa <bear@odin>

moved hardcron var setting from run to init

579. By Attila Csipa <bear@odin>

cleaner fail scenarios

580. By Attila Csipa <bear@odin>

sync merge

581. By Attila Csipa <bear@odin>

ignore cron.master, minor logging cosmetics

582. By Attila Csipa <bear@odin>

merge sync

583. By Attila Csipa <bear@odin>

@ prefixes

merge with latest

584. By Attila Csipa <bear@odin>

parametrized Popen, more whitespace friendly cron.py

585. By bear <bear@domU-12-31-38-01-B8-85>

erased superfluous logging statements

586. By bear <bear@domU-12-31-38-01-B8-85>

synced to 1.61.4

Unmerged revisions

586. By bear <bear@domU-12-31-38-01-B8-85>

synced to 1.61.4

585. By bear <bear@domU-12-31-38-01-B8-85>

erased superfluous logging statements

584. By Attila Csipa <bear@odin>

parametrized Popen, more whitespace friendly cron.py

583. By Attila Csipa <bear@odin>

@ prefixes

merge with latest

582. By Attila Csipa <bear@odin>

merge sync

581. By Attila Csipa <bear@odin>

ignore cron.master, minor logging cosmetics

580. By Attila Csipa <bear@odin>

sync merge

579. By Attila Csipa <bear@odin>

cleaner fail scenarios

578. By Attila Csipa <bear@odin>

moved hardcron var setting from run to init

577. By Attila (AchipA) Csipa <bear@odin>

upstream cleanup merge

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'applications/examples/views/default/cron.html'
--- applications/examples/views/default/cron.html 1970-01-01 00:00:00 +0000
+++ applications/examples/views/default/cron.html 2009-03-05 12:32:00 +0000
@@ -0,0 +1,87 @@
1{{extend 'layout.html'}}
2{{import os}}
3
4<h1>web2py<sup style="font-size:0.5em;">TM</sup> cron</h1>
5
6The web2py cron provides the ability for applications to execute tasks at preset times, in a platform independent manner (tested on Windows XP, Linux and MacOS X, but should work on any python 2.5 supported platform).
7
8Cron functionality is defined by a crontab file (in regular <A href="http://en.wikipedia.org/wiki/Cron#crontab_syntax">crontab syntax</A>) in your application's cron directory. This also means that every application can have a separate cron configuration and that cron config can be changed from within web2py without affecting the host OS itself.
9<BR>
10<BR>
11Example:
12{{=CODE(""" 0-59/1 * * * * root python /path/to/python/script.py
13*/30 * * * * root *applications/admin/cron/expire_sessions.py
14-1 * * * * root *mycontroller/myfunction""")}}
15
16As you might have noticed, some of the lines use <A href="#extensions">extensions</A> to regular cron syntax to provide additional web2py functionality.
17
18<BR>
19<h2>Modes of use</h2>
20
21Depending on how you are invoking web2py, there are four modes of operation for web2py cron.
22
23<UL>
24<LI><A href="#soft">Soft cron</A>, available under all execution modes</LI>
25<LI><A href="#hard">Hard cron</A>, available if using the built-in web server (either directly or via Apache mod_proxy)</LI>
26<LI><A href="#external">External cron</A>, available if you have access to the systems own cron service</LI>
27<LI><A href="#no">No cron</A></LI>
28</UL>
29
30The default is hard cron if you are using the built-in web server, in all other cases the default is soft-cron.
31
32
33<h3 id="soft">Soft cron</h3>
34
35Soft cron is the default if you are using CGI, FASTCGI or WSGI. Your tasks will be executed in the first call (page load) to web2py after the time specified in crontab (but AFTER processing the page, so no delay to the user is visible). Obviously, there is some uncertainty exactly when the task will be executed depending on the traffic the site receives. Also, the cron task may get interrupted if the web server has a page load timeout set. If these limitations are not acceptable, see <A href="#external">external cron</A>. Soft cron is a reasonable last resort, but if your web server allows other cron methods, they should be preferred over soft cron.
36
37<h3 id="hard">Hard cron</h3>
38
39Hard cron is the default if you are using the built-in web server (either directly or via Apache mod_proxy). Hard cron is executed in a parallel thread, so unlike soft cron there are no limitations with regard to run time or execution time precision.
40
41<h3 id="external">External cron</h3>
42
43External cron is not default in any scenarios, but requires you to have access to the system cron facilities. It runs in a parallel process, so none of the limitations of soft cron apply. This is the recommended way of using cron under WSGI or FASTCGI.
44<BR>
45<BR>
46
47Example of line to add to the system crontab, (usually /etc/crontab):
48{{=CODE("""0-59/1 * * * * web2py cd /var/www/web2py/ && python web2py.py -C -D 1 >> /tmp/cron.output 2>&1""")}}
49
50If you are running external cron, make sure you add the -N command line parameter to your web2py startup script or config so there is no collision of multiple types of cron.
51
52<h3 id="no">No cron</h3>
53
54In case you do not need any cron functionality, you can use the -N command line parameter to disable it. Note that this will disable some maintenance tasks (like the automatic cleaning of session dirs).
55
56<BR>
57<h2 id="extensions">Cron extensions</h2>
58
59Web2py cron has a some extra syntax to support web2py application specifics.
60
61<h3>Calling scripts in the web2py environment</h3>
62
63If the task/script is prefixed with an asterisk and ends with ".py", it will be executed in the web2py environment. This means you will have all the controllers and models at your disposal. Warning: be careful how you use models. While the execution happens in a separate process, database locks have to be taken into account in order to avoid pages waiting for cron tasks that be blocking the database.
64
65<BR>
66<BR>
67Example:
68{{=CODE("""*/30 * * * * root *applications/admin/cron/expire_sessions.py""")}}
69
70<h3>Calling controller functions</h3>
71
72Same as above, but a function from a controller is executed instead of a separate script file.
73<BR>
74<BR>
75
76Example:
77{{=CODE("""*/30 * * * * root *mycontroller/myfunction""")}}
78
79<h3>Application initialization</h3>
80
81If you specify -1 as minutes in the crontab file, the given task will be executed only ONCE, on web2py startup. You can use this feature if you want to precache, check or initialize data for an application on web2py startup.
82
83Example:
84{{=CODE("""
85-1 * * * * root *mycontroller/myfunction
86""")}}
87
088
=== removed file 'applications/examples/views/default/cron.html'
--- applications/examples/views/default/cron.html 2009-03-01 19:22:48 +0000
+++ applications/examples/views/default/cron.html 1970-01-01 00:00:00 +0000
@@ -1,87 +0,0 @@
1{{extend 'layout.html'}}
2{{import os}}
3
4<h1>web2py<sup style="font-size:0.5em;">TM</sup> cron</h1>
5
6The web2py cron provides the ability for applications to execute tasks at preset times, in a platform independent manner (tested on Windows XP, Linux and MacOS X, but should work on any python 2.5 supported platform).
7
8Cron functionality is defined by a crontab file (in regular <A href="http://en.wikipedia.org/wiki/Cron#crontab_syntax">crontab syntax</A>) in your application's cron directory. This also means that every application can have a separate cron configuration and that cron config can be changed from within web2py without affecting the host OS itself.
9<BR>
10<BR>
11Example:
12{{=CODE(""" 0-59/1 * * * * root python /path/to/python/script.py
13*/30 * * * * root *applications/admin/cron/expire_sessions.py
14-1 * * * * root *mycontroller/myfunction""")}}
15
16As you might have noticed, some of the lines use <A href="#extensions">extensions</A> to regular cron syntax to provide additional web2py functionality.
17
18<BR>
19<h2>Modes of use</h2>
20
21Depending on how you are invoking web2py, there are four modes of operation for web2py cron.
22
23<UL>
24<LI><A href="#soft">Soft cron</A>, available under all execution modes</LI>
25<LI><A href="#hard">Hard cron</A>, available if using the built-in web server (either directly or via Apache mod_proxy)</LI>
26<LI><A href="#external">External cron</A>, available if you have access to the systems own cron service</LI>
27<LI><A href="#no">No cron</A></LI>
28</UL>
29
30The default is hard cron if you are using the built-in web server, in all other cases the default is soft-cron.
31
32
33<h3 id="soft">Soft cron</h3>
34
35Soft cron is the default if you are using CGI, FASTCGI or WSGI. Your tasks will be executed in the first call (page load) to web2py after the time specified in crontab (but AFTER processing the page, so no delay to the user is visible). Obviously, there is some uncertainty exactly when the task will be executed depending on the traffic the site receives. Also, the cron task may get interrupted if the web server has a page load timeout set. If these limitations are not acceptable, see <A href="#external">external cron</A>. Soft cron is a reasonable last resort, but if your web server allows other cron methods, they should be preferred over soft cron.
36
37<h3 id="hard">Hard cron</h3>
38
39Hard cron is the default if you are using the built-in web server (either directly or via Apache mod_proxy). Hard cron is executed in a parallel thread, so unlike soft cron there are no limitations with regard to run time or execution time precision.
40
41<h3 id="external">External cron</h3>
42
43External cron is not default in any scenarios, but requires you to have access to the system cron facilities. It runs in a parallel process, so none of the limitations of soft cron apply. This is the recommended way of using cron under WSGI or FASTCGI.
44<BR>
45<BR>
46
47Example of line to add to the system crontab, (usually /etc/crontab):
48{{=CODE("""0-59/1 * * * * web2py cd /var/www/web2py/ && touch applications/admin/cron/cron.master && python web2py.py -C -D 1 >> /tmp/cron.output 2>&1""")}}
49
50If you are running external cron, make sure you add the -N command line parameter to your web2py startup script or config so there is no collision of multiple types of cron.
51
52<h3 id="no">No cron</h3>
53
54In case you do not need any cron functionality, you can use the -N command line parameter to disable it. Note that this will disable some maintenance tasks (like the automatic cleaning of session dirs).
55
56<BR>
57<h2 id="extensions">Cron extensions</h2>
58
59Web2py cron has a some extra syntax to support web2py application specifics.
60
61<h3>Calling scripts in the web2py environment</h3>
62
63If the task/script is prefixed with an asterisk and ends with ".py", it will be executed in the web2py environment. This means you will have all the controllers and models at your disposal. Warning: be careful how you use models. While the execution happens in a separate process, database locks have to be taken into account in order to avoid pages waiting for cron tasks that be blocking the database.
64
65<BR>
66<BR>
67Example:
68{{=CODE("""*/30 * * * * root *applications/admin/cron/expire_sessions.py""")}}
69
70<h3>Calling controller functions</h3>
71
72Same as above, but a function from a controller is executed instead of a separate script file.
73<BR>
74<BR>
75
76Example:
77{{=CODE("""*/30 * * * * root *mycontroller/myfunction""")}}
78
79<h3>Application initialization</h3>
80
81If you specify -1 as minutes in the crontab file, the given task will be executed only ONCE, on web2py startup. You can use this feature if you want to precache, check or initialize data for an application on web2py startup.
82
83Example:
84{{=CODE("""
85-1 * * * * root *mycontroller/myfunction
86""")}}
87
880
=== modified file 'gluon/contrib/cron.py'
--- gluon/contrib/cron.py 2009-02-22 15:32:13 +0000
+++ gluon/contrib/cron.py 2009-03-05 12:32:00 +0000
@@ -31,7 +31,9 @@
3131
32 def run(self):32 def run(self):
33 logging.debug('External cron invocation')33 logging.debug('External cron invocation')
34 crondance(apppath({'web2py_path': self.basedir}), 'ext')34 if tokenmaster(os.path.join(self.basedir, 'applications', 'admin', 'cron')):
35 crondance(apppath({'web2py_path': self.basedir}), 'ext')
36 tokenmaster(os.path.join(self.basedir, 'applications', 'admin', 'cron'), action = 'release')
3537
3638
37class hardcron(threading.Thread):39class hardcron(threading.Thread):
@@ -41,6 +43,12 @@
41 self.setDaemon(True)43 self.setDaemon(True)
42 self.basedir = os.getcwd()44 self.basedir = os.getcwd()
4345
46 def launch(self):
47 path = apppath({'web2py_path': self.basedir})
48 if tokenmaster(os.path.join(path, 'admin', 'cron')):
49 crondance(path, 'hard')
50 tokenmaster(os.path.join(path, 'admin', 'cron'), action = 'release')
51
44 def run(self):52 def run(self):
45 global crontype53 global crontype
46 crontype = 'Hard'54 crontype = 'Hard'
@@ -48,8 +56,7 @@
48 logging.info('Hard cron daemon started')56 logging.info('Hard cron daemon started')
49 while True:57 while True:
50 now = time.time()58 now = time.time()
51 s.enter(60 - now % 60, 1, crondance,59 s.enter(60 - now % 60, 1, self.launch, ())
52 (apppath({'web2py_path' : self.basedir}), 'hard'))
53 s.run()60 s.run()
5461
55class softcron(threading.Thread):62class softcron(threading.Thread):
@@ -62,33 +69,63 @@
6269
63 def run(self):70 def run(self):
64 path = apppath(self.env)71 path = apppath(self.env)
65 marker = os.path.join(path, 'admin/cron/cron.master') # location of Chronos, Master of All Time !
66 if not os.path.exists(marker): # cron master missing, try to recreate one
67 logging.warning('WEB2PY CRON: cron.master not found at %s. Trying to recreate.'
68 % marker)
69 mfile = open(marker, 'wb') # touch cron marker
70 mfile.close()
71
72 now = time.time()72 now = time.time()
73 if 60 > now - self.cronmaster: # our own thread did a cron check less than a minute ago, don't even bother checking the file73 if self.cronmaster and 60 > now - self.cronmaster: # our own thread did a cron check less than a minute ago, don't even bother checking the file
74 logging.debug("Don't bother with cron.master, it's only %s s old"74 logging.debug("Don't bother with cron.master, it's only %s s old"
75 % (now - self.cronmaster))75 % (now - self.cronmaster))
76 return76 return
7777
78 try:
79 self.cronmaster = os.stat(marker).st_mtime # get last_modified timestamp of cron.master file
80 except Exception, e:
81 self.cronmaster = 0
82 logging.warning('cron.master trouble: %s' % e)
83
84 logging.debug('Cronmaster stamp: %s, Now: %s'78 logging.debug('Cronmaster stamp: %s, Now: %s'
85 % (self.cronmaster, now))79 % (self.cronmaster, now))
86 if 60 <= now - self.cronmaster: # new minute, do the cron dance80 if 60 <= now - self.cronmaster: # new minute, do the cron dance
87 mfile = open(marker, 'wb') # touch cron marker81 self.cronmaster = tokenmaster(os.path.join(path, 'admin', 'cron'))
88 mfile.close()82 if self.cronmaster:
89 crondance(path, 'soft')83 crondance(path, 'soft')
9084 self.cronmaster = tokenmaster(os.path.join(path, 'admin', 'cron'), action = 'release')
9185
86def tokenmaster(path, db = None, action = 'claim'):
87 token = os.path.join(path, 'cron.master')
88 tokeninuse = os.path.join(path, 'cron.running')
89
90 if action == 'release':
91 logging.debug('WEB2PY CRON: Releasing cron lock')
92 os.unlink(tokeninuse)
93 return time.time()
94
95 tokentime = os.stat(token).st_mtime
96 if tokentime - ( tokentime % 60) + 60 > time.time(): # already ran in this minute
97 return 0
98
99 if os.path.exists(tokeninuse): # running now
100 logging.warning('alreadyrunning')
101 if os.stat(tokeninuse).st_mtime + 60 < time.time(): # check if stale, just in case
102 logging.warning('WEB2PY CRON: Stale cron.master detected')
103 os.unlink(tokeninuse)
104
105 if not (os.path.exists(token) or os.path.exists(tokeninuse)): # no tokens, new install ? Need to regenerate anyho
106 logging.warning("WEB2PY CRON: cron.master not found at %s. Trying to re-create." % token)
107 try:
108 mfile = open(token, 'wb')
109 mfile.close()
110 except:
111 logging.error('WEB2PY CRON: Unable to re-create cron.master, cron functionality likely not available')
112
113 if os.path.exists(token) and not os.path.exists(tokeninuse): # has unclaimed token and not running
114 logging.debug('WEB2PY CRON: Trying to acquire lock')
115 try:
116 os.rename(token, tokeninuse)
117 mfile = open(token, 'wb') # can't must recreate and not rename as we need a correct claim time
118 mfile.close()
119 logging.debug('WEB2PY CRON: Locked')
120 return os.stat(token).st_mtime
121
122 except:
123 logging.info('WEB2PY CRON: Failed to claim %s' % token)
124 return 0
125
126 logging.debug('WEB2PY CRON: already started from another process')
127 return 0
128
92def apppath(env=None):129def apppath(env=None):
93 try:130 try:
94 apppath = os.path.join(env.get('web2py_path'), 'applications')131 apppath = os.path.join(env.get('web2py_path'), 'applications')