Repository navigation
Releases: Flux159/nebula
Release list
v0.2.7
v0.2.6
v0.2.5
On Windows with an AMD CPU, the guest's clock ran about 11% slow, and in coarse steps. Nothing failed, so nothing reported it. Servers in the guest ran slow and in bursts instead: in Ragnarok Offline, monsters and players appeared to teleport on machines with plenty of power (ragnarokoffline.app#144). This release fixes the clock and writes the guest's clock health to nebulad.log. Intel machines, macOS and Linux were not affected.
The guest could not measure its own TSC on AMD
Linux reads the TSC frequency from CPUID leaf 0x15, which nebula's WHP backend provides. But Linux only trusts that leaf on Intel. Everywhere else it times the TSC by polling PIT channel 2, and needs at least 1000 reads in 10 ms. Under WHP every read exits to userspace, so the calibration fails:
tsc: Unable to calibrate against PIT
tsc: No reference (HPET/PMTIMER) available
tsc: Marking TSC unstable due to could not calculate TSC khz
clocksource: Switched to clocksource refined-jiffies
Unstable clock detected, switching default tracing clock to "global"
refined-jiffies only moves forward when a timer interrupt arrives, and on AMD those came from an emulated PIT that drifted.
On AMD hosts, and on any host where Linux won't read leaf 0x15, the krun worker now measures the host TSC against QueryPerformanceCounter. It boots the guest with tsc_early_khz=<khz> tsc=reliable. WHP runs the guest on the host's TSC, so the host's measurement is the guest's frequency.
The emulated PIT's channel 0 also fires on fixed deadlines now. Previously it slept for one period after each tick, so every oversleep added to the drift.
Measured on Windows 10 19045 with the AMD code path forced (guest max basic CPUID leaf clamped to 0x10), guest /proc/uptime against the host over 60 s:
| clocksource | guest time / host time | |
|---|---|---|
| v0.2.4 | refined-jiffies | 0.889 |
| v0.2.4 + deadline PIT only | refined-jiffies | 0.997 |
| v0.2.5 | tsc | 1.0000 |
| v0.2.5, Intel path (unchanged) | tsc | 1.0001 |
The guest clock in nebulad.log
About 30 s after boot, nebulad logs the guest's clocksource, its timer devices and the kernel's calibration messages, as guest timers: lines. After 90 s, and then every 5 minutes, it logs:
INFO nebulad::clock: guest clock: 1.0001x host over 61s, sleep(1s) took 1000ms, timer irqs/s pit=0 local=127
The line is a WARN when the guest clock is more than 1% off, when a one-second sleep takes over 1.1 s, or when the PIT is still driving the guest's timers. Set NEBULA_NO_CLOCK_REPORT to turn it off.
Embedders: new guest images come with this release, so install-image applies after upgrading. The clock fix itself is on the host side, in nebulad and the krun worker.
v0.2.4
On Windows, stopping the engine took ten seconds and then reported an error, and on slower machines it took minutes. An up sent during that time said the engine was already running while it was still shutting down. This release fixes both. Nothing else has changed since v0.2.3.
The guest could not power off on x86
libkrun's x86 machine has no ACPI, so when the guest asked to power off, the kernel printed reboot: Power off not available: System halted instead and halted every CPU. On Linux, KVM reports that halt to libkrun and the VM ends. On Windows, libkrun's WHP backend keeps waiting on a halted CPU, so every graceful stop waited out its full timeout and then the worker was killed.
On x86 the guest now reboots instead. The reset goes through the i8042 controller, which libkrun treats as a VM exit on both hypervisors, and libkrun's own init shuts down the same way. arm64 still powers off through PSCI.
Measured on Windows 11 (i9-13900K, WHP):
| v0.2.3 | v0.2.4 | |
|---|---|---|
nebula down |
10 s, then stop failed: VM entered Failed state (exit 1) |
0.5 s, exit 0 |
nebulad gone |
14 s after the command started | before down returns |
A forced stop no longer runs twice
Killing the krun worker makes it exit non-zero, and that reads as Failed. The stop path waited for Stopped, so it failed immediately. The shutdown then treated the VM as still running and repeated the whole graceful attempt, this time against an agent that no longer existed and with no read timeout. Now Failed also counts as stopped, and the shutdown request to the agent is capped at 5 seconds.
down and up agree about when the engine is gone
nebula downreturns only after the daemon has exited, not just after it replies. Before, a caller that started the engine again in that gap found the old daemon still holding the socket, the ports and the VM.nebula upwaits for a daemon that is shutting down instead of reporting it as already running.nebula statusshowsnebula: stopping (daemon shutting down)during a stop.- While a stop is in progress, the daemon answers every request with
nebulad is shutting down. It no longer ends its listener early, which also started a second shutdown that exited the process in the middle of the first.
Embedders: new guest images come with this release, so install-image applies after upgrading. Older CLIs keep working against the new daemon; during a stop they get an ordinary error message.
v0.2.3
release: 0.2.3 Carries the Windows docker-slim load hang fix (#34): a proxy that abandoned a drain when the guest's RX virtqueue ran dry had nothing to wake it, because Windows socket readiness is one-shot, so a bulk upload parked with both sides idle. Downstream this was a first run appearing to hang on "Loading the bundled server images". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012agyW3Xz5AjXUjNkAE2y2y
v0.2.2
v0.2.2 - WHP guests boot on AMD hosts
v0.2.1
v0.2.1 - data disk creation is atomic and sparse on Windows
v0.2.0
v0.1.9
Three fixes for problems an embedded app hit in production.
A port collision fails fast instead of half-starting (#22)
Two instances sharing api_port/dns_port/k8s_port both used to start. The loser bound what it could, reported itself healthy, and broke a docker build ten minutes later with an unrelated-looking gRPC error. nebulad now probes those ports before booting the VM and refuses to start, naming the port and the NEBULA_HOME holding it. Set port_conflict = "auto" in config.toml to bind free ports instead. nebula status and GET /v1alpha1/status now also report every listener and whether it actually bound.
nebulad says why it exited (#23)
Every exit — down, a signal, a dead vessel, a fatal error — logs its reason, uptime and what was running, and a start that finds no exit record reports that the previous run ended badly. SIGHUP is caught now too: nebula up spawns the daemon without its own session, so closing the launching terminal killed it silently.
install-image writes ~35x less to disk (#24)
The shipped rootfs is 1 GiB of which ~23 MB is non-zero, and installing it wrote ~3.2 GB: a staging copy plus two dense copies. It now decompresses straight to its destination and skips the zeros. Measured on Windows with Defender on, full install from the shipped .gz: 3182 MiB → 94 MiB on disk, 1.2s → 0.5s. vessels reset and vessel creation get the same treatment.
Upgrading an embedded engine is the same command as before — ship the new .gz pair and re-run nebula install-image.