Handle temporary RPC failures and daemon restarts without corrupting state (#35)
* Handle temporary RPC failures and daemon restarts without corrupting state watch_status()'s per-iteration RPC read had no error handling: an unexpected failure (the daemon crashing or restarting mid-call, or any other transient RPC hiccup) propagated straight out of the loop and crashed the whole container, indistinguishable from a real fatal error. The read is now wrapped: an unexpected exception logs a warning and retries on the next tick, leaving prev_status untouched so the next successful read still reports every real change. A stop signal or another DscError still ends the loop exactly as before - the wrapper explicitly re-raises DscError first, since GracefulShutdown is one and must not be swallowed by the broader except below it. Verified against the real image: a container survives seaf-daemon being killed outright, logging the failure instead of exiting. * Log a traceback on RPC failure and strengthen the state test Copilot review: - The RPC failure warning logged only the message; add exc_info so a repeated failure in production is actually debuggable. - The 'prior state is not corrupted' test asserted the warning fired but never proved a real change after the failure was still detected and logged, which is the actual property being claimed. --------- Co-authored-by: Maxim Shulga <maxim.shulga@alludo.com>
This commit is contained in:
1 parent
28db575c92
commit
123705aa53
4 files changed
+139
-5
No files matched your search
+45
-1
@@ -119,6 +119,49 @@ else
|
||||
fi
|
||||
docker rm -f dsc-smoke >/dev/null
|
||||
|
||||
echo "== a killed daemon does not crash the watch loop"
|
||||
docker rm -f dsc-smoke >/dev/null 2>&1 || true
|
||||
docker run -d $PLATFORM_ARG --name dsc-smoke "$IMAGE" python3 -c "
|
||||
import logging
|
||||
logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO)
|
||||
from dsc.client import SeafileClient
|
||||
|
||||
c = SeafileClient('example.invalid', 'u', 'pw', '/dsc', token='tok')
|
||||
c.init_config()
|
||||
c.start_daemon()
|
||||
c.watch_status() # the real thing, not a stub: this is what is under test
|
||||
" >/dev/null
|
||||
|
||||
started=0
|
||||
for _ in $(seq 1 30); do
|
||||
logs=$(docker logs dsc-smoke 2>&1)
|
||||
if printf '%s' "$logs" | grep -q "Seafile daemon is ready"; then started=1; break; fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
if [ "$started" -eq 0 ]; then
|
||||
fail "the daemon never became ready, kill scenario not measured"
|
||||
else
|
||||
# No procps in the image, so find the PID by hand instead of pkill.
|
||||
docker exec dsc-smoke sh -c '
|
||||
for p in /proc/[0-9]*; do
|
||||
[ "$(cat "$p/comm" 2>/dev/null)" = "seaf-daemon" ] && kill -9 "${p#/proc/}"
|
||||
done' >/dev/null 2>&1
|
||||
recovered=0
|
||||
for _ in $(seq 1 30); do
|
||||
logs=$(docker logs dsc-smoke 2>&1)
|
||||
if printf '%s' "$logs" | grep -q "Could not read sync status"; then recovered=1; break; fi
|
||||
sleep 1
|
||||
done
|
||||
if [ "$recovered" -eq 1 ] && [ "$(docker inspect -f '{{.State.Running}}' dsc-smoke)" = "true" ]; then
|
||||
pass "the watch loop logged the failure and kept the container running"
|
||||
else
|
||||
fail "the container did not survive a killed daemon"
|
||||
docker logs dsc-smoke 2>&1 | sed 's/^/ /' | tail -20
|
||||
fi
|
||||
fi
|
||||
docker rm -f dsc-smoke >/dev/null
|
||||
|
||||
echo "== the HEALTHCHECK command through the real entrypoint"
|
||||
# Runs through main(), unlike the fixture above: HOME/UID/GID come from the
|
||||
# same drop_privileges() path a real deployment uses, so .ccnet ends up
|
||||
@@ -130,7 +173,8 @@ docker run -d $PLATFORM_ARG --name dsc-smoke \
|
||||
|
||||
ready=0
|
||||
for _ in $(seq 1 30); do
|
||||
if docker logs dsc-smoke 2>&1 | grep -q "Seafile daemon is ready"; then ready=1; break; fi
|
||||
logs=$(docker logs dsc-smoke 2>&1)
|
||||
if printf '%s' "$logs" | grep -q "Seafile daemon is ready"; then ready=1; break; fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
|
||||
Reference in new issue
Block a user