feat(component-failover-adoption): unify scheduler's dual connect paths into single pipeline

Replace scheduler's inline per-candidate loop and separate single-host
branch with one unified pipeline (mirrors worker's task 2.1 fix): build
exactly one validated candidate list, whether from --peers or synthesized
as a single candidate from the legacy default, then connect through the
shared xce_cluster_config_to_client_config() helper exactly once.

Adds sched_on_reconnect(), a rejoin callback that only sets a flag and
returns immediately (no I/O, no xcecli_* calls) to avoid the self-deadlock
worker hit in task 2.3 — registered via xcecli_set_reconnect_handler()
after xcecli_start(), since xcecli_start() resets any handler registered
earlier back to NULL.

Adds test_scheduler_connect.c: behavioral tests confirm no-peers and
single-candidate configs produce identical connect log sequences and that
unreachable candidates fail fast without retry loops; structural tests
source-scan scheduler.c to confirm a single xcecli_ctx_init() call site, a
single xcecli_join() call site, and a rejoin callback body free of I/O and
xcecli_* calls.

Live-validated against a 3-node HA controller cluster: a scheduler
configured with the standby listed first in --peers fails over via
xcecli's built-in retry-next mechanism to the primary and joins
successfully.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYSEeLmzjtD4RLDuUDhLoK
This commit is contained in:
Rongsong Shen 2026-08-11 07:58:17 +00:00
parent 619b119ef5
commit a9f283dc64
Signed by: rshen
SSH key fingerprint: SHA256:8Vf2pZ3h6rJa7EhGK+WaAQ9Mta/apZvteW6g9U/WKKk
4 changed files with 609 additions and 60 deletions

View file

@ -761,6 +761,36 @@ sched_repl_equivalence_test = executable(
dependencies: [scheduler_test_deps, libedit_dep],
install: false)
# Tests for the scheduler's unified candidate-list connect/rejoin pipeline
# (component-failover-adoption, task 3.1, boundary: SchedulerControllerClient).
# Unlike the white-box #include-the-.c-file tests above, this target links
# the REAL scheduler_srcs (everything sched_init() needs -- core.c,
# schedobj.c, resource.c, plugin.c, the picker/allocator plugins, etc.) and
# calls the real, public sched_init()/sched_fini(): scheduler.c's own
# dependency surface is too large for whole-file inclusion (same "too many
# externals to stub" reasoning sched_picker_core_loop_boundary_test's own
# comment already gives for not #include-ing scheduler.c), so this avoids
# that by linking the real objects instead of textually including one.
# scheduler_srcs (defined in the parent meson.build) minus src/main.c (this
# test file supplies its own main()) -- built via a loop rather than a
# hand-duplicated list so it can never drift from the production source list.
scheduler_connect_test_srcs = ['test_scheduler_connect.c']
foreach f : scheduler_srcs
if f != 'src/main.c'
scheduler_connect_test_srcs += ['../' + f]
endif
endforeach
sched_scheduler_connect_test = executable(
'sched_scheduler_connect_test',
scheduler_connect_test_srcs,
include_directories:[incdir],
dependencies: [scheduler_deps, unity_dep],
c_args: ['-DXCE_SCHEDULER_C_PATH="'
+ (meson.current_source_dir() / '..' / 'src' / 'scheduler.c')
+ '"'],
install: false)
# Register as meson tests so `meson test --suite scheduler` runs them all.
# detect_leaks=0 matches the suite convention (fixtures are not torn down).
test('scheduler test', scheduler_test, suite: 'scheduler',
@ -863,3 +893,6 @@ test('scheduler repl snapshot params test',
test('scheduler repl equivalence test',
sched_repl_equivalence_test, suite: 'scheduler',
env: ['ASAN_OPTIONS=detect_leaks=0'])
test('scheduler connect pipeline test',
sched_scheduler_connect_test, suite: 'scheduler',
env: ['ASAN_OPTIONS=detect_leaks=0'])