Skip to content

Commit 7a82fe8

Browse files
CopilotOhYee
andauthored
fix: make delete_sandbox idempotent when data plane returns sandbox not found
Agent-Logs-Url: https://github.com/Serverless-Devs/agentrun-sdk-python/sessions/82dd0492-f264-497a-9397-ffb5e79a1d90 Co-authored-by: OhYee <13498329+OhYee@users.noreply.github.com>
1 parent ac00d45 commit 7a82fe8

2 files changed

Lines changed: 64 additions & 2 deletions

File tree

agentrun/sandbox/client.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -728,11 +728,20 @@ async def delete_sandbox_async(
728728

729729
# 判断返回结果是否成功
730730
if result.get("code") != "SUCCESS":
731+
# 数据面报告 sandbox 不存在时,视为幂等删除成功
732+
# When the data plane reports sandbox not found, treat as
733+
# idempotent success (control plane may still list TERMINATED
734+
# instances after the data plane has already removed them)
735+
message = result.get("message", "")
736+
if "sandbox not found" in message.lower():
737+
return Sandbox.model_validate(
738+
{"sandboxId": sandbox_id}, by_alias=True
739+
)
731740
raise ClientError(
732741
status_code=0,
733742
message=(
734743
"Failed to stop sandbox:"
735-
f" {result.get('message', 'Unknown error')}"
744+
f" {message or 'Unknown error'}"
736745
),
737746
)
738747

@@ -768,11 +777,20 @@ def delete_sandbox(
768777

769778
# 判断返回结果是否成功
770779
if result.get("code") != "SUCCESS":
780+
# 数据面报告 sandbox 不存在时,视为幂等删除成功
781+
# When the data plane reports sandbox not found, treat as
782+
# idempotent success (control plane may still list TERMINATED
783+
# instances after the data plane has already removed them)
784+
message = result.get("message", "")
785+
if "sandbox not found" in message.lower():
786+
return Sandbox.model_validate(
787+
{"sandboxId": sandbox_id}, by_alias=True
788+
)
771789
raise ClientError(
772790
status_code=0,
773791
message=(
774792
"Failed to stop sandbox:"
775-
f" {result.get('message', 'Unknown error')}"
793+
f" {message or 'Unknown error'}"
776794
),
777795
)
778796

tests/unittests/sandbox/test_client.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -810,6 +810,50 @@ def test_delete_sandbox_not_exist(
810810
with pytest.raises(ResourceNotExistError):
811811
client.delete_sandbox("nonexistent")
812812

813+
@patch("agentrun.sandbox.client.SandboxControlAPI")
814+
@patch("agentrun.sandbox.client.SandboxDataAPI")
815+
def test_delete_sandbox_not_found_in_response_is_idempotent(
816+
self, mock_data_api_class, mock_control_api_class
817+
):
818+
"""数据面返回 not found 时,delete_sandbox 应幂等成功
819+
820+
When the data plane returns a non-SUCCESS response whose message
821+
contains "not found", the SDK should treat the delete as a success
822+
rather than raising an error. This handles the case where the
823+
control-plane list API still shows a TERMINATED sandbox, but the
824+
data plane has already removed it.
825+
"""
826+
mock_data_api = MagicMock()
827+
mock_data_api.delete_sandbox.return_value = {
828+
"code": "FAILED",
829+
"message": "sandbox not found",
830+
}
831+
mock_data_api_class.return_value = mock_data_api
832+
833+
client = SandboxClient()
834+
result = client.delete_sandbox("sandbox-123")
835+
assert result.sandbox_id == "sandbox-123"
836+
837+
@patch("agentrun.sandbox.client.SandboxControlAPI")
838+
@patch("agentrun.sandbox.client.SandboxDataAPI")
839+
@pytest.mark.asyncio
840+
async def test_delete_sandbox_async_not_found_in_response_is_idempotent(
841+
self, mock_data_api_class, mock_control_api_class
842+
):
843+
"""数据面返回 not found 时,delete_sandbox_async 应幂等成功"""
844+
mock_data_api = MagicMock()
845+
mock_data_api.delete_sandbox_async = AsyncMock(
846+
return_value={
847+
"code": "FAILED",
848+
"message": "sandbox not found",
849+
}
850+
)
851+
mock_data_api_class.return_value = mock_data_api
852+
853+
client = SandboxClient()
854+
result = await client.delete_sandbox_async("sandbox-123")
855+
assert result.sandbox_id == "sandbox-123"
856+
813857
@patch("agentrun.sandbox.client.SandboxControlAPI")
814858
@patch("agentrun.sandbox.client.SandboxDataAPI")
815859
@pytest.mark.asyncio

0 commit comments

Comments
 (0)