feat: add v2 task control and asynchronous isolated workers

This commit is contained in:
WHC
2026-10-20 14:46:57 +08:00
parent 143dba203b
commit d971cb02b0
26 changed files with 1590 additions and 66 deletions
+125
View File
@@ -0,0 +1,125 @@
#define _POSIX_C_SOURCE 200809L
#include "cst_runtime.h"
#include "cst_worktree.h"
#include "cst_test_util.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <pthread.h>
static pthread_mutex_t probe_mu = PTHREAD_MUTEX_INITIALIZER;
static int active, peak;
static int work(void *p, volatile int *cancel, cst_tool_result_t *out) {
(void)p;
os_observe_output("live output", 11);
struct timespec delay = {0, 1000000};
for (int i = 0; i < 30 && !*cancel; i++) nanosleep(&delay, NULL);
out->out = strdup("verified job output"); out->out_len = strlen(out->out);
out->exit_code = *cancel ? 130 : 0; return 0;
}
static int locked_work(void *p, volatile int *cancel, cst_tool_result_t *out) {
pthread_mutex_lock(&probe_mu); active++; if (active > peak) peak = active; pthread_mutex_unlock(&probe_mu);
int rc = work(p, cancel, out);
pthread_mutex_lock(&probe_mu); active--; pthread_mutex_unlock(&probe_mu); return rc;
}
static int instant(void *p, volatile int *cancel, cst_tool_result_t *out) {
(void)p; (void)cancel; out->out = strdup("archived output"); out->out_len = strlen(out->out); return 0;
}
static void write_text(const char *dir, const char *name, const char *text) {
char p[8192]; assert(!os_path_join(p, sizeof(p), dir, name));
FILE *f = fopen(p, "wb"); assert(f); assert(fputs(text, f) >= 0); assert(!fclose(f));
}
static char *shell(const char *dir, const char *cmd) {
os_run_result_t out = {0}; assert(!os_run_shell_c(dir, cmd, 65536, &out));
assert(!out.exit_code); char *s = out.out; out.out = NULL; os_run_result_free(&out); return s;
}
int main(void) {
char dir[2048], path[4096], error[256], id[64];
assert(!test_tmpdir(dir, sizeof(dir), "runtime"));
snprintf(path, sizeof(path), "%s/session", dir);
cst_runtime_t *r = cst_runtime_open(path); assert(r);
unsigned long rev = cst_runtime_begin(r, "test execution");
assert(cst_runtime_check(r, rev, 1, 1));
assert(cst_runtime_finish(r, "completed", "unsupported claim", 1, error, sizeof(error)));
char *ref = cst_runtime_evidence(r, "read_file", 0, "actual evidence", 0);
assert(ref); free(ref);
assert(!cst_runtime_finish(r, "completed", "verified", 1, error, sizeof(error)));
assert(!cst_runtime_steer(r, "only inspect, do not delegate", 1, 0));
assert(!cst_runtime_check(r, rev, 0, 0));
assert(!cst_runtime_check(r, 0, 1, 0));
assert(!cst_runtime_check(r, 0, 0, 1));
char *s = cst_runtime_take_steering(r); assert(s && strstr(s, "only inspect")); free(s);
assert(!cst_runtime_take_steering(r));
assert(!cst_runtime_controls(r, 0, 1, 0));
cst_runtime_begin(r, "asynchronous work");
assert(!cst_job_start(r, "read", "", 1, work, NULL, NULL, id, sizeof(id)));
assert(cst_runtime_finish(r, "completed", "premature", 0, error, sizeof(error)));
s = cst_job_read(r, id, 2000); assert(s && strstr(s, "completed") && strstr(s, "verified job output")); free(s);
assert(!cst_job_start(r, "external", "device:test", 0, work, NULL, NULL, id, sizeof(id)));
/* Let the scheduler start the operation before requesting a safe stop. */
for (int i = 0; i < 100; i++) {
s = cst_job_read(r, id, 1); int running = s && strstr(s, "\"status\":\"running\""); free(s);
if (running) break;
}
assert(!cst_job_cancel(r, id, 0, "user"));
s = cst_job_read(r, id, 0); assert(s && strstr(s, "running_protected") && strstr(s, "user")); free(s);
assert(!cst_runtime_steer(r, "inspect something else", -1, -1));
assert(cst_runtime_jobs_pending(r) && !cst_runtime_current_jobs_pending(r));
s = cst_runtime_take_steering(r); free(s);
assert(!cst_job_cancel(r, id, 1, "user_force"));
s = cst_job_read(r, id, 2000); assert(s && strstr(s, "cancelled") && strstr(s, "user_force")); free(s);
assert(!cst_runtime_finish(r, "partial", "explicit partial", 0, error, sizeof(error)));
assert(!cst_runtime_finish(r, "completed", "must not overwrite partial", 0, error, sizeof(error)));
s = cst_runtime_json(r); assert(s && strstr(s, "explicit partial")); free(s);
assert(!cst_runtime_controls(r, 1, 0, 1));
cst_runtime_close(r);
r = cst_runtime_open(path); assert(r);
assert(!cst_runtime_check(r, 0, 0, 0));
s = cst_job_read(r, id, 0); assert(s && strstr(s, "cancelled")); free(s);
cst_runtime_close(r);
write_text(dir, "session.task-v2.json", "{\"revision\":7,\"sequence\":1,\"status\":\"running\",\"jobs\":[{\"job_id\":\"job-7-1\",\"status\":\"running\",\"kind\":\"external\"}]}");
r = cst_runtime_open(path); assert(r && !cst_runtime_jobs_pending(r));
s = cst_job_read(r, "job-7-1", 0); assert(s && strstr(s, "interrupted")); free(s);
cst_runtime_begin(r, "resource locks");
pthread_mutex_lock(&probe_mu); active = peak = 0; pthread_mutex_unlock(&probe_mu);
char second[64];
assert(!cst_job_start(r, "shell", "same-resource", 1, locked_work, NULL, NULL, id, sizeof(id)));
assert(!cst_job_start(r, "shell", "same-resource", 1, locked_work, NULL, NULL, second, sizeof(second)));
s = cst_job_read(r, id, 2000); free(s); s = cst_job_read(r, second, 2000); free(s);
assert(peak == 1);
char first[64] = "";
for (int i = 0; i < 66; i++) {
assert(!cst_job_start(r, "read", "", 1, instant, NULL, NULL, id, sizeof(id)));
if (!i) snprintf(first, sizeof(first), "%s", id);
s = cst_job_read(r, id, 2000); assert(s && strstr(s, "completed")); free(s);
}
s = cst_job_read(r, first, 0); assert(s && strstr(s, "archived output")); free(s);
cst_runtime_close(r);
assert(cst_worktree_path_allowed("[\"src\"]", "src/core.c"));
assert(!cst_worktree_path_allowed("[\"src\"]", "other.c"));
assert(!cst_worktree_path_allowed("[\".\"]", "../escape"));
s = shell(dir, "git init -q && git config user.name Test && git config user.email test@localhost"); free(s);
write_text(dir, "file.txt", "first\nmiddle\nlast\n");
s = shell(dir, "git add file.txt && git commit -qm initial"); free(s);
write_text(dir, "file.txt", "user staged\nmiddle\nlast\n");
s = shell(dir, "git add file.txt"); free(s);
char *index_before = shell(dir, "git write-tree");
char wt[4096];
assert(!cst_worktree_create(dir, "[\"file.txt\"]", wt, sizeof(wt), error, sizeof(error)));
write_text(wt, "file.txt", "user staged\nmiddle\nworker edit\n");
write_text(dir, "file.txt", "user newer\nmiddle\nlast\n");
int code;
s = cst_worktree_diff(dir, wt, 0, &code); assert(!code && strstr(s, "worker edit")); free(s);
s = cst_worktree_diff(dir, wt, 1, &code); assert(!code); free(s);
s = shell(dir, "git write-tree"); assert(!strcmp(s, index_before)); free(s); free(index_before);
s = shell(dir, "git diff -- file.txt"); assert(strstr(s, "user newer") && strstr(s, "worker edit")); free(s);
/* Conflicting integration must preserve the user's bytes. */
assert(!cst_worktree_create(dir, "[\"file.txt\"]", wt, sizeof(wt), error, sizeof(error)));
write_text(wt, "file.txt", "worker conflict\nmiddle\nworker edit\n");
write_text(dir, "file.txt", "user conflict\nmiddle\nworker edit\n");
s = cst_worktree_diff(dir, wt, 1, &code); assert(code && strstr(s, "conflict")); free(s);
s = shell(dir, "git diff -- file.txt"); assert(strstr(s, "user conflict") && !strstr(s, "<<<<<<<")); free(s);
test_rmrf(dir);
puts("test_runtime: OK"); return 0;
}