[FIX] race condition in ir.ui.menu leading to incomplete menus in web client
See bug, issue occurs with variable frequency when changing between simple and extended views in a user account (non-administrator at least): saving the user account leads to a clearing of the menu cache, this is followed by two search/read in parallel (one to get the full menu listing and one to get the list of applications for the home page), which leads to ir_ui_menu.search (thus ir_ui_menu._filter_visible_menus) being called concurrently, and this apparently somehow wrecks havoc on some browse_record's caches yielding to incoherent behaviors (menus which do have children in db not having children in the browse_record, and thus being pruned from the list of menus). Putting a big lock around 1. clear_cache (just in case) and 2. _filter_visible_menu (to make cache-filling essentially atomic) seems to solve the issue or at least make it disappear, ideally more time should be spent understanding what breaks in browse_record. A reentrant lock is needed as _filter_visible_menu may recurse when accessing e.g. a menu's child_id (which yield a search([parent_id=menu.id]) and thus a _filter_visible_menu) lp bug: https://launchpad.net/bugs/920332 fixed bzr revid: xmo@openerp.com-20120125115823-rpu03zdv14t11lp3
This commit is contained in:
parent
d181a5e7ac
commit
3049760fe6
|
@ -22,6 +22,7 @@
|
|||
|
||||
import base64
|
||||
import re
|
||||
import threading
|
||||
|
||||
import tools
|
||||
import openerp.modules
|
||||
|
@ -40,65 +41,68 @@ class ir_ui_menu(osv.osv):
|
|||
_name = 'ir.ui.menu'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._cache = {}
|
||||
self.cache_lock = threading.RLock()
|
||||
self.clear_cache()
|
||||
r = super(ir_ui_menu, self).__init__(*args, **kwargs)
|
||||
self.pool.get('ir.model.access').register_cache_clearing_method(self._name, 'clear_cache')
|
||||
return r
|
||||
|
||||
def clear_cache(self):
|
||||
# radical but this doesn't frequently happen
|
||||
self._cache = {}
|
||||
with self.cache_lock:
|
||||
# radical but this doesn't frequently happen
|
||||
self._cache = {}
|
||||
|
||||
def _filter_visible_menus(self, cr, uid, ids, context=None):
|
||||
"""Filters the give menu ids to only keep the menu items that should be
|
||||
visible in the menu hierarchy of the current user.
|
||||
Uses a cache for speeding up the computation.
|
||||
"""
|
||||
modelaccess = self.pool.get('ir.model.access')
|
||||
user_groups = set(self.pool.get('res.users').read(cr, 1, uid, ['groups_id'])['groups_id'])
|
||||
result = []
|
||||
for menu in self.browse(cr, uid, ids, context=context):
|
||||
# this key works because user access rights are all based on user's groups (cfr ir_model_access.check)
|
||||
key = (cr.dbname, menu.id, tuple(user_groups))
|
||||
if key in self._cache:
|
||||
if self._cache[key]:
|
||||
result.append(menu.id)
|
||||
#elif not menu.groups_id and not menu.action:
|
||||
# result.append(menu.id)
|
||||
continue
|
||||
|
||||
self._cache[key] = False
|
||||
if menu.groups_id:
|
||||
restrict_to_groups = [g.id for g in menu.groups_id]
|
||||
if not user_groups.intersection(restrict_to_groups):
|
||||
continue
|
||||
#result.append(menu.id)
|
||||
#self._cache[key] = True
|
||||
#continue
|
||||
|
||||
if menu.action:
|
||||
# we check if the user has access to the action of the menu
|
||||
data = menu.action
|
||||
if data:
|
||||
model_field = { 'ir.actions.act_window': 'res_model',
|
||||
'ir.actions.report.xml': 'model',
|
||||
'ir.actions.wizard': 'model',
|
||||
'ir.actions.server': 'model_id',
|
||||
}
|
||||
|
||||
field = model_field.get(menu.action._name)
|
||||
if field and data[field]:
|
||||
if not modelaccess.check(cr, uid, data[field], 'read', False):
|
||||
continue
|
||||
else:
|
||||
# if there is no action, it's a 'folder' menu
|
||||
if not menu.child_id:
|
||||
# not displayed if there is no children
|
||||
with self.cache_lock:
|
||||
modelaccess = self.pool.get('ir.model.access')
|
||||
user_groups = set(self.pool.get('res.users').read(cr, 1, uid, ['groups_id'])['groups_id'])
|
||||
result = []
|
||||
for menu in self.browse(cr, uid, ids, context=context):
|
||||
# this key works because user access rights are all based on user's groups (cfr ir_model_access.check)
|
||||
key = (cr.dbname, menu.id, tuple(user_groups))
|
||||
if key in self._cache:
|
||||
if self._cache[key]:
|
||||
result.append(menu.id)
|
||||
#elif not menu.groups_id and not menu.action:
|
||||
# result.append(menu.id)
|
||||
continue
|
||||
|
||||
result.append(menu.id)
|
||||
self._cache[key] = True
|
||||
return result
|
||||
self._cache[key] = False
|
||||
if menu.groups_id:
|
||||
restrict_to_groups = [g.id for g in menu.groups_id]
|
||||
if not user_groups.intersection(restrict_to_groups):
|
||||
continue
|
||||
#result.append(menu.id)
|
||||
#self._cache[key] = True
|
||||
#continue
|
||||
|
||||
if menu.action:
|
||||
# we check if the user has access to the action of the menu
|
||||
data = menu.action
|
||||
if data:
|
||||
model_field = { 'ir.actions.act_window': 'res_model',
|
||||
'ir.actions.report.xml': 'model',
|
||||
'ir.actions.wizard': 'model',
|
||||
'ir.actions.server': 'model_id',
|
||||
}
|
||||
|
||||
field = model_field.get(menu.action._name)
|
||||
if field and data[field]:
|
||||
if not modelaccess.check(cr, uid, data[field], 'read', False):
|
||||
continue
|
||||
else:
|
||||
# if there is no action, it's a 'folder' menu
|
||||
if not menu.child_id:
|
||||
# not displayed if there is no children
|
||||
continue
|
||||
|
||||
result.append(menu.id)
|
||||
self._cache[key] = True
|
||||
return result
|
||||
|
||||
def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
|
||||
if context is None:
|
||||
|
|
Loading…
Reference in New Issue