Merge lp:~openerp-dev/openobject-server/6.1-gunicorn-signaling-vmt into lp:openobject-server/6.1
- 6.1-gunicorn-signaling-vmt
- Merge into 6.1
Status: | Merged |
---|---|
Merged at revision: | 4126 |
Proposed branch: | lp:~openerp-dev/openobject-server/6.1-gunicorn-signaling-vmt |
Merge into: | lp:openobject-server/6.1 |
Diff against target: |
353 lines (+139/-34) 10 files modified
gunicorn.conf.py (+0/-1) openerp/__init__.py (+7/-0) openerp/addons/base/ir/ir_ui_menu.py (+5/-1) openerp/addons/base/module/module.py (+2/-0) openerp/cron.py (+7/-4) openerp/modules/registry.py (+109/-0) openerp/osv/orm.py (+1/-0) openerp/service/web_services.py (+4/-0) openerp/tools/cache.py (+2/-0) openerp/wsgi/core.py (+2/-28) |
To merge this branch: | bzr merge lp:~openerp-dev/openobject-server/6.1-gunicorn-signaling-vmt |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Vo Minh Thu (community) | Approve | ||
Christophe Simonis (OpenERP) | Approve | ||
Review via email: mp+95936@code.launchpad.net |
Commit message
Description of the change
- 4019. By Vo Minh Thu
-
[IMP] multi-process: moved signaling sequences to registry creation instead of base.sql.
- 4020. By Vo Minh Thu
-
[FIX] multi-process signaling: one query instead of two (based on chs idea).
- 4021. By Vo Minh Thu
-
[FIX] removed spurious print statement.
- 4022. By Vo Minh Thu
-
[FIX] typo.
Christophe Simonis (OpenERP) (kangol) : | # |
- 4023. By Vo Minh Thu
-
[MERGE] merged trunk.
- 4024. By Vo Minh Thu
-
[IMP] signaling: call also digits_change() when caches are cleared.
- 4025. By Vo Minh Thu
-
[REV] reverted local (and mistakenly commited) changes to gunicorn.conf.py.
Vo Minh Thu (thu) wrote : | # |
My latest comment is satisfied now.
Description of the patch:
This adds a simple signaling scheme between OpenERP server instances, but also between processes (managed by, say, gunicorn). The signaling is done through the database by using two PostgreSQL sequences: one for registry invalidation, and one for caches invalidation.
The former case happens when installing a new module, the later when an tools.ormcache is cleared.
The idea is to keep inside each process a value (one per signal) provided by the db. Whenever a registry has been changed, or a cache has been cleared inside a process, the value in database is incremented. Also, whenever a process notices its internal value and the database are out of sync, its registry or caches are reloaded/cleared. Each process tests its internal values against the database at the beginning of every request handling.
Preview Diff
1 | === modified file 'gunicorn.conf.py' | |||
2 | --- gunicorn.conf.py 2012-02-10 15:25:21 +0000 | |||
3 | +++ gunicorn.conf.py 2012-03-23 13:22:20 +0000 | |||
4 | @@ -25,7 +25,6 @@ | |||
5 | 25 | 25 | ||
6 | 26 | # Some application-wide initialization is needed. | 26 | # Some application-wide initialization is needed. |
7 | 27 | on_starting = openerp.wsgi.core.on_starting | 27 | on_starting = openerp.wsgi.core.on_starting |
8 | 28 | when_ready = openerp.wsgi.core.when_ready | ||
9 | 29 | pre_request = openerp.wsgi.core.pre_request | 28 | pre_request = openerp.wsgi.core.pre_request |
10 | 30 | post_request = openerp.wsgi.core.post_request | 29 | post_request = openerp.wsgi.core.post_request |
11 | 31 | 30 | ||
12 | 32 | 31 | ||
13 | === modified file 'openerp/__init__.py' | |||
14 | --- openerp/__init__.py 2011-09-27 16:51:33 +0000 | |||
15 | +++ openerp/__init__.py 2012-03-23 13:22:20 +0000 | |||
16 | @@ -45,5 +45,12 @@ | |||
17 | 45 | import workflow | 45 | import workflow |
18 | 46 | import wsgi | 46 | import wsgi |
19 | 47 | 47 | ||
20 | 48 | # Is the server running in multi-process mode (e.g. behind Gunicorn). | ||
21 | 49 | # If this is True, the processes have to communicate some events, | ||
22 | 50 | # e.g. database update or cache invalidation. Each process has also | ||
23 | 51 | # its own copy of the data structure and we don't need to care about | ||
24 | 52 | # locks between threads. | ||
25 | 53 | multi_process = False | ||
26 | 54 | |||
27 | 48 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: | 55 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
28 | 49 | 56 | ||
29 | 50 | 57 | ||
30 | === modified file 'openerp/addons/base/ir/ir_ui_menu.py' | |||
31 | --- openerp/addons/base/ir/ir_ui_menu.py 2012-02-10 08:26:37 +0000 | |||
32 | +++ openerp/addons/base/ir/ir_ui_menu.py 2012-03-23 13:22:20 +0000 | |||
33 | @@ -42,7 +42,7 @@ | |||
34 | 42 | 42 | ||
35 | 43 | def __init__(self, *args, **kwargs): | 43 | def __init__(self, *args, **kwargs): |
36 | 44 | self.cache_lock = threading.RLock() | 44 | self.cache_lock = threading.RLock() |
38 | 45 | self.clear_cache() | 45 | self._cache = {} |
39 | 46 | r = super(ir_ui_menu, self).__init__(*args, **kwargs) | 46 | r = super(ir_ui_menu, self).__init__(*args, **kwargs) |
40 | 47 | self.pool.get('ir.model.access').register_cache_clearing_method(self._name, 'clear_cache') | 47 | self.pool.get('ir.model.access').register_cache_clearing_method(self._name, 'clear_cache') |
41 | 48 | return r | 48 | return r |
42 | @@ -50,6 +50,10 @@ | |||
43 | 50 | def clear_cache(self): | 50 | def clear_cache(self): |
44 | 51 | with self.cache_lock: | 51 | with self.cache_lock: |
45 | 52 | # radical but this doesn't frequently happen | 52 | # radical but this doesn't frequently happen |
46 | 53 | if self._cache: | ||
47 | 54 | # Normally this is done by openerp.tools.ormcache | ||
48 | 55 | # but since we do not use it, set it by ourself. | ||
49 | 56 | self.pool._any_cache_cleared = True | ||
50 | 53 | self._cache = {} | 57 | self._cache = {} |
51 | 54 | 58 | ||
52 | 55 | def _filter_visible_menus(self, cr, uid, ids, context=None): | 59 | def _filter_visible_menus(self, cr, uid, ids, context=None): |
53 | 56 | 60 | ||
54 | === modified file 'openerp/addons/base/module/module.py' | |||
55 | --- openerp/addons/base/module/module.py 2012-03-01 01:47:08 +0000 | |||
56 | +++ openerp/addons/base/module/module.py 2012-03-23 13:22:20 +0000 | |||
57 | @@ -30,6 +30,7 @@ | |||
58 | 30 | import zipfile | 30 | import zipfile |
59 | 31 | import zipimport | 31 | import zipimport |
60 | 32 | 32 | ||
61 | 33 | import openerp | ||
62 | 33 | import openerp.modules as addons | 34 | import openerp.modules as addons |
63 | 34 | import pooler | 35 | import pooler |
64 | 35 | import release | 36 | import release |
65 | @@ -344,6 +345,7 @@ | |||
66 | 344 | if to_install_ids: | 345 | if to_install_ids: |
67 | 345 | self.button_install(cr, uid, to_install_ids, context=context) | 346 | self.button_install(cr, uid, to_install_ids, context=context) |
68 | 346 | 347 | ||
69 | 348 | openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname) | ||
70 | 347 | return dict(ACTION_DICT, name=_('Install')) | 349 | return dict(ACTION_DICT, name=_('Install')) |
71 | 348 | 350 | ||
72 | 349 | def button_immediate_install(self, cr, uid, ids, context=None): | 351 | def button_immediate_install(self, cr, uid, ids, context=None): |
73 | 350 | 352 | ||
74 | === modified file 'openerp/cron.py' | |||
75 | --- openerp/cron.py 2012-01-24 11:07:30 +0000 | |||
76 | +++ openerp/cron.py 2012-03-23 13:22:20 +0000 | |||
77 | @@ -204,9 +204,12 @@ | |||
78 | 204 | _logger.warning("Connection pool size (%s) is set lower than max number of cron threads (%s), " | 204 | _logger.warning("Connection pool size (%s) is set lower than max number of cron threads (%s), " |
79 | 205 | "this may cause trouble if you reach that number of parallel cron tasks.", | 205 | "this may cause trouble if you reach that number of parallel cron tasks.", |
80 | 206 | db_maxconn, _thread_slots) | 206 | db_maxconn, _thread_slots) |
85 | 207 | t = threading.Thread(target=runner, name="openerp.cron.master_thread") | 207 | if _thread_slots: |
86 | 208 | t.setDaemon(True) | 208 | t = threading.Thread(target=runner, name="openerp.cron.master_thread") |
87 | 209 | t.start() | 209 | t.setDaemon(True) |
88 | 210 | _logger.debug("Master cron daemon started!") | 210 | t.start() |
89 | 211 | _logger.debug("Master cron daemon started!") | ||
90 | 212 | else: | ||
91 | 213 | _logger.info("No master cron daemon (0 workers needed).") | ||
92 | 211 | 214 | ||
93 | 212 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: | 215 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
94 | 213 | 216 | ||
95 | === modified file 'openerp/modules/registry.py' | |||
96 | --- openerp/modules/registry.py 2012-01-24 12:42:52 +0000 | |||
97 | +++ openerp/modules/registry.py 2012-03-23 13:22:20 +0000 | |||
98 | @@ -51,6 +51,18 @@ | |||
99 | 51 | self.db_name = db_name | 51 | self.db_name = db_name |
100 | 52 | self.db = openerp.sql_db.db_connect(db_name) | 52 | self.db = openerp.sql_db.db_connect(db_name) |
101 | 53 | 53 | ||
102 | 54 | # Inter-process signaling (used only when openerp.multi_process is True): | ||
103 | 55 | # The `base_registry_signaling` sequence indicates the whole registry | ||
104 | 56 | # must be reloaded. | ||
105 | 57 | # The `base_cache_signaling sequence` indicates all caches must be | ||
106 | 58 | # invalidated (i.e. cleared). | ||
107 | 59 | self.base_registry_signaling_sequence = 1 | ||
108 | 60 | self.base_cache_signaling_sequence = 1 | ||
109 | 61 | |||
110 | 62 | # Flag indicating if at least one model cache has been cleared. | ||
111 | 63 | # Useful only in a multi-process context. | ||
112 | 64 | self._any_cache_cleared = False | ||
113 | 65 | |||
114 | 54 | cr = self.db.cursor() | 66 | cr = self.db.cursor() |
115 | 55 | has_unaccent = openerp.modules.db.has_unaccent(cr) | 67 | has_unaccent = openerp.modules.db.has_unaccent(cr) |
116 | 56 | if openerp.tools.config['unaccent'] and not has_unaccent: | 68 | if openerp.tools.config['unaccent'] and not has_unaccent: |
117 | @@ -114,6 +126,36 @@ | |||
118 | 114 | """ | 126 | """ |
119 | 115 | for model in self.models.itervalues(): | 127 | for model in self.models.itervalues(): |
120 | 116 | model.clear_caches() | 128 | model.clear_caches() |
121 | 129 | # Special case for ir_ui_menu which does not use openerp.tools.ormcache. | ||
122 | 130 | ir_ui_menu = self.models.get('ir.ui.menu') | ||
123 | 131 | if ir_ui_menu: | ||
124 | 132 | ir_ui_menu.clear_cache() | ||
125 | 133 | |||
126 | 134 | |||
127 | 135 | # Useful only in a multi-process context. | ||
128 | 136 | def reset_any_cache_cleared(self): | ||
129 | 137 | self._any_cache_cleared = False | ||
130 | 138 | |||
131 | 139 | # Useful only in a multi-process context. | ||
132 | 140 | def any_cache_cleared(self): | ||
133 | 141 | return self._any_cache_cleared | ||
134 | 142 | |||
135 | 143 | @classmethod | ||
136 | 144 | def setup_multi_process_signaling(cls, cr): | ||
137 | 145 | if not openerp.multi_process: | ||
138 | 146 | return | ||
139 | 147 | |||
140 | 148 | # Inter-process signaling: | ||
141 | 149 | # The `base_registry_signaling` sequence indicates the whole registry | ||
142 | 150 | # must be reloaded. | ||
143 | 151 | # The `base_cache_signaling sequence` indicates all caches must be | ||
144 | 152 | # invalidated (i.e. cleared). | ||
145 | 153 | cr.execute("""SELECT sequence_name FROM information_schema.sequences WHERE sequence_name='base_registry_signaling'""") | ||
146 | 154 | if not cr.fetchall(): | ||
147 | 155 | cr.execute("""CREATE SEQUENCE base_registry_signaling INCREMENT BY 1 START WITH 1""") | ||
148 | 156 | cr.execute("""SELECT nextval('base_registry_signaling')""") | ||
149 | 157 | cr.execute("""CREATE SEQUENCE base_cache_signaling INCREMENT BY 1 START WITH 1""") | ||
150 | 158 | cr.execute("""SELECT nextval('base_cache_signaling')""") | ||
151 | 117 | 159 | ||
152 | 118 | class RegistryManager(object): | 160 | class RegistryManager(object): |
153 | 119 | """ Model registries manager. | 161 | """ Model registries manager. |
154 | @@ -164,6 +206,7 @@ | |||
155 | 164 | 206 | ||
156 | 165 | cr = registry.db.cursor() | 207 | cr = registry.db.cursor() |
157 | 166 | try: | 208 | try: |
158 | 209 | Registry.setup_multi_process_signaling(cr) | ||
159 | 167 | registry.do_parent_store(cr) | 210 | registry.do_parent_store(cr) |
160 | 168 | registry.get('ir.actions.report.xml').register_all(cr) | 211 | registry.get('ir.actions.report.xml').register_all(cr) |
161 | 169 | cr.commit() | 212 | cr.commit() |
162 | @@ -215,5 +258,71 @@ | |||
163 | 215 | if db_name in cls.registries: | 258 | if db_name in cls.registries: |
164 | 216 | cls.registries[db_name].clear_caches() | 259 | cls.registries[db_name].clear_caches() |
165 | 217 | 260 | ||
166 | 261 | @classmethod | ||
167 | 262 | def check_registry_signaling(cls, db_name): | ||
168 | 263 | if openerp.multi_process and db_name in cls.registries: | ||
169 | 264 | registry = cls.get(db_name, pooljobs=False) | ||
170 | 265 | cr = registry.db.cursor() | ||
171 | 266 | try: | ||
172 | 267 | cr.execute(""" | ||
173 | 268 | SELECT base_registry_signaling.last_value, | ||
174 | 269 | base_cache_signaling.last_value | ||
175 | 270 | FROM base_registry_signaling, base_cache_signaling""") | ||
176 | 271 | r, c = cr.fetchone() | ||
177 | 272 | # Check if the model registry must be reloaded (e.g. after the | ||
178 | 273 | # database has been updated by another process). | ||
179 | 274 | if registry.base_registry_signaling_sequence != r: | ||
180 | 275 | _logger.info("Reloading the model registry after database signaling.") | ||
181 | 276 | # Don't run the cron in the Gunicorn worker. | ||
182 | 277 | registry = cls.new(db_name, pooljobs=False) | ||
183 | 278 | registry.base_registry_signaling_sequence = r | ||
184 | 279 | # Check if the model caches must be invalidated (e.g. after a write | ||
185 | 280 | # occured on another process). Don't clear right after a registry | ||
186 | 281 | # has been reload. | ||
187 | 282 | elif registry.base_cache_signaling_sequence != c: | ||
188 | 283 | _logger.info("Invalidating all model caches after database signaling.") | ||
189 | 284 | registry.base_cache_signaling_sequence = c | ||
190 | 285 | registry.clear_caches() | ||
191 | 286 | registry.reset_any_cache_cleared() | ||
192 | 287 | # One possible reason caches have been invalidated is the | ||
193 | 288 | # use of decimal_precision.write(), in which case we need | ||
194 | 289 | # to refresh fields.float columns. | ||
195 | 290 | for model in registry.models.values(): | ||
196 | 291 | for column in model._columns.values(): | ||
197 | 292 | if hasattr(column, 'digits_change'): | ||
198 | 293 | column.digits_change(cr) | ||
199 | 294 | finally: | ||
200 | 295 | cr.close() | ||
201 | 296 | |||
202 | 297 | @classmethod | ||
203 | 298 | def signal_caches_change(cls, db_name): | ||
204 | 299 | if openerp.multi_process and db_name in cls.registries: | ||
205 | 300 | # Check the registries if any cache has been cleared and signal it | ||
206 | 301 | # through the database to other processes. | ||
207 | 302 | registry = cls.get(db_name, pooljobs=False) | ||
208 | 303 | if registry.any_cache_cleared(): | ||
209 | 304 | _logger.info("At least one model cache has been cleared, signaling through the database.") | ||
210 | 305 | cr = registry.db.cursor() | ||
211 | 306 | r = 1 | ||
212 | 307 | try: | ||
213 | 308 | cr.execute("select nextval('base_cache_signaling')") | ||
214 | 309 | r = cr.fetchone()[0] | ||
215 | 310 | finally: | ||
216 | 311 | cr.close() | ||
217 | 312 | registry.base_cache_signaling_sequence = r | ||
218 | 313 | registry.reset_any_cache_cleared() | ||
219 | 314 | |||
220 | 315 | @classmethod | ||
221 | 316 | def signal_registry_change(cls, db_name): | ||
222 | 317 | if openerp.multi_process and db_name in cls.registries: | ||
223 | 318 | registry = cls.get(db_name, pooljobs=False) | ||
224 | 319 | cr = registry.db.cursor() | ||
225 | 320 | r = 1 | ||
226 | 321 | try: | ||
227 | 322 | cr.execute("select nextval('base_registry_signaling')") | ||
228 | 323 | r = cr.fetchone()[0] | ||
229 | 324 | finally: | ||
230 | 325 | cr.close() | ||
231 | 326 | registry.base_registry_signaling_sequence = r | ||
232 | 218 | 327 | ||
233 | 219 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: | 328 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
234 | 220 | 329 | ||
235 | === modified file 'openerp/osv/orm.py' | |||
236 | --- openerp/osv/orm.py 2012-03-09 08:19:29 +0000 | |||
237 | +++ openerp/osv/orm.py 2012-03-23 13:22:20 +0000 | |||
238 | @@ -2389,6 +2389,7 @@ | |||
239 | 2389 | try: | 2389 | try: |
240 | 2390 | getattr(self, '_ormcache') | 2390 | getattr(self, '_ormcache') |
241 | 2391 | self._ormcache = {} | 2391 | self._ormcache = {} |
242 | 2392 | self.pool._any_cache_cleared = True | ||
243 | 2392 | except AttributeError: | 2393 | except AttributeError: |
244 | 2393 | pass | 2394 | pass |
245 | 2394 | 2395 | ||
246 | 2395 | 2396 | ||
247 | === modified file 'openerp/service/web_services.py' | |||
248 | --- openerp/service/web_services.py 2012-03-16 16:02:16 +0000 | |||
249 | +++ openerp/service/web_services.py 2012-03-23 13:22:20 +0000 | |||
250 | @@ -581,8 +581,10 @@ | |||
251 | 581 | raise NameError("Method not available %s" % method) | 581 | raise NameError("Method not available %s" % method) |
252 | 582 | security.check(db,uid,passwd) | 582 | security.check(db,uid,passwd) |
253 | 583 | assert openerp.osv.osv.service, "The object_proxy class must be started with start_object_proxy." | 583 | assert openerp.osv.osv.service, "The object_proxy class must be started with start_object_proxy." |
254 | 584 | openerp.modules.registry.RegistryManager.check_registry_signaling(db) | ||
255 | 584 | fn = getattr(openerp.osv.osv.service, method) | 585 | fn = getattr(openerp.osv.osv.service, method) |
256 | 585 | res = fn(db, uid, *params) | 586 | res = fn(db, uid, *params) |
257 | 587 | openerp.modules.registry.RegistryManager.signal_caches_change(db) | ||
258 | 586 | return res | 588 | return res |
259 | 587 | 589 | ||
260 | 588 | 590 | ||
261 | @@ -662,8 +664,10 @@ | |||
262 | 662 | if method not in ['report', 'report_get', 'render_report']: | 664 | if method not in ['report', 'report_get', 'render_report']: |
263 | 663 | raise KeyError("Method not supported %s" % method) | 665 | raise KeyError("Method not supported %s" % method) |
264 | 664 | security.check(db,uid,passwd) | 666 | security.check(db,uid,passwd) |
265 | 667 | openerp.modules.registry.RegistryManager.check_registry_signaling(db) | ||
266 | 665 | fn = getattr(self, 'exp_' + method) | 668 | fn = getattr(self, 'exp_' + method) |
267 | 666 | res = fn(db, uid, *params) | 669 | res = fn(db, uid, *params) |
268 | 670 | openerp.modules.registry.RegistryManager.signal_caches_change(db) | ||
269 | 667 | return res | 671 | return res |
270 | 668 | 672 | ||
271 | 669 | def exp_render_report(self, db, uid, object, ids, datas=None, context=None): | 673 | def exp_render_report(self, db, uid, object, ids, datas=None, context=None): |
272 | 670 | 674 | ||
273 | === modified file 'openerp/tools/cache.py' | |||
274 | --- openerp/tools/cache.py 2011-11-22 08:58:48 +0000 | |||
275 | +++ openerp/tools/cache.py 2012-03-23 13:22:20 +0000 | |||
276 | @@ -57,10 +57,12 @@ | |||
277 | 57 | try: | 57 | try: |
278 | 58 | key = args[self.skiparg-2:] | 58 | key = args[self.skiparg-2:] |
279 | 59 | del d[key] | 59 | del d[key] |
280 | 60 | self2.pool._any_cache_cleared = True | ||
281 | 60 | except KeyError: | 61 | except KeyError: |
282 | 61 | pass | 62 | pass |
283 | 62 | else: | 63 | else: |
284 | 63 | d.clear() | 64 | d.clear() |
285 | 65 | self2.pool._any_cache_cleared = True | ||
286 | 64 | 66 | ||
287 | 65 | class ormcache_multi(ormcache): | 67 | class ormcache_multi(ormcache): |
288 | 66 | def __init__(self, skiparg=2, size=8192, multi=3): | 68 | def __init__(self, skiparg=2, size=8192, multi=3): |
289 | 67 | 69 | ||
290 | === modified file 'openerp/wsgi/core.py' | |||
291 | --- openerp/wsgi/core.py 2012-02-21 18:52:47 +0000 | |||
292 | +++ openerp/wsgi/core.py 2012-03-23 13:22:20 +0000 | |||
293 | @@ -447,7 +447,7 @@ | |||
294 | 447 | 447 | ||
295 | 448 | The WSGI server can be shutdown with stop_server() below. | 448 | The WSGI server can be shutdown with stop_server() below. |
296 | 449 | """ | 449 | """ |
298 | 450 | threading.Thread(target=serve).start() | 450 | threading.Thread(name='WSGI server', target=serve).start() |
299 | 451 | 451 | ||
300 | 452 | def stop_server(): | 452 | def stop_server(): |
301 | 453 | """ Initiate the shutdown of the WSGI server. | 453 | """ Initiate the shutdown of the WSGI server. |
302 | @@ -465,7 +465,7 @@ | |||
303 | 465 | def on_starting(server): | 465 | def on_starting(server): |
304 | 466 | global arbiter_pid | 466 | global arbiter_pid |
305 | 467 | arbiter_pid = os.getpid() # TODO check if this is true even after replacing the executable | 467 | arbiter_pid = os.getpid() # TODO check if this is true even after replacing the executable |
307 | 468 | #openerp.tools.cache = kill_workers_cache | 468 | openerp.multi_process = True # Yay! |
308 | 469 | openerp.netsvc.init_logger() | 469 | openerp.netsvc.init_logger() |
309 | 470 | openerp.osv.osv.start_object_proxy() | 470 | openerp.osv.osv.start_object_proxy() |
310 | 471 | openerp.service.web_services.start_web_services() | 471 | openerp.service.web_services.start_web_services() |
311 | @@ -482,11 +482,6 @@ | |||
312 | 482 | Maybe you forgot to add those addons in your addons_path configuration.""" | 482 | Maybe you forgot to add those addons in your addons_path configuration.""" |
313 | 483 | _logger.exception('Failed to load server-wide module `%s`.%s', m, msg) | 483 | _logger.exception('Failed to load server-wide module `%s`.%s', m, msg) |
314 | 484 | 484 | ||
315 | 485 | # Install our own signal handler on the master process. | ||
316 | 486 | def when_ready(server): | ||
317 | 487 | # Hijack gunicorn's SIGWINCH handling; we can choose another one. | ||
318 | 488 | signal.signal(signal.SIGWINCH, make_winch_handler(server)) | ||
319 | 489 | |||
320 | 490 | # Install limits on virtual memory and CPU time consumption. | 485 | # Install limits on virtual memory and CPU time consumption. |
321 | 491 | def pre_request(worker, req): | 486 | def pre_request(worker, req): |
322 | 492 | import os | 487 | import os |
323 | @@ -514,30 +509,9 @@ | |||
324 | 514 | 'too high, rebooting the worker.') | 509 | 'too high, rebooting the worker.') |
325 | 515 | worker.alive = False # Commit suicide after the request. | 510 | worker.alive = False # Commit suicide after the request. |
326 | 516 | 511 | ||
327 | 517 | # Our signal handler will signal a SGIQUIT to all workers. | ||
328 | 518 | def make_winch_handler(server): | ||
329 | 519 | def handle_winch(sig, fram): | ||
330 | 520 | server.kill_workers(signal.SIGQUIT) # This is gunicorn specific. | ||
331 | 521 | return handle_winch | ||
332 | 522 | |||
333 | 523 | # SIGXCPU (exceeded CPU time) signal handler will raise an exception. | 512 | # SIGXCPU (exceeded CPU time) signal handler will raise an exception. |
334 | 524 | def time_expired(n, stack): | 513 | def time_expired(n, stack): |
335 | 525 | _logger.info('CPU time limit exceeded.') | 514 | _logger.info('CPU time limit exceeded.') |
336 | 526 | raise Exception('CPU time limit exceeded.') # TODO one of openerp.exception | 515 | raise Exception('CPU time limit exceeded.') # TODO one of openerp.exception |
337 | 527 | 516 | ||
338 | 528 | # Kill gracefuly the workers (e.g. because we want to clear their cache). | ||
339 | 529 | # This is done by signaling a SIGWINCH to the master process, so it can be | ||
340 | 530 | # called by the workers themselves. | ||
341 | 531 | def kill_workers(): | ||
342 | 532 | try: | ||
343 | 533 | os.kill(arbiter_pid, signal.SIGWINCH) | ||
344 | 534 | except OSError, e: | ||
345 | 535 | if e.errno == errno.ESRCH: # no such pid | ||
346 | 536 | return | ||
347 | 537 | raise | ||
348 | 538 | |||
349 | 539 | class kill_workers_cache(openerp.tools.ormcache): | ||
350 | 540 | def clear(self, dbname, *args, **kwargs): | ||
351 | 541 | kill_workers() | ||
352 | 542 | |||
353 | 543 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: | 517 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
check_registry_ signaling should also refresh the fields.float columns, by calling digits_change(), when the decimal_precision module is installed.