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
1=== modified file 'openerp/cron.py'
2--- openerp/cron.py 2011-09-28 23:54:09 +0000
3+++ openerp/cron.py 2011-11-28 13:15:27 +0000
4@@ -177,8 +177,11 @@
5 if canceled:
6 continue
7 del _wakeup_by_db[db_name]
8- registry = openerp.pooler.get_pool(db_name)
9- if not registry._init:
10+ # The master cron thread never initilizes itself a new registry,
11+ # so we use blocking=False and don't need to test registry._init.
12+ registry = openerp.modules.registry.RegistryManager.get(
13+ db_name, blocking=False)
14+ if registry:
15 _logger.debug("Database '%s' wake-up! Firing multi-threaded cron job processing", db_name)
16 registry['ir.cron']._run_jobs_multithread()
17 amount = MAX_SLEEP
18
19=== modified file 'openerp/modules/registry.py'
20--- openerp/modules/registry.py 2011-09-30 15:00:26 +0000
21+++ openerp/modules/registry.py 2011-11-28 13:15:27 +0000
22@@ -42,6 +42,7 @@
23 """
24
25 def __init__(self, db_name):
26+ self.lock = threading.RLock()
27 self.models = {} # model name/model instance mapping
28 self._sql_error = {}
29 self._store_function = {}
30@@ -121,6 +122,17 @@
31 The manager is responsible for creation and deletion of model
32 registries (essentially database connection/model registry pairs).
33
34+ As the OpenERP server is multi-threaded and implements a lazy-loading
35+ of registries, the interaction between get() and new() has to ensure a
36+ few points:
37+ - One RegistryManager.new() execution at a time: all the module
38+ loading code is not thread-safe so it is protected from here.
39+ - It must be possible to get() an already loaded registry while
40+ another registre is being loaded.
41+ - get() must return a completely initialized registry (or None if
42+ it must not block). The thread doing the initialization can get
43+ a partially initialized registry. It can then check its _init
44+ attribute.
45 """
46 # Mapping between db name and model registry.
47 # Accessed through the methods below.
48@@ -129,13 +141,21 @@
49
50 @classmethod
51 def get(cls, db_name, force_demo=False, status=None, update_module=False,
52- pooljobs=True):
53+ pooljobs=True, blocking=True):
54 """ Return a registry for a given database name."""
55 try:
56- return cls.registries[db_name]
57- except KeyError:
58- return cls.new(db_name, force_demo, status,
59- update_module, pooljobs)
60+ registry = cls.registries[db_name]
61+ if registry.lock.acquire(blocking):
62+ registry.lock.release()
63+ else:
64+ registry = None
65+ except KeyError, e:
66+ if blocking:
67+ registry = cls.new(db_name, force_demo, status,
68+ update_module, pooljobs)
69+ else:
70+ registry = None
71+ return registry
72
73 @classmethod
74 def new(cls, db_name, force_demo=False, status=None,
75@@ -146,29 +166,31 @@
76
77 """
78 import openerp.modules
79+
80 with cls.registries_lock:
81 registry = Registry(db_name)
82-
83- # Initializing a registry will call general code which will in turn
84- # call registries.get (this object) to obtain the registry being
85- # initialized. Make it available in the registries dictionary then
86- # remove it if an exception is raised.
87- cls.delete(db_name)
88- cls.registries[db_name] = registry
89- try:
90- # This should be a method on Registry
91- openerp.modules.load_modules(registry.db, force_demo, status, update_module)
92- except Exception:
93- del cls.registries[db_name]
94- raise
95-
96- cr = registry.db.cursor()
97- try:
98- registry.do_parent_store(cr)
99- registry.get('ir.actions.report.xml').register_all(cr)
100- cr.commit()
101- finally:
102- cr.close()
103+ with registry.lock:
104+
105+ # Initializing a registry will call general code which will in turn
106+ # call registries.get (this object) to obtain the registry being
107+ # initialized. Make it available in the registries dictionary then
108+ # remove it if an exception is raised.
109+ cls.delete(db_name)
110+ cls.registries[db_name] = registry
111+ try:
112+ # This should be a method on Registry
113+ openerp.modules.load_modules(registry.db, force_demo, status, update_module)
114+ except Exception:
115+ del cls.registries[db_name]
116+ raise
117+
118+ cr = registry.db.cursor()
119+ try:
120+ registry.do_parent_store(cr)
121+ registry.get('ir.actions.report.xml').register_all(cr)
122+ cr.commit()
123+ finally:
124+ cr.close()
125
126 if pooljobs:
127 registry.schedule_cron_jobs()