Skip to content

fix computer controls hang - #92

Merged
rgarcia merged 1 commit into
mainfrom
computer-controls-hang
Nov 5, 2025
Merged

rgarcia merged 1 commit into
mainfrom
computer-controls-hang

Conversation

@rgarcia

@rgarcia rgarcia commented Nov 5, 2025 •

Copy link
Copy Markdown
Contributor

using --sync was ill-advised, it doesn't seem to detect when no mouse movements are necessary to achieve a mousemove, so getting rid of it for now


Note

Removes the --sync flag from all xdotool mousemove calls to prevent hanging during mouse operations.

  • Backend (computer controls) in server/cmd/api/api/computer.go:
    • Remove --sync from xdotool mousemove in:
      • MoveMouse
      • ClickMouse
      • Scroll
      • DragMouse (initial move to start point)

Written by Cursor Bugbot for commit 429cec9. This will update automatically on new commits. Configure here.

@rgarcia
rgarcia requested a review from Sayan- November 5, 2025 14:47
@mesa-dot-dev

mesa-dot-dev Bot commented Nov 5, 2025

Copy link
Copy Markdown

Mesa Description

using --sync was ill-advised, it doesn't seem to detect when no mouse movements are necessary to achieve a mousemove, so getting rid of it for now

Description generated by Mesa. Update settings

@rgarcia

rgarcia commented Nov 5, 2025 •

Copy link
Copy Markdown
Contributor Author

tested with this, which randomly hangs for 15s prior to the change

#!/bin/bash
# Test script for scroll down endpoint - runs 10 times with timing

BASE_URL="http://localhost:444"

echo "=== Testing /computer/scroll (vertical down) - 10 iterations ==="
echo ""

total_time=0
times=()

for i in {1..10}; do
  start_time=$(date +%s.%N)
  
  curl -X POST "${BASE_URL}/computer/scroll" \
    -H "Content-Type: application/json" \
    -d '{
      "x": 500,
      "y": 500,
      "delta_y": 3
    }' \
    -s -o /dev/null
  
  end_time=$(date +%s.%N)
  elapsed=$(awk "BEGIN {printf \"%.4f\", $end_time - $start_time}")
  times+=($elapsed)
  total_time=$(awk "BEGIN {printf \"%.4f\", $total_time + $elapsed}")
  
  echo "Iteration $i: ${elapsed}s"
done

echo ""
echo "=== Results ==="
echo "Total time: ${total_time}s"
avg_time=$(awk "BEGIN {printf \"%.4f\", $total_time / 10}")
echo "Average time: ${avg_time}s"

@rgarcia
rgarcia merged commit bb066c5 into main Nov 5, 2025
5 of 6 checks passed
@rgarcia
rgarcia deleted the computer-controls-hang branch November 5, 2025 14:49

@mesa-dot-dev mesa-dot-dev Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Performed full review of ce46982...429cec9

Analysis

  1. Removing the --sync flag from xdotool mousemove commands eliminates hanging issues but introduces potential race conditions where subsequent mouse actions (clicks, drags) may occur before the cursor reaches its intended position.

  2. The implementation lacks any alternative synchronization mechanism to ensure cursor positioning completes before dependent operations execute, which could lead to clicks, drags, and scrolls happening at incorrect coordinates.

  3. No specific testing has been implemented to verify operations work correctly at boundary conditions, such as when the cursor needs to move significant distances versus when it's already at the target position.

Tip

Help

Slash Commands:

  • /review - Request a full code review
  • /review latest - Review only changes since the last review
  • /describe - Generate PR description. This will update the PR body or issue comment depending on your configuration
  • /help - Get help with Mesa commands and configuration options

1 files reviewed | 0 comments | Edit Agent Settings • Read Docs

@mesa-dot-dev mesa-dot-dev Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Performed full review of ce46982...429cec9

Analysis

  1. Removing --sync trades synchronization guarantees for performance, potentially introducing race conditions if any calling code assumes mouse operations complete before the function returns.

  2. No tests were added to prevent regression or verify that mouse operations no longer hang, leaving the fix vulnerable to future changes.

  3. The PR lacks documentation updates explaining the new asynchronous behavior of mouse operations, which could lead to incorrect usage by other developers.

  4. No consideration of adding alternative verification logic or retry mechanisms for critical operations where position accuracy is essential.

  5. Inconsistent approach to synchronization across the codebase - some mouse operations already didn't use --sync, suggesting this problem was partially understood before.

