Skip to content

Replace blocking command execution with async libevent-driven dispatch#3

Draft
Copilot wants to merge 2 commits into
masterfrom
copilot/replace-command-execution-api
Draft

Replace blocking command execution with async libevent-driven dispatch#3
Copilot wants to merge 2 commits into
masterfrom
copilot/replace-command-execution-api

Conversation

Copy link
Copy Markdown

Copilot AI commented Mar 25, 2026

run_cmd_argv blocked the entire libevent event loop for the duration of every child process via a synchronous pipe-read loop and two blocking waitpid calls. A slow git pull or ./deploy.sh starved all concurrent requests.

Changes

commands.c

  • Removed both blocking waitpid calls and the synchronous pipe-read loop.
  • commands_set_event_base(base) — registers a SIGCHLD handler via evsignal_new that reaps all finished children using waitpid(-1, &status, WNOHANG).
  • pipe_drain_cb — libevent EV_READ | EV_PERSIST callback drains child output as it arrives; self-destructs (event_delevent_freeclose) on EOF/error.
  • run_cmd_argv — forks, sets the read-end O_NONBLOCK, registers the drain event, and returns immediately with exit_code=0 and an empty arena string. Output is drained by the event loop and not surfaced to callers (groundwork for job queue offload).
// Before: blocks until child exits
waitpid(pid, &status, 0);          // line 108 — gone
waitpid(pid, NULL, 0);             // line 56 error path — gone

// After: SIGCHLD handler reaps asynchronously
while (waitpid(-1, &status, WNOHANG) > 0);

commands.h

  • Added commands_set_event_base(struct event_base *) declaration with a forward declaration for struct event_base to avoid pulling in libevent headers.
  • Updated run_cmd_argv doc comment to document the async contract.

main.c

  • Single added line: commands_set_event_base(base) immediately after event_base_new(). Route table, endpoints, and auth middleware untouched.
Original prompt

This section details on the original issue you should resolve

<issue_title>Replace command execution api</issue_title>
<issue_description>Make command execution non-blocking (groundwork for job queue offload)
Problem
run_cmd_argv (commands.c:108) calls waitpid(pid, &status, 0) — blocking the process until the child exits. Because libevent is single-threaded, the entire server hangs for the duration of every command. A slow git pull or ./deploy.sh starves all other requests.
The full blocking path is:
validate_and_run (main.c:91)
→ runner() ← e.g. run_git_pull
→ run_cmd_argv (commands.c)
→ fork()
→ read(pipefd[0], ...) ← drains pipe synchronously
→ waitpid(pid, &status, 0) ← blocks until child exits
← returns output
→ send_json_response() ← only now does the response go out
There are also two additional blocking waitpid calls: one on the error path at commands.c:56 (allocation failure after fork) and the main one at commands.c:108.
What Needs to Change
run_cmd_argv in commands.c only. No new endpoints, no server changes, no protocol changes.
Replace the synchronous waitpid + synchronous read loop with non-blocking I/O so the function returns as soon as the job is submitted rather than after it completes. The response contract from the existing endpoints stays the same — this is purely an internal execution change to decouple dispatch from completion.
The job queue (separate project) will handle tracking completion and results. CMon's role is to fire the command and not block.
Approach

After fork(), set the pipe read end to non-blocking (O_NONBLOCK via fcntl).
Register the pipe read fd with libevent (event_new with EV_READ | EV_PERSIST) so output is drained into an arena buffer by the event loop as it arrives, without blocking.
Replace waitpid(pid, &status, 0) with a SIGCHLD handler registered via evsignal_new (same pattern as the existing SIGINT handler at main.c:190). On signal: call waitpid(-1, &status, WNOHANG), record the exit code, tear down the pipe event.
The existing callers (validate_and_run, validate_and_run_arg) respond to the client immediately after the fork succeeds rather than waiting for output.

Acceptance Criteria

A 30-second command does not block concurrent /health requests
Both waitpid calls in run_cmd_argv (lines 56 and 108) are gone or use WNOHANG
Existing test suite passes (./run_tests.sh)
No changes to main.c route table, endpoints, or auth middleware</issue_description>

<agent_instructions>Use a minimal implementation for async command execution</agent_instructions>

Comments on the Issue (you are @copilot in this section)

@Blazzzeee @hxrshit912 @Bisht-ji

Can you guys close this</comment_new>


📱 Kick off Copilot coding agent tasks wherever you are with GitHub Mobile, available on iOS and Android.

Copilot AI changed the title [WIP] Make command execution non-blocking for improved performance Replace blocking command execution with async libevent-driven dispatch Mar 25, 2026
Copilot AI requested a review from Blazzzeee March 25, 2026 04:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replace command execution api

2 participants