Fix server destroy lifecycle race - #712
Conversation
e3c5327 to
92c918d
Compare
92c918d to
bcf6d1f
Compare
|
Thank you @ViktorJannicke for this important PR. I can reproduce the issue you reported on Linux using the code below: void backend(webui_event_t* e) {
webui_destroy(e->window);
}
int main() {
size_t win = webui_new_window();
webui_bind(win, "backend", backend);
webui_show(win, "<html><head><script src=\"webui.js\"></script></head> <button onclick=\"backend()\">Call Backend</button> </html>");
webui_wait();
return 0;
}The recent days I was working on a new test design where the window server thread stays alive until the window is explicitly destroyed or when the app exits, instead of the current design where the server thread stops by a simple window close event. I believe the new design will make things simpler and safer. Please let me finish the new design first and fix the crash you reported. If the new design internal tests did not go well, I will switch to your PR, but we should avoid changing CivetWeb vendor code to allow a flawless future upgrade. |
|
A core re-design is done in 77cbaae and many other PRs are committed to pass the new Stress Test tool. Please update your WebUI library and let me know if your project is now more stable. |
|
Thanks! I retested current main at 85043e9 on my NixOS Machine. The original webui_destroy()-from-callback crash is gone, my 32-cycle server destroy regression passes, and all 25 CS-WebUI native/managed tests pass ontop of latest main. The lifecycle-related Stress Test stages—concurrent create/destroy, destroy from callback, and rapid open/close—also pass. |
|
Since this PR is redundant I will close it now. |
Happy to hear that.
Yes, it is a known issue. It passes on Windows but fails on Linux. I will fix it soon. |
Summary
Fix crashes, use-after-free races, and shutdown deadlocks when a WebUI window is destroyed while its server, callbacks, WebSocket work, WebView work, or show operations are still active.
This introduces coordinated two-phase destruction: new work is rejected immediately, while final memory reclamation waits until every owner of the window and its CivetWeb connections has retired.
Motivation
We recently started using WebUI in a project and, while integrating it more deeply, began exercising window and server lifecycle paths beyond the basic examples. This exposed reproducible crashes when destroying a window from a callback and during repeated server teardown.
Investigating those crashes required following ownership across WebUI’s window state, server threads, callbacks, and the embedded CivetWeb connection storage. The resulting diff is larger than the visible fix might suggest because the affected lifetime rules were spread across several teardown paths. This change consolidates those rules so destruction consistently stops new work, waits for active work to retire, and only then releases the underlying state.
Problem
webui_destroy()could free window state while it was still referenced by:webui_show*()orwebui_start_server()callsDestroying a window from one of its own callbacks was especially unsafe. Waiting synchronously would deadlock because CivetWeb was waiting for that callback to return, while freeing immediately caused use-after-free crashes.
CivetWeb also needed a way to stop accepting new work and interrupt blocked connection I/O without immediately joining workers and freeing connection storage still referenced by WebUI tasks.
Changes
Window lifecycle
webui_destroy()calls wait on the global window registry instead of touching potentially reclaimed per-window synchronization objects.WEBUI_MAX_IDS.Show and global cleanup coordination
webui_show*()andwebui_start_server().webui_clean()wait for both server threads and show operations.webui_clean()is called from a WebUI callback, request application exit and defer process-wide reclamation until the callback has returned.webui_start_server()result lifetimeWorker and monitor shutdown
Vendored CivetWeb carry patch
Add a WebUI-private quiesce operation that:
INVALID_SOCKET.The extension is private to WebUI and is not added to CivetWeb’s public API.
Compatibility
webui_start_server()now provides safer returned-string storage.Verification
Verified on Linux with:
ALTERNATIVE_QUEUECivetWeb configurationswebui_start_server()URL lifetime across destruction and thread exit