Merge lp:~openerp-dev/openobject-server/trunk-gunicorn-signaling-vmt into lp:openobject-server
- trunk-gunicorn-signaling-vmt
- Merge into trunk
Proposed by
Antony Lesuisse (OpenERP)
Status: | Rejected |
---|---|
Rejected by: | Vo Minh Thu |
Proposed branch: | lp:~openerp-dev/openobject-server/trunk-gunicorn-signaling-vmt |
Merge into: | lp:openobject-server |
Diff against target: |
352 lines (+138/-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 (+108/-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/trunk-gunicorn-signaling-vmt |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
OpenERP Core Team | Pending | ||
Review via email: mp+92150@code.launchpad.net |
Commit message
Description of the change
multiprocess signaling, wip
To post a comment you must log in.
Revision history for this message
Vo Minh Thu (thu) wrote : | # |
- 4018. By Vo Minh Thu
-
[IMP] multi-process: moved signaling sequences to registry creation instead of base.sql.
- 4019. By Vo Minh Thu
-
[MERGE] merged trunk.
- 4020. By Vo Minh Thu
-
[FIX] signaling sequences: create them only if they do not exist.
Revision history for this message
Vo Minh Thu (thu) wrote : | # |
Similar to https:/
Revision history for this message
Vo Minh Thu (thu) wrote : | # |
Was forward-ported from 6.1.
Unmerged revisions
- 4020. By Vo Minh Thu
-
[FIX] signaling sequences: create them only if they do not exist.
- 4019. By Vo Minh Thu
-
[MERGE] merged trunk.
- 4018. By Vo Minh Thu
-
[IMP] multi-process: moved signaling sequences to registry creation instead of base.sql.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'gunicorn.conf.py' | |||
2 | --- gunicorn.conf.py 2012-02-10 15:25:21 +0000 | |||
3 | +++ gunicorn.conf.py 2012-03-07 12:45:23 +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-07 12:45:23 +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-07 12:45:23 +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-02-14 15:18:46 +0000 | |||
56 | +++ openerp/addons/base/module/module.py 2012-03-07 12:45:23 +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-07 12:45:23 +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-07 12:45:23 +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,70 @@ | |||
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 | # Check if the model registry must be reloaded (e.g. after the | ||
170 | 265 | # database has been updated by another process). | ||
171 | 266 | registry = cls.get(db_name, pooljobs=False) | ||
172 | 267 | cr = registry.db.cursor() | ||
173 | 268 | registry_reloaded = False | ||
174 | 269 | try: | ||
175 | 270 | cr.execute('SELECT last_value FROM base_registry_signaling') | ||
176 | 271 | r = cr.fetchone()[0] | ||
177 | 272 | if registry.base_registry_signaling_sequence != r: | ||
178 | 273 | _logger.info("Reloading the model registry after database signaling.") | ||
179 | 274 | # Don't run the cron in the Gunicorn worker. | ||
180 | 275 | registry = cls.new(db_name, pooljobs=False) | ||
181 | 276 | registry.base_registry_signaling_sequence = r | ||
182 | 277 | registry_reloaded = True | ||
183 | 278 | finally: | ||
184 | 279 | cr.close() | ||
185 | 280 | |||
186 | 281 | # Check if the model caches must be invalidated (e.g. after a write | ||
187 | 282 | # occured on another process). Don't clear right after a registry | ||
188 | 283 | # has been reload. | ||
189 | 284 | cr = openerp.sql_db.db_connect(db_name).cursor() | ||
190 | 285 | try: | ||
191 | 286 | cr.execute('SELECT last_value FROM base_cache_signaling') | ||
192 | 287 | r = cr.fetchone()[0] | ||
193 | 288 | if registry.base_cache_signaling_sequence != r and not registry_reloaded: | ||
194 | 289 | _logger.info("Invalidating all model caches after database signaling.") | ||
195 | 290 | registry.base_cache_signaling_sequence = r | ||
196 | 291 | registry.clear_caches() | ||
197 | 292 | registry.reset_any_cache_cleared() | ||
198 | 293 | finally: | ||
199 | 294 | cr.close() | ||
200 | 295 | |||
201 | 296 | @classmethod | ||
202 | 297 | def signal_caches_change(cls, db_name): | ||
203 | 298 | if openerp.multi_process and db_name in cls.registries: | ||
204 | 299 | # Check the registries if any cache has been cleared and signal it | ||
205 | 300 | # through the database to other processes. | ||
206 | 301 | registry = cls.get(db_name, pooljobs=False) | ||
207 | 302 | if registry.any_cache_cleared(): | ||
208 | 303 | _logger.info("At least one model cache has been cleare, signaling through the database.") | ||
209 | 304 | cr = registry.db.cursor() | ||
210 | 305 | r = 1 | ||
211 | 306 | try: | ||
212 | 307 | cr.execute("select nextval('base_cache_signaling')") | ||
213 | 308 | r = cr.fetchone()[0] | ||
214 | 309 | finally: | ||
215 | 310 | cr.close() | ||
216 | 311 | registry.base_cache_signaling_sequence = r | ||
217 | 312 | registry.reset_any_cache_cleared() | ||
218 | 313 | |||
219 | 314 | @classmethod | ||
220 | 315 | def signal_registry_change(cls, db_name): | ||
221 | 316 | if openerp.multi_process and db_name in cls.registries: | ||
222 | 317 | registry = cls.get(db_name, pooljobs=False) | ||
223 | 318 | cr = registry.db.cursor() | ||
224 | 319 | r = 1 | ||
225 | 320 | try: | ||
226 | 321 | cr.execute("select nextval('base_registry_signaling')") | ||
227 | 322 | r = cr.fetchone()[0] | ||
228 | 323 | finally: | ||
229 | 324 | cr.close() | ||
230 | 325 | registry.base_registry_signaling_sequence = r | ||
231 | 218 | 326 | ||
232 | 219 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: | 327 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
233 | 220 | 328 | ||
234 | === modified file 'openerp/osv/orm.py' | |||
235 | --- openerp/osv/orm.py 2012-02-15 10:17:14 +0000 | |||
236 | +++ openerp/osv/orm.py 2012-03-07 12:45:23 +0000 | |||
237 | @@ -2389,6 +2389,7 @@ | |||
238 | 2389 | try: | 2389 | try: |
239 | 2390 | getattr(self, '_ormcache') | 2390 | getattr(self, '_ormcache') |
240 | 2391 | self._ormcache = {} | 2391 | self._ormcache = {} |
241 | 2392 | self.pool._any_cache_cleared = True | ||
242 | 2392 | except AttributeError: | 2393 | except AttributeError: |
243 | 2393 | pass | 2394 | pass |
244 | 2394 | 2395 | ||
245 | 2395 | 2396 | ||
246 | === modified file 'openerp/service/web_services.py' | |||
247 | --- openerp/service/web_services.py 2012-02-14 19:30:30 +0000 | |||
248 | +++ openerp/service/web_services.py 2012-03-07 12:45:23 +0000 | |||
249 | @@ -568,8 +568,10 @@ | |||
250 | 568 | raise NameError("Method not available %s" % method) | 568 | raise NameError("Method not available %s" % method) |
251 | 569 | security.check(db,uid,passwd) | 569 | security.check(db,uid,passwd) |
252 | 570 | assert openerp.osv.osv.service, "The object_proxy class must be started with start_object_proxy." | 570 | assert openerp.osv.osv.service, "The object_proxy class must be started with start_object_proxy." |
253 | 571 | openerp.modules.registry.RegistryManager.check_registry_signaling(db) | ||
254 | 571 | fn = getattr(openerp.osv.osv.service, method) | 572 | fn = getattr(openerp.osv.osv.service, method) |
255 | 572 | res = fn(db, uid, *params) | 573 | res = fn(db, uid, *params) |
256 | 574 | openerp.modules.registry.RegistryManager.signal_caches_change(db) | ||
257 | 573 | return res | 575 | return res |
258 | 574 | 576 | ||
259 | 575 | 577 | ||
260 | @@ -649,8 +651,10 @@ | |||
261 | 649 | if method not in ['report', 'report_get', 'render_report']: | 651 | if method not in ['report', 'report_get', 'render_report']: |
262 | 650 | raise KeyError("Method not supported %s" % method) | 652 | raise KeyError("Method not supported %s" % method) |
263 | 651 | security.check(db,uid,passwd) | 653 | security.check(db,uid,passwd) |
264 | 654 | openerp.modules.registry.RegistryManager.check_registry_signaling(db) | ||
265 | 652 | fn = getattr(self, 'exp_' + method) | 655 | fn = getattr(self, 'exp_' + method) |
266 | 653 | res = fn(db, uid, *params) | 656 | res = fn(db, uid, *params) |
267 | 657 | openerp.modules.registry.RegistryManager.signal_caches_change(db) | ||
268 | 654 | return res | 658 | return res |
269 | 655 | 659 | ||
270 | 656 | def exp_render_report(self, db, uid, object, ids, datas=None, context=None): | 660 | def exp_render_report(self, db, uid, object, ids, datas=None, context=None): |
271 | 657 | 661 | ||
272 | === modified file 'openerp/tools/cache.py' | |||
273 | --- openerp/tools/cache.py 2011-11-22 08:58:48 +0000 | |||
274 | +++ openerp/tools/cache.py 2012-03-07 12:45:23 +0000 | |||
275 | @@ -57,10 +57,12 @@ | |||
276 | 57 | try: | 57 | try: |
277 | 58 | key = args[self.skiparg-2:] | 58 | key = args[self.skiparg-2:] |
278 | 59 | del d[key] | 59 | del d[key] |
279 | 60 | self2.pool._any_cache_cleared = True | ||
280 | 60 | except KeyError: | 61 | except KeyError: |
281 | 61 | pass | 62 | pass |
282 | 62 | else: | 63 | else: |
283 | 63 | d.clear() | 64 | d.clear() |
284 | 65 | self2.pool._any_cache_cleared = True | ||
285 | 64 | 66 | ||
286 | 65 | class ormcache_multi(ormcache): | 67 | class ormcache_multi(ormcache): |
287 | 66 | def __init__(self, skiparg=2, size=8192, multi=3): | 68 | def __init__(self, skiparg=2, size=8192, multi=3): |
288 | 67 | 69 | ||
289 | === modified file 'openerp/wsgi/core.py' | |||
290 | --- openerp/wsgi/core.py 2012-02-21 18:54:41 +0000 | |||
291 | +++ openerp/wsgi/core.py 2012-03-07 12:45:23 +0000 | |||
292 | @@ -447,7 +447,7 @@ | |||
293 | 447 | 447 | ||
294 | 448 | The WSGI server can be shutdown with stop_server() below. | 448 | The WSGI server can be shutdown with stop_server() below. |
295 | 449 | """ | 449 | """ |
297 | 450 | threading.Thread(target=serve).start() | 450 | threading.Thread(name='WSGI server', target=serve).start() |
298 | 451 | 451 | ||
299 | 452 | def stop_server(): | 452 | def stop_server(): |
300 | 453 | """ Initiate the shutdown of the WSGI server. | 453 | """ Initiate the shutdown of the WSGI server. |
301 | @@ -465,7 +465,7 @@ | |||
302 | 465 | def on_starting(server): | 465 | def on_starting(server): |
303 | 466 | global arbiter_pid | 466 | global arbiter_pid |
304 | 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 |
306 | 468 | #openerp.tools.cache = kill_workers_cache | 468 | openerp.multi_process = True # Yay! |
307 | 469 | openerp.netsvc.init_logger() | 469 | openerp.netsvc.init_logger() |
308 | 470 | openerp.osv.osv.start_object_proxy() | 470 | openerp.osv.osv.start_object_proxy() |
309 | 471 | openerp.service.web_services.start_web_services() | 471 | openerp.service.web_services.start_web_services() |
310 | @@ -482,11 +482,6 @@ | |||
311 | 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.""" |
312 | 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) |
313 | 484 | 484 | ||
314 | 485 | # Install our own signal handler on the master process. | ||
315 | 486 | def when_ready(server): | ||
316 | 487 | # Hijack gunicorn's SIGWINCH handling; we can choose another one. | ||
317 | 488 | signal.signal(signal.SIGWINCH, make_winch_handler(server)) | ||
318 | 489 | |||
319 | 490 | # Install limits on virtual memory and CPU time consumption. | 485 | # Install limits on virtual memory and CPU time consumption. |
320 | 491 | def pre_request(worker, req): | 486 | def pre_request(worker, req): |
321 | 492 | import os | 487 | import os |
322 | @@ -514,30 +509,9 @@ | |||
323 | 514 | 'too high, rebooting the worker.') | 509 | 'too high, rebooting the worker.') |
324 | 515 | worker.alive = False # Commit suicide after the request. | 510 | worker.alive = False # Commit suicide after the request. |
325 | 516 | 511 | ||
326 | 517 | # Our signal handler will signal a SGIQUIT to all workers. | ||
327 | 518 | def make_winch_handler(server): | ||
328 | 519 | def handle_winch(sig, fram): | ||
329 | 520 | server.kill_workers(signal.SIGQUIT) # This is gunicorn specific. | ||
330 | 521 | return handle_winch | ||
331 | 522 | |||
332 | 523 | # SIGXCPU (exceeded CPU time) signal handler will raise an exception. | 512 | # SIGXCPU (exceeded CPU time) signal handler will raise an exception. |
333 | 524 | def time_expired(n, stack): | 513 | def time_expired(n, stack): |
334 | 525 | _logger.info('CPU time limit exceeded.') | 514 | _logger.info('CPU time limit exceeded.') |
335 | 526 | raise Exception('CPU time limit exceeded.') # TODO one of openerp.exception | 515 | raise Exception('CPU time limit exceeded.') # TODO one of openerp.exception |
336 | 527 | 516 | ||
337 | 528 | # Kill gracefuly the workers (e.g. because we want to clear their cache). | ||
338 | 529 | # This is done by signaling a SIGWINCH to the master process, so it can be | ||
339 | 530 | # called by the workers themselves. | ||
340 | 531 | def kill_workers(): | ||
341 | 532 | try: | ||
342 | 533 | os.kill(arbiter_pid, signal.SIGWINCH) | ||
343 | 534 | except OSError, e: | ||
344 | 535 | if e.errno == errno.ESRCH: # no such pid | ||
345 | 536 | return | ||
346 | 537 | raise | ||
347 | 538 | |||
348 | 539 | class kill_workers_cache(openerp.tools.ormcache): | ||
349 | 540 | def clear(self, dbname, *args, **kwargs): | ||
350 | 541 | kill_workers() | ||
351 | 542 | |||
352 | 543 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: | 517 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
We have to add --fixes for this bug report: /bugs.launchpad .net/openobject -server/ +bug/943794
https:/