Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cancel events from other events #2433

Merged
merged 19 commits into from
Oct 14, 2022
Merged

Cancel events from other events #2433

merged 19 commits into from
Oct 14, 2022

Conversation

freddyaboulton
Copy link
Collaborator

@freddyaboulton freddyaboulton commented Oct 11, 2022

Description

Fixes #2220

This PR allows you to cancel other running events when another event is triggered. The functionality for interface has not been implemented yet. Opening this up as a draft so others can test this out before proceeding.

Changes to user-facing API

Adds a cancels parameter to all event handlers. Users can specify a list of events to cancel.

buton.click(prediction, inputs=[...], outputs=[...], cancels=[other_prediction])

Limitations

  1. The queue must be enabled for this to work.

Example you can use to test this

Build the front-end first.

import time
import gradio as gr

def fake_diffusion(steps):
    for i in range(steps):
        print(f"Current step: {i}")
        time.sleep(1)
        yield str(i)

def long_prediction(*args, **kwargs):
    time.sleep(10)
    return 42


with gr.Blocks() as demo:
    with gr.Row():
        with gr.Column():
            n = gr.Slider(1, 10, value=9, step=1, label="Number Steps")
            run = gr.Button()
            output = gr.Textbox(label="Iterative Output")
            stop = gr.Button(value="Stop Iterating")
        with gr.Column():
            textbox = gr.Textbox(label="Prompt")
            prediction = gr.Number(label="Expensive Calculation")
            run_pred = gr.Button(value="Run Expensive Calculation")
        with gr.Column():
            cancel_on_change = gr.Textbox(label="Cancel Iteration and Expensive Calculation on Change")
            cancel_on_submit = gr.Textbox(label="Cancel Iteration and Expensive Calculation on Submit")
            echo = gr.Textbox(label="Echo")
    with gr.Row():
        with gr.Column():
            image = gr.Image(source="webcam", tool="editor", label="Cancel on edit", interactive=True)
        with gr.Column():
            video = gr.Video(source="webcam", label="Cancel on play", interactive=True)

    click_event = run.click(fake_diffusion, n, output)
    stop.click(fn=None, inputs=None, outputs=None, cancels=[click_event])
    pred_event = run_pred.click(fn=long_prediction, inputs=[textbox], outputs=prediction)

    cancel_on_change.change(None, None, None, cancels=[click_event, pred_event])
    cancel_on_submit.submit(lambda s: s, cancel_on_submit, echo, cancels=[click_event, pred_event])
    image.edit(None, None, None, cancels=[click_event, pred_event])
    video.play(None, None, None, cancels=[click_event, pred_event])


demo.queue(concurrency_count=20, max_size=20).launch()

Cancel on click

cancel_on_button

Cancel on change

cancel_on_change

Cancel on submit

cancel_on_submit

Cancel on edit

cancel_on_edit

Cancel on play

cancel_on_play

Checklist:

  • I have performed a self-review of my own code
  • I have added a short summary of my change to the CHANGELOG.md
  • My code follows the style guidelines of this project
  • I have commented my code in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

A note about the CHANGELOG

Hello 👋 and thank you for contributing to Gradio!

All pull requests must update the change log located in CHANGELOG.md, unless the pull request is labeled with the "no-changelog-update" label.

Please add a brief summary of the change to the Upcoming Release > Full Changelog section of the CHANGELOG.md file and include
a link to the PR (formatted in markdown) and a link to your github profile (if you like). For example, "* Added a cool new feature by [@myusername](link-to-your-github-profile) in [PR 11111](https://github.com/gradio-app/gradio/pull/11111)".

If you would like to elaborate on your change further, feel free to include a longer explanation in the other sections.
If you would like an image/gif/video showcasing your feature, it may be best to edit the CHANGELOG file using the
GitHub web UI since that lets you upload files directly via drag-and-drop.

@github-actions
Copy link
Contributor

All the demos for this PR have been deployed at https://huggingface.co/spaces/gradio-pr-deploys/pr-2433-all-demos

@freddyaboulton freddyaboulton changed the title 2220 stop button Cancel events from other events Oct 11, 2022
@dawoodkhan82
Copy link
Collaborator

@freddyaboulton maybe we can consolidate the api with #2436

@freddyaboulton
Copy link
Collaborator Author

@freddyaboulton maybe we can consolidate the api with #2436

@dawoodkhan82 Do you have something in mind?

@@ -119,7 +119,8 @@ def set_event_trigger(
js: Optional[str] = None,
no_target: bool = False,
queue: Optional[bool] = None,
) -> None:
cancels: List[int] | None = None,
Copy link
Member

Choose a reason for hiding this comment

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

Add cancels to docstring below