Tip

Help

Slash Commands:

  • /review - Request a full code review
  • /review latest - Review only changes since the last review
  • /describe - Generate PR description. This will update the PR body or issue comment depending on your configuration
  • /help - Get help with Mesa commands and configuration options

1 files reviewed | 0 comments | Edit Agent Settings • Read Docs

tnsardesai added a commit that referenced this pull request Oct 15, 2026
## What

- Adds runtime browser timezone, locale, and language updates through
`PUT /browser/location`, without restarting Chromium. It returns once
the running browser confirms the location, so `200` means applied,
except in a renderer blocked by a dialog or a long task, which applies
it when it resumes.
- Adds `GET /browser/location` to the OpenAPI spec, served by the
generated server: the accepted location, the location the running
browser has confirmed, per-component convergence, and the capability
version (`BrowserLocationBundle`, `BrowserLocationComponents`,
`BrowserLocationStatus`). It returns `501` when the running browser
lacks the location commands.
- Persists the accepted location so a restarted Chromium starts with its
timezone and `--accept-lang`, applies it again before an image-driven
restart returns (failing the restart if it can't), and lets Chromium's
timezone watcher follow later `/etc/localtime` changes.
- Adds a generated capability manifest (412 locales, 598 IANA timezones)
and location validation, with regenerated OpenAPI code.
- Exports convergence metrics and blocks the internal location CDP
commands on customer DevTools connections, including inside non-flat
`Target.sendMessageToTarget` messages.

## Why

- The Kernel API (kernel/kernel#4208) changes a running browser's
location and must report success only after Chromium has applied it to
renderers, workers, timezone state, and network contexts.

## How

- **Set:** `PUT /browser/location` applies the location within the
request. `200` means the browser confirmed it. A stale or conflicting
generation returns `409`; a browser known not to support location
returns `501` before anything is stored. If the location doesn't apply,
including a browser found unsupported only after acceptance, it returns
`500`; the location stays accepted, and resending it retries. The
endpoint is separate from `/configure` so the session's proxy can
restrict writes to the control plane, which persists the session's
location (kernel/kernel#4208).
- **Blocked renderers:** a renderer whose main thread is blocked by a
dialog or a long task can't acknowledge. After one second, renderers
Chromium reports as pending (`renderersPending`) no longer hold back an
update whose browser, timezone, and network components converged; they
apply it when they resume. `components.renderers_pending` reports them.
The image watches them until they acknowledge; if one then reports a
component that didn't converge, `applied` is cleared and the location is
sent again.
- **Ordering:** every location carries a generation (at most 2^53-1, the
largest a CDP number holds). The image accepts a higher generation,
treats a resend of the accepted location as a retry that the browser
must confirm again, and returns `409` for a lower or conflicting one. If
Chromium already holds a generation the image didn't send, the image
moves above it instead of retrying a stale one.
- **Timezone:** ICU names the host timezone after the file
`/etc/localtime` resolves to, and tzdata ships 151 of the 598
identifiers as links (`UTC` to `Etc/UTC`, `Europe/Bratislava` to
`Europe/Prague`). `/etc/localtime` therefore points at a regular-file
copy at `/run/kernel-location/zoneinfo/<id>`, so Chromium reports
exactly the requested identifier. `GMT` is hosted as `Etc/GMT`, because
ICU treats `GMT` as its built-in fixed zone, which pages report as
`+00:00`. The launcher uses the same path.
- **Apply:** the accepted location is persisted first. Each round
rewrites `/etc/localtime`, sends the location to Chromium once, and
polls its acknowledgements on one connection for up to 1.5 s; rounds
repeat for up to five seconds. `applied` is set when every component
acknowledges, apart from renderers Chromium reports pending. A new
attempt clears the previous attempt's error.
- **Support:** each Chromium process is probed once with
`Browser.getKernelBrowserLocation`. A browser without it makes both
`GET` and `PUT /browser/location` return `501`, and a location accepted
earlier fails once instead of retrying.
- kernel/kernel#4208 calls both endpoints through the generated client.
- **Live status:** `GET /browser/location` reads `applied` and its
components from the running Chromium, so a restarted or unreachable
browser never reports it.
- **Restarts:** every restart the image performs (configure, flags,
policies, extensions, display) applies the accepted location to the new
Chromium before it returns, so `start_url` and later requests don't see
the defaults. If the browser doesn't confirm it, configure, flags,
policies and extension upload return `500` (configure before navigating,
and without restarting Chromium again or rolling back extensions that
loaded); a display change logs it, as it does any restart failure. The
lifecycle loop keeps retrying either way.
- **Restarts the image didn't start** (a crash): Chromium starts with
the location's timezone and `Accept-Language`, but its `Intl` locale is
the region's until the lifecycle loop applies the location, about two
seconds later. On Linux Chromium ignores `--lang` and resolves its
locale from the environment to one it ships translations for (this build
ships only English ones), so no startup flag or variable can set it;
pages that load in that window see the mismatch.
- **Startup:** the launcher uses the persisted timezone, then
`KERNEL_BROWSER_TIMEZONE`, then `TZ`, and replaces `--accept-lang` with
the persisted location's languages. Chromium starts without `TZ` so it
observes `/etc/localtime` changes.
- **Validation:** values are checked against the image's versioned
Chromium/tzdata capability manifest.

## Context

- Requires kernel/kernel-browser#92 for the internal CDP commands.
Consumed by kernel/kernel#4208, which is gated by the flag in
kernel/infra#1601.
- Production images build from `kernel-images-private`. After merge,
sync this change there; with the current `KERNEL_BROWSER_VERSION`, those
images report location as unsupported (`501`), so they're safe to
promote.
- Bump `KERNEL_BROWSER_VERSION` to a release containing
kernel/kernel-browser#92 only after this PR is in the image and
kernel/kernel#4208's metro-api is deployed. Customer CDP connections
reach Chromium as root browser sessions, so only this PR's proxy filter
keeps the location commands off them, and only #4208's passthrough guard
keeps customers off `PUT /browser/location`.
- No breaking changes: both location endpoints are new.

## Testing & Visuals

- `go vet ./...` and `go test -race` for every non-e2e package. New unit
tests cover the zoneinfo copy (aliases resolve to the requested
identifier), a browser without the location commands (501, 400, no
retries), clearing the previous error on retry, moving above Chromium's
generation, the generation cap, and nested `Target.sendMessageToTarget`
filtering.
- E2E on the headless image with the kernel/kernel-browser#92 Chromium
build (the reviewed `368c617` and the final `b234c63`):
`TestBrowserLocationCapabilities`,
`TestBrowserTimezoneFollowsLocaltime`,
`TestBrowserLocationRuntimeUpdate`, and the new
`TestBrowserLocationEveryTimeZone`, which applies all 598 manifest
timezones in about 40 s and checks that the page and an existing worker
resolve each as Intl resolves the identifier. On stock Chromium, the new
`TestBrowserLocationUnsupportedBrowser` passes (it also runs in CI).
- Hot-patched A/B on a running Kernel browser (Unikraft VM), swapping in
this branch's image API and launcher and the #92 Chromium:
- Before: `UTC` and `US/Eastern` never applied (timezone component false
after 5 s). Resending after a failure still reported the old error. With
stock Chromium the image accepted a location, changed the timezone but
not the locale, and retried indefinitely. Non-flat
`Target.sendMessageToTarget` reached the internal commands.
- After: `UTC`, `US/Eastern`, `Europe/Bratislava`, `GMT`,
`Asia/Calcutta`, and `Europe/Kiev` each applied in about 140 ms, with
the page showing what Intl resolves for each. A resend after an injected
failure cleared the error at once and applied in 0.17 s. Stock Chromium
got `501`/`400`, nothing persisted, and no retries.
`Target.sendMessageToTarget` was rejected at the proxy, and with the
final #92 build `Target.exposeDevToolsProtocol` page script was rejected
by Chromium.
- Synchronous configure, hot-patched on a running Kernel browser
(Hypeman VM): with stock Chromium, `GET /browser/location` returned
`501` and configure `400`; with the #92 build, configure returned `200`
after 1.7 s (first update after a Chromium restart) and 112 ms, the
status read right after showed the location applied with every component
confirmed, and a stale generation returned `409`. Unit tests cover
waiting for the apply, a failed apply (`500`, still accepted), a
location replaced while waiting (`409`), a browser found unsupported
after acceptance (`500`), and a resend of an applied location that the
browser can no longer confirm (`500` rather than a `200` from the
earlier confirmation).
- Self-review follow-up at `97d5d94`: unit tests cover pending renderers
(applied after the one-second wait, with the pending count reported),
the confirmation rule, re-applying the accepted location after a restart
instead of trusting the previous Chromium, and the launcher's language
flags. Before the fix, on a running Kernel browser (Hypeman VM), a
configure that restarted Chromium with a `start_url` returned `200` with
the location unapplied, and that navigation sent `Accept-Language:
en-US`.
- Hot-patched at `52ad317` (the private sync branch with this PR) with
the kernel/kernel-browser#92 build `598340f`, on a running Kernel
browser (Hypeman VM):
- Stock Chromium: `GET` and `PUT /browser/location` both returned `501`.
That nothing is stored on a `501` is covered by a unit test, not checked
on the VM.
- `PUT` gen 1 `de-DE`/`Asia/Tokyo` returned `200` after 1.7 s (first
update after a Chromium restart); a page on httpbin then sent
`Accept-Language: de-DE,de;q=0.9`, with `navigator`, `Intl` and the
timezone matching. The same generation with other values returned `409`,
and `POST /configure` with `browser_location` returned `400 unknown form
field`.
- With an `alert()` open in a second tab, `PUT` gen 2
`fr-FR`/`Europe/Paris` returned `200` after 1.06 s with
`renderers_pending: 1`; once the alert closed, every component reported
converged and that tab reported `fr-FR` and `Europe/Paris`.
- A configure that restarted Chromium with a `start_url` returned `200`
with the location still applied right after, 3 s later and 5 s later,
and the `start_url` request sent `Accept-Language: fr-FR,fr;q=0.9`.
- Review follow-up at `7c237a1`, hot-patched (as `2616fe8` on the
private sync branch) on a running Kernel browser (Hypeman VM) with the
#92 build: with a location applied, a configure that restarted Chromium
with a `start_url` returned `200` and the page reported `de-DE` in
`Intl`, `navigator`, and `Accept-Language`. With stock Chromium swapped
in under the same accepted location, the same configure returned `500`
("browser location was not restored after chromium restart") and didn't
navigate; the tab Chromium restored read `Intl` `en-US` beside
`navigator.language` `de-DE`. Run locally, the #92 build with
`--lang=de-DE --accept-lang=de-DE,de` reported `Intl` `en-US`, and
`LANGUAGE=de_DE` didn't change it. Unit tests cover the restart failure
and a pending renderer that later reports a timezone that didn't
converge (sent again, then applied), and fail without the fix.
- The e2e suite wasn't re-run on this commit.
- Stock CI skips the custom-browser e2e tests; they need
`E2E_KERNEL_BROWSER=1` and an image with the #92 build.
- Visuals: not applicable; this PR changes image and control-plane
behavior only.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Touches Chromium lifecycle, host `/etc/localtime`, and synchronous API
behavior on restart; mis-convergence can fail configure/extension
restarts or leave sessions on wrong locale/timezone until retried.
> 
> **Overview**
> Adds **runtime browser location** (timezone, locale, languages) via
new `GET`/`PUT /browser/location`, with generation ordering, capability
validation, durable state, and reconciliation through Kernel CDP
commands until Chromium reports component convergence (with special
handling for pending renderers).
> 
> The image updates `/etc/localtime` via zoneinfo copies so ICU reports
the requested IANA id, persists the accepted bundle for restarts, and
**re-applies location after image-driven Chromium starts**—failing the
operation if restore does not confirm. The **chromium launcher** boots
with that timezone and `--accept-lang` (no `TZ`). Customer DevTools
traffic **cannot** call the internal location CDP methods (including
nested `Target.sendMessageToTarget`). Prometheus metrics and e2e/unit
tests cover the flow.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
7c237a1. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: tnsardesai <18272584+tnsardesai@users.noreply.github.com>
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.

2 participants