mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-21 21:14:17 +02:00
Bump pydocket to 0.16.2 for task cancellation support
Adds a test that verifies task cancellation actually interrupts running coroutines (they receive CancelledError) rather than just marking the task as cancelled in Redis while the coroutine continues to completion. This requires pydocket >= 0.16.2 which added best-effort cancellation via Redis pub/sub signaling to workers. Closes #2679 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
7dcccef82b
commit
7aae52d18d
3 changed files with 49 additions and 6 deletions
|
|
@ -10,7 +10,7 @@ dependencies = [
|
|||
"mcp>=1.24.0",
|
||||
"openapi-pydantic>=0.5.1",
|
||||
"platformdirs>=4.0.0",
|
||||
"pydocket>=0.16.0",
|
||||
"pydocket>=0.16.2",
|
||||
"rich>=13.9.4",
|
||||
"cyclopts>=4.0.0",
|
||||
"authlib>=1.6.5",
|
||||
|
|
|
|||
|
|
@ -172,3 +172,46 @@ async def test_task_cancellation_workflow(endpoint_server):
|
|||
# Task should be in cancelled state
|
||||
status = await task.status()
|
||||
assert status.status == "cancelled"
|
||||
|
||||
|
||||
async def test_task_cancellation_interrupts_running_coroutine(endpoint_server):
|
||||
"""Task cancellation actually interrupts the running coroutine.
|
||||
|
||||
This verifies that when a task is cancelled, the underlying asyncio
|
||||
coroutine receives CancelledError rather than continuing to completion.
|
||||
Requires pydocket >= 0.16.2.
|
||||
|
||||
See: https://github.com/jlowin/fastmcp/issues/2679
|
||||
"""
|
||||
started = asyncio.Event()
|
||||
was_interrupted = asyncio.Event()
|
||||
completed_normally = asyncio.Event()
|
||||
|
||||
@endpoint_server.tool(task=True)
|
||||
async def interruptible_tool() -> str:
|
||||
started.set()
|
||||
try:
|
||||
await asyncio.sleep(60)
|
||||
completed_normally.set()
|
||||
return "completed"
|
||||
except asyncio.CancelledError:
|
||||
was_interrupted.set()
|
||||
raise
|
||||
|
||||
async with Client(endpoint_server) as client:
|
||||
task = await client.call_tool("interruptible_tool", {}, task=True)
|
||||
|
||||
# Wait for the tool to actually start executing
|
||||
await asyncio.wait_for(started.wait(), timeout=5.0)
|
||||
|
||||
# Cancel the task
|
||||
await task.cancel()
|
||||
|
||||
# Wait for cancellation to propagate
|
||||
await asyncio.wait_for(was_interrupted.wait(), timeout=5.0)
|
||||
|
||||
# The coroutine should have been interrupted, not completed normally
|
||||
assert was_interrupted.is_set(), "Task was not interrupted by cancellation"
|
||||
assert not completed_normally.is_set(), (
|
||||
"Task completed instead of being cancelled"
|
||||
)
|
||||
|
|
|
|||
10
uv.lock
generated
10
uv.lock
generated
|
|
@ -1,5 +1,5 @@
|
|||
version = 1
|
||||
revision = 3
|
||||
revision = 2
|
||||
requires-python = ">=3.10"
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.11'",
|
||||
|
|
@ -752,7 +752,7 @@ requires-dist = [
|
|||
{ name = "platformdirs", specifier = ">=4.0.0" },
|
||||
{ name = "py-key-value-aio", extras = ["disk", "keyring", "memory"], specifier = ">=0.3.0,<0.4.0" },
|
||||
{ name = "pydantic", extras = ["email"], specifier = ">=2.11.7" },
|
||||
{ name = "pydocket", specifier = ">=0.16.0" },
|
||||
{ name = "pydocket", specifier = ">=0.16.2" },
|
||||
{ name = "pyperclip", specifier = ">=1.9.0" },
|
||||
{ name = "python-dotenv", specifier = ">=1.1.0" },
|
||||
{ name = "rich", specifier = ">=13.9.4" },
|
||||
|
|
@ -1775,7 +1775,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "pydocket"
|
||||
version = "0.16.0"
|
||||
version = "0.16.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cloudpickle" },
|
||||
|
|
@ -1792,9 +1792,9 @@ dependencies = [
|
|||
{ name = "typer" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f2/d3/ff57fb8ba8180c621b17270a272ee8821a64962887c42962c5a342ab82a9/pydocket-0.16.0.tar.gz", hash = "sha256:675e829a7ea6e978fdbbe0ace342f24c8ec7cf8ed5980694215418e59ff86005", size = 286316, upload-time = "2025-12-18T15:14:53.139Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f4/b6/b9b5af725be7ffb195750e1cbe0ad4aa413858f16af258a49ad84cff82ac/pydocket-0.16.2.tar.gz", hash = "sha256:b48e59ad247cbf23fd7064cd765b75e5b870451a7778afa3b208b3aae6474c1f", size = 295623, upload-time = "2025-12-23T19:24:46.751Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/74/dcf56132e03a24e2591c5f2391902c871e5f1248e0fc175b9f1792e4afaf/pydocket-0.16.0-py3-none-any.whl", hash = "sha256:3d7d0a805bcb68a3a03d96d3c6ba8e607584ed14b0fe3879333a8fff652dda92", size = 61064, upload-time = "2025-12-18T15:14:51.506Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/33/81132e8ca41e11000807cc1f457a391d5a364dd1eeb7de40f2ff971301bc/pydocket-0.16.2-py3-none-any.whl", hash = "sha256:044a74deec2062c305bf1d452a1348a4e3739b3a2cf0a411ca1e11368cef5baf", size = 66812, upload-time = "2025-12-23T19:24:45.643Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue