Repository: gradio-app/gradio
Pull request: Raise errror if event queued but queue is not enabled
# Description

The root cause of #3610 was that an event was queued but `.queue()` was not called. Raising an error during launch and `mount_gradio_app` now.

Without this ValueError users get `AttributeError: 'Blocks' object has no attribute '_queue'` anyways so I think there won't be negative impact of existing workflows

Closes: #3610

# 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.

Problem statement:
Can't run gradio in non-queue mode when using mount_gradio_app
### Describe the bug

We've been trying to run Gradio behind a local proxy. This works for one instance, but when you try to run multiple instances behind a proxy using path-based routing, you need to use `mount_gradio_app` to deploy your apps at different paths (you can't rewrite proxied URLs to `/` as the browser will request css resources at `/` and your server is serving them from whatever path you configured in your app.

We tried to use `mount_gradio_app` but we get an error. The error goes away if we explictly set up queueing:

```
AttributeError: 'Blocks' object has no attribute '_queue'
```
If this is intentional, the error should reflect the fact that queueing has to be enabled for this to work. 

### Is there an existing issue for this?

- [X] I have searched the existing issues

### Reproduction

CUSTOM_PATH='/mypath'
demo = gr.Blocks()
with demo:
      with gr.Row():
        gr.Markdown("#Hello")

app = FastAPI()

@app.get("/")
def read_main():
    return {"message": "This is your main app"}

gradio_app = gr.mount_gradio_app(app, demo, path=CUSTOM_PATH) 

`run with hypercorn app:app --bind "0.0.0.0:7860" --debug`

### Screenshot

_No response_

### Logs

```shell
Traceback (most recent call last):
  File "/home/dstieglitz/miniconda3/envs/transcribe/lib/python3.8/site-packages/hypercorn/asyncio/task_group.py", line 23, in _handle
    await app(scope, receive, send, sync_spawn, call_soon)
  File "/home/dstieglitz/miniconda3/envs/transcribe/lib/python3.8/site-packages/hypercorn/app_wrappers.py", line 33, in __call__
    await self.app(scope, receive, send)
  File "/home/dstieglitz/miniconda3/envs/transcribe/lib/python3.8/site-packages/fastapi/applications.py", line 276, in __call__
    await super().__call__(scope, receive, send)
  File "/home/dstieglitz/miniconda3/envs/transcribe/lib/python3.8/site-packages/starlette/applications.py", line 122, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/home/dstieglitz/miniconda3/envs/transcribe/lib/python3.8/site-packages/starlette/middleware/errors.py", line 149, in __call__
    await self.app(scope, receive, send)
  File "/home/dstieglitz/miniconda3/envs/transcribe/lib/python3.8/site-packages/starlette/middleware/exceptions.py", line 79, in __call__
    raise exc
  File "/home/dstieglitz/miniconda3/envs/transcribe/lib/python3.8/site-packages/starlette/middleware/exceptions.py", line 68, in __call__
    await self.app(scope, receive, sender)
  File "/home/dstieglitz/miniconda3/envs/transcribe/lib/python3.8/site-packages/fastapi/middleware/asyncexitstack.py", line 21, in __call__
    raise e
  File "/home/dstieglitz/miniconda3/envs/transcribe/lib/python3.8/site-packages/fastapi/middleware/asyncexitstack.py", line 18, in __call__
    await self.app(scope, receive, send)
  File "/home/dstieglitz/miniconda3/envs/transcribe/lib/python3.8/site-packages/starlette/routing.py", line 718, in __call__
    await route.handle(scope, receive, send)
  File "/home/dstieglitz/miniconda3/envs/transcribe/lib/python3.8/site-packages/starlette/routing.py", line 443, in handle
    await self.app(scope, receive, send)
  File "/home/dstieglitz/miniconda3/envs/transcribe/lib/python3.8/site-packages/fastapi/applications.py", line 276, in __call__
    await super().__call__(scope, receive, send)
  File "/home/dstieglitz/miniconda3/envs/transcribe/lib/python3.8/site-packages/starlette/applications.py", line 122, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/home/dstieglitz/miniconda3/envs/transcribe/lib/python3.8/site-packages/starlette/middleware/errors.py", line 149, in __call__
    await self.app(scope, receive, send)
  File "/home/dstieglitz/miniconda3/envs/transcribe/lib/python3.8/site-packages/starlette/middleware/cors.py", line 76, in __call__
    await self.app(scope, receive, send)
  File "/home/dstieglitz/miniconda3/envs/transcribe/lib/python3.8/site-packages/starlette/middleware/exceptions.py", line 79, in __call__
    raise exc
  File "/home/dstieglitz/miniconda3/envs/transcribe/lib/python3.8/site-packages/starlette/middleware/exceptions.py", line 68, in __call__
    await self.app(scope, receive, sender)
  File "/home/dstieglitz/miniconda3/envs/transcribe/lib/python3.8/site-packages/fastapi/middleware/asyncexitstack.py", line 21, in __call__
    raise e
  File "/home/dstieglitz/miniconda3/envs/transcribe/lib/python3.8/site-packages/fastapi/middleware/asyncexitstack.py", line 18, in __call__
    await self.app(scope, receive, send)
  File "/home/dstieglitz/miniconda3/envs/transcribe/lib/python3.8/site-packages/starlette/routing.py", line 718, in __call__
    await route.handle(scope, receive, send)
  File "/home/dstieglitz/miniconda3/envs/transcribe/lib/python3.8/site-packages/starlette/routing.py", line 341, in handle
    await self.app(scope, receive, send)
  File "/home/dstieglitz/miniconda3/envs/transcribe/lib/python3.8/site-packages/starlette/routing.py", line 82, in app
    await func(session)
  File "/home/dstieglitz/miniconda3/envs/transcribe/lib/python3.8/site-packages/fastapi/routing.py", line 289, in app
    await dependant.call(**values)
  File "/home/dstieglitz/miniconda3/envs/transcribe/lib/python3.8/site-packages/gradio/routes.py", line 488, in join_queue
    if blocks._queue.server_path is None:
AttributeError: 'Blocks' object has no attribute '_queue'
```


