Skip to content

Resume sessions - #13585

Open
dawoodkhan82 wants to merge 28 commits into
mainfrom
resume-sessions
Open

Resume sessions#13585
dawoodkhan82 wants to merge 28 commits into
mainfrom
resume-sessions

Conversation

@dawoodkhan82

@dawoodkhan82 dawoodkhan82 commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Description

Quick summary: an active queued job can now survive a refresh or short connection drop without being submitted again.

  • While a job is running, the UI saves its session and event ID. On reload it reconnects to the same app/session, resumes that event, replays the pending output, then acknowledges it and clears the recovery state.
  • This only applies to an in-flight job. Once the result has been shown, a normal later refresh starts clean and does not restore the user’s last output.
  • Restored sessions are checked against the current app so state cannot leak between different apps on the same origin.
  • Closing or navigating away sends a small goodbye request. The job gets a 5-second refresh grace, then it is cleaned up so abandoned jobs do not keep a queue spot or GPU running.
  • Unexpected disconnects and mobile backgrounding keep the full resume window. The default is 10 minutes and can be changed or disabled with GRADIO_QUEUE_SESSION_RESUME_TTL.
  • Expired sessions now run unload/state/cache cleanup, stale or missing jobs return an error instead of hanging, and normal demo.load() handlers still run on a resumed page.
  • The old mobile warning about losing your queue position was removed because backgrounded mobile sessions can now reconnect.
  • Existing clients keep the old behavior unless session resume is enabled. The Gradio UI enables it automatically.
  • The JS client can opt in with resume_sessions: true and call resume_jobs([{ event_id, fn_index }]).
  • The Python client can opt in with Client(..., resume_sessions=True), save client.session_hash, job.wait_for_id(), and job.fn_index, then call resume_jobs(..., session_hash=...) from a new client. A running client also reconnects automatically after a short network failure.

Closes: #13584
Closes: #8368

Screen.Recording.2026-07-16.at.3.36.05.PM.mov

AI Disclosure

  • I used AI to help review the issue, implementation, tests, and PR text. I self-reviewed the changes.
  • I did not use AI

Testing and Formatting Your Code

  • Client browser and Node unit tests
  • Python client tests
  • Queue and routes backend tests
  • Existing functional tests in SSR and non-SSR mode
  • Offline refresh and reconnect demo flow
  • Frontend formatting, type checks, and builds

No Playwright tests were added.

@gradio-pr-bot

gradio-pr-bot commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

🪼 branch checks and previews

Name Status URL
Spaces ready! Spaces preview
Website ready! Website preview
Storybook ready! Storybook preview
🦄 Changes detected! Details

Install Gradio from this PR

pip install https://huggingface.co/buckets/gradio/pypi-previews/resolve/9c2c8b2c6069d9c10fc96a6914b24195815da92a/gradio-6.20.0-py3-none-any.whl

Install Gradio Python Client from this PR

pip install "gradio-client @ git+https://github.com/gradio-app/gradio@9c2c8b2c6069d9c10fc96a6914b24195815da92a#subdirectory=client/python"

Import Gradio JS Client from this PR via CDN

import { Client } from "https://huggingface.co/buckets/gradio/npm-previews/resolve/9c2c8b2c6069d9c10fc96a6914b24195815da92a/browser.js";

@gradio-pr-bot

gradio-pr-bot commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

🦄 change detected

This Pull Request includes changes to the following packages.

Package Version
@gradio/client minor
gradio minor
gradio_client minor

  • Resume queued jobs and restore undelivered output after temporary disconnects or page refreshes

Something isn't right?

  • Maintainers can change the version label to modify the version bump.
  • If the bot has failed to detect any changes, or if this pull request needs to update multiple packages to different versions or requires a more comprehensive changelog entry, maintainers can update the changelog file directly.

@dawoodkhan82
dawoodkhan82 marked this pull request as ready for review July 17, 2026 02:17
@dawoodkhan82
dawoodkhan82 requested a review from Copilot July 17, 2026 16:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds opt-in “resume sessions” support so queued jobs can survive brief disconnects/page refreshes by reconnecting to the same session_hash + event_id, replaying buffered queue output, and then acknowledging completion to clear server/client recovery state.

