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:
Chris Guidry 2025-12-23 14:41:36 -05:00
commit 7aae52d18d
3 changed files with 49 additions and 6 deletions

View file

@ -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",

View file

@ -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
View file

@ -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]]