Kernel thread stop reap blocked#39
Conversation
| puts("Joining worker 0 thread"); | ||
| pthread_join(intpool_child_1, &ret); | ||
| puts("Joining worker 1 thread"); | ||
| // stop_threads = 1; |
There was a problem hiding this comment.
Please remove code lines which are in comments
Returning from main (or calling exit()) needs to tear down the entire VM, not just the calling thread. Previously sys_exit() funneled into pthread_exit, which left peer threads — especially ones blocked in a futex (H2K_STATUS_BLOCKED) or intpool wait (H2K_STATUS_INTBLOCKED) — stuck forever, leaking the vmblock. Introduce a new H2_TRAP_VM_STOP (#7) syscall vector dispatched to H2K_vm_stop, and route sys_exit through h2_vm_stop_trap unconditionally (any status, not just zero). H2K_vm_stop scans the vmblock's context array and reaps every non-DEAD peer regardless of state: * BLOCKED — remove from futex hash, cancel timer * INTBLOCKED — remove from intpool ring * READY / VMWAIT — remove from runlist * RUNNING peers on other HW threads — flagged via vmblock->exiting and IPI'd; resched.ref.c picks up the flag and self-reaps the context before scheduling Each reap path performs the common cleanup (ASID refcount, context clear, return to free list, num_cpus--), then the new helper H2K_vmblock_finalize_if_done_locked() signals the parent VM and frees the vmblock once num_cpus reaches zero. Track which context is "main" so future policy can hinge on it: vmblock->main_context is set by setup.ref.c for the boot VM and by create.ref.c for the first thread of every other VM. Trap-table test (kernel/event/trap/test/test.c) and the intpool h2 test (kernel/event/intpool/test_h2/test.c) are updated for the new vector and the new teardown semantics — the latter no longer needs to manually join workers since main's return now reaps them. Signed-off-by: Andrey Karpenko <andreyk@qti.qualcomm.com>
Add 36 self-contained tests under libs/posix/pthread/test_h2/ exercising
the pthread/sem/rwlock/barrier/TLS surface and the H2K_vm_stop teardown
path introduced in the previous commit. Each test is a directory with
test.c + Makefile + Makefile.inc following the existing test_h2
convention.
Register the new tests in scripts/testlist.v61 and scripts/testlist.v81
so they run as part of the unified test suite. pthread_exit_main is
checked in but commented out in both testlists pending follow-up.
Coverage groups:
* Basic API: attr_roundtrip, barrier_basic, cond_signal_broadcast,
detach_states, join_basic, join_invalid, mutex_recursive,
mutex_trylock, rwlock_readers_writers, sem_corner,
tls_keys
* exit() teardown (exercises H2K_vm_stop):
exit1_main_mutex, exit1_main_cond_wait,
exit1_worker_cond_wait, exit1_detached_worker_with_stuck
* pthread_exit-from-main (POSIX: terminate caller, keep process alive):
pthread_exit_main
* Blocked-thread reap on VM exit (workers parked in sync primitives
when main returns):
stuck_in_join, stuck_in_mutex, stuck_in_cond_wait,
stuck_in_cond_timedwait, stuck_in_sem_wait,
stuck_in_sem_timedwait, stuck_in_rwlock_rd,
stuck_in_rwlock_wr, stuck_in_barrier,
stuck_in_pthread_exit_joined
* Negative / misuse:
neg_attr_setstacksize_zero, neg_barrier_init_zero,
neg_cond_wait_no_mutex, neg_create_invalid_routine,
neg_join_self, neg_mutex_destroy_held,
neg_mutex_unlock_unowned, neg_rwlock_unlock_unheld,
neg_sem_overflow_post, neg_tls_use_after_delete
Signed-off-by: Andrey Karpenko <andreyk@qti.qualcomm.com>
Three independent fixes in the test build/run plumbing surfaced once
multiple ARCHV×TARGET variants started running in parallel and writing
into a shared in-source test directory.
makefile:
* Per-variant test_results.json rule prefixes the inner $(MAKE) with
'-' so unified-report aggregation runs even when one variant's
tests fail. The JSON file is still written by h2_test before
check-fail runs.
* h2_test reorders steps so test_report.html and test_results.json
are generated *before* the warning-grep gate; previously a stray
warning blocked report generation entirely.
scripts/Makefile.coverage:
* Per-test results.txt rule's inner $(MAKE) gets the '-' prefix so
.DELETE_ON_ERROR doesn't wipe FAIL details and abort tst — PASS/FAIL
is captured in results.txt content for check-fail and
gen_test_results.py.
* Symlink whitelist replaces the old "everything except Makefile.inc"
blacklist when populating the per-variant build dir. Without the
whitelist, every variant followed symlinks back to the source tree
and clobbered each other's *.elf / *.o / results.txt / gmon-*.out,
leaving only the last writer's outputs in the report. Whitelist
covers source extensions (Makefile, *.c/.h/.S/.s/.cpp/.cc/.py/.dat/
.cfg, tested_functions); explicit excludes drop generator outputs
whose extension matches a source extension (scenarios.h,
generated_tests.dat, threadmap.py).
scripts/Makefile.inc.test:
* Add 'test' as an alias for 'tst' and mark test/tst/all .PHONY so
they don't collide with stray files of the same name.
Signed-off-by: Andrey Karpenko <andreyk@qti.qualcomm.com>
Per POSIX, pthread_exit from the main thread terminates only the main
thread; the process must remain alive so other threads run to
completion. Two distinct bugs together broke that; both are fixed here,
and pthread_exit_main is enabled in v61 and v81 testlists to lock the
behavior in.
kernel/thread/stop: drop is_main shortcut
H2K_thread_stop (H2_TRAP_THREAD_STOP, used by pthread_safe_death)
treated main_context exiting as a VM teardown -- calling vm_stop_locked
to reap every sibling. That collapsed pthread_exit and exit() into the
same fatal path. Only exit() routes through H2_TRAP_VM_STOP, so the
kernel can distinguish: thread_stop now does the same per-me cleanup
for main as for any worker, then nulls main_context so the existing
all-blocked reaper at the bottom of the function can finalize the VM
cleanly when remaining threads settle. exit()/sys_exit() still gets
full VM teardown via H2K_vm_stop, untouched.
libs/posix/pthread: don't queue main's TLS for deferred free
pthread_exit defers freeing the exiting thread's TCB+TLS via static
old_freeptr / old_stack_freeptr -- set so the next exiting thread frees
the previous one. The math (char *)self - elftls_size only works for
worker threads (calloc'd by pthread_create with TCB at
tmpptr + elftls_size). For main:
* Small ELF TLS path: TCB lives in mainthread_static_storage (BSS).
Freeing into BSS is undefined.
* Large ELF TLS path: TCB sits at malloc'd + alignment correction --
the math gives main_thread_tls, not the malloc return.
Use aligned_alloc(TLS_ALIGN, round_up(size, TLS_ALIGN)) in the
large-TLS path so the math is correct (no manual alignment fixup), and
track main's TCB via a file-scope main_tcb pointer. pthread_exit on
main consumes the previously queued frees but skips queueing itself,
so neither static storage nor a non-malloc'd interior pointer ever
reaches free().
scripts/testlist.v{61,81}: enable pthread_exit_main
The test (added in 649bddf) exercises main calling pthread_exit with
a mix of joinable and detached worker threads. With both fixes above,
it passes under archsim and hexagon-sim.
Signed-off-by: Andrey Karpenko <andreyk@qti.qualcomm.com>
Mirrors pthread_exit_main except no worker calls exit(): every worker just `return NULL`s from its start routine, and the last one to bump the shared counter prints TEST PASSED before returning. Main still leaves via pthread_exit(NULL). This forces VM teardown through the all-blocked / all-dead reaper path -- nobody triggers the H2_TRAP_VM_STOP shortcut -- and locks in a clean exit status from that path. Registered in scripts/testlist.v61 and scripts/testlist.v81. Signed-off-by: Andrey Karpenko <andreyk@qti.qualcomm.com>
Signed-off-by: Andrey Karpenko <andreyk@qti.qualcomm.com>
3c19e13 to
6ac16fe
Compare
Erich Plondke (eplondke)
left a comment
There was a problem hiding this comment.
This is a great feature, it's something we need pretty badly: the ability to kill another thread, and building on top of that being able to kill a vm/process/etc.
There's some partial support for killing another task and we test it at least somewhat. If we set the H2K_VMSTATUS_KILL bit in a thread it's supposed to wake up and call H2K_thread_stop(). But we currently don't ever set that bit outside of testing! :-) But I think that infrastructure might be the best way to reuse some of the other infrastructure to get a thread killed off regardless of its state.
Conceptually, it seems like then the "vm death" sequence is:
- Mark the VM as "being killed"
- Mark all threads in the VCPU for death.
- Possibly, make sure we don't create new VCPUs in a VM "being killed"
- If the number of VCPUs in the VM goes to zero, we either "signal the parent" (like we might already do?) or "just tear down the VM" as a configured option.
- Hopefully this works if a guest is asking for itself to be torn down, or the parent is tearing down the child.
This seems pretty great, but I'm hopeful that we can leverage the existing infrastructure a little more if it's working. If it's not working... maybe we can fix it... or if it's too broken then we can create something new like this.
Let me know if you want to chat live about this!
| } | ||
|
|
||
| static inline void resched(u32_t unused, H2K_thread_context *me, u32_t hwtnum) { | ||
| if (me != NULL && me->vmblock != NULL && me->vmblock->exiting) { |
There was a problem hiding this comment.
Let's try to avoid this special case
There was a problem hiding this comment.
There is an analyze of the difference
2. Structure & size
┌────────────────────┬────────────────────────────────────────┬────────────────────────────────────┐
│ │ orig_nohint │ curr_hint │
├────────────────────┼────────────────────────────────────────┼────────────────────────────────────┤
│ resched body │ inlined twice (into both entry points) │ outlined once into .text │
├────────────────────┼────────────────────────────────────────┼────────────────────────────────────┤
│ Entry points │ full body each │ 4–9-packet stubs that jump to body │
├────────────────────┼────────────────────────────────────────┼────────────────────────────────────┤
│ .text.core.resched │ 0x13c (316 B) │ 0x38 (56 B, stubs only) │
├────────────────────┼────────────────────────────────────────┼────────────────────────────────────┤
│ .text │ — │ 0xd0 (208 B, shared body) │
├────────────────────┼────────────────────────────────────────┼────────────────────────────────────┤
│ Total code │ 316 B │ 264 B ✅ │
└────────────────────┴────────────────────────────────────────┴────────────────────────────────────┘
Despite curr_hint carrying the extra teardown logic, it's 52 B smaller — outlining emits the body once instead of duplicating it into H2K_resched and H2K_resched_cluster.
2. Hot path (me != NULL, the common reschedule)
orig_nohint — H2K_resched:
00 setup / allocframe
0c r17 = me ; save r19:18
10 ciad ← clear int
14 k0lock ← BKL
18 cmp me==0 ; if(!p0) jump:t 0x30 ← TAKEN to reach body (cold else falls through)
30 ┐
.. │ runlist_remove + ready_append (inlined)
60 │ call ring_append
88 ┘ call dosched
90 restore ; 94 deallocframe
- 1 conditional branch, taken (correctly hinted :t) to jump over the cold me==NULL code into the body.
- ~18 packets, 2 calls. Cold wait_mask path is the fall-through at 0x20 (so cold code sits in the middle).
curr_hint — stub → body:
stub H2K_resched: r0=me; ciad; k0lock; jump → body ← 1 unconditional taken jump
body:
00 cmp me==0 ; allocframe
0c if(p0) jump:nt 0x90 ← NOT taken (me!=0); cold wait_mask now at TAIL 0x90
18 runlist_remove (inlined)
38 r0 = me->vmblock ; if(vmblock==0) jump:t 0x4c
44 r2 = vmblock->exiting ; if(exiting!=0) jump:nt 0xa0 ← NOT taken; teardown at TAIL 0xa0
4c ready_append (inlined)
5c call ring_append
80 call dosched
88 restore ; 8c deallocframe
- Conditional branches all fall through in the hot case (predicted :nt), so the hot path is contiguous to dosched.
- Cost of the feature: +2 loads (vmblock @+0x14, exiting @+0xac) and the guard compares.
- Cost of outlining: 1 mandatory unconditional jump from stub to body (always-taken, fixed target — trivially predicted).
- ~23 packets, 2 calls.
| vmblock->num_cpus++; | ||
| tmp->vmblock = vmblock; | ||
|
|
||
| /* Record the first thread as main: its exit triggers VM teardown. */ |
There was a problem hiding this comment.
Do we want to tear down the whole VM? Or just put it in all-threads-stopped where the spawning task frees it (current approach)? This is kind of like "zombie" and needing to join in posix? Although also that's a hassle sometimes :-)
There was a problem hiding this comment.
Yeah, I don't think that we should have a concept of the "main" thread (vcpu) of a vm. For a paravirtualized guest like linux, the vcpus are just a symmetric set of cpus -- there is no "main" cpu that stops all the others when it stops -- in most architectures anyway -- and I don't think that's something we should introduce in the HVM architecture.
So it's ok (and necessary) to have the concept of a main posix thread, but that shouldn't map to a particular vcpu even though in h2os that's how we implement it.
pthread_exit() of the main thread should kill all the other pthreads, but not (directly) all the vcpus. It's quite possible that there is a vcpu that is not running the code of a pthread.
Therefore I think the mechanisms for "stop all pthreads" and "stop the vm" should be separate, logically, though they can share some infrastructure.
| #define H2K_STOP_H 1 | ||
|
|
||
| #include <context.h> | ||
| #include <vmblock.h> |
There was a problem hiding this comment.
Thread stop and vm stop should probably be separated (although perhaps one should call the other?)
There was a problem hiding this comment.
Yeah that. Should have kept reading...
Currently, when the last vcpu stops we send H2K_VM_CHILDINT to the parent vcpu if it's still alive (!H2K_STATUS_DEAD), then tear down the vm unconditionally. We also signal the parent if any child vcpu exits non-0. We can certainly add a vmop to set H2K_VMSTATUS_KILL on a vm (child only) and then interrupt any running vcpus from that vm so that they go do vmwork and discover that they need to terminate. |
Three independent kernel cleanups: - Remove the H2K_vmblock_t::main_context field and all its bookkeeping. Main and worker threads now share the H2K_thread_stop path uniformly; VM-wide teardown is driven solely by H2K_vm_stop (the exiting flag), so tracking which context was "main" is no longer needed. Drops the field from vmblock.h and its assignments in setup/create/stop. - Factor the duplicated per-context teardown tail (asid_dec -> context_clear -> push free list -> num_cpus--) into a shared H2K_free_context_locked() exported from stop.h. Collapses the four open-coded copies in reap_one_locked, vm_stop_locked, H2K_thread_stop, and resched's self-reap onto it. - Fold the exiting-VM self-reap in resched() into the normal if (me != NULL) path instead of a separate early-return branch. Removes the self_reap_locked helper along with its duplicated runlist_remove/dosched and the unreachable tail, leaving a single exit point. On a reap, me is set to NULL so the shared dosched(me) tail won't save the dying context. No behavior change. Verified: clean build + full v81/opt test suite (136 passed, 0 failed). Signed-off-by: Andrey Karpenko <andreyk@qti.qualcomm.com>
9f4f231 to
6653e29
Compare
|
The tests here are really awesome. They found some great problems. |
Annotate the common reschedule path with likely()/unlikely() from c_std.h: me != NULL is the common case, and the vmblock->exiting teardown branch is the rare one. This straightens the hot path to zero taken conditional branches and relocates both cold blocks (the me==NULL wait_mask case and the exiting teardown) to the function tail, improving I-cache density and giving the branch predictor the correct static hint on first encounter. No functional change; v81/opt total code size drops slightly. Signed-off-by: Andrey Karpenko <andreyk@qti.qualcomm.com>
0edb75e to
930c116
Compare
Erich Plondke (eplondke)
left a comment
There was a problem hiding this comment.
Let's discuss these two PRs esp. the race condition with blocking / bkl
| * Caller holds BKL. Decrements num_cpus on success. | ||
| * Skips H2K_STATUS_RUNNING contexts: those are executing on another HW thread | ||
| * and must self-reap via the exiting-vmblock path in resched. */ | ||
| static int reap_one_locked(H2K_thread_context *ctx) |
There was a problem hiding this comment.
I think this code has the race condition issue that I ran into on my branch. Let's discuss.
There was a problem hiding this comment.
Also, this function seems to duplicate a lot with other thread cancel functionality, duplicating it is likely to cause problems over time with maintenance.
There was a problem hiding this comment.
`● I have the complete picture. Let me give you the full evidence-based investigation.
Investigation: the "B stranded on a torn-down vmblock" claim
I traced the +653 scenario end-to-end in the debugger and compared against the pre-fix master code. My original one-line characterization was directionally
right but imprecise. Here is what actually happens.
What I measured (fixed kernel, +653)
The reap sequence during A's exit(0) teardown, watched live:
┌───────────────────────────────────┬────────────────────────────┬───────────────────────────────────────────────────────────────────────────────────────┐
│ event │ context │ path │
├───────────────────────────────────┼────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────┤
│ A frees itself │ me = 0xff015800 │ vm_stop_locked:108 (per-me cleanup) │
├───────────────────────────────────┼────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────┤
│ A reaps parked main (BLOCKED on │ 0xff015a80 │ vm_stop_locked:113 → reap_one_locked → H2K_futex_cancel + H2K_free_context_locked │
│ main_park) │ │ │
├───────────────────────────────────┼────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────┤
│ A scans B (still RUNNING) │ 0xff015940 │ reap_one_locked → case RUNNING: return 0 — skipped │
├───────────────────────────────────┼────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────┤
│ A's finalize │ vmblock 0xff013300 │ num_cpus=1, status=0, exiting=1 → guard (num_cpus!=0 && status==0) returns early — │
│ │ │ vmblock NOT freed │
├───────────────────────────────────┼────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────┤
│ A releases BKL (via H2K_dosched) │ — │ — │
├───────────────────────────────────┼────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────┤
│ B acquires BKL, commits BLOCKED │ 0xff015940, byte@+0xb: │ futex_classic.S:87 │
│ │ 0x01→0x03 │ │
├───────────────────────────────────┼────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────┤
│ B self-reaps │ 0xff015940 │ H2K_dosched → resched() sees me->vmblock->exiting → frees self, clears context (ring │
│ │ │ node → self-referential) │
└───────────────────────────────────┴────────────────────────────┴───────────────────────────────────────────────────────────────────────────────────────┘
The critical measured fact: at A's finalize, num_cpus == 1. That single outstanding count is B. The finalize guard therefore does not free the vmblock — B's
un-reaped slot keeps it alive. This is exactly what makes B's later BLOCKED commit safe: the vmblock is still valid when B touches me->vmblock->exiting.
The two mechanisms that catch B (this branch adds both)
1. vmblock->exiting gate in resched() (resched.ref.c:22-29) — new on this branch. When B finally yields via H2K_dosched, resched sees exiting=1 and self-reaps
B instead of requeuing it, then runs finalize_if_done which now sees num_cpus==0 and completes teardown.
2. Pass-2 IPI in vm_stop_locked — A fires CLUSTER_RESCHED_INT at every RUNNING peer's HW thread, forcing B into H2K_resched_cluster → same exiting self-reap
path even if B never voluntarily blocks.
Either path converges on the same self-reap. I confirmed the VM ends fully torn down (all HW threads in H2K_wait_forever, B's context recycled to the free
list).
What actually happens on the pre-fix kernel (master)
I checked — and my "use-after-free of the vmblock" wording was too strong. On master:
- There is no H2K_vm_stop, no exiting field, no reap_one_locked, no Pass-2 IPI at all. The whole VM-teardown-on-exit path is new on this branch.
- resched() on master unconditionally does H2K_ready_append(me); H2K_dosched(me) — no exiting-gate.
So the real pre-fix failure mode isn't "B dereferences a freed vmblock." It's that exit() had no whole-VM teardown mechanism: a peer blocking on a futex
during another thread's exit would either be stranded as a permanently BLOCKED thread that's never reaped (missed-wakeup / leaked context — the futex is never
resumed, no IPI ever comes), or the teardown accounting is simply absent. The UAF risk is the design hazard this branch had to avoid while adding teardown —
i.e., if teardown freed the vmblock while B was still RUNNING and about to block, B would then touch freed memory. The num_cpus==1 early-return I measured is
precisely the guard that prevents that.
Corrected statement
▎ B commits to BLOCKED after A's reap scan skips it (B still RUNNING). The fix keeps the vmblock alive across this window (A's finalize early-returns because
▎ num_cpus==1 accounts for un-reaped B), and guarantees B is subsequently reaped via the new vmblock->exiting self-reap path in resched — reached either when
▎ B voluntarily blocks or via A's Pass-2 CLUSTER_RESCHED_INT. Without this branch's teardown machinery, that late-blocking peer had no reaper at all: it would
▎ leak as a stranded BLOCKED context on a VM that's supposed to be gone.
`
| case H2K_STATUS_RUNNING: | ||
| return 0; | ||
| case H2K_STATUS_BLOCKED: | ||
| H2K_timer_cancel_withlock(ctx); |
There was a problem hiding this comment.
can this be moved to be common?
| */ | ||
| void h2_thread_stop_trap(int status); | ||
|
|
||
| /** |
There was a problem hiding this comment.
This allows the process to tear itself down, but doesn't allow you to kill a child VM... that's also a useful thing to be able to do.
Add an h2 test that reproduces the exit() (whole-VM teardown) vs
futex-block race that the kernel_thread_stop_reap_blocked branch fixes,
and make both this test and exit1_detached_worker_with_stuck sweepable
via a runtime spin parameter.
The race and why the fix is correct
-----------------------------------
Thread A calls exit(0) -> H2K_vm_stop -> vm_stop_locked: grabs the BKL,
sets vmblock->exiting = 1, reaps every non-RUNNING peer, then IPIs the
RUNNING peers (CLUSTER_RESCHED_INT) to self-reap. Thread B is a RUNNING
peer racing into H2K_futex_wait, where it grabs the BKL, removes itself
from the runlist and commits to H2K_STATUS_BLOCKED.
A's reap scan runs while B is still RUNNING, so reap_one_locked skips B
(the RUNNING case is a no-op). B only commits to BLOCKED after A drops
the BKL. Two mechanisms -- both new on this branch -- make that safe:
1. A's H2K_vmblock_finalize_if_done_locked early-returns while
num_cpus != 0. B's un-reaped slot keeps num_cpus == 1, so the
vmblock is NOT freed out from under B while B is still about to
dereference it. (Confirmed under lldb: at A's finalize the vmblock
shows num_cpus=1, status=0, exiting=1, and the freeing branch is
skipped.)
2. B is then reaped via the vmblock->exiting gate in resched():
reached either when B voluntarily blocks (H2K_dosched -> resched)
or via A's Pass-2 CLUSTER_RESCHED_INT. resched sees exiting,
self-reaps B and clears its context; finalize then completes
teardown once num_cpus reaches 0.
Without this branch's teardown machinery, exit() had no whole-VM reaper
at all (master has no H2K_vm_stop, no vmblock->exiting, no
reap_one_locked, and resched unconditionally requeues). A peer that
blocked on a futex during another thread's exit was stranded as a
permanently BLOCKED context on a VM that was supposed to be gone -- a
leaked / missed-wakeup thread -- and freeing the vmblock while B was
mid-commit would have been a use-after-free. The num_cpus guard in (1)
is exactly what closes that hazard.
Runtime spin parameter
-----------------------
A signed argv[1] selects which racing thread absorbs an artificial spin
delay, sliding one side's timing across the other's teardown so the
reproducer can land inside the race window:
argv[1] >= 0 : delay the BLOCKER by |argv[1]| spins (exiter tears down
immediately) so B commits to the futex wait late.
argv[1] < 0 : delay the EXITER by |argv[1]| spins so teardown lands
after B is already BLOCKED.
Default is 0 spins on both sides so the plain harness invocation still
runs; scripts/Makefile.inc.test now forwards ${GUEST_ARGS} to the guest
so a test's Makefile (or the command line) can pin the delay. On
v81/opt the tightest race -- A and B acquiring the BKL within one spin
iteration of each other -- sits at blocker_spins == 653 (bisected under
lldb: +652 the blocker wins, +653 the exiter wins); the large value is
because the exiter's puts("TEST PASSED") gives B a multi-thousand-cycle
head start. exit1_detached_worker_with_stuck gets the same signed-spin
treatment for consistency.
scripts/testlist.v61: enable exit_vs_futex_block (v81 already had it).
Signed-off-by: Andrey Karpenko <andreyk@qti.qualcomm.com>
No description provided.