Commit graph

60 commits

Author SHA1 Message Date
ef6e562cde
fix: close controller-peers-env-fallback validation gaps (bug-1909..1912)
- bug-1912: xce_parse_peers() ignores whitespace around entries and skips
  blank ones, matching xceadmin's resolve_controllers(), so "a=h:1, b=h:2"
  names the second candidate "b" (5 libxce tests + regression case).
- bug-1911: xcecd frees the -l log dir before the -h / --version returns
  (regression case: no LeakSanitizer report, exit 0).
- bug-1910: both version scripts build the apiserver with -mod=vendor
  (as the meson build does) instead of -mod=readonly, so they no longer
  need a Go module cache.
- bug-1909: test-version-startup-consistency.sh accepts exactly the
  dirty-content-fingerprint pairing ('<sha>-<8 hex>' in --version with
  '<sha>-dirty' in the startup log), with self-test cases (e)/(f).

Both version scripts now exit 0 on a dirty tree with XCE_CONTROLLER_PEERS
set; regression script 35/35; xce-build all: C 127/0, scheduler 16/0,
xceadmin 364 passed, no ASAN reports.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-03 13:06:58 +00:00
05a7e6dd0c
feat(controller-peers-env-fallback): fall back to XCE_CONTROLLER_PEERS in the worker (task 3.2)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-03 07:07:19 +00:00
8b7173bb09
fix(worker): keep a signal that arrives before the job has started (bug-1908)
The controller forwards a kill as soon as it has dispatched a job, but
the worker queues the start and only registers the job (and, for the
real worker, connects its IPC client) once it starts -- seconds later
for the first jobs after a worker starts. A signal arriving in between
was dropped ("Failed to find worker"), although the API reported it
accepted, and the job ran to completion.

