-
Notifications
You must be signed in to change notification settings - Fork 42
Add prune and re-enqueue signal functionality #322
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
Merged
Merged
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
c9d18d7
Add prune and re-enqueue signal functionality
NiveditJain e6367af
Fix typos in logging messages and comments
NiveditJain bd0a898
Update state-manager/app/models/signal_models.py
NiveditJain fc9c14c
Update state-manager/app/controller/re_queue_after_singal.py
NiveditJain 4cc63fe
Update state-manager/app/controller/re_queue_after_singal.py
NiveditJain b6127ff
Update state-manager/app/controller/prune_signal.py
NiveditJain 87870b0
Refactor re-queue after signal functionality and update state model
NiveditJain 94d740f
Merge branch 'signals' of https://github.com/NiveditJain/exospherehos…
NiveditJain c689a8e
Added import for time module in re_queue_after_signal.py to support t…
NiveditJain 55a4754
Add unit tests for prune and re-enqueue signal functionality
NiveditJain e5d61d8
Refactor test imports for prune and re-enqueue signal unit tests
NiveditJain d84b6ab
Implement prune and requeue signal functionality
NiveditJain ced39b9
Add tests for PruneSingal and ReQueueAfterSingal functionality
NiveditJain b018b03
Fix signal naming inconsistencies and enhance signal functionality
NiveditJain 5d66297
Correct signal naming in tests and enhance exception handling
NiveditJain ef84f0c
Enhance validation in ReEnqueueAfterRequestModel tests
NiveditJain 74f51a6
Add Signals documentation and update navigation in mkdocs.yml
NiveditJain c13b530
Update prune_signal status check to validate against QUEUED state
NiveditJain 49e2eb4
fixed all failing tests
NiveditJain ea760a1
namespace check would be added as a seprate unit later to take care o…
NiveditJain File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| # Signals | ||
|
|
||
| !!! beta "Beta Feature" | ||
| Signals are currently available in beta. The API and functionality may change in future releases. | ||
|
|
||
| Signals are a mechanism in Exosphere for controlling workflow execution flow and state management. They allow nodes to communicate with the state manager to perform specific actions like pruning states or requeuing them after a delay. | ||
|
|
||
| ## Overview | ||
|
|
||
| Signals are implemented as exceptions that should be raised from within node execution. When a signal is raised, the runtime automatically handles the communication with the state manager to perform the requested action. | ||
|
|
||
| ## Available Signals | ||
|
|
||
| ### PruneSignal | ||
|
|
||
| The `PruneSignal` is used to permanently remove a state from the workflow execution. This is typically used when a node determines that the current execution path should be terminated. | ||
|
|
||
| #### Usage | ||
|
|
||
| ```python | ||
| from exospherehost import PruneSignal | ||
|
|
||
| class MyNode(BaseNode): | ||
| class Inputs(BaseModel): | ||
| data: str | ||
|
|
||
| class Outputs(BaseModel): | ||
| result: str | ||
|
|
||
| async def execute(self, inputs: Inputs) -> Outputs: | ||
| if inputs.data == "invalid": | ||
| # Prune the state with optional data | ||
| raise PruneSignal({"reason": "invalid_data", "error": "Data validation failed"}) | ||
|
|
||
| return self.Outputs(result="processed") | ||
| ``` | ||
NiveditJain marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| #### Parameters | ||
|
|
||
| - `data` (dict[str, Any], optional): Additional data to include with the prune operation. Defaults to an empty dictionary. | ||
|
|
||
| ### ReQueueAfterSignal | ||
|
|
||
| The `ReQueueAfterSignal` is used to requeue a state for execution after a specified time delay. This is useful for implementing retry logic, scheduled tasks, or rate limiting. | ||
|
|
||
| #### Usage | ||
|
|
||
| ```python | ||
| from exospherehost import ReQueueAfterSignal | ||
| from datetime import timedelta | ||
|
|
||
| class RetryNode(BaseNode): | ||
| class Inputs(BaseModel): | ||
| retry_count: int | ||
| data: str | ||
|
|
||
| class Outputs(BaseModel): | ||
| result: str | ||
|
|
||
| async def execute(self, inputs: Inputs) -> Outputs: | ||
| if inputs.retry_count < 3: | ||
| # Requeue after 5 minutes | ||
| raise ReQueueAfterSignal(timedelta(minutes=5)) | ||
|
|
||
| return self.Outputs(result="completed") | ||
| ``` | ||
|
|
||
| #### Parameters | ||
|
|
||
| - `delay` (timedelta): The amount of time to wait before requeuing the state. Must be greater than 0. | ||
|
|
||
| ## Important Notes | ||
|
|
||
| 1. **Do not catch signals**: Signals are designed to bubble up to the runtime for handling. Do not catch these exceptions in your node code. | ||
|
|
||
| 2. **Automatic handling**: The runtime automatically sends signals to the state manager when they are raised. | ||
|
|
||
| 3. **State lifecycle**: Signals affect the state's lifecycle in the state manager: | ||
| - `PruneSignal`: Sets state status to `PRUNED` | ||
| - `ReQueueAfterSignal`: Sets state status to `CREATED` and schedules requeue | ||
|
|
||
| ## Error Handling | ||
|
|
||
| If signal sending fails (e.g., network issues), the runtime will log the error and continue processing other states. The failed signal will not be retried automatically. | ||
|
|
||
| ## Examples | ||
|
|
||
| ### Conditional Pruning | ||
|
|
||
| ```python | ||
| class ValidationNode(BaseNode): | ||
| class Inputs(BaseModel): | ||
| user_id: str | ||
| data: dict | ||
|
|
||
| async def execute(self, inputs: Inputs) -> Outputs: | ||
| if not self._validate_user(inputs.user_id): | ||
| raise PruneSignal({ | ||
| "reason": "invalid_user", | ||
| "user_id": inputs.user_id, | ||
| "timestamp": datetime.now().isoformat() | ||
| }) | ||
|
|
||
| return self.Outputs(validated=True) | ||
| ``` | ||
|
|
||
| ### Polling | ||
|
|
||
| ```python | ||
| class PollingNode(BaseNode): | ||
| class Inputs(BaseModel): | ||
| job_id: str | ||
|
|
||
| async def execute(self, inputs: Inputs) -> Outputs: | ||
| # Check if the job is complete | ||
| job_status = await self._check_job_status(inputs.job_id) | ||
|
|
||
| if job_status == "completed": | ||
| result = await self._get_job_result(inputs.job_id) | ||
| return self.Outputs(result=result) | ||
| elif job_status == "failed": | ||
| # Job failed, prune the state | ||
| raise PruneSignal({ | ||
| "reason": "job_failed", | ||
| "job_id": inputs.job_id, | ||
| "poll_count": inputs.poll_count | ||
| }) | ||
| else: | ||
| # Job still running, poll again in 30 seconds | ||
| raise ReQueueAfterSignal(timedelta(seconds=30)) | ||
| ``` | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| version = "0.0.2b1" | ||
| version = "0.0.2b2" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| from typing import Any | ||
| from aiohttp import ClientSession | ||
| from datetime import timedelta | ||
|
|
||
| class PruneSignal(Exception): | ||
| """ | ||
| Exception used to signal that a prune operation should be performed. | ||
|
|
||
| Args: | ||
| data (dict[str, Any], optional): Additional data to include with the signal. Defaults to {}. | ||
|
|
||
| Note: | ||
| Do not catch this Exception, let it bubble up to Runtime for handling at StateManager. | ||
| """ | ||
| def __init__(self, data: dict[str, Any] = {}): | ||
| self.data = data | ||
| super().__init__(f"Prune signal received with data: {data} \n NOTE: Do not catch this Exception, let it bubble up to Runtime for handling at StateManager") | ||
NiveditJain marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| async def send(self, endpoint: str, key: str): | ||
| """ | ||
| Sends the prune signal to the specified endpoint. | ||
|
|
||
| Args: | ||
| endpoint (str): The URL to send the signal to. | ||
| key (str): The API key to include in the request headers. | ||
|
|
||
| Raises: | ||
| Exception: If the HTTP request fails (status code != 200). | ||
| """ | ||
| async with ClientSession() as session: | ||
| async with session.post(endpoint, json=self.data, headers={"x-api-key": key}) as response: | ||
| if response.status != 200: | ||
| raise Exception(f"Failed to send prune signal to {endpoint}") | ||
NiveditJain marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
|
|
||
NiveditJain marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| class ReQueueAfterSignal(Exception): | ||
| """ | ||
| Exception used to signal that a requeue operation should be performed after a specified timedelta. | ||
|
|
||
| Args: | ||
| timedelta (timedelta): The amount of time to wait before requeuing. | ||
|
|
||
| Note: | ||
| Do not catch this Exception, let it bubble up to Runtime for handling at StateManager. | ||
| """ | ||
| def __init__(self, delay: timedelta): | ||
| self.delay = delay | ||
|
|
||
| if self.delay.total_seconds() <= 0: | ||
| raise Exception("Delay must be greater than 0") | ||
|
|
||
| super().__init__(f"ReQueueAfter signal received with timedelta: {timedelta} \n NOTE: Do not catch this Exception, let it bubble up to Runtime for handling at StateManager") | ||
|
|
||
| async def send(self, endpoint: str, key: str): | ||
| """ | ||
| Sends the requeue-after signal to the specified endpoint. | ||
|
|
||
| Args: | ||
| endpoint (str): The URL to send the signal to. | ||
| key (str): The API key to include in the request headers. | ||
|
|
||
| Raises: | ||
| Exception: If the HTTP request fails (status code != 200). | ||
| """ | ||
| body = { | ||
| "enqueue_after": int(self.delay.total_seconds() * 1000) | ||
| } | ||
| async with ClientSession() as session: | ||
| async with session.post(endpoint, json=body, headers={"x-api-key": key}) as response: | ||
| if response.status != 200: | ||
| raise Exception(f"Failed to send requeue after signal to {endpoint}") | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.