/*
 * DF-2849 PoC — taskqgroup_detach() leaves the grouptask in an
 * enqueue-able-but-inconsistent state and clears TASK_NOENQUEUE with an
 * unsynchronized, non-atomic read-modify-write of ta_flags OUTSIDE the
 * taskqueue lock (subr_gtaskqueue.c:746-747), racing
 * grouptaskqueue_enqueue()'s locked "ta_flags |= TASK_ENQUEUED"
 * (subr_gtaskqueue.c:218-219).
 *
 * Observable failure modes of the same root cause:
 *  (1) racer enqueues in the window where gt_taskqueue == NULL
 *      -> INVARIANTS panic("queue == NULL") / NULL deref otherwise;
 *  (2) racer's locked RMW interleaves with detach's unlocked RMW
 *      -> TASK_ENQUEUED bit lost while the gtask IS in the STAILQ
 *      -> next enqueue double-inserts -> STAILQ self-loop
 *      -> worker thread re-runs the task forever (fn_count keeps
 *         growing after all racers stopped) and starves every other
 *         task on that queue.
 *
 * Thread A ("detacher"): attach/detach churn, serialized.
 * Thread B ("racer"):    enqueue churn, skips the NULL-queue window to
 *                        chase mode (2); mode (1) still reachable.
 */
#include <sys/param.h>
#include <sys/conf.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/lock.h>
#include <sys/gtaskqueue.h>
#include <sys/thread.h>
#include <sys/thread2.h>

static struct taskqgroup *rg;
static struct grouptask gt;
static volatile int stop;
static volatile long enq_ok, enq_eagain, enq_null, fn_count;
static struct thread *racer_td, *det_td;

static void
gt_fn(void *ctx)
{
	atomic_add_long(&fn_count, 1);
}

static void
racer(void *arg)
{
	while (!stop) {
		/* racy read: skip the obvious NULL window (mode 1) to
		 * hunt the flag lost-update (mode 2) */
		struct gtaskqueue *q = gt.gt_taskqueue;

		if (q == NULL) {
			atomic_add_long(&enq_null, 1);
			continue;
		}
		if (grouptaskqueue_enqueue(q, &gt.gt_task) == 0)
			atomic_add_long(&enq_ok, 1);
		else
			atomic_add_long(&enq_eagain, 1);
	}
	lwkt_exit();
}

static void
detacher(void *arg)
{
	int i, loops = 200000;

	for (i = 0; i < loops && !stop; i++) {
		taskqgroup_attach(rg, &gt, NULL, NULL, NULL, "gt");
		taskqgroup_detach(rg, &gt);
	}
	kprintf("gtq_race: detacher finished %d attach/detach cycles\n", i);
	stop = 1;
	lwkt_exit();
}

static int
gtq_race_ev(module_t mod, int type, void *data)
{
	long c1, c2;

	switch (type) {
	case MOD_LOAD:
		rg = taskqgroup_create("gtq_race", 2, 1);
		if (rg == NULL)
			return (ENOMEM);
		GROUPTASK_INIT(&gt, 0, gt_fn, NULL);
		gt.gt_taskqueue = NULL;
		taskqgroup_attach(rg, &gt, NULL, NULL, NULL, "gt");

		lwkt_create(racer, NULL, &racer_td, NULL, TDF_NOSTART, 1,
		    "gtracer");
		lwkt_setpri_initial(racer_td, TDPRI_KERN_DAEMON);
		lwkt_schedule(racer_td);
		lwkt_create(detacher, NULL, &det_td, NULL, TDF_NOSTART, 2,
		    "gtdetach");
		lwkt_setpri_initial(det_td, TDPRI_KERN_DAEMON);
		lwkt_schedule(det_td);
		kprintf("gtq_race: racer(cpu1)/detacher(cpu2) running\n");
		return (0);
	case MOD_UNLOAD:
		/* threads must be done: wait for stop */
		while (!stop)
			tsleep(gtq_race_ev, 0, "gtrwait", hz / 4);
		/*
		 * Corruption check (mode 2): nobody is enqueueing anymore,
		 * so fn_count must be frozen.  A self-looped STAILQ makes
		 * the queue's worker re-run gt forever.
		 */
		c1 = fn_count;
		tsleep(gtq_race_ev, 0, "gtrchk", 2 * hz);
		c2 = fn_count;
		kprintf("gtq_race: enq_ok=%ld enq_eagain=%ld enq_null=%ld "
		    "fn_count=%ld -> %ld\n", enq_ok, enq_eagain, enq_null,
		    c1, c2);
		if (c2 > c1) {
			kprintf("gtq_race: CORRUPTION PROVEN - task fn still "
			    "running with no enqueuer (STAILQ self-loop)\n");
		} else {
			kprintf("gtq_race: no post-stop growth observed this "
			    "run\n");
		}
		return (0);
	default:
		break;
	}
	return (EOPNOTSUPP);
}
DEV_MODULE(gtq_race, gtq_race_ev, NULL);
