diff --git a/.cargo/config.toml b/.cargo/config.toml index 88a654a8ee..b446d07017 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -2,6 +2,7 @@ bundle = "run -p perspective-bundle" [build] +# rustflags = ["--cfg=web_sys_unstable_apis", "--cfg=pyo3_disable_reference_pool"] rustflags = ["--cfg=web_sys_unstable_apis"] target-dir = "rust/target" diff --git a/Cargo.lock b/Cargo.lock index 8744d7e569..c3acd9dc9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -350,7 +350,7 @@ dependencies = [ "spdx", "structopt", "strum 0.24.1", - "thiserror", + "thiserror 1.0.61", "toml", ] @@ -374,7 +374,7 @@ dependencies = [ "semver 1.0.23", "serde", "serde_json", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -1140,7 +1140,7 @@ dependencies = [ "serde", "serde-wasm-bindgen 0.5.0", "serde_urlencoded", - "thiserror", + "thiserror 1.0.61", "wasm-bindgen", "web-sys", ] @@ -1157,7 +1157,7 @@ dependencies = [ "serde", "serde-wasm-bindgen 0.6.5", "serde_urlencoded", - "thiserror", + "thiserror 1.0.61", "wasm-bindgen", "web-sys", ] @@ -1177,7 +1177,7 @@ dependencies = [ "pin-project", "serde", "serde_json", - "thiserror", + "thiserror 1.0.61", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -1198,7 +1198,7 @@ dependencies = [ "pin-project", "serde", "serde_json", - "thiserror", + "thiserror 1.0.61", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -1234,7 +1234,7 @@ dependencies = [ "js-sys", "serde", "serde_json", - "thiserror", + "thiserror 1.0.61", "wasm-bindgen", "web-sys", ] @@ -1249,7 +1249,7 @@ dependencies = [ "js-sys", "serde", "serde_json", - "thiserror", + "thiserror 1.0.61", "wasm-bindgen", "web-sys", ] @@ -1330,7 +1330,7 @@ dependencies = [ "js-sys", "pinned", "serde", - "thiserror", + "thiserror 1.0.61", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -2092,7 +2092,7 @@ dependencies = [ "serde", "serde_bytes", "serde_json", - "thiserror", + "thiserror 1.0.61", "tracing", "ts-rs", ] @@ -2119,7 +2119,7 @@ dependencies = [ "serde-wasm-bindgen 0.6.5", "serde_bytes", "serde_json", - "thiserror", + "thiserror 1.0.61", "tracing", "tracing-subscriber", "ts-rs", @@ -2214,7 +2214,7 @@ dependencies = [ "serde_json", "serde_with", "strum 0.26.2", - "thiserror", + "thiserror 1.0.61", "tracing", "tracing-subscriber", "ts-rs", @@ -2276,7 +2276,7 @@ checksum = "a829027bd95e54cfe13e3e258a1ae7b645960553fb82b75ff852c29688ee595b" dependencies = [ "futures", "rustversion", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -2493,17 +2493,16 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.23.4" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57fe09249128b3173d092de9523eaa75136bf7ba85e0d69eca241c7939c933cc" +checksum = "8970a78afe0628a3e3430376fc5fd76b6b45c4d43360ffd6cdd40bdde72b682a" dependencies = [ - "cfg-if", "indoc", "libc", "memoffset", "once_cell", "portable-atomic", - "pyo3-build-config 0.23.4", + "pyo3-build-config 0.25.1", "pyo3-ffi", "pyo3-macros", "serde", @@ -2512,9 +2511,9 @@ dependencies = [ [[package]] name = "pyo3-async-runtimes" -version = "0.23.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "977dc837525cfd22919ba6a831413854beb7c99a256c03bf8624ad707e45810e" +checksum = "d73cc6b1b7d8b3cef02101d37390dbdfe7e450dfea14921cae80a9534ba59ef2" dependencies = [ "futures", "once_cell", @@ -2526,9 +2525,9 @@ dependencies = [ [[package]] name = "pyo3-async-runtimes-macros" -version = "0.23.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2df2884957d2476731f987673befac5d521dff10abb0a7cbe12015bc7702fe9" +checksum = "ca31e43a0f205f2960208938135e37e579e61e10b36b4e7f49b0e8f60fab5b83" dependencies = [ "proc-macro2 1.0.94", "quote 1.0.39", @@ -2542,34 +2541,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b14b5775b5ff446dd1056212d778012cbe8a0fbffd368029fd9e25b514479c38" dependencies = [ "once_cell", - "target-lexicon", + "target-lexicon 0.12.14", ] [[package]] name = "pyo3-build-config" -version = "0.23.4" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd3927b5a78757a0d71aa9dff669f903b1eb64b54142a9bd9f757f8fde65fd7" +checksum = "458eb0c55e7ece017adeba38f2248ff3ac615e53660d7c71a238d7d2a01c7598" dependencies = [ "once_cell", - "target-lexicon", + "target-lexicon 0.13.2", ] [[package]] name = "pyo3-ffi" -version = "0.23.4" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dab6bb2102bd8f991e7749f130a70d05dd557613e39ed2deeee8e9ca0c4d548d" +checksum = "7114fe5457c61b276ab77c5055f206295b812608083644a5c5b2640c3102565c" dependencies = [ "libc", - "pyo3-build-config 0.23.4", + "pyo3-build-config 0.25.1", ] [[package]] name = "pyo3-macros" -version = "0.23.4" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91871864b353fd5ffcb3f91f2f703a22a9797c91b9ab497b1acac7b07ae509c7" +checksum = "a8725c0a622b374d6cb051d11a0983786448f7785336139c3c94f5aa6bef7e50" dependencies = [ "proc-macro2 1.0.94", "pyo3-macros-backend", @@ -2579,13 +2578,13 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.23.4" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43abc3b80bc20f3facd86cd3c60beed58c3e2aa26213f3cda368de39c60a27e4" +checksum = "4109984c22491085343c05b0dbc54ddc405c3cf7b4374fc533f5c3313a572ccc" dependencies = [ "heck 0.5.0", "proc-macro2 1.0.94", - "pyo3-build-config 0.23.4", + "pyo3-build-config 0.25.1", "quote 1.0.39", "syn 2.0.99", ] @@ -2601,9 +2600,9 @@ dependencies = [ [[package]] name = "pythonize" -version = "0.23.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91a6ee7a084f913f98d70cdc3ebec07e852b735ae3059a1500db2661265da9ff" +checksum = "597907139a488b22573158793aa7539df36ae863eba300c75f3a0d65fc475e27" dependencies = [ "pyo3", "serde", @@ -2710,7 +2709,7 @@ checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ "getrandom 0.2.15", "libredox", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -3236,6 +3235,12 @@ version = "0.12.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" +[[package]] +name = "target-lexicon" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" + [[package]] name = "tempfile" version = "3.10.1" @@ -3278,7 +3283,16 @@ version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.61", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", ] [[package]] @@ -3292,6 +3306,17 @@ dependencies = [ "syn 2.0.99", ] +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2 1.0.94", + "quote 1.0.39", + "syn 2.0.99", +] + [[package]] name = "thread_local" version = "1.1.8" @@ -3350,9 +3375,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.43.0" +version = "1.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" dependencies = [ "backtrace", "bytes", @@ -3564,21 +3589,20 @@ dependencies = [ [[package]] name = "ts-rs" -version = "10.0.0" +version = "11.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a2f31991cee3dce1ca4f929a8a04fdd11fd8801aac0f2030b0fa8a0a3fef6b9" +checksum = "6ef1b7a6d914a34127ed8e1fa927eb7088903787bcded4fa3eef8f85ee1568be" dependencies = [ - "lazy_static", "serde_json", - "thiserror", + "thiserror 2.0.12", "ts-rs-macros", ] [[package]] name = "ts-rs-macros" -version = "10.0.0" +version = "11.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea0b99e8ec44abd6f94a18f28f7934437809dd062820797c52401298116f70e" +checksum = "e9d4ed7b4c18cc150a6a0a1e9ea1ecfa688791220781af6e119f9599a8502a0a" dependencies = [ "proc-macro2 1.0.94", "quote 1.0.39", @@ -3600,7 +3624,7 @@ dependencies = [ "log", "rand", "sha1", - "thiserror", + "thiserror 1.0.61", "url", "utf-8", ] @@ -3975,7 +3999,7 @@ dependencies = [ "strum 0.24.1", "strum_macros 0.24.3", "tempfile", - "thiserror", + "thiserror 1.0.61", "wasm-opt-cxx-sys", "wasm-opt-sys", ] @@ -4241,7 +4265,7 @@ dependencies = [ "rustversion", "serde", "slab", - "thiserror", + "thiserror 1.0.61", "tokio", "tracing", "wasm-bindgen", diff --git a/cpp/protos/perspective.proto b/cpp/protos/perspective.proto index b44af29b0b..00820bee64 100644 --- a/cpp/protos/perspective.proto +++ b/cpp/protos/perspective.proto @@ -25,6 +25,7 @@ option optimize_for = LITE_RUNTIME; enum StatusCode { SERVER_ERROR = 0; VIEW_NOT_FOUND = 1; + TRANSPORT_ERROR = 2; } // Recoverable, user-readable error reporting from the engine. diff --git a/examples/python-tornado/server.py b/examples/python-tornado/server.py index 89b5bb04a7..363da11600 100644 --- a/examples/python-tornado/server.py +++ b/examples/python-tornado/server.py @@ -44,7 +44,8 @@ def make_app(perspective_server): tornado.web.StaticFileHandler, {"path": "./", "default_filename": "index.html"}, ), - ] + ], + websocket_max_message_size=100 * 1024 * 1024, ) diff --git a/packages/perspective-viewer-datagrid/src/js/style_handlers/group_header.js b/packages/perspective-viewer-datagrid/src/js/style_handlers/group_header.js index 1f891b92e8..a71e9ca8bb 100644 --- a/packages/perspective-viewer-datagrid/src/js/style_handlers/group_header.js +++ b/packages/perspective-viewer-datagrid/src/js/style_handlers/group_header.js @@ -27,7 +27,9 @@ export function group_header_style_listener(regularTable) { td.style.backgroundColor = ""; const metadata = regularTable.getMeta(td); let needs_border = - metadata.row_header_x === header_depth || metadata.x >= 0; + (header_depth > 0 && metadata.row_header_x === header_depth) || + metadata.x >= 0; + td.classList.toggle("psp-align-right", false); td.classList.toggle("psp-align-left", false); td.classList.toggle("psp-header-group", true); @@ -37,8 +39,8 @@ export function group_header_style_listener(regularTable) { "psp-header-group-corner", typeof metadata.x === "undefined" ); - td.classList.toggle("psp-color-mode-bar", false); + td.classList.toggle("psp-color-mode-bar", false); td.classList.toggle("psp-header-sort-asc", false); td.classList.toggle("psp-header-sort-desc", false); td.classList.toggle("psp-header-sort-col-asc", false); diff --git a/packages/perspective-workspace/src/less/menu.less b/packages/perspective-workspace/src/less/menu.less index a011e9d4db..55522fe945 100644 --- a/packages/perspective-workspace/src/less/menu.less +++ b/packages/perspective-workspace/src/less/menu.less @@ -20,6 +20,8 @@ border: 1px solid var(--inactive--color); border-radius: 3px; max-width: 350px; + top: 0; + left: 0; } .lm-Menu.perspective-workspace-menu { diff --git a/packages/perspective-workspace/src/ts/workspace/menu.ts b/packages/perspective-workspace/src/ts/workspace/menu.ts index 6e942130fe..3c477b0eb2 100644 --- a/packages/perspective-workspace/src/ts/workspace/menu.ts +++ b/packages/perspective-workspace/src/ts/workspace/menu.ts @@ -17,19 +17,43 @@ import { Menu, Widget } from "@lumino/widgets"; export class WorkspaceMenu extends Menu { private _host: ShadowRoot; + private _workspace: HTMLElement; init_overlay?: () => void; - constructor(host: ShadowRoot, options: Menu.IOptions) { + constructor( + host: ShadowRoot, + workspace: HTMLElement, + options: Menu.IOptions + ) { options.renderer = new MenuRenderer(); super(options); this._host = host; + this._workspace = workspace; (this as any)._openChildMenu = this._overrideOpenChildMenu.bind(this); } open(x: number, y: number, options?: Menu.IOpenOptions) { options ||= {}; options.host = this._host as any as HTMLElement; + const box = this._workspace.getBoundingClientRect(); super.open(x, y, options); + const menu_box = this.node.getBoundingClientRect(); + if ( + menu_box.height + y > box.height && + menu_box.height + y < document.documentElement.clientHeight + ) { + this.node.style.top = `-${menu_box.height}`; + } + + if (menu_box.width + x > box.width) { + if (menu_box.width + x < document.documentElement.clientWidth) { + this.node.style.left = `-${menu_box.width + x - box.width}px`; + } else { + this.node.style.left = `-${ + document.documentElement.clientWidth - box.width + }px`; + } + } } // Override this lumino private method because it will otherwise always diff --git a/packages/perspective-workspace/src/ts/workspace/tabbar.ts b/packages/perspective-workspace/src/ts/workspace/tabbar.ts index 263c5dece6..f8f0e5f1eb 100644 --- a/packages/perspective-workspace/src/ts/workspace/tabbar.ts +++ b/packages/perspective-workspace/src/ts/workspace/tabbar.ts @@ -93,9 +93,13 @@ export class PerspectiveTabBar extends TabBar { onClick(otherTitles: Title[], index: number, event: MouseEvent) { const commands = new CommandRegistry(); - this._menu = new WorkspaceMenu(this._workspace.menu_elem.shadowRoot!, { - commands, - }); + this._menu = new WorkspaceMenu( + this._workspace.menu_elem.shadowRoot!, + this._workspace.element, + { + commands, + } + ); this._menu.addClass("perspective-workspace-menu"); this._menu.dataset.minwidth = this.__titles[index]; diff --git a/packages/perspective-workspace/src/ts/workspace/workspace.ts b/packages/perspective-workspace/src/ts/workspace/workspace.ts index 3c209c0464..2b7d581ee8 100644 --- a/packages/perspective-workspace/src/ts/workspace/workspace.ts +++ b/packages/perspective-workspace/src/ts/workspace/workspace.ts @@ -645,9 +645,13 @@ export class PerspectiveWorkspace extends SplitPanel { */ createContextMenu(widget: PerspectiveViewerWidget | null) { - this._menu = new WorkspaceMenu(this.menu_elem.shadowRoot!, { - commands: this.commands, - }); + this._menu = new WorkspaceMenu( + this.menu_elem.shadowRoot!, + this.element, + { + commands: this.commands, + } + ); const tabbar = find( this.dockpanel.tabBars(), @@ -678,6 +682,7 @@ export class PerspectiveWorkspace extends SplitPanel { submenu: (() => { const submenu = new WorkspaceMenu( this.menu_elem.shadowRoot!, + this.element, { commands: this.commands, } diff --git a/rust/generate-metadata/Cargo.toml b/rust/generate-metadata/Cargo.toml index 3a1d5f6fc7..024caa1002 100644 --- a/rust/generate-metadata/Cargo.toml +++ b/rust/generate-metadata/Cargo.toml @@ -26,7 +26,7 @@ version = "2.0.0" artifact = ["bin"] [dependencies.ts-rs] -version = "10.0.0" +version = "11.0.1" features = ["serde-json-impl", "no-serde-warnings"] [dependencies.perspective-server] diff --git a/rust/perspective-client/Cargo.toml b/rust/perspective-client/Cargo.toml index 2b3b31cd15..413d72b180 100644 --- a/rust/perspective-client/Cargo.toml +++ b/rust/perspective-client/Cargo.toml @@ -74,5 +74,5 @@ default-features = false features = ["prost-derive", "std"] [dependencies.ts-rs] -version = "10.0.0" -features = ["serde-json-impl", "no-serde-warnings"] +version = "11.0.1" +features = ["serde-json-impl", "no-serde-warnings", "import-esm"] diff --git a/rust/perspective-client/src/rust/client.rs b/rust/perspective-client/src/rust/client.rs index f33d89510b..ad0451db3f 100644 --- a/rust/perspective-client/src/rust/client.rs +++ b/rust/perspective-client/src/rust/client.rs @@ -232,7 +232,7 @@ impl Client { { tracing::error!("{}", message); } else { - tracing::warn!("Received unsolicited server message"); + tracing::debug!("Received unsolicited server response: {}", msg); } Ok(false) @@ -262,9 +262,34 @@ impl Client { })); tasks.await.into_iter().collect::>()?; + self.close_and_error_subscriptions(&message).await + } + + /// TODO Synthesize an error to provide to the caller, since the + /// server did not respond and the other option is to just drop the call + /// which results in a non-descript error message. It would be nice to + /// have client-side failures be a native part of the Client API. + async fn close_and_error_subscriptions(&self, message: &ClientError) -> ClientResult<()> { + let synthetic_error = |msg_id| Response { + msg_id, + entity_id: "".to_string(), + client_resp: Some(ClientResp::ServerError(ServerError { + message: format!("{}", message), + status_code: 2, + })), + }; + self.subscriptions.write().await.clear(); - self.subscriptions_once.write().await.clear(); - Ok(()) + let callbacks_once = self + .subscriptions_once + .write() + .await + .drain() + .collect::>(); + + callbacks_once + .into_iter() + .try_for_each(|(msg_id, f)| f(synthetic_error(msg_id))) } pub async fn on_error(&self, on_error: T) -> ClientResult @@ -365,7 +390,7 @@ impl Client { self.subscribe_once(req, on_update).await?; receiver .await - .map_err(|_| ClientError::Unknown("Internal error".to_owned())) + .map_err(|_| ClientError::Unknown(format!("Internal error for req {}", req))) } pub(crate) fn get_features(&self) -> ClientResult { @@ -576,7 +601,7 @@ impl Client { on_update().await; Ok(()) }, - resp => Err(ClientError::OptionResponseFailed(resp.into())), + resp => Err(resp.into()), } }); diff --git a/rust/perspective-client/src/rust/table.rs b/rust/perspective-client/src/rust/table.rs index 778ab0ba0a..e42cd3b2a5 100644 --- a/rust/perspective-client/src/rust/table.rs +++ b/rust/perspective-client/src/rust/table.rs @@ -363,7 +363,7 @@ impl Table { on_delete(); Ok(()) }, - resp => Err(ClientError::OptionResponseFailed(resp.into())), + resp => Err(resp.into()), }; let msg = self.client_message(ClientReq::TableOnDeleteReq(TableOnDeleteReq {})); diff --git a/rust/perspective-client/src/rust/utils/logging.rs b/rust/perspective-client/src/rust/utils/logging.rs index 9b6d6d7219..d301ae5f91 100644 --- a/rust/perspective-client/src/rust/utils/logging.rs +++ b/rust/perspective-client/src/rust/utils/logging.rs @@ -12,19 +12,21 @@ use prost::Message; +use crate::ViewOnUpdateResp; use crate::proto::make_table_data::Data; use crate::proto::request::ClientReq; use crate::proto::response::ClientResp; use crate::proto::{ - MakeTableData, MakeTableReq, Request, Response, TableUpdateReq, ViewToColumnsStringResp, + MakeTableData, MakeTableReq, Request, Response, TableUpdateReq, ViewToArrowResp, + ViewToColumnsStringResp, ViewToCsvResp, ViewToNdjsonStringResp, ViewToRowsStringResp, }; fn replace(x: Data) -> Data { match x { Data::FromArrow(_) => Data::FromArrow("<< redacted >>".to_string().encode_to_vec()), Data::FromRows(_) => Data::FromRows("<< redacted >>".to_string()), - Data::FromCols(_) => Data::FromCols("".to_string()), - Data::FromCsv(_) => Data::FromCsv("".to_string()), + Data::FromCols(_) => Data::FromCols("<< redacted >>".to_string()), + Data::FromCsv(_) => Data::FromCsv("<< redacted >>".to_string()), x => x, } } @@ -100,6 +102,52 @@ impl std::fmt::Display for Response { )), ..msg.clone() }, + Response { + client_resp: Some(ClientResp::ViewToRowsStringResp(_)), + .. + } => Response { + client_resp: Some(ClientResp::ViewToRowsStringResp(ViewToRowsStringResp { + json_string: "<< redacted >>".to_owned(), + })), + ..msg.clone() + }, + Response { + client_resp: Some(ClientResp::ViewToNdjsonStringResp(_)), + .. + } => Response { + client_resp: Some(ClientResp::ViewToNdjsonStringResp(ViewToNdjsonStringResp { + ndjson_string: "<< redacted >>".to_owned(), + })), + ..msg.clone() + }, + Response { + client_resp: Some(ClientResp::ViewToArrowResp(_)), + .. + } => Response { + client_resp: Some(ClientResp::ViewToArrowResp(ViewToArrowResp { + arrow: vec![], + })), + ..msg.clone() + }, + Response { + client_resp: Some(ClientResp::ViewToCsvResp(_)), + .. + } => Response { + client_resp: Some(ClientResp::ViewToCsvResp(ViewToCsvResp { + csv: "<< redacted >>".to_owned(), + })), + ..msg.clone() + }, + Response { + client_resp: Some(ClientResp::ViewOnUpdateResp(ref x)), + .. + } => Response { + client_resp: Some(ClientResp::ViewOnUpdateResp(ViewOnUpdateResp { + delta: x.delta.as_ref().map(|_| vec![]), + port_id: x.port_id, + })), + ..msg.clone() + }, x => x, }; diff --git a/rust/perspective-client/src/rust/utils/mod.rs b/rust/perspective-client/src/rust/utils/mod.rs index a1ce8aebd2..33ea0581bf 100644 --- a/rust/perspective-client/src/rust/utils/mod.rs +++ b/rust/perspective-client/src/rust/utils/mod.rs @@ -33,6 +33,9 @@ pub enum ClientError { #[error("Abort(): {0}")] Internal(String), + #[error("Transport error: {0}")] + TransportError(String), + #[error("Client not yet initialized")] NotInitialized, @@ -42,15 +45,15 @@ pub enum ClientError { #[error("Unwrapped option")] Option, - #[error("Unexpected response {0:?}")] - OptionResponseFailed(Box>), - #[error("Bad string")] Utf8(#[from] std::str::Utf8Error), #[error("Undecipherable server message {0:?}")] DecodeError(#[from] prost::DecodeError), + #[error("Response aborted")] + ResponseAborted, + #[error("Unexpected response {0:?}")] ResponseFailed(Box), @@ -84,12 +87,27 @@ impl<'a, A> From>> for Clien } } +impl From> for ClientError { + fn from(value: Option) -> Self { + match value { + Some(proto::response::ClientResp::ServerError(x)) => match x.status_code() { + proto::StatusCode::ServerError => ClientError::Internal(x.message), + proto::StatusCode::ViewNotFound => ClientError::ViewNotFound, + proto::StatusCode::TransportError => ClientError::TransportError(x.message), + }, + Some(x) => ClientError::ResponseFailed(Box::new(x)), + None => ClientError::ResponseAborted, + } + } +} + impl From for ClientError { fn from(value: proto::response::ClientResp) -> Self { match value { proto::response::ClientResp::ServerError(x) => match x.status_code() { proto::StatusCode::ServerError => ClientError::Internal(x.message), proto::StatusCode::ViewNotFound => ClientError::ViewNotFound, + proto::StatusCode::TransportError => ClientError::TransportError(x.message), }, x => ClientError::ResponseFailed(Box::new(x)), } diff --git a/rust/perspective-client/src/rust/view.rs b/rust/perspective-client/src/rust/view.rs index 856e5d04e2..4ba5b4955c 100644 --- a/rust/perspective-client/src/rust/view.rs +++ b/rust/perspective-client/src/rust/view.rs @@ -455,7 +455,7 @@ impl View { on_update(resp).await; Ok(()) }, - resp => Err(ClientError::OptionResponseFailed(resp.into())), + resp => Err(resp.into()), } } }; diff --git a/rust/perspective-js/Cargo.toml b/rust/perspective-js/Cargo.toml index f96b7f938b..afcb6784f9 100644 --- a/rust/perspective-js/Cargo.toml +++ b/rust/perspective-js/Cargo.toml @@ -65,9 +65,10 @@ serde = { version = "1.0", features = ["derive"] } serde_bytes = "0.11" serde_json = { version = "1.0.107", features = ["raw_value"] } serde-wasm-bindgen = "0.6.0" -ts-rs = { version = "10.0.0", features = [ +ts-rs = { version = "11.0.1", features = [ "serde-json-impl", "no-serde-warnings", + "import-esm", ] } thiserror = "1.0.55" tracing = { version = ">=0.1.36" } diff --git a/rust/perspective-js/build.js b/rust/perspective-js/build.js index 16667f2cbf..f9e58c7026 100644 --- a/rust/perspective-js/build.js +++ b/rust/perspective-js/build.js @@ -130,6 +130,13 @@ async function build_all() { "inherit", "inherit" ); + + await $`npx tsc --project ./tsconfig.node.json`.stdio( + "inherit", + "inherit", + "inherit" + ); + await cpy("target/themes/*", "dist/css"); await cpy("target/themes/*", "dist/css"); } diff --git a/rust/perspective-js/src/rust/client.rs b/rust/perspective-js/src/rust/client.rs index 09a609eb63..9ae23949a3 100644 --- a/rust/perspective-js/src/rust/client.rs +++ b/rust/perspective-js/src/rust/client.rs @@ -209,7 +209,6 @@ impl Client { Ok(()) } - #[doc(hidden)] #[wasm_bindgen] pub async fn handle_response(&self, value: &JsValue) -> ApiResult<()> { let uint8array = Uint8Array::new(value); @@ -218,7 +217,6 @@ impl Client { Ok(()) } - #[doc(hidden)] #[wasm_bindgen] pub async fn handle_error(&self, error: String, reconnect: Option) -> ApiResult<()> { self.client @@ -253,7 +251,6 @@ impl Client { Ok(()) } - #[doc(hidden)] #[wasm_bindgen] pub async fn on_error(&self, callback: Function) -> ApiResult { let callback = JsReconnect::from( diff --git a/rust/perspective-js/src/ts/perspective.node.ts b/rust/perspective-js/src/ts/perspective.node.ts index 355d53f04f..3f139266df 100644 --- a/rust/perspective-js/src/ts/perspective.node.ts +++ b/rust/perspective-js/src/ts/perspective.node.ts @@ -265,6 +265,10 @@ export function system_info() { return SYNC_CLIENT.system_info(); } +export function on_error(callback: Function) { + return SYNC_CLIENT.on_error(callback); +} + /** * Create a table from the global Perspective instance. * @param init_data @@ -305,6 +309,7 @@ export default { get_hosted_table_names, on_hosted_tables_update, remove_hosted_tables_update, + on_error, system_info, WebSocketServer, }; diff --git a/rust/perspective-js/src/ts/websocket.ts b/rust/perspective-js/src/ts/websocket.ts index 0fb51ff6a7..bedd5c7654 100644 --- a/rust/perspective-js/src/ts/websocket.ts +++ b/rust/perspective-js/src/ts/websocket.ts @@ -23,17 +23,11 @@ function invert_promise(): [(t: T) => void, Promise, (t: any) => void] { return [sender!, receiver, reject!]; } -/** - * Create a new client connected via WebSocket to a server implemnting the - * Perspective Protocol. - * @param module - * @param url - * @returns - */ export async function websocket( WebSocket: typeof window.WebSocket, Client: typeof perspective_client.Client, - url: string | URL + url: string | URL, + options?: { maxPayload: number } ): Promise { let client: perspective_client.Client, ws: WebSocket; @@ -47,12 +41,16 @@ export async function websocket( } let [sender, receiver, reject] = invert_promise(); - ws = new WebSocket(url); + + // @ts-ignore + ws = new WebSocket(url, options); ws.onopen = sender; ws.binaryType = "arraybuffer"; - ws.onerror = (_event) => { - client.handle_error(`WebSocket error`, connect); - reject(`WebSocket error`); + ws.onerror = (event) => { + // @ts-expect-error + const msg = event.message || "Generic Websocket Error"; + client.handle_error(msg, connect); + reject(msg); }; ws.onclose = (event) => { diff --git a/rust/perspective-js/tsconfig.browser.json b/rust/perspective-js/tsconfig.browser.json index 6c81603b74..76106be6c0 100644 --- a/rust/perspective-js/tsconfig.browser.json +++ b/rust/perspective-js/tsconfig.browser.json @@ -11,5 +11,5 @@ "allowImportingTsExtensions": true, "types": [] }, - "files": ["./src/ts/perspective.cdn.ts"] + "files": ["./src/ts/perspective.browser.ts"] } diff --git a/rust/perspective-js/tsconfig.node.json b/rust/perspective-js/tsconfig.node.json new file mode 100644 index 0000000000..d841d9adb1 --- /dev/null +++ b/rust/perspective-js/tsconfig.node.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "allowImportingTsExtensions": true, + "strict": true, + "module": "NodeNext", + "target": "ESNext", + "declaration": true, + "emitDeclarationOnly": true, + "outDir": "./dist/esm", + "rootDir": "./src/ts", + "moduleResolution": "NodeNext", + "skipLibCheck": true, + "resolveJsonModule": true + }, + "files": ["./src/ts/perspective.node.ts"] +} diff --git a/rust/perspective-python/Cargo.toml b/rust/perspective-python/Cargo.toml index 2e964f614e..647e009093 100644 --- a/rust/perspective-python/Cargo.toml +++ b/rust/perspective-python/Cargo.toml @@ -64,21 +64,21 @@ async-lock = "2.5.0" pollster = "0.3.0" extend = "1.1.2" futures = "0.3.28" -pyo3 = { version = "0.23.4", features = [ +pyo3 = { version = "0.25.1", features = [ "experimental-async", "extension-module", "serde", ] } -pythonize = "0.23.0" +pythonize = "0.25.0" tracing = { version = ">=0.1.36" } tracing-subscriber = { version = "0.3.15", features = ["env-filter"] } [target.'cfg(target_os="emscripten")'.dependencies] -pyo3-async-runtimes = { version = "0.23", features = ["attributes"] } +pyo3-async-runtimes = { version = "0.25.0", features = ["attributes"] } [target.'cfg(not(target_os="emscripten"))'.dependencies] -pyo3-async-runtimes = { version = "0.23", features = [ +pyo3-async-runtimes = { version = "0.25.0", features = [ "attributes", "tokio-runtime", ] } -tokio = { version = "1.43.0", features = ["full"] } +tokio = { version = "1.45.1", features = ["full"] } diff --git a/rust/perspective-python/perspective/handlers/tornado.py b/rust/perspective-python/perspective/handlers/tornado.py index 8e8af60d94..4b8a431885 100644 --- a/rust/perspective-python/perspective/handlers/tornado.py +++ b/rust/perspective-python/perspective/handlers/tornado.py @@ -23,6 +23,10 @@ class PerspectiveTornadoHandler(WebSocketHandler): connect to a JavaScript (Wasm) `Client`, providing a virtual interface to the `Server`'s resources for e.g. ``. + You may need to increase the `websocket_max_message_size` kwarg + to the `tornado.web.Application` constructor, as well as provide the + `max_buffer_size` optional arg, for large datasets. + Args: loop: An optional `IOLoop` instance to use for scheduling IO calls, defaults to `IOLoop.current()`. @@ -45,11 +49,17 @@ def check_origin(self, origin): return True def initialize( - self, perspective_server=perspective.GLOBAL_SERVER, loop=None, executor=None + self, + perspective_server=perspective.GLOBAL_SERVER, + loop=None, + executor=None, + max_buffer_size=None, ): self.server = perspective_server self.loop = loop or IOLoop.current() self.executor = executor + if max_buffer_size is not None: + self.request.connection.stream.max_buffer_size = max_buffer_size def open(self): def write(msg): diff --git a/rust/perspective-python/src/client/client_async.rs b/rust/perspective-python/src/client/client_async.rs index 5da5da5565..430ff5f0eb 100644 --- a/rust/perspective-python/src/client/client_async.rs +++ b/rust/perspective-python/src/client/client_async.rs @@ -77,7 +77,12 @@ impl AsyncClient { Ok(None) } })? { - fut.await?; + let result = fut.await; + Python::with_gil(|_| { + result + .map(|_| ()) + .map_err(perspective_server::ServerError::from) + })? } Ok(()) diff --git a/rust/perspective-python/src/server/session_async.rs b/rust/perspective-python/src/server/session_async.rs index b81110b05e..29dc2c9b0b 100644 --- a/rust/perspective-python/src/server/session_async.rs +++ b/rust/perspective-python/src/server/session_async.rs @@ -36,12 +36,19 @@ impl SessionHandler for PyConnection { &'a mut self, msg: &'a [u8], ) -> Result<(), perspective_server::ServerError> { - Python::with_gil(move |py| { + let result = Python::with_gil(move |py| { self.0 .call1(py, (PyBytes::new(py, msg),)) .and_then(|x| py_async::py_into_future(x.into_bound(py))) })? - .await?; + .await; + + // test + Python::with_gil(|_py| { + result + .map(|_| ()) + .map_err(perspective_server::ServerError::from) + })?; Ok(()) } @@ -78,17 +85,15 @@ impl PyAsyncSession { } pub async fn close(&self) -> PyResult<()> { - AllowThreads(pin!(async move { - let mut lock = self.session.write().await; - lock.take() - .ok_or_else(|| { - PyValueError::new_err(format!("Session {:?} already deleted", self.session)) - })? - .close() - .await; - Ok(()) - })) - .await + AllowThreads(pin!(async move { self.session.write().await.take() })) + .await + .ok_or_else(|| { + PyValueError::new_err(format!("Session {:?} already deleted", self.session)) + })? + .close() + .await; + + Ok(()) } } diff --git a/rust/perspective-python/src/server/session_sync.rs b/rust/perspective-python/src/server/session_sync.rs index abbf830b2a..74b0ecb8da 100644 --- a/rust/perspective-python/src/server/session_sync.rs +++ b/rust/perspective-python/src/server/session_sync.rs @@ -34,8 +34,12 @@ impl SessionHandler for PyConnectionSync { &'a mut self, msg: &'a [u8], ) -> Result<(), perspective_server::ServerError> { - Python::with_gil(move |py| self.0.call1(py, (PyBytes::new(py, msg),)))?; - + Python::with_gil(move |py| { + self.0 + .call1(py, (PyBytes::new(py, msg),)) + .map(|_| ()) + .map_err(perspective_server::ServerError::from) + })?; Ok(()) } } @@ -75,7 +79,7 @@ impl PySession { } pub fn close(&self, py: Python<'_>) -> PyResult<()> { - py.allow_threads(|| { + let z = py.allow_threads(|| { let lock = self .session .write() @@ -83,14 +87,17 @@ impl PySession { if let Ok(mut lock) = lock { lock.take() - .ok_or_else(|| { - PyValueError::new_err(format!("Session {:?} already deleted", self.session)) - })? - .close() - .block_on(); + } else { + None } + }); - Ok(()) - }) + z.ok_or_else(|| { + PyValueError::new_err(format!("Session {:?} already deleted", self.session)) + })? + .close() + .block_on(); + + Ok(()) } } diff --git a/rust/perspective-viewer/Cargo.toml b/rust/perspective-viewer/Cargo.toml index a8aca997b4..eddd6a7e84 100644 --- a/rust/perspective-viewer/Cargo.toml +++ b/rust/perspective-viewer/Cargo.toml @@ -99,9 +99,10 @@ serde-wasm-bindgen = "0.6.0" thiserror = { version = "1.0.55" } # specta = { version = "2.0.0-rc.9", features = ["typescript"] } -ts-rs = { version = "10.0.0", features = [ +ts-rs = { version = "11.0.1", features = [ "serde-json-impl", "no-serde-warnings", + "import-esm", ] } # Async-aware logging that can be disabled at compile-time. diff --git a/rust/perspective-viewer/src/rust/components/column_selector/filter_column.rs b/rust/perspective-viewer/src/rust/components/column_selector/filter_column.rs index dfe9b3d6c3..e8f0c792a0 100644 --- a/rust/perspective-viewer/src/rust/components/column_selector/filter_column.rs +++ b/rust/perspective-viewer/src/rust/components/column_selector/filter_column.rs @@ -490,7 +490,7 @@ impl Component for FilterColumn { }, }; - let final_col_type = col_type.expect("Unknown column"); + let final_col_type = col_type.unwrap_or(perspective_client::ColumnType::Integer); html! {
true, ComponentSelectPlugin(plugin_name) => { if !ctx.props().session.is_errored() { - ctx.props() + let metadata = ctx + .props() .renderer - .update_plugin(&PluginUpdate::Update(plugin_name)) - .unwrap(); + .get_next_plugin_metadata(&PluginUpdate::Update(plugin_name)); let mut update = ViewConfigUpdate::default(); - ctx.props() - .session - .set_update_column_defaults(&mut update, &ctx.props().renderer.metadata()); + ctx.props().session.set_update_column_defaults( + &mut update, + metadata + .as_ref() + .unwrap_or(&*ctx.props().renderer.metadata()), + ); if let Ok(task) = ctx.props().update_and_render(update) { ApiFuture::spawn(task); diff --git a/rust/perspective-viewer/src/rust/model/restore_and_render.rs b/rust/perspective-viewer/src/rust/model/restore_and_render.rs index a77afb584f..7993faa1c0 100644 --- a/rust/perspective-viewer/src/rust/model/restore_and_render.rs +++ b/rust/perspective-viewer/src/rust/model/restore_and_render.rs @@ -69,14 +69,14 @@ pub trait RestoreAndRender: HasRenderer + HasSession + HasPresentation { _ => false, }; - let plugin_changed = renderer.update_plugin(&plugin)?; - if plugin_changed { - session.set_update_column_defaults(&mut view_config, &renderer.metadata()); + if let Some(metadata) = renderer.get_next_plugin_metadata(&plugin) { + session.set_update_column_defaults(&mut view_config, &metadata); } session.update_view_config(view_config)?; let draw_task = renderer.draw(async { task.await?; + renderer.apply_pending_plugin()?; let plugin = renderer.get_active_plugin()?; let plugin_update = if let Some(x) = plugin_config { wasm_bindgen::JsValue::from_serde_ext(&*x).unwrap() diff --git a/rust/perspective-viewer/src/rust/model/update_and_render.rs b/rust/perspective-viewer/src/rust/model/update_and_render.rs index a2fb04019b..eb44d654de 100644 --- a/rust/perspective-viewer/src/rust/model/update_and_render.rs +++ b/rust/perspective-viewer/src/rust/model/update_and_render.rs @@ -66,6 +66,7 @@ async fn update_and_render(session: Session, renderer: Renderer) -> ApiResult<() return Ok(()); } + renderer.apply_pending_plugin()?; let view = session.validate().await?; renderer.draw(view.create_view()).await } diff --git a/rust/perspective-viewer/src/rust/renderer.rs b/rust/perspective-viewer/src/rust/renderer.rs index a1477a44a1..c42998ec80 100644 --- a/rust/perspective-viewer/src/rust/renderer.rs +++ b/rust/perspective-viewer/src/rust/renderer.rs @@ -70,6 +70,7 @@ pub struct RendererMutData { plugins_idx: Option, timer: MovingWindowRenderTimer, selection: Option, + pending_plugin: Option, } type RenderLimits = (usize, usize, Option, Option); @@ -114,6 +115,7 @@ impl Renderer { plugins_idx: None, selection: None, timer: MovingWindowRenderTimer::default(), + pending_plugin: None, }), draw_lock: Default::default(), plugin_changed: Default::default(), @@ -172,7 +174,7 @@ impl Renderer { /// error. pub fn get_active_plugin(&self) -> ApiResult { if self.0.borrow().plugins_idx.is_none() { - self.set_plugin(Some(&PLUGIN_REGISTRY.default_plugin_name()))?; + let _ = self.apply_pending_plugin()?; } let idx = self.0.borrow().plugins_idx.unwrap_or(0); @@ -221,20 +223,61 @@ impl Renderer { self.get_active_plugin().unwrap().set_render_warning(false); } - /// Set the active plugin to the plugin registerd as `name`, or the default - /// plugin if `None` is provided. - /// - /// # Arguments - /// - `update` The `PluginUpdate` behavior to set. - pub fn update_plugin(&self, update: &PluginUpdate) -> ApiResult { - match update { - PluginUpdate::Missing => Ok(false), - PluginUpdate::SetDefault => self.set_plugin(None), - PluginUpdate::Update(plugin) => self.set_plugin(Some(plugin)), + pub fn get_next_plugin_metadata( + &self, + update: &PluginUpdate, + ) -> Option { + let default_plugin_name = PLUGIN_REGISTRY.default_plugin_name(); + let name = match update { + PluginUpdate::Missing => return None, + PluginUpdate::SetDefault => default_plugin_name.as_str(), + PluginUpdate::Update(plugin) => plugin, + }; + + let idx = self.find_plugin_idx(name).expect("f"); + + let changed = !matches!( + self.0.borrow().plugins_idx, + Some(selected_idx) if selected_idx == idx + ); + + if changed { + self.borrow_mut().pending_plugin = Some(idx); + self.get_plugin(name) + .and_then(|x| x.get_requirements()) + .ok() + } else { + None + } + } + + pub fn apply_pending_plugin(&self) -> ApiResult { + let xxx = self.borrow_mut().pending_plugin.take(); + if let Some(idx) = xxx { + let changed = !matches!( + self.0.borrow().plugins_idx, + Some(selected_idx) if selected_idx == idx + ); + + if changed { + self.borrow_mut().plugins_idx = Some(idx); + let plugin: JsPerspectiveViewerPlugin = self.get_active_plugin()?; + self.borrow_mut().metadata = plugin.get_requirements()?; + self.plugin_changed.emit(plugin); + } + + Ok(changed) + } else { + if self.0.borrow().plugins_idx.is_none() { + self.set_plugin(Some(&PLUGIN_REGISTRY.default_plugin_name()))?; + } + + Ok(false) } } fn set_plugin(&self, name: Option<&str>) -> ApiResult { + self.borrow_mut().pending_plugin = None; let default_plugin_name = PLUGIN_REGISTRY.default_plugin_name(); let name = name.unwrap_or(default_plugin_name.as_str()); let idx = self diff --git a/rust/perspective-viewer/src/rust/session.rs b/rust/perspective-viewer/src/rust/session.rs index 2bedc42fef..14ed602dd9 100644 --- a/rust/perspective-viewer/src/rust/session.rs +++ b/rust/perspective-viewer/src/rust/session.rs @@ -166,6 +166,10 @@ impl Session { let poll_loop = LocalPollLoop::new(move |(message, reconnect): (ApiError, _)| { set_error(message.clone()); session.borrow_mut().error = Some(TableErrorState(message, reconnect)); + if let Some(sub) = session.borrow_mut().view_sub.take() { + sub.dismiss(); + } + Ok(JsValue::UNDEFINED) }); @@ -601,7 +605,7 @@ impl Session { } async fn validate_view_config(&self) -> ApiResult<()> { - let config = self.borrow().config.clone(); + let mut config = self.borrow().config.clone(); let table_columns = self .metadata() .get_table_columns() @@ -619,13 +623,11 @@ impl Session { .ok_or_else(|| apierror!(NoTableError))? .clone(); - let valid_recs = table.validate_expressions(config.expressions).await?; - let expression_names = self.metadata_mut().update_expressions(&valid_recs)?; - - // re-fetch config after `await`; `expressions` and `all_columns` are ok, - // but `config` may have changed as it is unlocked. - let mut config = self.borrow().config.clone(); + let valid_recs = table + .validate_expressions(config.expressions.clone()) + .await?; + let expression_names = self.metadata_mut().update_expressions(&valid_recs)?; if config.columns.is_empty() { config.columns = table_columns.into_iter().map(Some).collect(); } diff --git a/rust/perspective-viewer/src/rust/session/view_subscription.rs b/rust/perspective-viewer/src/rust/session/view_subscription.rs index 6b8fb1427d..767dd1d24c 100644 --- a/rust/perspective-viewer/src/rust/session/view_subscription.rs +++ b/rust/perspective-viewer/src/rust/session/view_subscription.rs @@ -155,6 +155,10 @@ impl ViewSubscription { pub async fn delete(self) -> ApiResult<()> { self.data.internal_delete().await } + + pub fn dismiss(self) { + self.data.is_deleted.set(true); + } } // Conveniently lift [`ViewSubscription::delete`] to a commonly used storage diff --git a/tools/perspective-bench/cross_platform_suite.mjs b/tools/perspective-bench/cross_platform_suite.mjs index af171b4c19..cace820806 100644 --- a/tools/perspective-bench/cross_platform_suite.mjs +++ b/tools/perspective-bench/cross_platform_suite.mjs @@ -174,6 +174,10 @@ export async function view_suite(perspective, metadata) { export async function table_suite(perspective, metadata) { async function before_all() { + // perspective.on_error((msg) => { + // console.error("Fucked", msg); + // }); + try { const table = await perspective.table( new_superstore_table(metadata) diff --git a/tools/perspective-bench/python_suite.mjs b/tools/perspective-bench/python_suite.mjs index 49d036b8e1..76274f791b 100644 --- a/tools/perspective-bench/python_suite.mjs +++ b/tools/perspective-bench/python_suite.mjs @@ -28,6 +28,10 @@ const __dirname = url.fileURLToPath(new URL(".", import.meta.url)).slice(0, -1); const CLIENT_VERSION = { master: "@finos/perspective", + "3.6.0": "perspective-3-6-0", + "3.5.0": "perspective-3-5-0", + "3.4.0": "perspective-3-4-0", + "3.3.0": "perspective-3-3-0", "3.2.0": "perspective-3-2-0", "3.1.0": "perspective-3-1-0", "3.0.3": "perspective-3-0-0", @@ -53,7 +57,8 @@ perspective_bench.suite( console.log(version); const { default: perspective } = await import(CLIENT_VERSION[version]); const client = await perspective.websocket( - "ws://127.0.0.1:8082/websocket" + "ws://127.0.0.1:8082/websocket", + { maxPayload: 1024 * 1024 * 1024 } ); const metadata = { version, version_idx }; diff --git a/tools/perspective-bench/src/python/server.py b/tools/perspective-bench/src/python/server.py index 497597ba1c..782d6aa7a0 100644 --- a/tools/perspective-bench/src/python/server.py +++ b/tools/perspective-bench/src/python/server.py @@ -61,7 +61,8 @@ def make_app(): tornado.web.StaticFileHandler, {"path": "../../node_modules/"}, ), - ] + ], + websocket_max_message_size=1000 * 1024 * 1024, ) if __name__ == "__main__": @@ -86,7 +87,8 @@ def make_app(perspective_server): tornado.web.StaticFileHandler, {"path": "../../node_modules/"}, ), - ] + ], + websocket_max_message_size=1000 * 1024 * 1024, ) if __name__ == "__main__":