fix: defer background cleanup until pending display is confirmed
This commit is contained in:
+2
-2
@@ -1,7 +1,7 @@
|
||||
# pigway-frame-flow v0.1.23
|
||||
|
||||
- 自动调度和自动重试在同一设备已有图片等待同步时排队,避免 /upload 自动激活新图片而替换尚未确认的目标。等待沿用现有同步超时;保留用户手动切换与优先级行为。
|
||||
- 自动调度、自动重试和后台清理在同一设备已有图片等待同步时排队,避免 /upload 激活新图或删除操作更新云端计数,干扰尚未确认的目标。等待沿用现有同步超时;保留用户手动切换与优先级行为。
|
||||
- 在操作回执中保留最多 12 条设备读回状态及受限错误字段,记录实际图片、同步、在线和心跳,供定位未确认原因;不保存原始异常、URL 或凭据。采样不延长同步超时,原始上传证据保留。
|
||||
- 沿用安全基础镜像、完整构建和固定镜像 ID 的预构建验收流程;不重发不确定的设备写入。
|
||||
|
||||
验证:600 项测试(全量 599 项及新增超时边界用例)、Ruff、格式和 mypy 通过。历史设备物理刷新状态无法从缺失的原始采样追溯,不能将此版本描述为已修复所有网络或设备故障。
|
||||
验证:601 项测试、Ruff、格式和 mypy 通过。历史设备物理刷新状态无法从缺失的原始采样追溯,不能将此版本描述为已修复所有网络或设备故障。
|
||||
|
||||
+4
-4
@@ -10,7 +10,7 @@ c6fc984a1ca46cbd3e06de4591e41d21e4eb592416385d4000ade8fd570aad0c Dockerfile
|
||||
250a2bcec248b074786d90e0329874628d70d5a0fe3b4fba47c8c57d4f84dcce NOTICE.md
|
||||
26ad0d9063c0ee843f0f532604b8fd66e8b39fd72d129595ed078677c0d6d13c README.md
|
||||
ee00cb267b38485f1e574d610f0a57b51d4ae575731a18e57d06a7c43dc0a8b0 README.zh-CN.md
|
||||
668d9245385b20a1d7052b96c982a4d4654e40b0e2e978522d4bef513b85f668 RELEASE_NOTES.md
|
||||
5ec5770a6e0202d981c5719de8cb09fefbd5d0a79b537d949b91ec67c8bce72b RELEASE_NOTES.md
|
||||
6a0c85c8d52a2d4a3b779dce306a93c4efc4dfe3ab3b23e4a6a3bb7c55079910 SECURITY.md
|
||||
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 build-cache/wheels/.gitkeep
|
||||
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 config/.gitkeep
|
||||
@@ -49,7 +49,7 @@ e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 pigway_frame_f
|
||||
81effa34d6c452579cfa65781909f7c5a9e8ddad136e4c47ee0704939851879a pigway_frame_flow/display/cloud_library.py
|
||||
5e7e272e671a6d01d9b677416c77967ca488b4e2aa559f4211cf0de4ba1e88bc pigway_frame_flow/display/recovery.py
|
||||
e1e4ae3ba321ae7282c5a4e061c52f24d2a6a6e3d4637e69e4b1fbd05004f5f6 pigway_frame_flow/display/retention.py
|
||||
faff3c264ef1ffe496d2989da0b7ee0add60bda1b7ff92cf47e4fc7690b95242 pigway_frame_flow/display/service.py
|
||||
6b09243081d53fb6c8a780d77e064f0685267c61e05f514262c8514ee3a36fda pigway_frame_flow/display/service.py
|
||||
0975b24488be19f24e768c4c76d9f3c0348417c1351c4e410897da00f7be6656 pigway_frame_flow/display/upload_reconciliation.py
|
||||
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 pigway_frame_flow/drivers/__init__.py
|
||||
9a8386ef368a6df7fd8cb23bcc442c28e784729a5ce4a78911ad86245d5c5794 pigway_frame_flow/drivers/base.py
|
||||
@@ -207,8 +207,8 @@ f14bad45534041ff0b7dead21d2952ec9d6c60e9f718ab5393bdf5a47b2a6d3e tests/test_pho
|
||||
ab773ca6868ec007786e7142d5425663a5c8da35b9aa90837ac8e46b51e51507 tests/test_production_bootstrap.py
|
||||
fee152c5f6eaeda712d38cf88a79213f5e885ba323eb15dfa5a12cf4c88028c3 tests/test_production_deploy.py
|
||||
f0601f5ed89e8a14a4584a9683611aa7510a964f50a642348b8ab6c62df72278 tests/test_publication_security.py
|
||||
1f6ca49673eae895dacfb5dd74cecf72b2136c218078ab99e24ae766a1340ed8 tests/test_recovery.py
|
||||
072e8ca2fa68ca73fb078b107ced96ea683652410b286017db883c97608d78f6 tests/test_retention.py
|
||||
915992a2ef9aeb8ae5c688d5dd50c6acd37ba726eb5aff1a0df991ffe374d94e tests/test_recovery.py
|
||||
e0a426eed6f1b6f59207b4de7d8286b6852550bfa28ad30e20f7e911ad9865ee tests/test_retention.py
|
||||
ab2cd30163453fe571b55c30cb26dc047a104a536ec01d2c64aa2bc91996ba11 tests/test_scheduler.py
|
||||
0cccc08adf35a06dbf704eb5c35966bc1c3de0ce39d3014efd7ca751253ce79f tests/test_server_release.py
|
||||
a7ae1b4a64bfcbcf2b7ff795b30ba91db24ee2ad7544073dca2bf43d021cd266 tests/test_smoke_dependencies.py
|
||||
|
||||
@@ -518,11 +518,11 @@ class DisplayService:
|
||||
) and self.blocked(op["device_id"]):
|
||||
continue
|
||||
payload = json.loads(op["payload"])
|
||||
if op["action"] == "display":
|
||||
if op["client_id"] in (
|
||||
"scheduler",
|
||||
"scheduler-retry",
|
||||
) and self.db.one(
|
||||
if (
|
||||
op["action"] in ("display", "delete", "restore")
|
||||
and op["client_id"]
|
||||
in ("scheduler", "scheduler-retry", "cloud-library", "retention")
|
||||
and self.db.one(
|
||||
"SELECT id FROM operations WHERE device_id=? AND action='display' "
|
||||
"AND status='SYNCING' AND write_started=1 AND updated_at>? LIMIT 1",
|
||||
(
|
||||
@@ -532,10 +532,12 @@ class DisplayService:
|
||||
- timedelta(seconds=self.settings.sync_timeout)
|
||||
).isoformat(),
|
||||
),
|
||||
)
|
||||
):
|
||||
# Upload activates a new image upstream. Give the prior
|
||||
# image its existing confirmation window before replacing it.
|
||||
# Both upload and deletion update the cloud frame counter.
|
||||
# Automatic writes must not invalidate a pending display ack.
|
||||
continue
|
||||
if op["action"] == "display":
|
||||
priority = payload["options"]["priority"]
|
||||
top = self.top(op["device_id"])
|
||||
if top and priority < top["priority"]:
|
||||
|
||||
@@ -270,3 +270,24 @@ def test_automatic_wait_does_not_extend_past_sync_timeout(service):
|
||||
)
|
||||
service.process_one()
|
||||
assert service.operation(second)["status"] == "SYNCING"
|
||||
|
||||
|
||||
def test_automatic_cleanup_waits_for_display_confirmation(service, monkeypatch):
|
||||
first = submit(service)
|
||||
service.process_one()
|
||||
monkeypatch.setattr(service.cloud_library, "eligible", lambda *args: True)
|
||||
cleanup = service.submit(
|
||||
"delete",
|
||||
{"image_id": 1, "cloud_cleanup": True},
|
||||
"living-room",
|
||||
"cloud-library",
|
||||
"fixture-cleanup",
|
||||
)
|
||||
service.process_one()
|
||||
assert service.operation(cleanup)["status"] == "QUEUED"
|
||||
assert 1 in [x.image_id for x in service.registry.get("living-room").images()]
|
||||
service.sync()
|
||||
service.process_one()
|
||||
assert service.operation(first)["status"] == "COMPLETED"
|
||||
assert service.operation(cleanup)["status"] == "COMPLETED"
|
||||
assert 1 not in [x.image_id for x in service.registry.get("living-room").images()]
|
||||
|
||||
@@ -37,6 +37,8 @@ def due(service, image_id):
|
||||
"UPDATE image_retention SET expires_at=? WHERE image_id=?",
|
||||
((datetime.now(UTC) - timedelta(days=1)).isoformat(), image_id),
|
||||
)
|
||||
# Complete the upload before simulating expiry days later and switching away.
|
||||
service.sync()
|
||||
# The test driver switches to a different image, not a real device.
|
||||
driver = service.registry.get("living-room")
|
||||
driver.display(1)
|
||||
|
||||
Reference in New Issue
Block a user