-
Notifications
You must be signed in to change notification settings - Fork 97
Interrupt heartbeating activity on pause #854
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
temporalio/client.py
Outdated
@@ -6250,6 +6258,9 @@ async def heartbeat_async_activity( | |||
) | |||
if resp_by_id.cancel_requested: | |||
raise AsyncActivityCancelledError() | |||
if resp_by_id.activity_paused: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hrmm, looks like we never added "raises" docs to AsyncActivityHandle.heartbeat
, wonder if we should now
d5107b0
to
9763505
Compare
5c8299a
to
4451e77
Compare
@@ -135,6 +138,29 @@ def _logger_details(self) -> Mapping[str, Any]: | |||
_current_context: contextvars.ContextVar[_Context] = contextvars.ContextVar("activity") | |||
|
|||
|
|||
@dataclass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be frozen
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We mutate the fields in this object to reflect changes across running activity & _context
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed to frozen class + holder
@@ -436,6 +438,7 @@ async def _run_activity( | |||
runtime_metric_meter=None | |||
if sync_non_threaded | |||
else self._metric_meter, | |||
cancellation_details=running_activity.cancellation_details, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The context says it is optional, but this is set with a never-None
value. I think you may need some kind of "holder" wrapper.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would be preferred over a non-optional field?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed to frozen class + holder
temporalio/testing/_activity.py
Outdated
if cancellation_details: | ||
self.cancellation_details.set_details(cancellation_details) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this needs to always be set, even if not provided. May want to change param to be non-option w/ a default of temporalio.activity.ActivityCancellationDetails()
. Usually using a default param of a full expression is bad in Python because it isn't called for each call, but since the details are immutable, sharing the default for everyone is safe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this needs to always be set, even if not provided.
Why?
May want to change param to be non-option w/ a default of temporalio.activity.ActivityCancellationDetails()
Sort of defeats the purpose of being able to return None
without checking all fields are False
, in which case, maybe the holder indirection isn't so useful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the logic needs to be "you can guarantee activity.cancellation_details()
is not None
when the activity is being canceled", but this breaks that guarantee.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unresolving, I think this is still an issue. We should always set an instance of this on cancel in test environment. So the parameter should default to an empty instance with no optional accepted.
tests/worker/test_workflow.py
Outdated
except (CancelledError, asyncio.CancelledError): | ||
return activity.cancellation_details() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need some tests to confirm what happens when this is not caught
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a test for pausing/unpausing activities, we don't catch cancel errors.
Added the unpause activity method to the bridge client - it was missing.
I made it such that a cancelled error on activity pause returns an activity task failure w/ application failure:
- When going through the default failure converter, a canceled error populates cancel failure info, but core expects application failure info because we are populating the
failed
field of the completion instead ofcancel
. - A bit of a weird situation, because we can't populate cancel for the reason you mentioned above (we tell server to cancel and server complains that a cancel hasn't been requested yet - like you mention above)
I can revert this change if you prefer, I don't think it causes any notable server effect anyway (state of paused activities are suspended server-side, iiuc), but probably a good thing to have failures sent properly just as a general rule
temporalio/testing/_activity.py
Outdated
cancellation_details: Optional[ | ||
temporalio.activity.ActivityCancellationDetails | ||
] = None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated #854 (comment) thread, I think this parameter should not be Optional
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
whoops didn't mean to resolve - param is non-optional. Updated callers.
c5ca36e
to
c3634fe
Compare
temporalio/testing/_activity.py
Outdated
def cancel(self) -> None: | ||
def cancel( | ||
self, | ||
cancellation_details: temporalio.activity.ActivityCancellationDetails, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To clarify, providing the parameter still needs to be optional (this is a backwards incompatible change otherwise), it just needs to not be Optional
. So:
cancellation_details: temporalio.activity.ActivityCancellationDetails, | |
cancellation_details: temporalio.activity.ActivityCancellationDetails = temporalio.activity.ActivityCancellationDetails(cancel_requested=True), |
I just picked cancel_requested
as the default (might as well set one to True
by default). Usually having an expression as a parameter default like this in Python is bad because that same instance is reused for every default so mutations are shared, but this object is immutable so it's safe.
Sorry for the confusion
c3634fe
to
58bd687
Compare
4977651
to
e055766
Compare
What was changed
Adds support to interrupt heartbeating activities from pause requests.
Why?
Functionally support users pausing their activities via the CLI
Closes Heartbeating activities should be interrupted when the activities are paused. #812
How was this tested:
Simple integration test
Any docs updates needed?
Unsure