New pending_signal table: the start handler records the job, a signal
for a queued or still-starting job is kept there, and it is delivered
when the job reaches RUNNING (virtual: killed_signal; real: IPC). A
failed start or a job that ends drops the entry.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 02:50:32 +00:00
8ac637ad36
fix(worker): xcecd_virtual ends a killed running job (bug-1905)
The virtual worker's job-end decision lived inside the branches of the
systemd exit-code check, and only the exit-code-0 branch looked at
killed_signal. Without a system bus that call returns -1, so a kill was
recorded but ignored until the job's sleep time elapsed. A virtual job
has no systemd unit: decide its end from the kill and the timer alone,
before asking systemd.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 02:26:38 +00:00
7aa00f0d9d
feat(job-array-one-based-index): XCE_JOBINDEX is the element number (task 3.2)
The worker sets XCE_JOBINDEX to the element number for jobs carrying the
array marking (a one-element array's element gets 1) and to 0 otherwise.
A submission-supplied XCE_JOBINDEX is not applied and a WARN names the
job and the ignored variable; XCE_JOBID handling is unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 10:34:59 +00:00
44173f74ae
fix: spurious errors/warnings from a pc13v HA deploy (bug-1872..1877)
- bug-1872 (worker): "Failed to find any disk statistics in
  /proc/diskstats" ERROR every 15 s on an NVMe-only host. The whole-disk
  filter guessed from the name and skipped anything ending in a digit or
  containing 'p' -- every nvmeXnY, mmcblkN, even sdp -- so io was never
  reported. diskstats.h now counts a device only when
  /sys/block/<name>/device exists (real disks; not partitions, loop, dm,
  md, zram), and a host with no disk warns once.
- bug-1873 (worker): "Failed to get worker pid" WARN on job start. MainPID
  reads 0 until systemd has forked the new transient unit; xcecd.c already
  retries, so the miss is DEBUG and the final failure names the unit.
- bug-1874 (scheduler): deprecation WARN for the legacy xce_sched_fcfs on
  every start, though builtin_fcfs is the picker. Registration still wraps
  a job_elect_fn plugin but no longer warns; selecting one warns once.
- bug-1875 (apiserver): "Not running as root -- PAM authentication will not
  work" with the default provider config_file; now only for provider pam.
- bug-1876 (controller): standbys logged "cluster_init_phase2 ... failed
  (rc=-22)" WARN every 3 s while waiting for the primary's config push,
  a state the function itself calls normal. It returns -EAGAIN for that
  wait; callers log it at INFO, other failures still WARN.
- bug-1877 (controller): "WAL/snapshot stored unencrypted" warned without
  looking. persist_path_encrypted() checks fscrypt (statx) and dm-crypt
  (sysfs dm/uuid CRYPT-, through LVM slaves); the warning stays when the
  storage is not shown to be encrypted (e.g. a plain /tmp).

Tests: worker diskstats_test (2 of 4 RED on the old filter with pc13v's
/proc/diskstats), controller test_phase2_cold_boot and
test_persist_encryption, scheduler picker tests, apiserver
TestRootWarning. Full ASAN build: all 113 suites pass (controller 319/0,
harness 141/0).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 04:35:19 +00:00
e84a8d4332
fix(worker): remove_worker frees every string add_worker copied (bug-1866)
add_worker strndup's launch_type and the four queue/job pre/post-exec
commands out of the startinfo, but remove_worker only freed exechome,
execcwd, execuser, job_cmd, pids and exdata, so every finished job leaked
up to five strings (LeakSanitizer at xcecd_virtual exit reported the
launch_type one).

New test in test_finished_job_retention.c compares ASAN's live-heap byte
count across an add/remove round trip (the worker suite runs with
detect_leaks=0): RED leaked exactly the 58 bytes of those five strings,
GREEN after. Worker suite 12/12.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 13:14:14 +00:00
0cad68507f
feat: acknowledged job start/finish/fail reports (bug-1741)
A worker reported job start, finish and failure with a fire-and-forget
OPCode_notify the controller never answers, so a report lost while the
worker stayed connected (the live incident: 6 of 7 finish reports in a
burst) left the job RUN forever; the rejoin resync (165fc158) only
recovers reports lost around a reconnect.

- controller.capnp: OPCode_notifyAcked -- the same NotifyData body,
  answered. A separate opcode because replies are matched through the
  client's single wbuf, not by sequence number: answering plain notifies
  would feed stale statuses to the next synchronous request.
- controller (cluster.c): do_notify_acked() applies the fact like
  do_notify, then waits on the commit barrier and replies 0 only once it
  is majority-committed. A barrier failure (the fact is still appended and
  retained) replies -E_XCE_NOT_READY, so the worker retries in place
  instead of rotating away from a healthy primary (cf. bug-1850). Admitted
  by the no-quorum fact-bearing gate like OPCode_notify.
- libxce: xce_notify_acked_req/xce_notify_acked (send, wait for the
  status) and xcecli_notify_acked (XCECLI_FAILOVER_CALL retry/failover).
  xcecli_notify_jobstart/jobfinish/jobfail use it; on -E_XCE_BAD_OPCODE
  (a controller predating the opcode) they fall back to the plain
  fire-and-forget notify, so workers and controllers can be upgraded in
  either order.
- Resends are safe: START/FINISH are duplicate-guarded and a repeated FAIL
  yields the same state.
- worker: logs a WARN when a report is still unacknowledged after the
  retry budget (the rejoin resync remains the backstop). These calls run
  on the worker daemon's IPC callback, so while the controller lacks
  quorum other jobs' IPC messages wait (buffered, not lost) up to the
  retry budget.

Tests: test_notify_acked_reply_only_after_commit and
test_notify_acked_withheld_replies_not_ready (no reply before the covering
ACK; -E_XCE_NOT_READY with the fact retained when ACKs are withheld).
Written with the opcode, so no separate RED run. Full /xce-build all:
111 suites pass; controller suite 1245/0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 06:47:47 +00:00
5df33e69d9
fix(worker): xcecd's fixed /tmp files never follow planted links (bug-1869)
xcecd runs as root and kept files at fixed names in world-writable /tmp,
opened by path:

- main.c created <logdir or /tmp>/xcecd_worker.log and then chmod(path,
  0666)'d it: a symlink planted there made root chmod 0666 any file
  (e.g. /etc/shadow).
- process_worker_manager.c appended xcecd_worker's stdout/stderr to
  /tmp/xcecd_worker.log through any planted symlink/hardlink.
- xcecd_prepare_restart() created /tmp/.xcecd.restarting through a
  dangling symlink (an empty root-owned file anywhere), and
  xcecd_check_restart_type() trusted access() on it, so any user could make
  an unplanned restart look planned.

xcecd_tmpfile.h:
- xcecd_open_owned_file(): reuses an existing file only if it is our own
  regular single-link file (so a legitimate log keeps being appended to);
  otherwise unlinks whatever is there and creates a fresh file with
  O_EXCL|O_NOFOLLOW. O_NONBLOCK so a planted FIFO cannot hang startup.
- xcecd_owned_file_exists(): lstat-based; only our own regular file counts
  as the restart flag. Anything else at the name is removed and ignored.
- The worker log's 0666 mode is now set with fchmod() on the fd.

New test_xcecd_tmpfile.c (7 tests; with the old behaviour the symlink,
dangling-symlink and hardlink cases FAIL and the FIFO case hangs);
worker suite 12/12. Sequential HA soak (5 rounds, xcecd_virtual) PASS.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 08:13:24 +00:00
7864a3dab7
fix(worker): don't follow links planted at a job's output file name (bug-1868)
uv_proc_exec() created /tmp/job_<jobid>_task<N>.out as root with
O_WRONLY|O_CREAT|O_TRUNC. The name is predictable and /tmp is world-
writable, so a local user could plant a symlink (or hardlink) there before
the job started and make the root worker truncate and write job output
into the target. Only the host's fs.protected_* sysctls stood in the way.

xcecd_create_job_output() (job_output_path.h) now unlinks whatever is at
the name and creates a fresh file with O_EXCL|O_NOFOLLOW|O_CLOEXEC, mode
0600 as before; O_EXCL never follows a symlink and fails if anything
reappears, so a race can only fail the create, never redirect it (retried
3 times). A re-run job still starts with an empty file, as O_TRUNC gave.

New test_job_output_path.c (4 tests, 2 RED before the fix); worker suite
11/11.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 07:55:23 +00:00
7723a4f444
fix(worker): fetchfile only reads the job's own output file (bug-1508)
handle_fetchfile() runs as root in xcecd and open()ed whatever filepath
the request carried. Any libxce client could read any file on a worker
(/etc/shadow, keys) with a fetchfile RPC, and so could any REST user: the
apiserver resolves the path from the job's user-supplied out_file/err_file,
so submitting a job with out_file=/etc/shadow and then reading its output
passed the apiserver's ownership check and read the file as root.

- job_output_path.h: the per-task output name uv_proc.c writes,
  <dir>/job_<jobid>_task<N>.out, now shared by the writer (xcecd_worker)
  and the reader (xcecd), plus xcecd_is_job_output_path(), an exact
  string match for that job's names only.
- fetchfile_open_checked(): rejects any other path with -EACCES, opens
  with O_NOFOLLOW|O_NONBLOCK|O_CLOEXEC and requires a regular file with a
  single link, so a symlink, hardlink, FIFO or directory planted at the
  output name is refused.

Three new tests in test_controller_connector.c (2 RED before the fix);
worker suite 10/10. Sequential HA soak (5 rounds, xcecd_virtual) PASS.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 07:40:04 +00:00
c29518b21e
fix(worker): lock the worker list against vtask-thread readers (bug-1862)
The main loop adds and reaps worker-list entries (add_worker,
cleanup_worker_list -> remove_worker) while controller request handlers on
vtask threads walk the same list with no lock. A rejoin resync overlapping
a job reap read a freed node (ASAN heap-use-after-free in
find_worker_by_jobid), killing the worker mid chaotic HA soak.

- xcecd_worker_list_lock/unlock: add_worker links a fully built entry under
  it; remove_worker unlinks under it and frees after.
- handle_jobstatequery holds it from reading the head through the query
  page (the report send is a non-blocking xce_request); job control and
  remote task start copy what they need under it instead of keeping the
  entry pointer.
- The finished-job retention store (appended by the main loop, read by
  resync) gets its own mutex around its public entry points.

Test: test_build_report_safe_against_concurrent_list_mutation -- UAF 3/3
with no-op lock stubs, clean 5/5 with the fix; worker suite 10/10; chaotic
HA soak 2/2 PASS, 6/6 resync jobs reconciled, no ASAN reports.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 13:15:03 +00:00
7c98c056c9
fix(worker): don't let a failover outage expire a finished job's retained outcome (bug-1859)
The finish notify is fire-and-forget, so the retained outcome is the only
way a controller learns a finish lost in a failover. Its 60s window ran
from the finish time, so an 84s outage expired it before the rejoin
resync could report it; the controller got "unrecognized" and requeued a
completed job (then stuck: scheduler kept RUN, see bug-1860).

Expiry is now measured from max(finish_time, last reconnect)
(xcecd_on_reconnect), and nothing expires while checking_connection's
hello probe reports the connection unhealthy. The reported finish_time_ms
is unchanged.

Verified: worker suite 10/10 (two new retention tests, red first); chaotic
HA soak 3/3 PASS (was 1 in 3 failing).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 13:15:03 +00:00
9369b87152
fix(worker): stop treating systemd's UINT64_MAX sentinel as real rusage data
collect_job_rusages() (xcecd_cgroups.c) reads CPUUsageNSec/MemoryCurrent/
MemorySwapCurrent/TasksCurrent via D-Bus and only checked whether the
round trip itself succeeded (r >= 0). systemd's own convention -- not
previously accounted for here -- is to report UINT64_MAX for a counter
that isn't actually being tracked yet, not an error and not zero.

For MemoryCurrent specifically, an unguarded sentinel read produced
(float)UINT64_MAX / (1024*1024) == 17592186044416.0 (2^44) as a single
job's "memory usage", which the scheduler's rusage reconciliation then
summed across every RUN job that hit the same condition -- a live replay
session showed exactly 19 * 2^44 = 334251534843904.0 ("used=304.00 TB")
for 19 affected jobs, matching to the bit.

Added sd_resource_counter_is_available() and gated all four property
reads on it. Applied uniformly (not just MemoryCurrent) since systemd
uses the identical convention for the other three counters. Left the
existing memory_rss MB wire unit alone (bug-1735's established
contract, also relied on by the controller's own display pipeline) --
changing it would need auditing that separate consumer, out of scope
here.

bug-1814.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RLSNJhNoULvVcUhXEFafQC
2026-09-15 11:21:55 +00:00
a35f597e06
fix(worker): real maxswap/maxtmp detection; feat(scheduler): annotate multi-slot assigned jobs
bug-1554: init_host_attrs() replaces hardcoded placeholder stubs with real
detection -- maxswap from sysinfo()'s totalswap (reusing the same syscall
snapshot already taken for maxmem), maxtmp from a fresh statvfs("/tmp")
total-capacity measurement. No synthetic fallback on detection failure, so
a system with swap genuinely disabled correctly reports 0.

bug-1776: the scheduler simulator's "info worker" assigned-jobs listing now
annotates a job consuming more than one "slots" unit (e.g. "jobid*2"),
using per-host allocation data that was already being walked but discarded.
Falls back to the pre-existing per-job listing when jobs on the same worker
have differing slot counts, since a single suffix on a compressed range
would be ambiguous.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hb6Edtj4KbS8CrgEWYdA2E
2026-09-07 04:39:22 +00:00
f02bedc52b
test(resreq-maxtasks-wiring): close worker launch-type dispatch coverage gap
Adds white-box tests for job_start_remote_task()'s launch-type gate
(MPI types defer to their own remote-launch mechanism; default type
fans out via xcecli_runtask() across every allocated host, verified
via a #define-before-#include mock capturing taskrun/taskhost
contents) and create_mpi_cmdline()'s host-list/command construction
for openmpi and mpich. Zero production code changes -- this closes a
previously zero-coverage gap in existing, unmodified worker code
(Requirement 4.4), independent of this feature's resreq wiring work.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XYdtrEWWZfKRRGED7raEYg
2026-09-06 05:26:32 +00:00
b6a2f5ec14
fix(worker-rejoin-resync): task 4.4 - gate periodic load-indices send on resync_pending
Extends task 4.3's post-join gating to resource_sync_thread()'s
independent periodic reporting pthread, closing the Requirement 3.8 gap
where load-indices sends could interleave with an in-flight job-state
reconciliation report. Also fixes two pre-existing latent bugs
(bug-1756, bug-1757) in resource_sync.c that blocked a truthful RED/GREEN
signal for the new tests.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ypUeKgbAqLWUkaSJ79poN
2026-09-02 03:01:56 +00:00
a3d12d37d9
feat(worker-rejoin-resync): task 6.1 - trigger reconciliation on rejoin
Arm a reconciliation round in apply_join_state() the moment a worker
host becomes connected (first join, reconnect, or failover-reconnect -
all routed through the same shared call site, Req 1.1-1.3), then send
the query from on_worker_join() strictly after the join reply and
existing config push (Req 2.1-2.2), verified via a structural test
that reads real wire order off the endpoint queue.

Also closes Req 3.7's config-delivery seam end-to-end: worker_resync_
window_ms now rides the existing Conf4Worker push (clustername
precedent, no new opcode) and the worker applies it to its retention
window on receipt.

Fixes bug-1752: myreq_handler had no OPCode_push dispatch case at all,
so every config push - including the pre-existing clustername field -
was silently discarded. Confirmed harmless until now (clustername was
never read anywhere in src/worker/); closed narrowly for
worker_resync_window_ms only.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y4M5MJM2zK6nU43da267Ku
2026-09-01 19:43:18 +00:00
5b69973677
feat(worker-rejoin-resync): task 4.3 - wire query receipt and post-join gate
Add OPCode_jobStateQuery dispatch to myreq_handler with multi-page query
reassembly, routing to task 4.2's xcecd_resync_answer_query() (Req
8.1, 8.3). Gate the worker's resumption of normal post-join operation
on a resync_pending flag until the full report has sent (Req 3.8).

