Merge lp:~openerp-dev/openobject-server/trunk-registry-lock-vmt into lp:openobject-server

Proposed by Vo Minh Thu
Status: Work in progress
Proposed branch: lp:~openerp-dev/openobject-server/trunk-registry-lock-vmt
Merge into: lp:openobject-server
Diff against target: 127 lines (+53/-28)
2 files modified
openerp/cron.py (+5/-2)
openerp/modules/registry.py (+48/-26)
To merge this branch: bzr merge lp:~openerp-dev/openobject-server/trunk-registry-lock-vmt
Reviewer Review Type Date Requested Status
OpenERP Core Team Pending
Review via email: mp+83602@code.launchpad.net
To post a comment you must log in.

Unmerged revisions

3831. By Vo Minh Thu

[IMP] registries: with lock, instead of acquire/release.

3830. By Vo Minh Thu

[MERGE] merged trunk.

3829. By Vo Minh Thu

[IMP] registries locking behavior:
RegistryManager.get() must always be able to return a currently
being initialized registry. This is true for the thread doing
the initialization. For other threads, get() should return only
completely initialized registries. (This is more consistent as
the first call to get() has thus the same behavior for any
calling thread.)
We also make sure the the cron master thread never ends up
calling new() by making possible to call get() in a non-
blocking way.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'openerp/cron.py'
--- openerp/cron.py 2011-09-28 23:54:09 +0000
+++ openerp/cron.py 2011-11-28 13:15:27 +0000
@@ -177,8 +177,11 @@
177 if canceled:177 if canceled:
178 continue178 continue
179 del _wakeup_by_db[db_name]179 del _wakeup_by_db[db_name]
180 registry = openerp.pooler.get_pool(db_name)180 # The master cron thread never initilizes itself a new registry,
181 if not registry._init:181 # so we use blocking=False and don't need to test registry._init.
182 registry = openerp.modules.registry.RegistryManager.get(
183 db_name, blocking=False)
184 if registry:
182 _logger.debug("Database '%s' wake-up! Firing multi-threaded cron job processing", db_name)185 _logger.debug("Database '%s' wake-up! Firing multi-threaded cron job processing", db_name)
183 registry['ir.cron']._run_jobs_multithread()186 registry['ir.cron']._run_jobs_multithread()
184 amount = MAX_SLEEP187 amount = MAX_SLEEP
185188
=== modified file 'openerp/modules/registry.py'
--- openerp/modules/registry.py 2011-09-30 15:00:26 +0000
+++ openerp/modules/registry.py 2011-11-28 13:15:27 +0000
@@ -42,6 +42,7 @@
42 """42 """
4343
44 def __init__(self, db_name):44 def __init__(self, db_name):
45 self.lock = threading.RLock()
45 self.models = {} # model name/model instance mapping46 self.models = {} # model name/model instance mapping
46 self._sql_error = {}47 self._sql_error = {}
47 self._store_function = {}48 self._store_function = {}
@@ -121,6 +122,17 @@
121 The manager is responsible for creation and deletion of model122 The manager is responsible for creation and deletion of model
122 registries (essentially database connection/model registry pairs).123 registries (essentially database connection/model registry pairs).
123124
125 As the OpenERP server is multi-threaded and implements a lazy-loading
126 of registries, the interaction between get() and new() has to ensure a
127 few points:
128 - One RegistryManager.new() execution at a time: all the module
129 loading code is not thread-safe so it is protected from here.
130 - It must be possible to get() an already loaded registry while
131 another registre is being loaded.
132 - get() must return a completely initialized registry (or None if
133 it must not block). The thread doing the initialization can get
134 a partially initialized registry. It can then check its _init
135 attribute.
124 """136 """
125 # Mapping between db name and model registry.137 # Mapping between db name and model registry.
126 # Accessed through the methods below.138 # Accessed through the methods below.
@@ -129,13 +141,21 @@
129141
130 @classmethod142 @classmethod
131 def get(cls, db_name, force_demo=False, status=None, update_module=False,143 def get(cls, db_name, force_demo=False, status=None, update_module=False,
132 pooljobs=True):144 pooljobs=True, blocking=True):
133 """ Return a registry for a given database name."""145 """ Return a registry for a given database name."""
134 try:146 try:
135 return cls.registries[db_name]147 registry = cls.registries[db_name]
136 except KeyError:148 if registry.lock.acquire(blocking):
137 return cls.new(db_name, force_demo, status,149 registry.lock.release()
138 update_module, pooljobs)150 else:
151 registry = None
152 except KeyError, e:
153 if blocking:
154 registry = cls.new(db_name, force_demo, status,
155 update_module, pooljobs)
156 else:
157 registry = None
158 return registry
139159
140 @classmethod160 @classmethod
141 def new(cls, db_name, force_demo=False, status=None,161 def new(cls, db_name, force_demo=False, status=None,
@@ -146,29 +166,31 @@
146166
147 """167 """
148 import openerp.modules168 import openerp.modules
169
149 with cls.registries_lock:170 with cls.registries_lock:
150 registry = Registry(db_name)171 registry = Registry(db_name)
151172 with registry.lock:
152 # Initializing a registry will call general code which will in turn173
153 # call registries.get (this object) to obtain the registry being174 # Initializing a registry will call general code which will in turn
154 # initialized. Make it available in the registries dictionary then175 # call registries.get (this object) to obtain the registry being
155 # remove it if an exception is raised.176 # initialized. Make it available in the registries dictionary then
156 cls.delete(db_name)177 # remove it if an exception is raised.
157 cls.registries[db_name] = registry178 cls.delete(db_name)
158 try:179 cls.registries[db_name] = registry
159 # This should be a method on Registry180 try:
160 openerp.modules.load_modules(registry.db, force_demo, status, update_module)181 # This should be a method on Registry
161 except Exception:182 openerp.modules.load_modules(registry.db, force_demo, status, update_module)
162 del cls.registries[db_name]183 except Exception:
163 raise184 del cls.registries[db_name]
164185 raise
165 cr = registry.db.cursor()186
166 try:187 cr = registry.db.cursor()
167 registry.do_parent_store(cr)188 try:
168 registry.get('ir.actions.report.xml').register_all(cr)189 registry.do_parent_store(cr)
169 cr.commit()190 registry.get('ir.actions.report.xml').register_all(cr)
170 finally:191 cr.commit()
171 cr.close()192 finally:
193 cr.close()
172194
173 if pooljobs:195 if pooljobs:
174 registry.schedule_cron_jobs()196 registry.schedule_cron_jobs()