-
Notifications
You must be signed in to change notification settings - Fork 0
feat: spawn worker process #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
739e635
044c5d2
14d5a23
210dd6a
419b705
1e5554b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,144 @@ | ||
| # Standalone Worker Process Control | ||
|
|
||
| ## Summary | ||
|
|
||
| Add support for starting/stopping a user-specified worker command from the Standalone Settings page. The GUI will spawn the worker with `ABSURD_DATABASE_PATH` (current database path) and `ABSURD_DATABASE_EXTENSION_PATH` (bundled extension path) set, show the running PID, surface a crash indicator when the worker repeatedly exits unexpectedly, and stream recent worker logs. | ||
|
|
||
| ## Goals | ||
|
|
||
| - Let users configure a worker command (e.g. `npx ...`, `uvx ...`) in Settings. | ||
| - Allow start/stop control from Settings. | ||
| - Display worker PID while running. | ||
| - Display a crash indicator if the worker is crashing (rapid, repeated exits). | ||
| - Pass required environment variables when starting the worker. | ||
| - Capture and display recent worker logs (in-memory, capped). | ||
|
|
||
| ## Non-Goals | ||
|
|
||
| - Managing auto-start on app launch beyond "start when command is set". | ||
| - Managing multiple worker processes. | ||
| - Persisting logs to disk or long-term log retention. | ||
| - Bundling a worker binary with the app. | ||
|
|
||
| ## User Experience | ||
|
|
||
| - Settings page shows a "Worker" card with: | ||
| - Text input for a worker command. | ||
| - Status line: "Running (PID ####)", "Stopped", or "Crashing". | ||
| - Start/Stop button (disabled if no command is set). | ||
| - Collapsible "Logs" section with a toggle; when open, display recent output. | ||
| - Crash indicator appears if the worker exits unexpectedly multiple times within a short window (e.g., 3 exits within 60 seconds). | ||
|
|
||
| ### UI Layout (Settings) | ||
|
|
||
| ``` | ||
| Settings | ||
| ------------------------------------------------------------ | ||
| [Version Card] [Database Card] | ||
|
|
||
| [Migrations Card] | ||
|
|
||
| [Worker Card] | ||
| ------------------------------------------------------------ | ||
| Worker | ||
| Run a local worker process for this database. | ||
|
|
||
| Command [ npx absurd-worker.................. ] | ||
| Status [ Running (PID 12345) | Stopped | Crashing ] | ||
|
|
||
| [ Start/Stop ] | ||
|
|
||
| [ Logs ▾ ] | ||
| ------------------------------------------------------------ | ||
| 12:01:22 [stdout] worker started | ||
| 12:01:23 [stderr] warning: ... | ||
| ... | ||
| ``` | ||
|
|
||
| ## Data Model & Persistence | ||
|
|
||
| - Use `tauri_plugin_store` to persist worker configuration in a JSON store (e.g. `worker.json`). | ||
| - Keys: | ||
| - `worker_binary_path` (string, command line). | ||
|
|
||
| ## Backend Design (Tauri) | ||
|
|
||
| ### New State | ||
|
|
||
| - `WorkerState` managed in `AppHandle`: | ||
| - `binary_path: Mutex<Option<String>>` | ||
| - `running: Mutex<Option<RunningWorker>>` | ||
| - `crash_history: Mutex<VecDeque<Instant>>` | ||
| - `log_buffer: Mutex<VecDeque<WorkerLogLine>>` | ||
| - `RunningWorker`: | ||
| - `pid: u32` | ||
| - `child: tauri_plugin_shell::process::Child` | ||
| - `rx: CommandEvent` receiver task handle | ||
| - `WorkerLogLine`: | ||
| - `timestamp: String` (formatted) | ||
| - `stream: "stdout" | "stderr"` | ||
| - `line: String` | ||
|
|
||
| ### New Commands | ||
|
|
||
| - `get_worker_status` -> `{ configuredPath, running, pid, crashing }` | ||
| - `set_worker_binary_path(path: String)` -> updated status | ||
| - `start_worker()` -> updated status | ||
| - `stop_worker()` -> updated status | ||
| - `get_worker_logs` -> `{ lines: WorkerLogLine[] }` | ||
| - `clear_worker_logs` -> `{ lines: WorkerLogLine[] }` (optional) | ||
|
|
||
| ### Spawn Behavior | ||
|
|
||
| - Parse command into program + args (basic quoting supported). | ||
| - Use `DatabaseHandle` to resolve the current `ABSURD_DATABASE_PATH`. | ||
| - Expose a helper to resolve the bundled extension path from `db.rs` for `ABSURD_DATABASE_EXTENSION_PATH`. | ||
| - Spawn using `tauri_plugin_shell`: | ||
| - Command = configured program + args. | ||
| - Env: | ||
| - `ABSURD_DATABASE_PATH=<db_path>` | ||
| - `ABSURD_DATABASE_EXTENSION_PATH=<extension_path>` | ||
| - Capture stdout/stderr lines and append to `log_buffer`. | ||
| - Track process exit: | ||
| - If terminated while `start_worker` initiated and not explicitly stopped, record crash time. | ||
| - Crash indicator = N exits within rolling window (e.g., 3 in 60s). | ||
| - If command changes while running, stop the previous process and restart. | ||
| - Attempt to start on app launch when a command is configured. | ||
|
|
||
| ### Stop Behavior | ||
|
|
||
| - If running, send SIGTERM on Unix (fallback to kill on other platforms). | ||
| - Clear `running` state. | ||
| - Do not mark crash on user-initiated stop. | ||
|
|
||
| ## Frontend Design (Svelte) | ||
|
|
||
| - Extend `SettingsInfo` or add a new API payload for worker status. | ||
| - New UI section in `standalone/src/routes/settings/+page.svelte`: | ||
| - Input bound to worker command. | ||
| - Start/Stop button next to the status badge. | ||
| - Toggleable logs section (collapsed by default). | ||
| - When open, poll or subscribe to log updates from backend. | ||
| - Status badge: | ||
| - Running with PID. | ||
| - Stopped. | ||
| - Crashing (if `crashing === true`). | ||
| - Refresh status on mount and after any start/stop/path changes. | ||
|
|
||
| ## Error Handling | ||
|
|
||
| - Surface start/stop errors to the UI (e.g., toast or inline message). | ||
| - If extension path cannot be resolved, block start and show error. | ||
| - If log streaming fails, show a non-blocking message and keep controls active. | ||
|
|
||
| ## Testing | ||
|
|
||
| - Backend unit tests for: | ||
| - Parsing/persistence of stored worker path. | ||
| - Crash indicator threshold logic. | ||
| - Log buffer trimming to 500 lines. | ||
| - Manual smoke test: | ||
| - Set a valid worker path, start, verify PID. | ||
| - Stop and verify state. | ||
| - Use a dummy executable that exits immediately to trigger crash indicator. | ||
| - Verify logs appear and cap at 500 lines. |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,7 +3,7 @@ use tauri::{async_runtime, Manager}; | |||||||||
| use tauri_plugin_cli::CliExt; | ||||||||||
|
|
||||||||||
| use crate::dev_api::{load_dev_api_enabled, DevApiState}; | ||||||||||
| use crate::{db::DatabaseHandle, worker::spawn_worker}; | ||||||||||
| use crate::{db::DatabaseHandle, worker::load_worker_binary_path}; | ||||||||||
|
|
||||||||||
| mod db; | ||||||||||
| mod db_commands; | ||||||||||
|
|
@@ -48,7 +48,12 @@ pub fn run() { | |||||||||
| db_commands::apply_migrations_all, | ||||||||||
| db_commands::apply_migration, | ||||||||||
| dev_api::get_dev_api_status, | ||||||||||
| dev_api::set_dev_api_enabled | ||||||||||
| dev_api::set_dev_api_enabled, | ||||||||||
| worker::get_worker_status, | ||||||||||
| worker::get_worker_logs, | ||||||||||
| worker::set_worker_binary_path, | ||||||||||
| worker::start_worker, | ||||||||||
| worker::stop_worker | ||||||||||
| ]) | ||||||||||
| .on_menu_event(|_app, _event| { | ||||||||||
| // DevTools is only available in debug builds | ||||||||||
|
|
@@ -79,6 +84,8 @@ pub fn run() { | |||||||||
|
|
||||||||||
| app_handle.manage(db_handle); | ||||||||||
| app_handle.manage(DevApiState::new(enable_dev_api, None)); | ||||||||||
| let worker_path = load_worker_binary_path(&app_handle); | ||||||||||
| app_handle.manage(worker::WorkerState::new(worker_path.clone())); | ||||||||||
| if enable_dev_api { | ||||||||||
| let app_handle = app_handle.clone(); | ||||||||||
| async_runtime::spawn(async move { | ||||||||||
|
|
@@ -88,6 +95,15 @@ pub fn run() { | |||||||||
| }); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| if worker_path.is_some() { | ||||||||||
| let app_handle = app_handle.clone(); | ||||||||||
| async_runtime::spawn(async move { | ||||||||||
| if let Err(err) = worker::start_worker_inner(&app_handle) { | ||||||||||
| log::error!("Failed to start worker on launch: {}", err); | ||||||||||
|
||||||||||
| log::error!("Failed to start worker on launch: {}", err); | |
| log::error!("Failed to start worker on launch: {}", err); | |
| // Notify the frontend so it can surface the failure to the user. | |
| let _ = app_handle.emit_all("worker_start_failed", err.to_string()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The bin field points to "dist/absurd-sqlite-sample.js" but this file needs to be executable on Unix systems. While the shebang is added via the esbuild banner, the file permissions need to be set appropriately (chmod +x) for the bin to work correctly when installed globally.
Consider documenting that users need to run chmod +x on the generated file, or add a postbuild step to set the executable permission automatically.