-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Make flow/task engine release slots after a timeout
- Loading branch information
1 parent
85bea69
commit 70e76c4
Showing
9 changed files
with
223 additions
and
7 deletions.
There are no files selected for viewing
This file contains 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 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 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,24 @@ | ||
from contextvars import ContextVar | ||
from typing import List, Tuple | ||
|
||
from prefect.client.orchestration import get_client | ||
from prefect.context import ContextModel, Field | ||
|
||
|
||
class ConcurrencyContext(ContextModel): | ||
__var__: ContextVar = ContextVar("concurrency") | ||
|
||
# Track the slots that have been acquired but were not able to be released | ||
# due to cancellation or some other error. These slots are released when | ||
# the context manager exits. | ||
cleanup_slots: List[Tuple[List[str], int, float]] = Field(default_factory=list) | ||
|
||
def __exit__(self, *exc_info): | ||
if self.cleanup_slots: | ||
with get_client(sync_client=True) as client: | ||
for names, occupy, occupancy_seconds in self.cleanup_slots: | ||
client.release_concurrency_slots( | ||
names=names, slots=occupy, occupancy_seconds=occupancy_seconds | ||
) | ||
|
||
return super().__exit__(*exc_info) |
This file contains 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 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 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 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,62 @@ | ||
import asyncio | ||
import time | ||
|
||
import pytest | ||
|
||
from prefect.client.orchestration import PrefectClient | ||
from prefect.concurrency.asyncio import concurrency as aconcurrency | ||
from prefect.concurrency.context import ConcurrencyContext | ||
from prefect.concurrency.sync import concurrency | ||
from prefect.server.schemas.core import ConcurrencyLimitV2 | ||
from prefect.utilities.asyncutils import run_coro_as_sync | ||
from prefect.utilities.timeout import timeout, timeout_async | ||
|
||
|
||
async def test_concurrency_context_releases_slots_async( | ||
concurrency_limit: ConcurrencyLimitV2, prefect_client: PrefectClient | ||
): | ||
async def expensive_task(): | ||
async with aconcurrency(concurrency_limit.name): | ||
response = await prefect_client.read_global_concurrency_limit_by_name( | ||
concurrency_limit.name | ||
) | ||
assert response.active_slots == 1 | ||
|
||
# Occupy the slot for longer than the timeout | ||
await asyncio.sleep(1) | ||
|
||
with pytest.raises(TimeoutError): | ||
with timeout_async(seconds=0.5): | ||
with ConcurrencyContext(): | ||
await expensive_task() | ||
|
||
response = await prefect_client.read_global_concurrency_limit_by_name( | ||
concurrency_limit.name | ||
) | ||
assert response.active_slots == 0 | ||
|
||
|
||
async def test_concurrency_context_releases_slots_sync( | ||
concurrency_limit: ConcurrencyLimitV2, prefect_client: PrefectClient | ||
): | ||
def expensive_task(): | ||
with concurrency(concurrency_limit.name): | ||
response = run_coro_as_sync( | ||
prefect_client.read_global_concurrency_limit_by_name( | ||
concurrency_limit.name | ||
) | ||
) | ||
assert response and response.active_slots == 1 | ||
|
||
# Occupy the slot for longer than the timeout | ||
time.sleep(1) | ||
|
||
with pytest.raises(TimeoutError): | ||
with timeout(seconds=0.5): | ||
with ConcurrencyContext(): | ||
expensive_task() | ||
|
||
response = await prefect_client.read_global_concurrency_limit_by_name( | ||
concurrency_limit.name | ||
) | ||
assert response.active_slots == 0 |
This file contains 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 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