Monorepo for Tangled tangled.org
2

Configure Feed

Select the types of activity you want to include in your feed.

spindle/microvm: poll qemu running status, and parse query-status strictly

Signed-off-by: dawn <dawn@tangled.org>

author
dawn
date (Jun 22, 2026, 7:55 PM +0300) commit 200d4f0b parent d67b91ef change-id rqsssssl
+51 -7
+51 -7
spindle/engines/microvm/qemu.go
··· 253 253 if qmpTimeout == 0 { 254 254 qmpTimeout = defaultQMPTimeout 255 255 } 256 - if err := handle.waitForQMP(ctx, qmpTimeout); err != nil { 256 + qmpCtx, cancelQMP := context.WithTimeout(ctx, qmpTimeout) 257 + defer cancelQMP() 258 + if err := handle.waitForQMP(qmpCtx, qmpTimeout); err != nil { 257 259 return nil, err 258 260 } 259 261 260 - status, err := handle.QMPQueryStatus() 262 + status, err := handle.waitForQMPRunning(qmpCtx, qmpTimeout) 261 263 if err != nil { 262 264 return nil, err 263 - } 264 - if status != "running" { 265 - return nil, fmt.Errorf("qemu guest not running (status: %s)", status) 266 265 } 267 266 logger.Info("qemu microvm running", "cid", cid, "status", status) 268 267 ··· 381 380 } 382 381 383 382 var resp struct { 384 - Return struct { 383 + Return *struct { 385 384 Status string `json:"status"` 386 385 } `json:"return"` 387 386 } 388 387 if err := json.Unmarshal(raw, &resp); err != nil { 389 - return "", fmt.Errorf("qmp query-status parse: %w", err) 388 + return "", fmt.Errorf("qmp query-status parse %q: %w", raw, err) 389 + } 390 + if resp.Return == nil { 391 + return "", fmt.Errorf("qmp query-status missing return: %s", raw) 392 + } 393 + if resp.Return.Status == "" { 394 + return "", fmt.Errorf("qmp query-status missing return.status: %s", raw) 390 395 } 391 396 return resp.Return.Status, nil 397 + } 398 + 399 + func (h *QEMUVMHandle) waitForQMPRunning(ctx context.Context, timeout time.Duration) (string, error) { 400 + statusCtx, cancel := context.WithTimeout(ctx, timeout) 401 + defer cancel() 402 + 403 + ticker := time.NewTicker(25 * time.Millisecond) 404 + defer ticker.Stop() 405 + 406 + var lastStatus string 407 + var lastErr error 408 + 409 + for { 410 + status, err := h.QMPQueryStatus() 411 + if err != nil { 412 + lastErr = err 413 + } else { 414 + lastErr = nil 415 + lastStatus = status 416 + 417 + switch status { 418 + case "running": 419 + return status, nil 420 + case "shutdown", "internal-error", "io-error", "guest-panicked": 421 + return "", fmt.Errorf("qemu guest entered unhealthy state before running (status: %s)", status) 422 + } 423 + } 424 + 425 + select { 426 + case <-statusCtx.Done(): 427 + if lastErr != nil { 428 + return "", fmt.Errorf("qemu guest did not reach running state (last status: %s): %w", lastStatus, errors.Join(statusCtx.Err(), lastErr)) 429 + } 430 + return "", fmt.Errorf("qemu guest did not reach running state (last status: %s): %w", lastStatus, statusCtx.Err()) 431 + case <-h.done: 432 + return "", fmt.Errorf("qemu exited before reaching running state: %w", h.Wait()) 433 + case <-ticker.C: 434 + } 435 + } 392 436 } 393 437 394 438 func (h *QEMUVMHandle) QMPSystemPowerdown() error {