### System Info

```shell
gradio 3.23.0
gradio_client 0.0.4
python 3.8
Ubuntu 20.04
```


### Severity

annoying

Hints from issue discussion:
Hi @dstieglitz ! Thanks for filing the issue.

Can you try with:

`hypercorn app:gradio_app --bind "0.0.0.0:7860" --debug`

That's working for me:

<img width="1240" alt="image" src="https://user-images.githubusercontent.com/41651716/227648004-cbb5a719-78f3-4263-90e7-a053e44d8e7f.png">

In your code snippet, `gradio_app` is the name of the combined app, not `app`.

Thanks for pointing that out. It's still happening though, so I'll have to come up with a better script to reproduce it without copying over our whole project. What else in the blocks interface could trigger that to happen?
Try this: 
```

import gradio as gr
from fastapi import FastAPI

def run_stuff(input):
    pass

#
# Gradio code
#
demo = gr.Blocks()
with demo:
    with gr.Row():
        gr.Markdown("# Bug test")
    with gr.Row():
        input = gr.Video()
        with gr.Column(scale=1, min_width=600):
            go = gr.Button("Go")
    with gr.Row():
        video_output = gr.PlayableVideo()
    with gr.Row():
        go.click(fn=run_stuff, inputs=[input], outputs=[video_output],
                 queue=True, show_progress=True, status_tracker=None)

app = FastAPI()

@app.get("/")
def read_main():
    return {"message": "This is your main app"}


gradio_app = gr.mount_gradio_app(app, demo, path='/mypath') 
```
`http://127.0.0.1:8000/mypath`

Don't forget to click "go"
Im away from my computer at the moment, but can you try calling .queue() on demo prior to mount_gradio_app?
That removes the error, and the app runs. Is queue() required for this to work?

We then run into #3612, not sure if it's related or not.
If you set queue=True for an event, then the queue must be enabled via queue(). I think we raise an error for this but maybe not when mounting as a subapplication. I’ll look into it. I think we can make that clearer!

Not sure about #3612, I’ll look into it!


Oh! Thanks. I see it now.

Commit message: CHANGELOG

Patch to review:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8ec9a69966..7a92df41ed 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -36,6 +36,7 @@ No changes to highlight.
 * Mobile responsive iframes in themes guide by [@aliabd](https://github.com/aliabd) in [PR 3562](https://github.com/gradio-app/gradio/pull/3562) 
 * Remove extra $demo from theme guide by [@aliabd](https://github.com/aliabd) in [PR 3563](https://github.com/gradio-app/gradio/pull/3563)
 * Set the theme name to be the upstream repo name when loading from the hub by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3595](https://github.com/gradio-app/gradio/pull/3595)   
+* Raise error when an event is queued but the queue is not configured by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3640](https://github.com/gradio-app/gradio/pull/3640)    
 
 ## Contributors Shoutout:
 
diff --git a/gradio/blocks.py b/gradio/blocks.py
index cccb1c8ded..58776b397b 100644
--- a/gradio/blocks.py
+++ b/gradio/blocks.py
@@ -1332,6 +1332,30 @@ def queue(
         self.app = routes.App.create_app(self)
         return self
 
+    def validate_queue_settings(self):
+        for fn_index, dep in enumerate(self.dependencies):
+            if not self.enable_queue and self.queue_enabled_for_fn(fn_index):
+                raise ValueError(
+                    f"The queue is enabled for event {dep['api_name'] if dep['api_name'] else fn_index} "
+                    "but the queue has not been enabled for the app. Please call .queue() "
+                    "on your app. Consult https://gradio.app/docs/#blocks-queue for information on how "
+                    "to configure the queue."
+                )
+            for i in dep["cancels"]:
+                if not self.queue_enabled_for_fn(i):
+                    raise ValueError(
+                        "In order to cancel an event, the queue for that event must be enabled! "
+                        "You may get this error by either 1) passing a function that uses the yield keyword "
+                        "into an interface without enabling the queue or 2) defining an event that cancels "
+                        "another event without enabling the queue. Both can be solved by calling .queue() "
+                        "before .launch()"
+                    )
+            if dep["batch"] and (
+                dep["queue"] is False
+                or (dep["queue"] is None and not self.enable_queue)
+            ):
+                raise ValueError("In order to use batching, the queue must be enabled.")
+
     def launch(
         self,
         inline: bool | None = None,
@@ -1453,21 +1477,7 @@ def reverse(text):
         if not self.enable_queue and self.progress_tracking:
             raise ValueError("Progress tracking requires queuing to be enabled.")
 
-        for dep in self.dependencies:
-            for i in dep["cancels"]:
-                if not self.queue_enabled_for_fn(i):
-                    raise ValueError(
-                        "In order to cancel an event, the queue for that event must be enabled! "
-                        "You may get this error by either 1) passing a function that uses the yield keyword "
-                        "into an interface without enabling the queue or 2) defining an event that cancels "
-                        "another event without enabling the queue. Both can be solved by calling .queue() "
-                        "before .launch()"
-                    )
-            if dep["batch"] and (
-                dep["queue"] is False
-                or (dep["queue"] is None and not self.enable_queue)
-            ):
-                raise ValueError("In order to use batching, the queue must be enabled.")
+        self.validate_queue_settings()
 
         self.config = self.get_config_file()
         self.max_threads = max(
diff --git a/gradio/routes.py b/gradio/routes.py
index 3a017983c5..278085144c 100644
--- a/gradio/routes.py
+++ b/gradio/routes.py
@@ -757,6 +757,7 @@ def read_main():
     blocks.dev_mode = False
     blocks.root = path[:-1] if path.endswith("/") else path
     blocks.config = blocks.get_config_file()
+    blocks.validate_queue_settings()
     gradio_app = App.create_app(blocks)
 
     @app.on_event("startup")
diff --git a/test/test_blocks.py b/test/test_blocks.py
index f0600c9dfe..61230772ee 100644
--- a/test/test_blocks.py
+++ b/test/test_blocks.py
@@ -342,6 +342,23 @@ def test_exit_called_at_launch(self):
         demo.launch(prevent_thread_lock=True)
         assert len(demo.get_config_file()["dependencies"]) == 1
 
+    def test_raise_error_if_event_queued_but_queue_not_enabled(self):
+        with gr.Blocks() as demo:
+            with gr.Row():
+                with gr.Column():
+                    input_ = gr.Textbox()
+                    btn = gr.Button("Greet")
+                with gr.Column():
+                    output = gr.Textbox()
+            btn.click(
+                lambda x: f"Hello, {x}", inputs=input_, outputs=output, queue=True
+            )
+
+        with pytest.raises(ValueError, match="The queue is enabled for event 0"):
+            demo.launch(prevent_thread_lock=True)
+
+        demo.close()
+
 
 class TestComponentsInBlocks:
     def test_slider_random_value_config(self):
diff --git a/test/test_routes.py b/test/test_routes.py
index 374a48fe3a..75ab046eea 100644
--- a/test/test_routes.py
+++ b/test/test_routes.py
@@ -271,6 +271,27 @@ def test_mount_gradio_app(self):
             assert client.get("/ps").is_success
             assert client.get("/py").is_success
 
+    def test_mount_gradio_app_raises_error_if_event_queued_but_queue_disabled(self):
+        with gr.Blocks() as demo:
+            with gr.Row():
+                with gr.Column():
+                    input_ = gr.Textbox()
+                    btn = gr.Button("Greet")
+                with gr.Column():
+                    output = gr.Textbox()
+            btn.click(
+                lambda x: f"Hello, {x}",
+                inputs=input_,
+                outputs=output,
+                queue=True,
+                api_name="greet",
+            )
+
+        with pytest.raises(ValueError, match="The queue is enabled for event greet"):
+            demo.launch(prevent_thread_lock=True)
+
+        demo.close()
+
 
 class TestGeneratorRoutes:
     def test_generator(self):


Code review comments:
diff_hunk: @@ -1453,21 +1477,7 @@ def reverse(text):
         if not self.enable_queue and self.progress_tracking:
             raise ValueError("Progress tracking requires queuing to be enabled.")
path: gradio/blocks.py
text: @user1:
We should move this logic into `validate_queue_settings()` so that it is also checked when the gradio app is mounted.

@author:
Good idea! Done

diff_hunk: @@ -1332,6 +1332,30 @@ def queue(
         self.app = routes.App.create_app(self)
         return self
 
+    def validate_queue_settings(self):
+        for fn_index, dep in enumerate(self.dependencies):
+            if not self.enable_queue and self.queue_enabled_for_fn(fn_index):
+                raise ValueError(
+                    f"The queue is enabled for event {dep['api_name'] if dep['api_name'] else fn_index} "
+                    "but the queue has not been enabled for the app. Please call .queue() "
+                    "on your app. Consult https://gradio.app/docs/#blocks-queue for information on how "
+                    "to configure the queue."
+                )
+            for i in dep["cancels"]:
+                if not self.queue_enabled_for_fn(i):
+                    raise ValueError(
+                        "In order to cancel an event, the queue for that event must be enabled! "
path: gradio/blocks.py
text: @user1:
This error is a bit confusing if a generator with `Interface` is used as it's not apparent what this has to do with canceling. I'd suggest:
```suggestion
                        "Queue needs to be enabled! "
```

@author:
But this error couldn't be reached from an interface because we take care of setting up all the event cancelling properly? 

@user1:
Isn't this error reached if you pass in a generator as the `fn` to an `Interface` but don't enable queuing?

@author:
Yes you're right. Committed this change!