Fixes bug-1749 (xcecd_resync.c was never added to the worker's
production source lists - task 4.2's build only ever exercised it via
the test binary's white-box include).

Round 1 review caught bug-1750: the gate sat as checking_connection()'s
entry guard, so a connection drop mid multi-page query left
resync_pending stuck forever, permanently disabling the worker's only
reconnect mechanism (Req 6.1 violation). Round 2 narrows the gate to
guard only the rejoin action, and resets it from xcecd_on_reconnect()
so a stalled query can never wedge it past one reconnect cycle.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y4M5MJM2zK6nU43da267Ku
2026-09-01 18:01:03 +00:00
cfe58c3dcf
feat(worker-rejoin-resync): task 4.2 - worker answers job-state query with extras
Classify each queried job ID as running/finished-ok/finished-fail/
unrecognized (Req 3.1-3.3, 3.6), report unqueried running/recently-
finished jobs as extras, and send both via task 1.4's paginated wire
helper (Req 8.2, 8.3). Composes correctly with the already-implemented
controller-side consumer (resync_apply_report).

Adds xcecd_finished_job_retention_snapshot() to xcecd_worker_utils, a
small additive companion needed to enumerate all retained jobs for the
extras set (task 4.1 only exposed single-jobid lookup).

Folds the still-open OPCode_jobStateQuery receive-dispatch wiring gap
into task 4.3's scope in tasks.md, since 4.3 already needs the real
receive path to implement its post-join gate.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y4M5MJM2zK6nU43da267Ku
2026-09-01 16:35:28 +00:00
e31012e8a7
feat(worker-rejoin-resync): task 4.1 - bounded finished-job retention on worker
Retain a job's exit status/code/true finish time for a configurable
window after update_worker_status() detects its exit, before
cleanup_worker_list() would otherwise discard all trace of it
(Requirements 3.4, 3.5, 3.7). Hooked into the single choke point both
production exit-detection paths (libuv IPC callback, dbus polling)
converge through, so retention happens-before any reap regardless of
which mechanism is active.

Also prunes on retain (not just on lookup), closing a review finding:
lookup currently has zero production callers until task 4.2 lands, so
without this the store would grow unboundedly for the daemon's uptime.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y4M5MJM2zK6nU43da267Ku
2026-09-01 16:03:19 +00:00
347e996e76
feat(worker): distinguish signal-killed job exits from real crashes in logs
A JOB_EXITED status could mean either a deliberate, expected termination
(worker_signal_callback() only ever sends SIGTERM/SIGKILL to cancel/
force-kill a job) or a genuine unexpected crash signal. Both logged at
ERROR, drowning real crashes in expected-cancellation noise. Added
job_exit_log_level(): exit_code == 128+SIGTERM/SIGKILL logs at INFO
(matching process_exit_callback()'s existing plain-INFO treatment for
the same case), anything else stays ERROR. Verified: worker test suite
5/5 including the new test_uv_ipc_job_exit_severity coverage.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QxaSzHRB7pmYFp4cVnwkA
2026-08-29 11:43:52 +00:00
1c906e955c
fix(worker): job ids print human-readable format via fmt_jobid() (bug-1588)
Extends bug-1588's job.c fix to the remaining raw-%lu jobid logging sites:
8 worker daemon files (xcecd_worker.c, controller_connector.c, uv_ipc.c,
xcecd_worker_utils.c, xcecd_dbus_manager.c, xcecd.c, main.c, xcecd_cgroups.c)
and src/controller/testsuite/test.c (xce_test, heavily used for controller
testing). 99 call sites converted to fmt_jobid()+%s, matching the existing
convention. Non-jobid %lu values (lsn, client id, byte offset) and raw-numeric
identifier/path construction (checkpoint dir, systemd unit name, XCE_JOBID
env var) are deliberately left untouched.

Verified via /xce-build worker and controller (asan, --incremental): both
compile clean with zero warnings tied to the change; worker suite 4/4 OK,
controller suite 4/5 OK (the one failure is the pre-existing bug-1561
overlayfs fdatasync flake, unrelated to this change).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 02:42:01 +00:00
d3d9522f27
feat(dirty-content-fingerprint): worker shows fingerprint in --version
Worker's hand-rolled OPT_VERSION case now composes via a genuinely
separate version_display_buf/xce_compose_version_ex() call, mirroring
task 3.2's already-approved controller/scheduler pattern exactly. Both
startup-log XCECD_LOG call sites (--peers and no-peers) are left
completely untouched, same xce_compose_version() call, same version_buf.

Verified independently by review with a real build and live daemon runs
in both branches: --version shows <commit>-<fingerprint> matching
compile_commands.json, -dirty correctly stripped not doubled; both
startup log branches show the old fingerprint-less format unchanged.

All three C daemons (controller, scheduler, worker) are now complete.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 05:57:37 +00:00
bc5c669b94
feat(build-time-versioning): worker --version and startup log
Worker uses plain getopt_long(), not argp, so it has no free --version
handling like controller/scheduler. Adds a long-only OPT_VERSION=257
option and a handling case that computes xce_compose_version() into a
static buffer and returns before any startup work (check_user()/
xcecd_init/xcecd_start). Both startup INFO log branches (--peers and
no-peers) extended to log the identical buffer.

Live-verified: --version and both startup-log branches are
byte-identical (captured via a real daemon run under sudo).

Also updates bug-1519 (pre-existing, unrelated ASAN "Joining already
joined thread" abort during worker shutdown): independently reproduced
under real root during this task's review, refining its prior "never
reproduced under real root" note -- not a userns/unshare artifact, still
unfixed, does not affect this task's code path.

Task 3.3/13 of build-time-versioning. This closes out the "Core: C
daemon version display" phase (tasks 3.1-3.3).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 11:47:12 +00:00
70fe9e463b
fix(worker): dbus_workers_update() starves workers after first DONE/EXITED (bug-1589)
Every early-exit branch in the worker_list scan either re-tested the same
node forever (bare `continue`) or returned out of the whole function on the
first DONE/EXITED worker, so job array elements past the head of the list
never got their on_job_start report. Capture `next` before touching each
node and always advance to it. Affects xcecd_virtual and real xcecd alike
(same DBUS_MANAGER-gated function).

Also records a roadmap follow-up (xcecd_virtual concurrent-job support,
MXJ > 1) discovered while investigating this bug.
2026-08-19 09:48:23 +00:00
6e5c3a891a
fix(worker): bug-1569 periodic resval reports tagged with wrong host
collect_load_indices() (resource_sync.c) tagged every periodic
mem/r1m/r15m/r15s/ut/pg/ls/it/swp/io/tmp report with gethostname()'s
raw OS/container hostname instead of the worker's own join identity
(xcecd_ctx.virtual_name, the -n/--name value). Every other worker-keyed
lookup downstream (scheduler's sched_get_worker(), and the same
HOOK_DETAIL_RESOURCE_UPDATE payload forwarded on to the Go apiserver)
matches by that join identity, so the mismatch silently dropped every
periodic resource report for the real worker.

Also adds a startup INFO log (main.c) naming the resolved job picker
plugin, used to rule out a legacy-picker hypothesis while chasing a
related (still-open) dispatch issue.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 12:01:52 +00:00
45400c3a28
fix(controller): close 6 data-safety races + apply-pump O(N^2) regression + boot TOCTOU
Bundles the fixes validated this session across ha.c/persist.c/worker.c:

- raft-apply-decoupling Task 7.4: apply-pump O(N^2) throughput regression
  fixed via a segment-read cache.
- bug-1416: boot-time TOCTOU race in persist_verify_apply_correctness()
  closed with a recovery-complete gate that closes at process-lifecycle
  start, not function entry.
- bug-451: HOOK_DETAIL_JOB_RUSAGE_UPDATE now has its own persist hook arm
  instead of silently falling through the default job-state case.
- bug-1426: trim_wal_segments() also caps its trim floor at the local
  apply pump's own progress, not just remote standby-acked positions.
- bug-1427: persist_verify_apply_correctness() extended with a worker-count
  check mirroring the existing job-count check (count_jobmgr_entries and
  count_workermgr_entries now share one count_manager_entries(var) helper).
- bug-1428: persist_register_pending_apply's critical section widened to
  close the actual race instead of patching a symptom.
- bug-1472 (remainder): WORKER_DEAD replay in worker_from_capnp() now
  applies mark_worker_unavail()'s mutations via a shared
  apply_worker_dead_mutation() helper instead of duplicating them.
- bug-1474 (angle-a): on_standby_connected's stale-duplicate detection
  fixed for promoted vote endpoints via a shared votes_idx_for_peer()
  index-scale converter.

Each fix independently verified via TDD (RED before fix, GREEN after).
Full ASAN rebuild + controller_unit_test: 1101 tests, 0 failures. Live
single-node HA deployment validated Task 7.4 and bug-1416 across 5
restart/recovery cycles (45/45 job submissions, zero APPLY_DIVERGENT
fences).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 10:20:02 +00:00
619b119ef5
feat(component-failover-adoption): validate worker thread isolation, fix rejoin bugs found by E2E
Confirm connector thread is isolated from worker_process_update() (main
thread) and resource_sync_thread() (own thread) via code read plus a real
pthread concurrency test. Add unit and live 3-node E2E coverage for
candidate/reconnect behavior, all-candidates-unreachable diagnostics, and
liveness cadence under concurrent reconnect.

The E2E run surfaced two production bugs the 2.1/2.2 unit tests missed:
xcecli_start() resets the reconnect callback to NULL after it runs, so
registering it before xcecli_start() silently no-ops the whole rejoin
mechanism; and a successful worker join returns its assigned slot index
as reply.status (any rc >= 0), not always exactly 0, so checking_connection()'s
rc == 0 success check never fired on a real non-zero-slot join. Both fixed,
plus per-candidate names added to the all-unreachable failure log.

Root-check bypass needed for the E2E run is gated at compile time via the
existing VIRTUAL_CLUSTER flag, not a runtime env var, so the real xcecd
binary's root requirement has zero bypass surface.
2026-08-11 07:29:41 +00:00
331fa02423
feat(component-failover-adoption): fix worker startup ordering, add rejoin retry-on-failure
Thread cluster_cfg by value through xcecd_config_t -> xcecd_context_t ->
cc_ctx so it's populated before controller_connector_start() fires the
first join, instead of after xcecd_start() returns. checking_connection()
now retries a failed post-reconnect xcecli_join() on the next liveness
probe instead of leaving needs_rejoin cleared on mere attempt.
2026-08-11 06:21:36 +00:00
ce9f734a38
feat(component-failover-adoption): unify worker's dual connect paths into single pipeline
Replace worker's separate default/single-host and multi-candidate connect
branches with one path: synthesize-or-accept a candidate list, validate,
convert via xce_cluster_config_to_client_config(), connect once. Register
a flag-only rejoin callback (no I/O, no client-lib reentry) and remove the
liveness probe's bounded give-up so the library owns retry/recovery.
2026-08-11 05:42:48 +00:00
fe8b4b86bf
feat(component-failover-adoption): reconcile N_MAX_MASTERS with MAX_HA_CANDIDATES
N_MAX_MASTERS (client.h) now derives from config.h's MAX_HA_CANDIDATES
instead of an independently-maintained literal (3 vs 4 previously).
Fixes worker's and scheduler's hardcoded `> 3` startup checks to
reference the constant symbolically, and updates the client-retry
test fixture so it doesn't exercise an uninitialized 4th slot.

Task 1.1 of component-failover-adoption (Requirement 1 AC7).
2026-08-11 04:31:53 +00:00
0df1cf5f8d
feat(worker): add -v flag for verbose logging
Multiple -v increase verbosity: -v=DEBUG, -vv=DEBUG2, -vvv=DEBUG3.
Same pattern as controller and scheduler.
2026-08-10 11:03:42 +00:00
e959ee61fc
fix(worker): default logdir to /tmp when -l not given
logdir was NULL without -l flag, causing stat(NULL)/mkdir(NULL) → EFAULT.
Set default to /tmp after argument parsing, before directory creation.
2026-08-10 10:58:15 +00:00
b3124767bb
chore(worker): remove sample worker.yaml config file
Worker no longer uses YAML configuration files. CLI defaults or --peers handle all settings.
2026-08-10 10:47:45 +00:00
f0f46f87ad
chore(worker): remove dead config-file code (xcecd_conf)
xcecd_conf.c/h provided worker.yaml parsing (config_load, config_result_t).
Removed along with -c/-p/-m CLI flags. Worker now uses CLI defaults or --peers.
Deleted: include/xcecd_conf.h, src/xcecd_conf.c. meson.build updated.
2026-08-10 10:46:29 +00:00
5e4baa2745
refactor(worker): remove config file and deprecated CLI flags
Remove -c (config file), -p (remote port), -m (remote host) flags.
Worker now uses CLI defaults (localhost:9990) or --peers for HA.
No config file dependency — four settings don't justify file overhead.
Default remote_port changed from 6789 to 9990 (controller default).
2026-08-10 10:40:59 +00:00
9ffb1d511f
fix(worker): xcecd -h exits immediately without root or config check
Reorder startup: parse arguments before check_user() and config_load()
so -h/--help short-circuits before any initialization. Exit code 0.
2026-08-10 10:13:24 +00:00
d70bf6a402 update documents 2026-08-10 17:51:51 +08:00
ff1be48001 merge 2026-07-23 12:35:10 +08:00
Lei Chen
3a2094a461 Merge branch 'working' into job_peek_lchen
job_peek_lchen: Add fetchfile feature (job output/error peek proxy)
working: HA/Raft infrastructure (consensus, replication, config mgmt)

Conflict resolution summary:
- controller.capnp: fetchfile @41, fetchfilereply @18, fetchfilemsg @30
- cluster.c: bifurcated OPCode_reply for fetchfile + config-push ACK
- job.c: kept full fetchfile pending-request subsystem (~183 lines)
- request.c: kept both xce_fetchfile_req() and XCE_RETRY_DEFAULTS
- helper.c: added missing preVoteRequest/preVoteReply mappings
- All 15 new test registrations (test_cbuf.c + 14 HA tests)
2026-07-22 13:57:25 +08:00
94f50ce6e1 fix(scheduler,cli): bug-1453/1454/1455/1456 remove dead CA-cert plumbing, fix scheduler HA config bugs
bug-1453: remove unused client-side CA-certificate plumbing. C-side:
drop ca_cert_path from xce_cluster_config_t, do_connect()'s ca_cert
param, --ca-cert flags on worker/scheduler, and controller's cert
population (TLS is server-side only, SSL_VERIFY_NONE everywhere on
the client side already, verified zero SSL_CTX_load_verify_locations
calls in the C tree). Go-side: delete xcecli's self-signed cert
TOFU-pinning flow (cmd/auth/cert/*), CACert field, SetRootCAs/
loadRootCAs, and every call site.

bug-1454: fix xcecli_ctx_t leak in scheduler.c sched_init() — the
unconditional initial ctx_init was never freed before the HA
candidate loop re-initialized it on the first iteration.

bug-1455: sched_init()'s memset(sched, 0, ...) was silently wiping
sched->cluster_cfg that main.c had populated from --peers just before
calling sched_init(), so --peers was always ignored and the scheduler
fell back to the hardcoded localhost:9990 single-host default.
Restructured sched_init() to take cluster_cfg as an explicit parameter
(assigned after the memset) instead of relying on caller-side
ordering with a separate sched_set_cluster_cfg() setter, which is now
removed.

bug-1456: revert an unsafe stack-local xce_client_config_t introduced
while fixing bug-1455 — xcecli_ctx_init() stores that pointer as
ctx->conf and client_failover.c dereferences it for the ctx's entire
life (runtime reconnect), so a per-loop-iteration stack copy would
dangle the moment sched_init() returned. Back to mutating the
caller-owned, process-lifetime config in place.

Verified: full ASAN build (all components), meson test suite 9/9 OK,
manual scheduler testsuite 15/15, Go build/vet/test all pass.
2026-07-20 12:01:10 +00:00
c8e0f65a39 enhance xce-build skill 2026-05-30 08:13:59 +08:00
Lei Chen
d2b47e3985 Init commit of jobs output/error message peek project. 2026-05-09 16:57:54 +08:00
7e4528a0b9 refactor: consolidate parse_peers into xce_parse_peers in libxce
Three identical static copies (controller, worker, scheduler) replaced
with a single xce_parse_peers() in src/lib/cluster.c.

The libxce version is the canonical implementation: stores the logical
name from 'name=host:port' syntax so daemons on the same dev host can
identify themselves by name rather than IP address.
2026-05-08 07:06:57 +00:00
9bc84f1ca1 fix: add xcecli_ctx_fini() before continue in HA candidate loop (resource leak) 2026-05-08 03:15:26 +00:00
b75703ba6b feat: wire HA candidate-list reconnect into worker+scheduler join paths
Worker (xcecd):
- Add xce_cluster_config_t cluster_cfg field to controller_connector_ctx_t
  (controller_connector.h includes xce/cluster.h)
- Populate cc_ctx->cluster_cfg from parsed cluster_cfg after xcecd_start()
  sets cc_ctx (main.c, removes the P3 placeholder)
- Replace xcecd_join_cluster() single-shot connect with a candidate-iterating
  loop: tries each candidate, calls xcecli_stop() before the next attempt,
  falls back to the original single-host path when candidates_count == 0

Scheduler (xce_scheduler):
- Add xce_cluster_config_t cluster_cfg field to xce_scheduler_t
  (scheduler.h includes xce/cluster.h)
- Add sched_set_cluster_cfg() setter
- Replace the hardcoded xcecli_start/xcecli_join pair in sched_init() with
  the same candidate-iterating pattern; original single-host path preserved
- main.c calls sched_set_cluster_cfg() before sched_init() when --peers given
  (removes the P3 placeholder comment)

checking_connection() already calls xcecd_join_cluster() on retry, giving
the worker free reconnect-after-failover behaviour for no additional code.
2026-05-08 03:05:56 +00:00
fd9cfce1c1 feat: P2.5-5/6 add --peers/--ca-cert parsing to worker+scheduler with validation
Worker (P2.5-5):
- Added parse_peers() static helper to src/worker/src/main.c
- Switched getopt to getopt_long to support --peers and --ca-cert long options
- When --peers is given: parses into xce_cluster_config_t, calls
  xce_cluster_config_validate(), logs INFO with candidate count/identity/ca_cert
- If validation fails: prints clear error and exits(1)
- WARN logged when candidates_count > 3
- Backward compatible: no --peers falls back to existing single-controller path

Scheduler (P2.5-6):
- Added parse_peers() static helper to src/scheduler/src/main.c
- Extended argp struct with --peers (-P) and --ca-cert (-C) options
- Same validation/logging pattern as worker
- Backward compatible: no --peers falls back to existing single-controller path

Tests (new):
- src/lib/testsuite/test_cluster_parse.c: 8 unit tests for parse_peers logic
  (single entry, multiple entries, no-name format, empty/null/missing-port errors,
  name-stripped correctly, MAX_HA_CANDIDATES cap respected)
- Registered in test.c and meson.build
- All 8 tests GREEN; 3 pre-existing failures in unrelated GC/CSE tests unchanged
2026-05-08 02:32:56 +00:00
c9dc8c4676 Merge Go frontend (apiserver + xcecli) into meson build 2026-04-14 11:23:18 +08:00
280c22671f support shared libxce(for api server) 2026-01-19 11:02:49 +08:00