@@ -22,6 +76,7 @@ def change(
queue: Optional[bool] = None,
preprocess: bool = True,
postprocess: bool = True,
cancels: List[Dict[str, Any]] | None = None,
Copy link
Member

Choose a reason for hiding this comment

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

Add cancels to docstring for all events

gradio/routes.py Outdated
async def reset_iterator(body: ResetBody):
if body.session_hash not in app.iterators:
return {"success": False}
async with asyncio.Lock():
Copy link
Member

Choose a reason for hiding this comment

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

I don't think async with asyncio.Lock(): will have any effect here since each request is instantiating a new asyncio.Lock() object. A single asyncio.Lock() object should be instantiated outside of the this function and that object should be acquired here. https://docs.python.org/3/library/asyncio-sync.html

gradio/events.py Outdated
event_name,
cancel_fn,
inputs=None,
outputs=output,
Copy link
Member

Choose a reason for hiding this comment

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

Out of curiosity, do we actually need to supply the outputs?

@abidlabs
Copy link
Member

Hey @freddyaboulton this is a really really cool implementation!

I was testing with the following script and everything seems to work well:

import gradio as gr
import time

def run(x):
    time.sleep(3)
    return x

def run2(x):
    for i in range(3):
        time.sleep(1)
        yield i

with gr.Blocks() as demo:
    x = gr.Textbox()
    y = gr.Textbox()
    b = gr.Button()
    b2 = gr.Button()
    c = gr.Button("Cancel")
    e = b.click(run, x, y)
    e2 = b2.click(run2, x, y)
    c.click(None, None, None, cancels=[e, e2])
    
    demo.queue()
    demo.launch()

However, one piece of feedback is that currently, when you "cancel" an event, the output component that was being populated clears (i.e. its value gets reset). I don't think this should happen -- rather, the final value should remain there. For example, if someone is generating a sequence of images, they may want to "stop" on a particular image. WDYT?

@abidlabs
Copy link
Member

One other small thing -- if a gradio app includes cancel events but does not have queue enabled, we should raise an Exception.

@abidlabs
Copy link
Member

This might close #1323 as well?

@freddyaboulton
Copy link
Collaborator Author

@abidlabs I addressed your comments! Cancelling will no longer reset the state of the component. And we raise exceptions if the queue is disabled but there are cancel events.

This is that the stop button looks like for interfaces:

cancel_interface

I ended up creating a new button variant. @pngwn Do you like the approach? Feel free to suggest better colors/styling for the stop button.

Should be good for a final review!

@freddyaboulton freddyaboulton marked this pull request as ready for review October 14, 2022 15:40
@pngwn
Copy link
Member

pngwn commented Oct 14, 2022

@freddyaboulton looks good to me! I like the extra variant (will be helpful for theming).

Could we add "cancels" to the union in the button component defined here: https://github.com/gradio-app/gradio/blob/main/ui/packages/button/src/Button.svelte#L8

@abidlabs
Copy link
Member

abidlabs commented Oct 14, 2022

Fantastic PR @freddyaboulton!

One tiny thing I noticed was that when running in a colab / jupyter notebook, because of the iframe's width, the stop button is pushed to the next row:

image

We could either (1) increase the iframe width, but it seems like you need an iframe width of at least 1280 for it to render correctly, which itself can cause the Interface to be cut off:

image

Or (2) we could move it next to the "Flag" button:

image

Otherwise, LGTM!

@freddyaboulton
Copy link
Collaborator Author

@abidlabs Thanks for the catch about rendering in the notebook! I'm worried about putting it under the output component because if it's really long (like an image in diffusion or an RL env frame) the button might be pushed outside the screen.

For example, look at the atari agent space:

image

I like the idea of keeping the stop button in the same group as submit and don't mind it's on the next row. Maybe we keep as is for now? Happy to jump on a huddle to discuss further.

@abidlabs
Copy link
Member

Ok I think that’s fine. LGTM thank you!

@freddyaboulton
Copy link
Collaborator Author

Thanks for the review @abidlabs !

@freddyaboulton freddyaboulton merged commit 831ae14 into main Oct 14, 2022
@freddyaboulton freddyaboulton deleted the 2220-stop-button branch October 14, 2022 22:43
@dawoodkhan82
Copy link
Collaborator

@freddyaboulton Good stuff! Just tested it, and works great.

pseudotensor added a commit to h2oai/h2ogpt that referenced this pull request Apr 10, 2023
@Seedmanc
Copy link

How to make an event cancel itself?
When I'm trying to do run = button.click(..., cancels=[run]), it's not yet defined at that moment.

@taoari
Copy link

taoari commented Jul 16, 2023

if an event can be cancelled, is it possible to recover it? For example, in #1323, after we stop the streaming, how can we resume it?

@abidlabs
Copy link
Member

@taoari it is not possible to resume an event after it has been canceled. If you have a job that is running, perhaps you could store the intermediate results in a gr.State() and then your event trigger can read the value of the gr.State() and resume from there?

@taoari
Copy link

taoari commented Jul 17, 2023

@abidlabs Thank you for your suggestion! But currently, I would like to enable webcam streaming again for new results, so it can not store intermediate results in a gr.State().

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.

Pressing "clear" should interrupt and clear an iterative output
6 participants