Changes:

  • Backend queue: track per-session message history, mark sessions detached/attached, expire detached sessions via TTL, and add a new /queue/ack endpoint for explicit completion acknowledgement.
  • Frontend client: persist active queued event_ids in browser storage, reconnect SSE streams with resume_event_id, and acknowledge completed events to clear recovery state.
  • Tests: add unit coverage for client stream reconnect/session storage and a backend test validating detached session resumption + acknowledgement cleanup.

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
test/test_queueing.py Adds backend test covering detach → resume replay → ack cleanup flow.
js/spa/test/cancel_events.spec.ts Updates expectation: iterative job continues after page close.
js/spa/src/Index.svelte Enables resume_sessions for the SPA client connections.
js/core/src/dependency.ts Acknowledges submissions and adds dependency-level resume flow.
js/core/src/Blocks.svelte Resumes events on ready when resumable events exist, otherwise dispatches load events.
js/app/src/routes/[...catchall]/+page.ts Enables resume_sessions in SvelteKit load client connect.
js/app/src/routes/[...catchall]/+page.svelte Enables resume_sessions on reconnect in the app page.
gradio/routes.py Adds resume params to /queue/data, session attach/detach handling, and /queue/ack.
gradio/queueing.py Adds message history, detach TTL tracking, resume/ack helpers, and expiry cleanup.
gradio/data_classes.py Introduces QueueAckBody request model.
client/js/src/utils/submit.ts Adds resumable-event tracking, resume submission path, and acknowledgement hook.
client/js/src/utils/stream.ts Adds SSE reconnect/backoff, resume query params, and “normal close” reopening.
client/js/src/utils/session.ts New session storage + cookie utilities for resumable queued events.
client/js/src/types.ts Adds resume_sessions option and SubmitIterable.acknowledge().
client/js/src/test/stream.test.ts Adds coverage for normal close reopen + reconnect/resume behavior.
client/js/src/test/session.test.ts Adds coverage for session storage semantics and SSR cookie read.
client/js/src/test/init.test.ts Adds test ensuring sessions are not restored across different apps.
client/js/src/constants.ts Adds ACK_URL constant.
client/js/src/client.ts Adds restored session hash support, resume helper, and reconnect timer cleanup.
.changeset/quiet-sessions-return.md Announces minor feature release for both gradio and @gradio/client.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread gradio/routes.py Outdated
Comment thread gradio/routes.py
Comment thread client/js/src/utils/submit.ts
Comment thread client/js/src/utils/submit.ts
Comment thread gradio/queueing.py
Comment thread gradio/queueing.py
Comment thread client/js/src/utils/stream.ts
Resolve conflicts by keeping soft-reload from main and acknowledging
resumable submissions before clear_submission.

Co-authored-by: Cursor <cursoragent@cursor.com>
@abidlabs

Copy link
Copy Markdown
Member

I haven't had a chance to test this yet, but a few things that come to mind:

  • We can remove the warning that we show on mobile that the connection will be lost on mobile?
  • Will this work with gr.Server apps? I assume so since the js client supports this but would be good to confirm with an example demo and add it to the docs
  • Have we tested this with streaming audio/video where the backend returns chunks that are concatenated in the frontend?
  • How is Long-running tasks require open connection, looking for polling solution #8368 solved? I understand that the mechanism to resume sessions is in place, but how can someone using the python client actually retrieve a job? Some more docs around all of this would be nice
  • I'm slightly concerned about the fact that we now have different behaviors if a submission is still in the queue
    (refreshing keeps the output) vs. if a submission has finished and output has been returned (refreshing erases the output). perhaps this is something to reconcile in Gradio 7

# Conflicts:
#	js/app/src/routes/[...catchall]/+page.svelte
#	js/core/src/dependency.ts
#	js/spa/src/Index.svelte
@hysts

hysts commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

I tried this out on a ZeroGPU Space and the resume flow works nicely.

One thing I noticed while testing: closing the tab no longer cancels jobs. On 6.20.0 a running generator stops as soon as the tab closes (the GeneratorExit even reaches the ZeroGPU worker, so the GPU is freed), but with this PR it keeps running with nobody watching, until it completes or the TTL expires. This applies to every client, not just ones that opted into resume, so for ZeroGPU streaming demos, leaving mid-generation keeps burning GPU time and the visitor's quota. Queued events in regular (non-ZeroGPU) apps have the same issue: closing the tab while waiting used to free the queue spot, but now the event keeps its position and still runs when its turn comes, even though the closed tab can't receive the result anymore. The cancel_events.spec.ts rewrite codifies the new behavior, but the PR description doesn't mention it, so I wanted to raise it explicitly.

The two cases do seem distinguishable from the client side, though: closing the tab or navigating away fires pagehide, while network drops and mobile tab switching don't. So a small sendBeacon goodbye on pagehide could shorten the grace period for deliberate departures, while silent disconnects keep the full TTL (a mid-job reload would still reattach within the grace). That would also free the queue spot of a deliberately closed tab while keeping it for someone waiting in a background mobile tab.

Either way, it would be good to document the change and give app authors a way to tune or opt out of it. Right now the only knob is the undocumented GRADIO_QUEUE_SESSION_RESUME_TTL env var, and authors of expensive GPU apps may prefer the old behavior.

A few smaller things I noticed:

  • When a session disconnects while it has pending events, the heartbeat route now returns early, and the TTL cleanup doesn't cover what that path used to do: demo.unload() never fires for that session and its state is never marked for deletion.
  • Resuming an event whose session was already cleaned up hangs forever instead of erroring. delete_session -> clean_events kills a running job but leaves its id in pending_event_ids_session (probably the same cleanup gap as above), so a later resume gets neither a replay (history is gone) nor the session_not_found error (the stale id suppresses it), and the client just shows a pending status counting up. Easy to reproduce: set GRADIO_QUEUE_SESSION_RESUME_TTL=10, start a job, close the tab, wait past the TTL, then restore the tab (Ctrl+Shift+T). Also reachable with a reconnect after an outage longer than the TTL, or resume_jobs() with an expired event.
  • On a resumed page, dep_manager.resume(...) runs instead of dispatch_load_events(), so demo.load(...) handlers don't run and apps that build their initial UI in them come back half-empty.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Resume long running sessions Long-running tasks require open connection, looking for polling solution

5 participants