1#include "internal.h"
2
3#include <asm.h>
4#include <console/panic.h>
5#include <console/printf.h>
6#include <global.h>
7#include <mem/alloc.h>
8#include <mem/pmm.h>
9#include <ndjson.h>
10#include <nightmare/perturb.h>
11#include <nightmare/record.h>
12#include <smp/smp.h>
13#include <string.h>
14#include <thread/thread.h>
15#include <time/time.h>
16
17#ifndef CHARMOS_COMMIT
18#define CHARMOS_COMMIT "unknown"
19#endif
20
21#define NIGHTMARE_MIB (1024ULL * 1024ULL)
22#define NIGHTMARE_DEFAULT_DURATION_MS 1000
23#define NIGHTMARE_DEFAULT_DRAIN_MS 20000
24#define NIGHTMARE_DEFAULT_STAT_MS 5000
25
26struct nightmare_runtime nightmare_runtime;
27
28static void nightmare_exit(uint8_t code, const char *reason) {
29 ndjson_bye(code, reason);
30 qemu_exit(code);
31 while (true)
32 cpu_relax();
33}
34
35const char *nightmare_result_string(enum nightmare_result result) {
36 switch (result) {
37 case NIGHTMARE_RESULT_OK: return "ok";
38 case NIGHTMARE_RESULT_FINDING: return "finding";
39 case NIGHTMARE_RESULT_FAIL: return "fail";
40 case NIGHTMARE_RESULT_STALL: return "stall";
41 case NIGHTMARE_RESULT_SKIP: return "skip";
42 default: return "unknown";
43 }
44}
45
46const char *nightmare_skip_string(enum nightmare_skip_reason reason) {
47 switch (reason) {
48 case NIGHTMARE_SKIP_NONE: return "none";
49 case NIGHTMARE_SKIP_NOT_COMPILED: return "not_compiled";
50 case NIGHTMARE_SKIP_NO_SUCH_NIGHTMARE: return "no_such_nightmare";
51 case NIGHTMARE_SKIP_NEEDS_SMP: return "needs_smp";
52 case NIGHTMARE_SKIP_NEEDS_PREEMPT: return "needs_preempt";
53 case NIGHTMARE_SKIP_NEEDS_ASAN: return "needs_asan";
54 case NIGHTMARE_SKIP_NEEDS_INJECT: return "needs_inject";
55 case NIGHTMARE_SKIP_RAM_LOW: return "ram_low";
56 case NIGHTMARE_SKIP_SEED_UNUSED: return "seed_unused";
57 case NIGHTMARE_SKIP_SEED_MISSING: return "seed_missing";
58 case NIGHTMARE_SKIP_SERVICE_MISSING: return "service_missing";
59 case NIGHTMARE_SKIP_PREPARE_REFUSED: return "prepare_refused";
60 default: return "unknown";
61 }
62}
63
64const char *nightmare_seed_policy_string(enum nightmare_seed_policy policy) {
65 switch (policy) {
66 case NIGHTMARE_SEED_IGNORED: return "ignored";
67 case NIGHTMARE_SEED_OPTIONAL: return "optional";
68 case NIGHTMARE_SEED_REQUIRED: return "required";
69 default: return "unknown";
70 }
71}
72
73const char *nightmare_seed_mode_string(enum nightmare_seed_mode mode) {
74 switch (mode) {
75 case NIGHTMARE_SEED_SPLIT: return "split";
76 case NIGHTMARE_SEED_SEEDFUL: return "seedful";
77 case NIGHTMARE_SEED_SEEDLESS: return "seedless";
78 default: return "unknown";
79 }
80}
81
82static uint8_t nightmare_exit_for_result(enum nightmare_result result) {
83 switch (result) {
84 case NIGHTMARE_RESULT_OK: return NIGHTMARE_EXIT_OK;
85 case NIGHTMARE_RESULT_FINDING: return NIGHTMARE_EXIT_FINDING;
86 case NIGHTMARE_RESULT_FAIL: return NIGHTMARE_EXIT_FAIL;
87 case NIGHTMARE_RESULT_STALL: return NIGHTMARE_EXIT_STALL;
88 case NIGHTMARE_RESULT_SKIP: return NIGHTMARE_EXIT_SKIP;
89 default: return NIGHTMARE_EXIT_FAIL;
90 }
91}
92
93static uint64_t nightmare_progress_snapshot(void) {
94#ifdef TEST_NIGHTMARE_ENABLED
95 return nightmare_progress_sum_irq();
96#else
97 return 0;
98#endif
99}
100
101static void append_cap(char *caps, size_t cap, const char *value) {
102 size_t used = strlen(str: caps);
103 if (used >= cap - 1)
104 return;
105 snprintf(buffer: caps + used, buffer_len: (int) (cap - used), format: "%s%s", used ? "," : "", value);
106}
107
108static void nightmare_build_caps(void) {
109 nightmare_runtime.caps[0] = '\0';
110 append_cap(caps: nightmare_runtime.caps, cap: sizeof(nightmare_runtime.caps),
111 value: "preempt");
112#ifdef DEBUG_ASAN
113 append_cap(nightmare_runtime.caps, sizeof(nightmare_runtime.caps), "asan");
114#endif
115#ifdef DEBUG_LOCK_CHK
116 append_cap(nightmare_runtime.caps, sizeof(nightmare_runtime.caps),
117 "lock_chk");
118#endif
119#ifdef INJECT_ENABLED
120 append_cap(nightmare_runtime.caps, sizeof(nightmare_runtime.caps),
121 "inject");
122#endif
123}
124
125#ifdef TEST_NIGHTMARE_ENABLED
126static const struct nightmare *nightmare_resolve(const char *name) {
127 const struct nightmare *match = NULL;
128 for (struct nightmare *nm = __skernel_nightmares; nm < __ekernel_nightmares;
129 nm++) {
130 if (!nm->name)
131 nightmare_panic("nightmare registry contains an unnamed entry");
132 if (strcmp(str1: nm->name, str2: name) != 0)
133 continue;
134 if (match)
135 nightmare_panic("duplicate nightmare name '%s' in %s and %s", name,
136 match->fname, nm->fname);
137 match = nm;
138 }
139 return match;
140}
141#endif
142
143static void
144nightmare_record_resolved_boot(const struct nightmare_cmdline_config *config,
145 const char *requested) {
146 const struct nightmare *nm = nightmare_runtime.ctx.nm;
147 nightmare_record_boot(record: &(struct nightmare_boot_record){
148 .test = nm ? nm->name : requested,
149 .seed = config->seed,
150 .seed_policy =
151 nm ? nightmare_seed_policy_string(policy: nm->seed_policy) : "unknown",
152 .seed_mode = nightmare_seed_mode_string(mode: config->seed_mode),
153 .intensity = nightmare_runtime.ctx.intensity,
154 .intensity_val = nightmare_runtime.ctx.intensity_val,
155 .workers = nightmare_runtime.ctx.worker_count,
156 .smp = global.core_count,
157 .mem_mib = pmm_get_usable_ram() / NIGHTMARE_MIB,
158 .caps = nightmare_runtime.caps,
159 .campaign_id = config->campaign_id ? config->campaign_id : "",
160 .boot_index = config->boot_index,
161 .commit = CHARMOS_COMMIT,
162 });
163}
164
165static void nightmare_emit_verdict(struct nightmare_verdict verdict,
166 const char *fallback_reason) {
167 size_t findings = atomic_load_explicit(&nightmare_runtime.finding_count,
168 memory_order_acquire);
169 enum nightmare_result result =
170 nightmare_result_with_findings(result: verdict.result, findings);
171
172 const char *reason = verdict.reason;
173 if (result == NIGHTMARE_RESULT_SKIP)
174 reason = nightmare_skip_string(reason: verdict.skip_reason);
175 if (!reason)
176 reason = fallback_reason;
177
178 time_ms_t now = time_get_ms();
179 uint64_t elapsed =
180 nightmare_runtime.started_ms ? now - nightmare_runtime.started_ms : 0;
181 nightmare_record_verdict(record: &(struct nightmare_verdict_record){
182 .result = nightmare_result_string(result),
183 .reason = reason ? reason : "unknown",
184 .duration_ms = elapsed,
185 .progress = nightmare_progress_snapshot(),
186 .findings = findings,
187 .msg = verdict.msg ? verdict.msg : "",
188 });
189 nightmare_exit(code: nightmare_exit_for_result(result),
190 reason: reason ? reason : nightmare_result_string(result));
191}
192
193enum nightmare_result
194nightmare_result_with_findings(enum nightmare_result result, size_t findings) {
195 if (result == NIGHTMARE_RESULT_OK && findings)
196 return NIGHTMARE_RESULT_FINDING;
197 return result;
198}
199
200#ifdef TEST_NIGHTMARE_ENABLED
201static enum nightmare_skip_reason
202nightmare_seed_refusal(const struct nightmare *nm,
203 const struct nightmare_cmdline_config *config) {
204 if (config->seed_mode != NIGHTMARE_SEED_SEEDLESS && !config->seed_present)
205 return NIGHTMARE_SKIP_SEED_MISSING;
206 if (nm->seed_policy == NIGHTMARE_SEED_IGNORED &&
207 config->seed_mode == NIGHTMARE_SEED_SEEDFUL)
208 return NIGHTMARE_SKIP_SEED_UNUSED;
209 if (nm->seed_policy == NIGHTMARE_SEED_REQUIRED &&
210 config->seed_mode != NIGHTMARE_SEED_SEEDFUL)
211 return NIGHTMARE_SKIP_SEED_MISSING;
212 return NIGHTMARE_SKIP_NONE;
213}
214
215static bool
216nightmare_has_missing_service(const struct nightmare *nm,
217 const struct nightmare_cmdline_config *config) {
218 if (nm->perturb) {
219 for (const char *const *name = nm->perturb; *name; name++) {
220 if (!nightmare_perturb_lookup(name: *name))
221 return true;
222 }
223 }
224
225 if (!config->perturb_present)
226 return false;
227 for (size_t i = 0; i < config->perturb.count; i++) {
228 const char *name = NULL;
229 if (cmdline_extract_const_string(val: &config->perturb.items[i], out: &name) !=
230 ERR_OK ||
231 !nightmare_perturb_lookup(name))
232 return true;
233 }
234 return false;
235}
236
237static enum nightmare_skip_reason
238nightmare_preflight(const struct nightmare *nm,
239 const struct nightmare_cmdline_config *config) {
240 if ((nm->requires & NIGHTMARE_REQ_SMP) && global.core_count < 2)
241 return NIGHTMARE_SKIP_NEEDS_SMP;
242 /* Today, we always have preemption enabled, however TODO: someday
243 * we can implement support for a non-preemptive kernel*/
244 if ((nm->requires & NIGHTMARE_REQ_PREEMPT) == 0) {
245 /* No-op */
246 }
247#ifndef DEBUG_ASAN
248 if (nm->requires & NIGHTMARE_REQ_ASAN)
249 return NIGHTMARE_SKIP_NEEDS_ASAN;
250#endif
251#ifndef INJECT_ENABLED
252 if (nm->requires & NIGHTMARE_REQ_INJECT)
253 return NIGHTMARE_SKIP_NEEDS_INJECT;
254#endif
255 if (pmm_get_usable_ram() / NIGHTMARE_MIB < nm->min_mem_mib)
256 return NIGHTMARE_SKIP_RAM_LOW;
257
258 enum nightmare_skip_reason seed = nightmare_seed_refusal(nm, config);
259 if (seed != NIGHTMARE_SKIP_NONE)
260 return seed;
261 if (nightmare_has_missing_service(nm, config))
262 return NIGHTMARE_SKIP_SERVICE_MISSING;
263 return NIGHTMARE_SKIP_NONE;
264}
265
266static void nightmare_soft_deadline(struct timer *timer) {
267 (void) timer;
268 nightmare_publish_stop(reason: NM_STOP_BUDGET);
269}
270
271static void nightmare_hard_deadline(struct timer *timer) {
272 (void) timer;
273 bool expected = false;
274 if (!atomic_compare_exchange_strong_explicit(
275 &nightmare_runtime.terminal, &expected, true, memory_order_acq_rel,
276 memory_order_acquire))
277 return;
278
279 nightmare_publish_stop(reason: NM_STOP_STALL);
280 ndjson_enter_panic();
281 nightmare_record_verdict(record: &(struct nightmare_verdict_record){
282 .result = "stall",
283 .reason = "drain_timeout",
284 .duration_ms = time_get_ms() - nightmare_runtime.started_ms,
285 .progress = nightmare_progress_sum_irq(),
286 .findings = atomic_load_explicit(&nightmare_runtime.finding_count,
287 memory_order_relaxed),
288 .msg = "hard deadline expired before teardown completed",
289 });
290 nightmare_exit(code: NIGHTMARE_EXIT_STALL, reason: "hard deadline");
291}
292
293static void nightmare_arm_deadlines(time_ms_t duration_ms,
294 time_ms_t drain_grace_ms) {
295 time_us_t now = time_get_us();
296 timer_init(timer: &nightmare_runtime.soft_timer, func: nightmare_soft_deadline, NULL);
297 nightmare_runtime.soft_timer.flags = TIMER_FLAG_IRQ;
298 nightmare_runtime.soft_timer.expiration_us = now + MS_TO_US(duration_ms);
299 timer_add_global(timer: &nightmare_runtime.soft_timer);
300
301 timer_init(timer: &nightmare_runtime.hard_timer, func: nightmare_hard_deadline, NULL);
302 nightmare_runtime.hard_timer.flags = TIMER_FLAG_IRQ;
303 nightmare_runtime.hard_timer.expiration_us =
304 now + MS_TO_US(duration_ms + drain_grace_ms);
305 timer_add_global(timer: &nightmare_runtime.hard_timer);
306}
307
308static uint64_t nightmare_worker_seed(const struct nightmare_ctx *ctx,
309 size_t index) {
310 bool seeded =
311 ctx->seed_mode == NIGHTMARE_SEED_SEEDFUL ||
312 (ctx->seed_mode == NIGHTMARE_SEED_SPLIT && index >= ctx->worker_count);
313 uint64_t base = seeded ? ctx->seed : time_get_ns();
314 base ^= PRNG_SPLITMIX64_GAMMA * (index + 1);
315 struct nightmare_rng rng = {.state = base};
316 return nightmare_rand(rng: &rng);
317}
318
319static bool nightmare_spawn_threads(void) {
320 size_t created = 0;
321 for (size_t i = 0; i < nightmare_runtime.total_worker_count; i++) {
322 struct nightmare_worker *worker = &nightmare_runtime.workers[i];
323 worker->index = i;
324 if (i < nightmare_runtime.ctx.worker_count) {
325 worker->role = "worker";
326 } else {
327 size_t pidx = i - nightmare_runtime.ctx.worker_count;
328 worker->role = nightmare_runtime.perturbers[pidx]->name;
329 }
330 worker->rng.state =
331 nightmare_worker_seed(ctx: &nightmare_runtime.ctx, index: worker->index);
332 struct thread *thread = thread_spawn_joinable(
333 name: "nightmare_%s_%zu", entry: nightmare_thread_main, arg: worker, worker->role, i);
334 if (!thread)
335 goto fail;
336 atomic_store_explicit(&worker->th, thread, memory_order_release);
337 created++;
338 }
339
340 nightmare_runtime.heartbeat = thread_spawn_joinable(
341 name: "nightmare_heartbeat", entry: nightmare_heartbeat_main, NULL);
342 if (!nightmare_runtime.heartbeat)
343 goto fail;
344 return true;
345
346fail:
347 nightmare_publish_stop(reason: NM_STOP_FAIL);
348 complete_all(c: &nightmare_runtime.start);
349 for (size_t i = created; i > 0; i--) {
350 struct thread *th = atomic_load_explicit(
351 &nightmare_runtime.workers[i - 1].th, memory_order_acquire);
352 if (th)
353 thread_join(t: th);
354 }
355 return false;
356}
357
358static void nightmare_join_threads(void) {
359 for (size_t i = nightmare_runtime.ctx.worker_count;
360 i < nightmare_runtime.total_worker_count; i++) {
361 struct thread *thread = atomic_load_explicit(
362 &nightmare_runtime.workers[i].th, memory_order_acquire);
363 if (thread)
364 thread_join(t: thread);
365 }
366 for (size_t i = 0; i < nightmare_runtime.ctx.worker_count; i++) {
367 struct thread *thread = atomic_load_explicit(
368 &nightmare_runtime.workers[i].th, memory_order_acquire);
369 if (thread)
370 thread_join(t: thread);
371 }
372 if (nightmare_runtime.heartbeat)
373 thread_join(t: nightmare_runtime.heartbeat);
374}
375
376void nightmare_publish_perturb_verdict(struct nightmare_verdict verdict) {
377 if (verdict.result == NIGHTMARE_RESULT_OK ||
378 atomic_load_explicit(&nightmare_runtime.perturb_verdict_ready,
379 memory_order_acquire))
380 return;
381
382 if (verdict.reason) {
383 snprintf(buffer: nightmare_runtime.perturb_reason,
384 buffer_len: sizeof(nightmare_runtime.perturb_reason), format: "%s",
385 verdict.reason);
386 verdict.reason = nightmare_runtime.perturb_reason;
387 }
388 if (verdict.msg) {
389 snprintf(buffer: nightmare_runtime.perturb_msg,
390 buffer_len: sizeof(nightmare_runtime.perturb_msg), format: "%s", verdict.msg);
391 verdict.msg = nightmare_runtime.perturb_msg;
392 }
393 nightmare_runtime.perturb_verdict = verdict;
394 atomic_store_explicit(&nightmare_runtime.perturb_verdict_ready, true,
395 memory_order_release);
396}
397
398bool nightmare_load_perturb_verdict(struct nightmare_verdict *out) {
399 if (!atomic_load_explicit(&nightmare_runtime.perturb_verdict_ready,
400 memory_order_acquire))
401 return false;
402 *out = nightmare_runtime.perturb_verdict;
403 return true;
404}
405static void nightmare_add_perturber(const struct nightmare_perturb_desc *desc) {
406 if (!desc || nightmare_runtime.perturber_count >= NIGHTMARE_MAX_PERTURBERS)
407 return;
408 for (size_t i = 0; i < nightmare_runtime.perturber_count; i++) {
409 if (nightmare_runtime.perturbers[i] == desc)
410 return;
411 }
412 nightmare_runtime.perturbers[nightmare_runtime.perturber_count++] = desc;
413}
414
415static void
416nightmare_collect_perturbers(const struct nightmare *nm,
417 const struct nightmare_cmdline_config *config) {
418 nightmare_runtime.perturber_count = 0;
419 if (nm->perturb) {
420 for (const char *const *name = nm->perturb; *name; name++)
421 nightmare_add_perturber(desc: nightmare_perturb_lookup(name: *name));
422 }
423 if (config->perturb_present) {
424 for (size_t i = 0; i < config->perturb.count; i++) {
425 const char *name = NULL;
426 if (cmdline_extract_const_string(val: &config->perturb.items[i],
427 out: &name) == ERR_OK &&
428 name)
429 nightmare_add_perturber(desc: nightmare_perturb_lookup(name));
430 }
431 }
432}
433
434static struct nightmare_verdict
435nightmare_finalize_verdict(const struct nightmare *nm) {
436 struct nightmare_verdict final = NIGHTMARE_OK;
437 struct nightmare_verdict perturb_verdict;
438 bool has_perturb_verdict = nightmare_load_perturb_verdict(out: &perturb_verdict);
439 if (nm->ops && nm->ops->quiesce_check) {
440 final = nm->ops->quiesce_check(&nightmare_runtime.ctx);
441 nightmare_record_quiesce(record: &(struct nightmare_quiesce_record){
442 .result = nightmare_result_string(result: final.result),
443 .checks = 1,
444 });
445 }
446 if (has_perturb_verdict)
447 final = perturb_verdict;
448 else if (final.result == NIGHTMARE_RESULT_OK && nm->ops && nm->ops->finish)
449 final = nm->ops->finish(&nightmare_runtime.ctx);
450
451 enum nightmare_stop stop =
452 atomic_load_explicit(&nightmare_runtime.stop, memory_order_acquire);
453 return nightmare_verdict_for_stop(verdict: final, stop);
454}
455
456struct nightmare_verdict
457nightmare_verdict_for_stop(struct nightmare_verdict final,
458 enum nightmare_stop stop) {
459 if (stop == NM_STOP_STALL)
460 final = (struct nightmare_verdict){
461 .result = NIGHTMARE_RESULT_STALL,
462 .reason = "liveness",
463 };
464 else if (stop == NM_STOP_FAIL && final.result == NIGHTMARE_RESULT_OK)
465 final = NIGHTMARE_FAIL("harness", "harness requested termination");
466
467 return final;
468}
469#endif
470
471void nightmare_run(void) {
472 struct nightmare_cmdline_config config;
473 nightmare_cmdline_get(config: &config);
474 if (!config.selector)
475 return;
476
477 nightmare_runtime = (struct nightmare_runtime){
478 .ctx = {.seed = config.seed,
479 .seed_present = config.seed_present,
480 .seed_mode = config.seed_mode},
481 .active = ATOMIC_VAR_INIT(false),
482 .stop = ATOMIC_VAR_INIT(NM_RUN),
483 .quiesce_requested = ATOMIC_VAR_INIT(false),
484 .parked_count = ATOMIC_VAR_INIT(0),
485 .finding_count = ATOMIC_VAR_INIT(0),
486 .terminal = ATOMIC_VAR_INIT(false),
487 .perturb_verdict_ready = ATOMIC_VAR_INIT(false),
488 .stat_interval_ms = config.stat_interval_ms ? config.stat_interval_ms
489 : NIGHTMARE_DEFAULT_STAT_MS,
490 .campaign_id = config.campaign_id,
491 .boot_index = config.boot_index,
492 };
493 completion_init(c: &nightmare_runtime.start, COMPLETION_INIT_NORMAL);
494 nightmare_runtime.started_ms = time_get_ms();
495 nightmare_build_caps();
496
497#ifndef TEST_NIGHTMARE_ENABLED
498 nightmare_record_resolved_boot(&config, config.selector);
499 nightmare_emit_verdict(NIGHTMARE_SKIP(NIGHTMARE_SKIP_NOT_COMPILED),
500 "not_compiled");
501#else
502 const struct nightmare *nm = nightmare_resolve(name: config.selector);
503 nightmare_runtime.ctx.nm = nm;
504 if (!nm) {
505 nightmare_record_resolved_boot(config: &config, requested: config.selector);
506 nightmare_emit_verdict(NIGHTMARE_SKIP(NIGHTMARE_SKIP_NO_SUCH_NIGHTMARE),
507 fallback_reason: "no_such_nightmare");
508 }
509
510 nightmare_runtime.ctx.intensity =
511 config.intensity != NIGHTMARE_INTENSITY_SENTINEL
512 ? config.intensity
513 : (nm->intensity != NIGHTMARE_INTENSITY_SENTINEL
514 ? nm->intensity
515 : NIGHTMARE_INTENSITY_DEFAULT);
516 nightmare_runtime.ctx.intensity_val =
517 scaled_param_eval(desc: &nm->intensity_desc, value: nightmare_runtime.ctx.intensity);
518 nightmare_runtime.ctx.worker_count =
519 nightmare_runtime.ctx.intensity_val
520 ? nightmare_runtime.ctx.intensity_val
521 : 1;
522
523 time_ms_t duration_ms =
524 config.duration_ms
525 ? config.duration_ms
526 : (nm->default_duration_ms ? nm->default_duration_ms
527 : NIGHTMARE_DEFAULT_DURATION_MS);
528 time_ms_t drain_ms = config.drain_grace_ms ? config.drain_grace_ms
529 : NIGHTMARE_DEFAULT_DRAIN_MS;
530 nightmare_runtime.ctx.soft_deadline_ms =
531 nightmare_runtime.started_ms + duration_ms;
532 nightmare_runtime.ctx.hard_deadline_ms =
533 nightmare_runtime.ctx.soft_deadline_ms + drain_ms;
534
535 nightmare_record_resolved_boot(config: &config, requested: config.selector);
536
537 enum nightmare_skip_reason refusal = nightmare_preflight(nm, config: &config);
538 if (refusal != NIGHTMARE_SKIP_NONE)
539 nightmare_emit_verdict(NIGHTMARE_SKIP(refusal),
540 fallback_reason: nightmare_skip_string(reason: refusal));
541
542 atomic_store_explicit(&nightmare_runtime.active, true,
543 memory_order_release);
544 nightmare_arm_deadlines(duration_ms, drain_grace_ms: drain_ms);
545
546 struct nightmare_verdict prepared = NIGHTMARE_OK;
547 if (nm->ops && nm->ops->prepare)
548 prepared = nm->ops->prepare(&nightmare_runtime.ctx);
549 if (prepared.result != NIGHTMARE_RESULT_OK) {
550 timer_shutdown_sync(timer: &nightmare_runtime.soft_timer);
551 timer_shutdown_sync(timer: &nightmare_runtime.hard_timer);
552 if (prepared.result == NIGHTMARE_RESULT_SKIP)
553 prepared = NIGHTMARE_SKIP(NIGHTMARE_SKIP_PREPARE_REFUSED);
554 nightmare_emit_verdict(verdict: prepared, fallback_reason: "prepare");
555 }
556
557 nightmare_collect_perturbers(nm, config: &config);
558
559 nightmare_runtime.total_worker_count =
560 nightmare_runtime.ctx.worker_count + nightmare_runtime.perturber_count;
561
562 nightmare_runtime.workers = kmalloc(nightmare_runtime.total_worker_count *
563 sizeof(*nightmare_runtime.workers),
564 ALLOC_FLAGS_ZERO);
565 if (!nightmare_runtime.workers) {
566 timer_shutdown_sync(timer: &nightmare_runtime.soft_timer);
567 timer_shutdown_sync(timer: &nightmare_runtime.hard_timer);
568 nightmare_emit_verdict(
569 NIGHTMARE_FAIL("worker_alloc", "could not allocate worker state"),
570 fallback_reason: "worker_alloc");
571 }
572
573 if (!nightmare_spawn_threads()) {
574 timer_shutdown_sync(timer: &nightmare_runtime.soft_timer);
575 timer_shutdown_sync(timer: &nightmare_runtime.hard_timer);
576 nightmare_emit_verdict(
577 NIGHTMARE_FAIL("thread_create", "could not create all threads"),
578 fallback_reason: "thread_create");
579 }
580
581 if (!nightmare_liveness_start(threshold_ms: config.stall_threshold_ms, policy: config.on_stall))
582 nightmare_panic("could not start liveness detector");
583 complete_all(c: &nightmare_runtime.start);
584 nightmare_join_threads();
585 nightmare_liveness_stop();
586 nightmare_publish_stop(reason: NM_STOP_BUDGET);
587
588 timer_shutdown_sync(timer: &nightmare_runtime.soft_timer);
589 timer_shutdown_sync(timer: &nightmare_runtime.hard_timer);
590
591 struct nightmare_verdict final = nightmare_finalize_verdict(nm);
592 atomic_store_explicit(&nightmare_runtime.active, false,
593 memory_order_release);
594
595 kfree(nightmare_runtime.workers);
596 nightmare_runtime.workers = NULL;
597 if (nightmare_runtime.ctx.private) {
598 kfree(nightmare_runtime.ctx.private);
599 nightmare_runtime.ctx.private = NULL;
600 }
601
602 bool expected = false;
603 if (atomic_compare_exchange_strong_explicit(
604 &nightmare_runtime.terminal, &expected, true, memory_order_acq_rel,
605 memory_order_acquire))
606 nightmare_emit_verdict(verdict: final, fallback_reason: "completed");
607#endif
608}
609