Prevent duplicate chapter-generation workers per session - #73
Conversation
- Backend: guard in /generate_chapters returns 409 (error_code: generation_in_progress) when a running worker already exists for the session; returns the existing token so the client can attach to it - Frontend: btn-approve-outline stays disabled for the full duration of generation; re-enabled only on approve error, generation error, or generation done - Frontend: 409 response is handled gracefully — client attaches to the existing in-progress token and resumes polling instead of erroring out - Tests: replaced test_two_generation_requests_get_different_tokens with four focused tests covering: first request succeeds, duplicate blocked with 409, new request allowed after completion, rapid-fire calls all blocked after first Agent-Logs-Url: https://github.com/CyberSecDef/NovelForge/sessions/419ef6c7-f0ca-4371-a42a-163df2591924 Co-authored-by: CyberSecDef <[email protected]>
…-seed call Agent-Logs-Url: https://github.com/CyberSecDef/NovelForge/sessions/419ef6c7-f0ca-4371-a42a-163df2591924 Co-authored-by: CyberSecDef <[email protected]>
There was a problem hiding this comment.
Pull request overview
Prevents multiple /generate_chapters background workers from being launched for the same user session (e.g., repeated clicks / multiple tabs), and updates the UI + tests to support the new behavior.
Changes:
- Added a session-scoped guard in
/generate_chaptersto return HTTP 409 with the existing progress token when generation is already running. - Updated frontend button enable/disable lifecycle to avoid re-enabling “Approve & Write Chapters” while generation is in progress, and to attach to an existing token on 409.
- Reworked concurrency tests to validate first-call success, duplicate-call 409 behavior, and “new run allowed after done”.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
novelforge/routes/generation.py |
Adds a guard intended to prevent starting duplicate generation workers per session. |
static/js/script.js |
Keeps the approve button disabled during generation; handles 409 by attaching to the existing token and polling. |
tests/test_concurrency.py |
Replaces prior token-expectations test with targeted tests for 200/409 behavior and post-completion regeneration. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # Guard against duplicate workers for the same session | ||
| existing_token = session.get("progress_token") | ||
| if existing_token: | ||
| with _progress_lock: | ||
| existing = _progress_store.get(existing_token) | ||
| if existing and existing.get("status") == "running": | ||
| return jsonify({ | ||
| "error": "A chapter generation is already in progress for this session. Please wait for it to complete.", | ||
| "error_code": "generation_in_progress", | ||
| "token": existing_token, | ||
| }), 409 |
There was a problem hiding this comment.
The duplicate-worker guard relies on reading session["progress_token"], which is only updated at the end of the request. Two truly concurrent /generate_chapters requests from the same session can both observe no progress_token (or a stale one) and each start a new worker, so this doesn’t fully prevent duplicates under real concurrency.
Consider making the check-and-reserve atomic using a server-side key (e.g., session_id = get_session_id()), protected by _progress_lock: maintain a session_id -> active_token mapping (or store session_id in each progress record and look it up) and set it before spawning the thread. Also keep the status check within the same lock to avoid TOCTOU edge cases where status flips between read and decision.
/generate_chaptershad no guard against launching multiple background workers for the same session — repeated clicks or multiple tabs could spawn competing threads writing to the same session state.Backend — session-scoped guard (
novelforge/routes/generation.py)Before spawning a worker, the route now checks whether
session["progress_token"]already points to a"running"entry in_progress_store. If so, it returns HTTP 409 with the existing token:{ "error": "A chapter generation is already in progress for this session.", "error_code": "generation_in_progress", "token": "<existing-token>" }Frontend — button lifecycle (
static/js/script.js)btn-approve-outlineis disabled on first click and stays disabled for the full duration of generation. Removed the blanket re-enable from theapprove_outlinecomplete handler.approve_outlinefailure,generate_chaptersnon-409 failure, generation done (showDoneStep), or generation error (poll error branch).Tests (
tests/test_concurrency.py)Replaced
test_two_generation_requests_get_different_tokens(which incorrectly expected both requests to succeed) with four targeted tests:test_first_generation_request_succeedsrunningtest_duplicate_generation_request_blocked_with_409test_new_generation_allowed_after_previous_completesdonetest_rapid_repeat_calls_all_blocked_after_first[200, 409, 409, 409, 409]Warning
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
api.openai.com/usr/bin/python python -m pytest tests/ -v(dns block)If you need me to access, download, or install something from one of these locations, you can either: