uWSGI harakiri timeouts and client retries: preventing duplicate writes in WSGI handlers
0 reputation · 26 Jan 2021, 21:02 UTC
0 reputation · 26 Jan 2021, 21:02 UTC
I run a WSGI application behind uWSGI (2.0.x) where POST endpoints perform database inserts. uWSGI's harakiri option aborts requests that exceed a configured time limit, and the client or upstream proxy may retry the same request afterward. Since uWSGI itself does not deduplicate retried requests, the WSGI handler can be invoked twice for what the user sees as one operation.
The application must tolerate retries without creating duplicate rows, and I want to keep the fix at the right layer rather than relying on uWSGI internals. I am unsure whether uWSGI offers any supported mechanism (for example the lock plugin or worker-level request tracking) that can make a retried request safe, or whether idempotency must be implemented entirely in application code with a unique request token. I also want to confirm that harakiri kills the worker without guaranteeing the in-flight database transaction is rolled back.
harakiri triggers, is the request's database work guaranteed to be aborted, or can it commit after the worker is killed?lock plugin suitable for serializing duplicate submissions, and does it require a minimum uWSGI version?35275 reputation · 27 Jan 2021, 01:28 UTC
uWSGI offers no supported mechanism that stops a retried request from re-invoking your WSGI callable. harakiri only kills the worker after the timeout; it says nothing about what the client, load balancer, or proxy does next, and uWSGI keeps no record of "this request already ran." Idempotency has to live in your application, backed by a database constraint. The lock plugin can serialize concurrent executions but does not provide idempotency, and it is the wrong tool for this problem.
Confirmed behavior: harakiri sends a fatal signal to the worker when a request exceeds the limit, then the master respawns it. There is no hook that marks the request as completed or deduplicates a retry. Any retry — from the client, nginx (proxy_next_upstream), HAProxy, or a browser refresh — arrives as a brand-new request and runs your handler again.
Likely, but verify in your stack: when the worker is killed, the fate of the in-flight database transaction depends on your driver and connection state. In most configurations the TCP connection drops and the database eventually rolls back the uncommitted transaction, but this is not instantaneous and not guaranteed in every driver/pooler combination (for example, a statement that already reached the server and autocommitted, or a pooler holding the session). Do not design around an assumed rollback.
Idempotency-Key, or a hidden form field for browser posts).UNIQUE constraint on the token column, plus INSERT ... ON CONFLICT DO NOTHING (PostgreSQL) or equivalent upsert on your database.The unique constraint is the actual guarantee; the application check is just a fast path. This survives harakiri kills, proxy retries, double-clicks, and worker restarts, because it does not depend on any uWSGI state.
1. Deduplication in uWSGI? No documented feature prevents re-execution of the WSGI callable on retry. Request tracking exists for stats and logging, not for suppressing repeats. Application-level idempotency is the only reliable option.
2. Is the transaction aborted on harakiri? Not guaranteed. The worker is killed; the database session is abandoned. Cleanup (rollback of uncommitted work) is normally handled by the server detecting a dead connection, but timing and behavior vary by driver, pooler, and OS. Treat any write that may have reached the database as possibly committed, and let the unique constraint resolve the ambiguity on retry.
3. Is the lock plugin suitable? Not for this. It can serialize concurrent requests sharing a key via an external store (Redis, memcached, file), but it does nothing about a retry that arrives after the first attempt ended — which is exactly the harakiri case. It also adds a hard dependency on the lock backend being reachable. Plugin availability and options vary by build and release; check yours with:
uwsgi --version
uwsgi --plugin lock # confirm it loads without error
Even where it works, it cannot replace a database-enforced idempotency key.
harakiri (e.g. 2–3s), make a slow test endpoint insert with a unique token, and have a client retry automatically. Confirm exactly one row results.kill -TERM <pid>) against a test database and inspect whether the in-flight insert committed, rolled back, or lingered — repeat with your real driver and pooler, since that is the configuration that matters.One caveat: exact lock plugin options and signal-handling details differ across uWSGI 2.0.x builds and database drivers, so treat the verification steps above as the authoritative answer for your deployment rather than any general claim — including this one.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.