fix: stabilize webvnc reconnects

This commit is contained in:
Peter Steinberger 2026-05-06 07:40:01 +01:00
parent 5aaa848d46
commit 6ba12e4872
No known key found for this signature in database
6 changed files with 32 additions and 11 deletions

View File

@ -3,6 +3,7 @@
package cli
import (
"os"
"os/exec"
"syscall"
)
@ -10,3 +11,14 @@ import (
func configureDaemonCommand(cmd *exec.Cmd) {
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
}
func stopDaemonProcess(process *os.Process) error {
if process == nil {
return nil
}
err := syscall.Kill(-process.Pid, syscall.SIGTERM)
if err == syscall.ESRCH {
return nil
}
return err
}

View File

@ -2,6 +2,16 @@
package cli
import "os/exec"
import (
"os"
"os/exec"
)
func configureDaemonCommand(_ *exec.Cmd) {}
func stopDaemonProcess(process *os.Process) error {
if process == nil {
return nil
}
return process.Kill()
}

View File

@ -194,7 +194,7 @@ func (a App) startWebVNCDaemon(args []string, leaseID string) error {
return exit(5, "release WebVNC daemon process: %v", err)
}
fmt.Fprintf(a.Stdout, "webvnc daemon: pid=%d log=%s\n", pid, logPath)
fmt.Fprintf(a.Stdout, "webvnc daemon: stop with kill $(cat %s)\n", pidPath)
fmt.Fprintln(a.Stdout, "webvnc daemon: stop with crabbox webvnc --id <lease-id-or-slug> --stop")
return nil
}
@ -277,7 +277,7 @@ func (a App) stopWebVNCDaemon(leaseID string) error {
if err != nil {
return exit(5, "find WebVNC daemon pid %d: %v", pid, err)
}
if err := process.Kill(); err != nil {
if err := stopDaemonProcess(process); err != nil {
return exit(5, "stop WebVNC daemon pid %d: %v", pid, err)
}
_ = os.Remove(pidPath)

View File

@ -1226,14 +1226,12 @@ export class FleetDurableObject implements DurableObject {
);
}
const existingViewer = this.webVNCViewers.get(lease.id);
if (existingViewer) {
closeSocket(existingViewer, 1012, "replaced by a newer WebVNC viewer");
this.clearWebVNCViewer(lease.id, existingViewer);
if (existingViewer?.readyState === WebSocket.OPEN) {
const command = webVNCBridgeCommand(lease);
return json(
{
error: "webvnc_bridge_reset",
message: `another viewer was active; restart or wait for the bridge to reconnect with: ${command}`,
error: "webvnc_viewer_active",
message: `another viewer is active; close stale WebVNC tabs or wait before reconnecting with: ${command}`,
command,
},
{ status: 409 },

View File

@ -231,10 +231,10 @@ export function portalVNC(lease: LeaseRecord): Response {
return;
}
if (state?.viewerConnected) {
setStatus("another viewer is connected; close stale tabs if this resets", "warn");
} else {
setStatus(retryAttempt ? "bridge connected; opening viewer" : "connecting");
scheduleRetry("another viewer is connected; close stale WebVNC tabs");
return;
}
setStatus(retryAttempt ? "bridge connected; opening viewer" : "connecting");
rfb = new RFB(screen, wsURL.toString(), options);
rfb.scaleViewport = true;
rfb.resizeSession = false;

View File

@ -745,6 +745,7 @@ describe("fleet lease identity and idle", () => {
expect(pageBody).toContain("/portal/leases/cbx_000000000001/vnc/status");
expect(pageBody).toContain("vnc-copy");
expect(pageBody).toContain("no bridge connected; run the bridge command below");
expect(pageBody).toContain("another viewer is connected; close stale WebVNC tabs");
expect(pageBody).toContain('fragment.get("username")');
expect(pageBody).toContain('types.includes("username")');
expect(pageBody).not.toContain("cdn.jsdelivr.net");