feat(scheduler-core): implement REPL execution control (task 12.1)
Adds the interactive simulation REPL's execution-control slice (Req 5 AC 22): a single-threaded input loop (readline-based) supporting step [N], continue, quit, and help, pausing before the first replay event and blocking the driver loop while active. Review round 1 raised two findings, both resolved here: - GNU readline (GPL) was linked directly into xce_scheduler, a shipped production daemon. Switched the whole project to libedit (BSD) for line editing -- scheduler and controller (xce_test) both now use editline/readline.h, API-compatible (readline()/add_history()). - Getting a clean regression run after the libedit swap required a full clean rebuild, which exposed a genuine pre-existing SIGSEGV (bug-1504): SnapshotData.internalState was a capnp Data field, and this codebase's capnpc-c backend silently no-ops Data-field encoding without zero-initializing the caller's stack struct, leaving an uninitialized capn_ptr written onto the wire. Fixed by switching internalState to List(UInt8) (with a new internalStateLen wire field), matching the working FetchFileReply.content/bytesRead precedent -- no hand-written accessor code needed. A second, unrelated instance of the same bug class was found in the controller's WAL-catchup path (WalCatchupEntry.payload) and deliberately left unfixed as bug-1505, out of scheduler-core's boundary; needs its own project-wide Data-field audit. Scheduler suite: 34/34 clean. RED-phase reproduction recorded in tasks.md (repl_should_pause() step-budget check disabled -> 2/4 tests fail as expected -> reverted -> 34/34 green). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JPPXeKSUXUqcqvzNXY1rzJ
This commit is contained in:
parent
de00ccff37
commit
02f7f48ff9
17 changed files with 1491 additions and 81 deletions
|
|
@ -56,9 +56,12 @@ controller = executable('xce_controller',
|
|||
|
||||
xce_test_src = [ 'testsuite/test.c', 'testsuite/waldump.c', 'testsuite/walcompare.c', 'testsuite/snapverify.c', 'testsuite/snapdiff.c', 'testsuite/configcompare.c']
|
||||
|
||||
readline_dep = dependency('readline')
|
||||
ncurses_dep = dependency('ncurses')
|
||||
history_dep = dependency('history')
|
||||
# BSD-licensed readline replacement (scheduler-core spec, task 12.1 review
|
||||
# finding): GNU readline is GPL; xce_test's REPL only needs readline()/
|
||||
# add_history(), both provided by libedit's editline/readline.h emulation
|
||||
# header under a permissive license. Single dependency, no separate
|
||||
# ncurses/history split (libedit's own .pc pulls ncurses in privately).
|
||||
libedit_dep = dependency('libedit')
|
||||
ssl_dep = dependency('openssl') # also needed by configcompare.c (SHA256());
|
||||
# controller_unit_test_deps below re-assigns
|
||||
# the same dependency() result, harmless
|
||||
|
|
@ -67,9 +70,7 @@ xce_test_deps = [
|
|||
libxce_dep,
|
||||
librtti_dep,
|
||||
struct2json_dep,
|
||||
readline_dep,
|
||||
history_dep,
|
||||
ncurses_dep,
|
||||
libedit_dep,
|
||||
tinylog_dep,
|
||||
zlib_dep, # waldump: crc32 WAL validation
|
||||
c_capnproto_dep, # waldump: decode JOB_SUBMIT jobid
|
||||
|
|
|
|||
|
|
@ -10,8 +10,9 @@
|
|||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <readline/history.h>
|
||||
#include <readline/readline.h>
|
||||
#include <editline/readline.h> /* libedit (BSD) -- provides readline()/
|
||||
* add_history(), same as GNU readline's
|
||||
* split readline.h/history.h pair */
|
||||
|
||||
#include <cjson/cJSON.h>
|
||||
#include <cjson/s2j.h>
|
||||
|
|
|
|||
|
|
@ -1080,14 +1080,16 @@ static void test_replay_decision_dispatch_union_round_trip(void)
|
|||
/* non-trivial data (including >=1 entry in each of the top-level */
|
||||
/* List(...) fields: queues/users/projects) and round-trips them */
|
||||
/* field-for-field through encode_SnapshotData_ptr/decode_SnapshotData */
|
||||
/* -- EXCEPT internalState (a Data field), whose generated codec is a */
|
||||
/* documented no-op (same limitation as every other Data field in this */
|
||||
/* schema, e.g. ReplayHeader.configHash in Test 7) -- verified instead */
|
||||
/* via the low-level SnapshotData_get/set_internalState accessor on the */
|
||||
/* SAME struct pointer the codec bridge already populated, same hybrid */
|
||||
/* technique used nowhere else in this file but safe because encode_/ */
|
||||
/* set_ operate on independent field slots of one already-allocated */
|
||||
/* capnp struct. */
|
||||
/* -- INCLUDING internalState, which is List(UInt8) (task 12.1 review */
|
||||
/* finding: was Data, whose generated codec is a silent no-op in this */
|
||||
/* codebase's capnpc-c setup and left write_SnapshotData() writing an */
|
||||
/* uninitialized stack capn_ptr onto the wire -- a real, reproducible */
|
||||
/* crash; see controller.capnp's SnapshotData comment for the full */
|
||||
/* story). List(UInt8) gets a real generated encode_/decode_ (same */
|
||||
/* precedent as FetchFileReply.content/bytesRead), so internal_state/ */
|
||||
/* internal_state_len round-trip through the ordinary codec bridge */
|
||||
/* like every other field here -- no low-level accessor workaround */
|
||||
/* needed any more. */
|
||||
/* */
|
||||
/* Also demonstrates this task's internalVersion Observable: "a */
|
||||
/* snapshot encoded with one internal-state version and decoded */
|
||||
|
|
@ -1228,14 +1230,6 @@ static void test_snapshot_data_round_trip(void)
|
|||
SnapshotData_ptr p;
|
||||
encode_SnapshotData_ptr(cs, &p, &src);
|
||||
|
||||
/* internalState (Data field): codec no-op, set via low-level accessor
|
||||
* on the same already-allocated struct pointer (see comment above). */
|
||||
capn_list8 state_list = capn_new_list8(cs, (int)sizeof(internal_bytes));
|
||||
capn_setv8(state_list, 0, internal_bytes, sizeof(internal_bytes));
|
||||
capn_data state_data;
|
||||
state_data.p = state_list.p;
|
||||
SnapshotData_set_internalState(p, state_data);
|
||||
|
||||
capn_setp(capn_root(&c), 0, p.p);
|
||||
int64_t sz = capn_size(&c);
|
||||
TEST_ASSERT_TRUE(sz > 0);
|
||||
|
|
@ -1316,16 +1310,13 @@ static void test_snapshot_data_round_trip(void)
|
|||
TEST_ASSERT_EQUAL_UINT64(123456789ULL, dst.prng_seed);
|
||||
TEST_ASSERT_EQUAL_UINT32(2u, dst.internal_version);
|
||||
|
||||
/* internalState -- low-level accessor read-back (Data field, codec
|
||||
* no-op; see comment above). */
|
||||
capn_data out_state = SnapshotData_get_internalState(dp);
|
||||
TEST_ASSERT_EQUAL_INT((int)sizeof(internal_bytes),
|
||||
capn_len((capn_list8){ out_state.p }));
|
||||
uint8_t out_state_bytes[sizeof(internal_bytes)];
|
||||
TEST_ASSERT_EQUAL_INT((int)sizeof(internal_bytes),
|
||||
capn_getv8((capn_list8){ out_state.p }, 0,
|
||||
out_state_bytes, sizeof(internal_bytes)));
|
||||
TEST_ASSERT_EQUAL_MEMORY(internal_bytes, out_state_bytes, sizeof(internal_bytes));
|
||||
/* internalState -- ordinary codec-bridge read-back (List(UInt8), real
|
||||
* generated decode; see comment above). */
|
||||
TEST_ASSERT_EQUAL_UINT32((uint32_t)sizeof(internal_bytes),
|
||||
dst.internal_state_len);
|
||||
TEST_ASSERT_NOT_NULL(dst.internal_state);
|
||||
TEST_ASSERT_EQUAL_MEMORY(internal_bytes, dst.internal_state,
|
||||
sizeof(internal_bytes));
|
||||
|
||||
/* Observable: version-mismatch rejection. A reader that expects the
|
||||
* SAME internal_version accepts; a reader that expects a DIFFERENT
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue