Skip to content
Open
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
16 changes: 15 additions & 1 deletion buckaroo/server/websocket_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,21 @@ def on_message(self, message):
def _handle_buckaroo_state_change(self, new_state):
sessions = self.application.settings["sessions"]
session = sessions.get(self.session_id)
if not session or session.mode != "buckaroo":
if not session:
return
if session.mode != "buckaroo":
# Lazy mode (and any future read-only mode) doesn't currently
# plumb state_change → filter into its dataflow. Return a
# structured error rather than the pre-#793 silent drop so the
# client can render "filtering not supported in this mode"
# instead of hanging on the WS read.
self.write_message(json.dumps({
"type": "error",
"error_code": "state_change_unsupported_mode",
"message": (
f"buckaroo_state_change is not supported in "
f"mode={session.mode!r}; this reader is read-only "
"(no filter / cleaning / post-processing).")}))
return
dataflow = session.xorq_dataflow if session.backend == "xorq" else session.dataflow
if dataflow is None:
Expand Down
42 changes: 42 additions & 0 deletions tests/unit/server/test_load_expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,48 @@ async def test_ws_search_pushdown(self):
finally:
shutil.rmtree(builds_root, ignore_errors=True)

@tornado.testing.gen_test
async def test_lazy_state_change_returns_explicit_error(self):
"""Regression for #793: ``_handle_buckaroo_state_change``
returned silently for ``mode != "buckaroo"`` — lazy-polars
sessions had their state_change messages dropped on the floor
with no response, hanging the client until its read timed out.

Must return a structured error instead so callers can render
a sensible "not supported" message.
"""
import asyncio
csv_fd, csv_path = tempfile.mkstemp(suffix=".csv")
os.close(csv_fd)
try:
pd.DataFrame({"a": [1, 2, 3], "b": ["x", "y", "z"]}).to_csv(csv_path, index=False)
await _post(self.get_http_port(), "/load",
{"session": "lazy-sc", "path": csv_path, "mode": "lazy"})

ws = await tornado.websocket.websocket_connect(
f"ws://localhost:{self.get_http_port()}/ws/lazy-sc")
await ws.read_message() # discard initial_state

ws.write_message(json.dumps({
"type": "buckaroo_state_change",
"new_state": {
"post_processing": "", "cleaning_method": "",
"quick_command_args": {"search": ["x"]},
"df_display": "main", "show_commands": False,
"sampled": False, "search_string": "x",
}}))

# Today: nothing comes back; client times out.
# After fix: a structured error frame within a couple seconds.
frame = await asyncio.wait_for(ws.read_message(), timeout=3.0)
self.assertIsNotNone(frame)
d = json.loads(frame)
self.assertEqual(d.get("type"), "error")
self.assertIn("error_code", d)
ws.close()
finally:
os.unlink(csv_path)

@tornado.testing.gen_test
async def test_session_reuse_xorq_then_pandas(self):
"""A client that POSTs /load_expr and then POSTs /load with the
Expand Down
Loading