Skip to content

Conversation

@filintod
Copy link

@filintod filintod commented Nov 8, 2025

This is a split from asyncio PR #13 . Removing changes not related to asyncio changes

@filintod filintod requested a review from a team as a code owner November 8, 2025 17:41
self._stub = stubs.TaskHubSidecarServiceStub(channel)
self._logger = shared.get_logger("client", log_handler, log_formatter)

def __enter__(self):
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add context manager option for clean closing

# gRPC timeout mapping (pytest unit tests may pass None explicitly)
grpc_timeout = None if (timeout is None or timeout == 0) else timeout

# If timeout is None or 0, skip pre-checks/polling and call server-side wait directly
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

improves resource consumption on server side that might also lag behind client side

pass


class NonRetryableError(Exception):
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a new helper, that is present in Temporal but not us, where we can defined errors that are non-retryable so activities don't attempt to retry when raised

next_delay_f, self._retry_policy.max_retry_interval.total_seconds()
)
return timedelta(seconds=next_delay_f)
return timedelta(seconds=next_delay_f)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this fixes a bug with retry, as the login in line 400 above f datetime.utcnow() < retry_expiration: means that we should retry, but as this was badly indented if for some reason max_retry_interval is not none this was not working.

Copy link
Author

@filintod filintod Nov 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is also kind of mentioned in one of the gotchas in dapr/python-sdk#836, I found this bug beforehand, the other gotchas are gotchas or not-explained behavior

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added some info in README to cover the gotchas, but we might need to add to python-sdk

@@ -0,0 +1,16 @@
apiVersion: dapr.io/v1alpha1
Copy link
Author

@filintod filintod Nov 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needed for e2e tests with dapr that should substitute durabletask-go tests with dapr setup

@filintod
Copy link
Author

@acroca ptal

Signed-off-by: Filinto Duran <1373693+filintod@users.noreply.github.com>
@filintod filintod changed the title add new deterministic functions, non-retryable errors, and shutdown h… add non-retryable errors, and shutdown helpers Nov 10, 2025
Signed-off-by: Filinto Duran <1373693+filintod@users.noreply.github.com>
if log_handler is None:
log_handler = logging.StreamHandler()
log_handler.setLevel(logging.INFO)
log_handler.setLevel(logging.DEBUG)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why? Won't it get very noisy?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, a debugging leftover

commands =
!e2e: pytest -m "not e2e" --verbose
e2e: pytest -m e2e --verbose
e2e: pytest -m e2e --verbose
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
e2e: pytest -m e2e --verbose
e2e: pytest -m e2e --verbose

Comment on lines +218 to +220
res: pb.GetInstanceResponse = self._stub.WaitForInstanceCompletion(
req, timeout=grpc_timeout
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this. grpc_timeout is set to None in both 0 and None cases but if I understand correctly, when timeout is None we wait forever, but timeout 0 won't wait at all, right?

return current_state

# Poll for completion with exponential backoff to handle eventual consistency
import time
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move the import to the top of the file 🙏

Comment on lines +230 to +234
if current_state and current_state.runtime_status in [
OrchestrationStatus.COMPLETED,
OrchestrationStatus.FAILED,
OrchestrationStatus.TERMINATED,
]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From https://github.com/dapr/durabletask-go/blob/7f28b2408db77ed48b1b03ecc71624fc456ccca3/api/orchestration.go#L196-L201, CANCELLED is also a condition for a workflow to be considered in a terminal state.
But what's the reason for this check? Why not just call the WaitForInstanceCompletion? You are still sending a call to the runtime to get the current state.

self._retry_timeout = retry_timeout
# Normalize non-retryable error type names to a set of strings
names: Optional[set[str]] = None
if non_retryable_error_types:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if non_retryable_error_types:
if non_retryable_error_types is not None:

Comment on lines +547 to +548
if isinstance(t, str):
if t:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't we check it all at once?

Suggested change
if isinstance(t, str):
if t:
if isinstance(t, str) and len(t)>0:

self._channel_options = channel_options
self._stop_timeout = stop_timeout
# Track in-flight activity executions for graceful draining
import threading as _threading
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this import to the top of the file 🙏

current_reader_thread.start()
loop = asyncio.get_running_loop()
while not self._shutdown.is_set():
try:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see why this try was removed. If I understand correctly, the exceptions that were captured here will now be captured outside of the while, right? Why is this preferred now?

"""
end: Optional[float] = None
if timeout is not None:
import time as _t
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move all the imports to the top please

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants