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

Make the HTTP requests for the Wasm worker wait for the initial run_code() or run_file() to finish #5958

Merged
merged 4 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/crazy-dancers-allow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@gradio/wasm": minor
---

feat:Make the HTTP requests for the Wasm worker wait for the initial `run_code()` or `run_file()` to finish
26 changes: 26 additions & 0 deletions js/wasm/src/promise-delegate.ts
Copy link
Member Author

@whitphx whitphx Oct 17, 2023

Choose a reason for hiding this comment

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
type PromiseImplFn<T> = ConstructorParameters<typeof Promise<T>>[0];

export class PromiseDelegate<T> {
private promiseInternal: Promise<T>;
private resolveInternal!: Parameters<PromiseImplFn<T>>[0];
private rejectInternal!: Parameters<PromiseImplFn<T>>[1];

constructor() {
this.promiseInternal = new Promise((resolve, reject) => {
this.resolveInternal = resolve;
this.rejectInternal = reject;
});
}

get promise(): Promise<T> {
return this.promiseInternal;
}

public resolve(value: T): void {
this.resolveInternal(value);
}

public reject(reason: unknown): void {
this.rejectInternal(reason);
}
}
4 changes: 2 additions & 2 deletions js/wasm/src/webworker/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ let call_asgi_app_from_js: (
receive: () => Promise<unknown>,
send: (event: any) => Promise<void>
) => Promise<void>;
let run_script: (path: string) => void;
let run_script: (path: string) => Promise<void>;
let unload_local_modules: (target_dir_path?: string) => void;

async function loadPyodideAndPackages(
Expand Down Expand Up @@ -218,7 +218,7 @@ self.onmessage = async (event: MessageEvent<InMessage>): Promise<void> => {
case "run-python-file": {
unload_local_modules();

run_script(msg.data.path);
await run_script(msg.data.path);

const replyMessage: ReplyMessageSuccess = {
type: "reply:success",
Expand Down
15 changes: 12 additions & 3 deletions js/wasm/src/webworker/py/script_runner.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import ast
import tokenize
import types
import sys
from inspect import CO_COROUTINE

# BSD 3-Clause License
#
Expand Down Expand Up @@ -63,6 +65,7 @@ def __exit__(self, type, value, traceback):


# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022)
# Copyright (c) Yuichiro Tachibana (2023)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -80,9 +83,10 @@ def _new_module(name: str) -> types.ModuleType:
return types.ModuleType(name)


def _run_script(script_path: str) -> None:
async def _run_script(script_path: str) -> None:
# This function is based on the following code from Streamlit:
# https://github.com/streamlit/streamlit/blob/1.24.0/lib/streamlit/runtime/scriptrunner/script_runner.py#L519-L554
# with modifications to support top-level await.

with tokenize.open(script_path) as f:
filebody = f.read()
Expand All @@ -98,7 +102,7 @@ def _run_script(script_path: str) -> None:
# mode (as opposed to "eval" or "single").
mode="exec",
# Don't inherit any flags or "future" statements.
flags=0,
flags=ast.PyCF_ALLOW_TOP_LEVEL_AWAIT, # Allow top-level await. Ref: https://github.com/whitphx/streamlit/commit/277dc580efb315a3e9296c9a0078c602a0904384
dont_inherit=1,
# Use the default optimization options.
optimize=-1,
Expand All @@ -117,4 +121,9 @@ def _run_script(script_path: str) -> None:
module.__dict__["__file__"] = script_path

with modified_sys_path(script_path):
exec(bytecode, module.__dict__)
# Allow top-level await. Ref: https://github.com/whitphx/streamlit/commit/277dc580efb315a3e9296c9a0078c602a0904384
if bytecode.co_flags & CO_COROUTINE:
# The source code includes top-level awaits, so the compiled code object is a coroutine.
await eval(bytecode, module.__dict__)
else:
exec(bytecode, module.__dict__)
11 changes: 11 additions & 0 deletions js/wasm/src/worker-proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
ReplyMessage
} from "./message-types";
import { MessagePortWebSocket } from "./messageportwebsocket";
import { PromiseDelegate } from "./promise-delegate";

export interface WorkerProxyOptions {
gradioWheelUrl: string;
Expand All @@ -20,6 +21,8 @@ export interface WorkerProxyOptions {
export class WorkerProxy {
private worker: globalThis.Worker;

private firstRunPromiseDelegate = new PromiseDelegate<void>();

constructor(options: WorkerProxyOptions) {
console.debug("WorkerProxy.constructor(): Create a new worker.");
// Loading a worker here relies on Vite's support for WebWorkers (https://vitejs.dev/guide/features.html#web-workers),
Expand Down Expand Up @@ -49,6 +52,7 @@ export class WorkerProxy {
code
}
});
this.firstRunPromiseDelegate.resolve();
}

public async runPythonFile(path: string): Promise<void> {
Expand All @@ -58,6 +62,7 @@ export class WorkerProxy {
path
}
});
this.firstRunPromiseDelegate.resolve();
}

// A wrapper for this.worker.postMessage(). Unlike that function, which
Expand All @@ -84,6 +89,12 @@ export class WorkerProxy {
}

public async httpRequest(request: HttpRequest): Promise<HttpResponse> {
// Wait for the first run to be done
// to avoid the "Gradio app has not been launched." error
// in case running the code takes long time.
// Ref: https://github.com/gradio-app/gradio/issues/5957
await this.firstRunPromiseDelegate.promise;

console.debug("WorkerProxy.httpRequest()", request);
const result = await this.postMessageAsync({
type: "http-request",
Expand Down
Loading