Commit a016ed04 authored by Vincent Pelletier's avatar Vincent Pelletier

CMFActivity: Optimise validation queries.

See SQLBase._getExecutableMessageSet for operation principle.
Removes the notion of order_validation_text: activity validation is no
longer evaluated per-activity , but per-dependency for multiple activities
at a time. In this context, order_validation_text does not make sense as
it flattens all dependency types for a given activity.
Rework activity-dependency-to-SQL methods: use a dict rather
dynamically-generated method names.
Based on initial work by Julien Muchembled.
parent f09e1a36
Pipeline #13170 failed with stage
...@@ -89,74 +89,9 @@ class Queue(object): ...@@ -89,74 +89,9 @@ class Queue(object):
def distribute(self, activity_tool, node_count): def distribute(self, activity_tool, node_count):
raise NotImplementedError raise NotImplementedError
def getExecutableMessageList(self, activity_tool, message, message_dict,
validation_text_dict, now_date=None):
"""Get messages which have no dependent message, and store them in the dictionary.
If the passed message itself is executable, simply store only that message.
Otherwise, try to find at least one message executable from dependent messages.
This may result in no new message, if all dependent messages are already present
in the dictionary, if all dependent messages are in different activities, or if
the message has a circular dependency.
The validation text dictionary is used only to cache the results of validations,
in order to reduce the number of SQL queries.
"""
if message.uid in message_dict:
# Nothing to do. But detect a circular dependency.
if message_dict[message.uid] is None:
LOG('CMFActivity', ERROR,
'message uid %r has a circular dependency' % (message.uid,))
return
cached_result = validation_text_dict.get(message.order_validation_text)
if cached_result is None:
message_list = activity_tool.getDependentMessageList(message, self)
if message_list:
# The result is not empty, so this message is not executable.
validation_text_dict[message.order_validation_text] = 0
if now_date is None:
now_date = DateTime()
for activity, m in message_list:
# Note that the messages may contain ones which are already assigned or not
# executable yet.
if activity is self and m.processing_node == -1 and m.date <= now_date:
# Call recursively. Set None as a marker to detect a circular dependency.
message_dict[message.uid] = None
try:
self.getExecutableMessageList(activity_tool, m, message_dict,
validation_text_dict, now_date=now_date)
finally:
del message_dict[message.uid]
else:
validation_text_dict[message.order_validation_text] = 1
message_dict[message.uid] = message
elif cached_result:
message_dict[message.uid] = message
def flush(self, activity_tool, object, **kw): def flush(self, activity_tool, object, **kw):
pass pass
def getOrderValidationText(self, message):
# Return an identifier of validators related to ordering.
order_validation_item_list = [
(key, value)
for key, value in sorted(
message.activity_kw.iteritems(), key=lambda x: x[0],
)
if value is not None and
getattr(self, "_validate_" + key, None) is not None
]
if order_validation_item_list:
return sha1(repr(order_validation_item_list)).hexdigest()
# When no order validation argument is specified, skip the computation
# of the checksum for speed. Here, 'none' is used, because this never be
# identical to SHA1 hexdigest (which is always 40 characters), and 'none'
# is true in Python. This is important, because dtml-if assumes that an empty
# string is false, so we must use a non-empty string for this.
return 'none'
def getMessageList(self, activity_tool, processing_node=None,**kw): def getMessageList(self, activity_tool, processing_node=None,**kw):
return [] return []
......
This diff is collapsed.
...@@ -106,11 +106,10 @@ CREATE TABLE %s ( ...@@ -106,11 +106,10 @@ CREATE TABLE %s (
values_list = [] values_list = []
max_payload = self._insert_max_payload max_payload = self._insert_max_payload
sep_len = len(self._insert_separator) sep_len = len(self._insert_separator)
hasDependency = self._hasDependency
for m in message_list: for m in message_list:
if m.is_registered: if m.is_registered:
active_process_uid = m.active_process_uid active_process_uid = m.active_process_uid
order_validation_text = m.order_validation_text = \
self.getOrderValidationText(m)
date = m.activity_kw.get('at_date') date = m.activity_kw.get('at_date')
row = ','.join(( row = ','.join((
'@uid+%s' % i, '@uid+%s' % i,
...@@ -118,7 +117,7 @@ CREATE TABLE %s ( ...@@ -118,7 +117,7 @@ CREATE TABLE %s (
'NULL' if active_process_uid is None else str(active_process_uid), 'NULL' if active_process_uid is None else str(active_process_uid),
"UTC_TIMESTAMP(6)" if date is None else quote(render_datetime(date)), "UTC_TIMESTAMP(6)" if date is None else quote(render_datetime(date)),
quote(m.method_id), quote(m.method_id),
'0' if order_validation_text == 'none' else '-1', '-1' if hasDependency(m) else '0',
str(m.activity_kw.get('priority', 1)), str(m.activity_kw.get('priority', 1)),
quote(m.getGroupId()), quote(m.getGroupId()),
quote(m.activity_kw.get('tag', '')), quote(m.activity_kw.get('tag', '')),
......
...@@ -1811,31 +1811,9 @@ class ActivityTool (BaseTool): ...@@ -1811,31 +1811,9 @@ class ActivityTool (BaseTool):
REQUEST['RESPONSE'].redirect( 'manage_main' ) REQUEST['RESPONSE'].redirect( 'manage_main' )
return obj return obj
security.declarePrivate('getDependentMessageList') security.declarePrivate('getSQLQueueTableNameSet')
def getDependentMessageList(self, message, validating_queue=None): def getSQLQueueTableNameSet(self):
activity_kw = message.activity_kw return [x.sql_table for x in activity_dict.itervalues()]
db = self.getSQLConnection()
quote = db.string_literal
queries = []
for activity in activity_dict.itervalues():
q = activity.getValidationSQL(
quote, activity_kw, activity is validating_queue)
if q:
queries.append(q)
if queries:
message_list = []
for line in Results(db.query("(%s)" % ") UNION ALL (".join(queries))):
activity = activity_dict[line.activity]
m = Message.load(line.message,
line=line,
uid=line.uid,
date=line.date,
processing_node=line.processing_node)
if not hasattr(m, 'order_validation_text'): # BBB
m.order_validation_text = activity.getOrderValidationText(m)
message_list.append((activity, m))
return message_list
return ()
# Required for tests (time shift) # Required for tests (time shift)
def timeShift(self, delay): def timeShift(self, delay):
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment