Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 34 additions & 32 deletions Products/zms/ZMSMetaobjManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,34 @@ def renderTemplate(self, obj):
obj.clear_request_context(obj.REQUEST, prefix)
return v


def __aq_metaobjs__(self, aqs=[]):
"""
Resolve acquired meta-object entries from portal master.
"""
aq_obs = {}
if not aqs: return aq_obs
unresolved = []
for aq in aqs:
subobjects = aq.get('subobjects')
ob = self.model.get(aq['id'])
if ob and not ob.get('acquired') == 1:
aq_ob = {'acquired': 1, 'subobjects': subobjects, **ob}
aq_obs[id] = aq_ob
if aq_ob.get('type') == 'ZMSPackage' and subobjects == 1:
package = aq_ob['id']
for sub_id, sub_ob in self.model.items():
if sub_ob.get('package') == package:
aq_sub_ob = {'acquired': 1, **sub_ob}
aq_obs[sub_id] = aq_sub_ob
else:
unresolved.append(aq)
if unresolved:
portalMaster = self.getPortalMaster()
if portalMaster:
# merge, portal master first to allow local override
aq_obs = {**portalMaster.metaobj_manager.__aq_metaobjs__(unresolved), **aq_obs}
return aq_obs

def __get_metaobjs__(self):
"""
Return all meta-objects, including acquired entries from portal master.
Expand All @@ -523,36 +550,11 @@ def __get_metaobjs__(self):
try: return self.fetchReqBuff(reqBuffId)
except: pass
# Get value.
obs = {}
m = self.model
aq_obs = None
for id in m:
ob = m[id]
# handle acquisition
if ob.get('acquired', 0) == 1:
acquired = 1
subobjects = ob.get('subobjects', 1)
if aq_obs is None:
portalMaster = self.getPortalMaster()
if portalMaster is not None:
aq_obs = portalMaster.metaobj_manager.__get_metaobjs__()
if aq_obs is not None:
if id in aq_obs:
ob = aq_obs[id].copy()
else:
ob = {'id':id,'type':'ZMSUnknown'}
ob['acquired'] = acquired
ob['subobjects'] = subobjects
obs[id] = ob
if ob['type'] == 'ZMSPackage' and ob['subobjects'] == 1:
for aq_id in aq_obs:
ob = aq_obs[aq_id].copy()
if ob.get( 'package') == id:
ob['acquired'] = 1
obs[aq_id] = ob
else:
obs[id] = ob
return self.storeReqBuff( reqBuffId, obs)
own_obs = {id: ob for id, ob in self.model.items() if not ob.get('acquired') == 1}
aq_obs = [ob for ob in self.model.values() if ob.get('acquired') == 1]
master_obs = self.__aq_metaobjs__(aq_obs)
total_obs = {**own_obs, **master_obs}
return self.storeReqBuff( reqBuffId, total_obs)


def __get_metaobj__(self, id):
Expand Down Expand Up @@ -620,7 +622,7 @@ def getMetaobjIds(self, sort=None, excl_ids=[]):
if sort == True:
ids = sorted(ids,key=lambda x:self.display_type(meta_id=x))
elif sort == False:
ids = sorted(ids,key=lambda x:obs[x].get('name',x))
ids = sorted(ids,key=lambda x:obs.get(x, {}).get('name', x))
return ids


Expand Down
119 changes: 106 additions & 13 deletions Products/zms/_cachemanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,72 @@
from Products.zms import standard
from zope.globalrequest import getRequest

# Kill switch for shared cache usage. Set to False to disable shared caching.
shared_cache_enabled = True

# ID of the shared cache.
# Valid Neta-Types are 'RAM Cache Manager' and 'MemCache Cache Manager'.
shared_cache_id = 'shared_cache'

# Keys that are shared across requests and should be stored in the shared cache.
# This list can be extended with additional keys as needed.
shared_keys = ['ZMSMetaobjManager.__get_metaobjs__']

# Class that wraps an object to provide the ZCacheable interface for shared caching.
class SharedCacheable:
def __init__(self, ob):
self._ob = ob

def ZCacheable_getModTime(self, *args, **kwargs):
return 0

def ZCacheable_getIdentifier(self):
return "/".join(self._ob.getPhysicalPath())

def ZCacheable_isCachingEnabled(self):
return True

def getPhysicalPath(self):
return self._ob.getPhysicalPath()

# Helper function to retrieve the shared cache manager if available.
def get_cache(self):
cache = None
if shared_cache_enabled and hasattr(self, shared_cache_id):
shared_cache = getattr(self, shared_cache_id)
cache = shared_cache.ZCacheManager_getCache()
return cache


# Request-local buffer management.
buff_key = '__buff__'

# Lightweight attribute container used for request-local buffering.
class Buff(object):
"""Lightweight attribute container used for request-local buffering."""
pass

# Helper functions to get and set the request-local buffer.
def get_buff(request):
buff = getattr(request, buff_key, None)
if buff is None:
buff = Buff()
set_buff(request, buff)
return buff

# Helper function to set the request-local buffer.
def set_buff(request, buff):
setattr(request, buff_key, buff)


# Helper function to retrieve the current request object, either from the instance or globally.
def get_request(self):
request = getattr(self, 'REQUEST', None)
if request:
return request
return getRequest()

# Request-scoped buffer helpers for expensive values computed during one request.
class ReqBuff(object):
"""Request-scoped buffer helpers for expensive values computed during one request."""

Expand All @@ -44,7 +106,8 @@ def getReqBuffId(self, key):
@return: Namespaced buffer key.
@rtype: C{str}
"""
return '%s_%s'%('_'.join(self.getPhysicalPath()[2:]), key)
path = self.getPhysicalPath()
return f"{hash(path)}_{key}"


def clearReqBuff(self, prefix='', REQUEST=None):
Expand All @@ -56,15 +119,31 @@ def clearReqBuff(self, prefix='', REQUEST=None):
@param REQUEST: Optional request object.
@type REQUEST: C{object}
"""
request = getattr(self, 'REQUEST', getRequest())
buff = request.get('__buff__', Buff())
reqBuffId = self.getReqBuffId(prefix)
request = get_request(self)
buff = get_buff(request)
if len(prefix) > 0:
reqBuffId += '.'
for key in list(buff.__dict__):
if key.startswith(reqBuffId):
delattr(buff, key)

set_buff(request, buff)


def fetchSharedCache(self, key):
# RAM cache is optional, so we ignore errors if it's not available.
try:
if key in shared_keys:
cache = get_cache(self)
if cache:
cacheable = SharedCacheable(self)
# Note: keywords/view_name can be used for namespacing if needed.
return cache.ZCache_get(cacheable, view_name='shared', keywords={'key': key})
except Exception as e:
print("SharedCache not available:", key, e)
pass
return None


def fetchReqBuff(self, key=None, REQUEST=None):
"""
Expand All @@ -75,11 +154,17 @@ def fetchReqBuff(self, key=None, REQUEST=None):
@return: The buffered value.
@rtype: C{object}
"""
request = getattr(self, 'REQUEST', getRequest())
if key is None: # For debugging purposes, return whole buffer.
return None # request.get('__buff__',{})
buff = request['__buff__']
reqBuffId = self.getReqBuffId(key)
request = get_request(self)
buff = get_buff(request)
if not hasattr(buff, reqBuffId):
# RAM cache is optional, so we ignore errors if it's not available.
value = self.fetchSharedCache(key)
if value:
# Store the value in the request buffer for future access.
setattr(buff, reqBuffId, value)
set_buff(request, buff)
return value
return getattr(buff, reqBuffId)


Expand All @@ -96,11 +181,19 @@ def storeReqBuff(self, key, value, REQUEST=None):
@return: The value that was stored.
@rtype: C{object}
"""
request = getattr(self, 'REQUEST', getRequest())
buff = request.get('__buff__', None)
if buff is None:
buff = Buff()
reqBuffId = self.getReqBuffId(key)
request = get_request(self)
buff = get_buff(request)
setattr(buff, reqBuffId, value)
request.set('__buff__', buff)
set_buff(request, buff)
# RAM cache is optional, so we ignore errors if it's not available.
try:
if key in shared_keys:
cache = get_cache(self)
if cache:
cacheable = SharedCacheable(self)
cache.ZCache_set(cacheable, value, view_name='shared', keywords={'key': key})
except Exception as e:
print("SharedCache not available:", key, e)
pass
return value
19 changes: 19 additions & 0 deletions Products/zms/_confmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,25 @@ def getReqProperty(self, key, default=None, REQUEST=None):
return REQUEST.get(key, default)


def __aq_conf__(self, deep=0):
if deep == 0:
reqBuffId = 'CacheManager.__aq_conf__'
try: return self.fetchReqBuff(reqBuffId)
except: pass
aq_conf = self.get_conf_properties()
portalMaster = self.getPortalMaster()
if portalMaster:
# merge, portal master first to allow local override
master_conf = {k:v for k,v in portalMaster.__aq_conf__(deep+1).items() \
if not k in UNINHERITED_PROPERTIES \
and not k[:k.find('.')] in UNINHERITED_PROPERTIES \
and not k in ['UniBE.Alias', 'UniBE.Server']}
aq_conf = {**master_conf, **aq_conf}
if deep == 0:
return self.storeReqBuff( reqBuffId, aq_conf)
return aq_conf


def get_conf_property(self, *args, **kwargs):
"""
Return a configuration value, resolving local and inherited defaults.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ manage_test_perf_multisite:
name: Test Performance Multisite
nodes: '{$}'
package: com.zms.test.performance.multisite
revision: 0.0.1
revision: 0.0.2
roles:
- ZMSAdministrator
title: Test Performance Multisite
Expand Down
Loading
Loading