Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
erp5
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
francois
erp5
Commits
04f3c88a
Commit
04f3c88a
authored
Oct 19, 2011
by
Julien Muchembled
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
CMFActivity: new 'on_error_callback' message parameter
parent
795dc233
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
98 additions
and
36 deletions
+98
-36
product/CMFActivity/Activity/SQLBase.py
product/CMFActivity/Activity/SQLBase.py
+37
-33
product/CMFActivity/ActivityRuntimeEnvironment.py
product/CMFActivity/ActivityRuntimeEnvironment.py
+9
-0
product/CMFActivity/ActivityTool.py
product/CMFActivity/ActivityTool.py
+3
-1
product/CMFActivity/tests/testCMFActivity.py
product/CMFActivity/tests/testCMFActivity.py
+49
-2
No files found.
product/CMFActivity/Activity/SQLBase.py
View file @
04f3c88a
...
...
@@ -42,6 +42,7 @@ def sort_message_key(message):
# same sort key as in SQL{Dict,Queue}_readMessageList
return
message
.
line
.
priority
,
message
.
line
.
date
,
message
.
uid
_DequeueMessageException
=
Exception
()
class
SQLBase
:
"""
...
...
@@ -288,6 +289,15 @@ class SQLBase:
self
.
_log
(
TRACE
,
'(no message was reserved)'
)
return
[],
0
,
None
,
{}
def
_abort
(
self
):
try
:
transaction
.
abort
()
except
:
# Unfortunately, database adapters may raise an exception against abort.
self
.
_log
(
PANIC
,
'abort failed, thus some objects may be modified accidentally'
)
raise
# Queue semantic
def
dequeueMessage
(
self
,
activity_tool
,
processing_node
):
message_list
,
group_method_id
,
uid_to_duplicate_uid_list_dict
=
\
...
...
@@ -320,42 +330,36 @@ class SQLBase:
# Try to invoke
try
:
method
(
*
args
)
# Abort if at least 1 message failed. On next tic, only those that
# succeeded will be selected because their at_date won't have been
# increased.
for
m
in
message_list
:
if
m
.
getExecutionState
()
==
MESSAGE_NOT_EXECUTED
:
raise
_DequeueMessageException
transaction
.
commit
()
except
:
exc_info
=
sys
.
exc_info
()
if
exc_info
[
1
]
is
not
_DequeueMessageException
:
self
.
_log
(
WARNING
,
'Exception raised when invoking messages (uid, path, method_id) %r'
%
[(
m
.
uid
,
m
.
object_path
,
m
.
method_id
)
for
m
in
message_list
])
try
:
transaction
.
abort
()
except
:
# Unfortunately, database adapters may raise an exception against
# abort.
self
.
_log
(
PANIC
,
'abort failed, thus some objects may be modified accidentally'
)
raise
# Abort if something failed.
if
[
m
for
m
in
message_list
if
m
.
getExecutionState
()
==
MESSAGE_NOT_EXECUTED
]:
endTransaction
=
transaction
.
abort
else
:
endTransaction
=
transaction
.
commit
try
:
endTransaction
()
except
:
self
.
_log
(
WARNING
,
'Failed to end transaction for messages (uid, path, method_id) %r'
%
[(
m
.
uid
,
m
.
object_path
,
m
.
method_id
)
for
m
in
message_list
])
if
endTransaction
==
transaction
.
abort
:
self
.
_log
(
PANIC
,
'Failed to abort executed messages.'
' Some objects may be modified accidentally.'
)
else
:
try
:
transaction
.
abort
()
except
:
self
.
_log
(
PANIC
,
'Failed to abort executed messages which also'
' failed to commit. Some objects may be modified accidentally.'
)
raise
exc_info
=
sys
.
exc_info
()
for
m
in
message_list
:
m
.
setExecutionState
(
MESSAGE_NOT_EXECUTED
,
exc_info
,
log
=
False
)
self
.
_abort
()
exc_info
=
message_list
[
0
].
exc_info
if
exc_info
:
try
:
# Register it again.
tv
[
'activity_runtime_environment'
]
=
activity_runtime_environment
cancel
=
message
.
on_error_callback
(
*
exc_info
)
del
exc_info
,
message
.
exc_info
transaction
.
commit
()
if
cancel
:
message
.
setExecutionState
(
MESSAGE_EXECUTED
)
except
:
self
.
_log
(
WARNING
,
'Exception raised when processing error callbacks'
)
message
.
setExecutionState
(
MESSAGE_NOT_EXECUTED
)
self
.
_abort
()
self
.
finalizeMessageExecution
(
activity_tool
,
message_list
,
uid_to_duplicate_uid_list_dict
)
transaction
.
commit
()
...
...
product/CMFActivity/ActivityRuntimeEnvironment.py
View file @
04f3c88a
...
...
@@ -25,6 +25,12 @@ class BaseMessage:
# For errors happening after message invocation (ConflictError),
# should we retry quickly without increasing 'retry' count ?
conflict_retry
=
__property
(
conflict_retry
=
True
)
# Called if any error happened, after the transaction is aborted.
# The message is cancelled if a non zero value is returned.
# A transaction commit is done after it is called.
# If the callback fails, the transaction is aborted again and the
# notification contains this failure instead of the original one.
on_error_callback
=
__property
(
on_error_callback
=
None
)
class
ActivityRuntimeEnvironment
(
object
):
...
...
@@ -36,4 +42,7 @@ class ActivityRuntimeEnvironment(object):
# There is no point allowing to modify other attributes from a message
for
k
in
kw
:
getattr
(
BaseMessage
,
k
)
if
k
==
'on_error_callback'
and
\
self
.
_message
.
activity_kw
.
get
(
k
)
is
not
None
:
raise
RuntimeError
(
"An error callback is already registered"
)
self
.
_message
.
activity_kw
.
update
(
kw
)
product/CMFActivity/ActivityTool.py
View file @
04f3c88a
...
...
@@ -161,10 +161,10 @@ class Message(BaseMessage):
active_process
=
None
active_process_uid
=
None
call_traceback
=
None
exc_info
=
None
is_executed
=
MESSAGE_NOT_EXECUTED
processing
=
None
traceback
=
None
exc_type
=
None
def
__init__
(
self
,
obj
,
active_process
,
activity_kw
,
method_id
,
args
,
kw
):
if
isinstance
(
obj
,
str
):
...
...
@@ -394,6 +394,8 @@ Named Parameters: %r
if
is_executed
!=
MESSAGE_EXECUTED
:
if
not
exc_info
:
exc_info
=
sys
.
exc_info
()
if
self
.
on_error_callback
is
not
None
:
self
.
exc_info
=
exc_info
self
.
exc_type
=
exc_info
[
0
]
if
exc_info
[
0
]
is
None
:
# Raise a dummy exception, ignore it, fetch it and use it as if it was the error causing message non-execution. This will help identifyting the cause of this misbehaviour.
...
...
product/CMFActivity/tests/testCMFActivity.py
View file @
04f3c88a
...
...
@@ -48,10 +48,11 @@ from ZODB.POSException import ConflictError
from
DateTime
import
DateTime
import
cPickle
as
pickle
from
Products.CMFActivity.ActivityTool
import
Message
import
gc
import
random
import
threading
import
sys
import
weakref
import
transaction
class
CommitFailed
(
Exception
):
...
...
@@ -1585,7 +1586,7 @@ class TestCMFActivity(ERP5TypeTestCase, LogInterceptor):
if
edit_kw
:
self
.
getActivityRuntimeEnvironment
().
edit
(
**
edit_kw
)
if
conflict
is
not
None
:
raise
conflict
and
ConflictError
or
Exception
raise
ConflictError
if
conflict
else
Exception
def
check
(
retry_list
,
**
activate_kw
):
fail
=
retry_list
[
-
1
][
0
]
is
not
None
and
1
or
0
for
activity
in
'SQLDict'
,
'SQLQueue'
:
...
...
@@ -3819,6 +3820,52 @@ class TestCMFActivity(ERP5TypeTestCase, LogInterceptor):
newconn
=
portal
.
cmf_activity_sql_connection
self
.
assertEquals
(
newconn
.
meta_type
,
'CMFActivity Database Connection'
)
def
test_onErrorCallback
(
self
):
activity_tool
=
self
.
portal
.
portal_activities
obj
=
activity_tool
.
newActiveProcess
()
transaction
.
commit
()
self
.
tic
()
def
_raise
(
exception
):
# I wish exceptions are callable raising themselves
raise
exception
def
doSomething
(
self
,
conflict_error
,
cancel
):
self
.
activity_count
+=
1
error
=
ConflictError
()
if
conflict_error
else
Exception
()
def
onError
(
exc_type
,
exc_value
,
traceback
):
assert
exc_value
is
error
env
=
self
.
getActivityRuntimeEnvironment
()
weakref_list
.
extend
(
map
(
weakref
.
ref
,
(
env
,
env
.
_message
)))
self
.
on_error_count
+=
1
return
cancel
self
.
getActivityRuntimeEnvironment
().
edit
(
on_error_callback
=
onError
)
if
not
self
.
on_error_count
:
if
not
conflict_error
:
raise
error
transaction
.
get
().
addBeforeCommitHook
(
_raise
,
(
error
,))
obj
.
__class__
.
doSomething
=
doSomething
try
:
for
activity
in
'SQLDict'
,
'SQLQueue'
:
for
conflict_error
in
False
,
True
:
weakref_list
=
[]
obj
.
activity_count
=
obj
.
on_error_count
=
0
obj
.
activate
(
activity
=
activity
).
doSomething
(
conflict_error
,
True
)
transaction
.
commit
()
self
.
tic
()
self
.
assertEqual
(
obj
.
activity_count
,
0
)
self
.
assertEqual
(
obj
.
on_error_count
,
1
)
gc
.
collect
()
self
.
assertEqual
([
x
()
for
x
in
weakref_list
],
[
None
,
None
])
weakref_list
=
[]
obj
.
activate
(
activity
=
activity
).
doSomething
(
conflict_error
,
False
)
obj
.
on_error_count
=
0
transaction
.
commit
()
self
.
tic
()
self
.
assertEqual
(
obj
.
activity_count
,
1
)
self
.
assertEqual
(
obj
.
on_error_count
,
1
)
gc
.
collect
()
self
.
assertEqual
([
x
()
for
x
in
weakref_list
],
[
None
,
None
])
finally
:
del
obj
.
__class__
.
doSomething
def
test_suite
():
suite
=
unittest
.
TestSuite
()
suite
.
addTest
(
unittest
.
makeSuite
(
TestCMFActivity
))
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment