/* * bug-1864: the vtaskmgr dispatcher checked each queued task's endpoint * "busy" flag one item at a time while the vcpu finishing that endpoint's * previous message cleared the flag without the queue lock. Within ONE * scan, task N was skipped (endpoint still busy), the endpoint unblocked, * and task N+1 for the SAME endpoint was then dequeued first -- a standby * processed WAL entry seq 258 before 257 and NACKed (log-mismatch storms). * * vtask_deq_eligible() takes the busy check as a parameter so these tests * can make an endpoint unblock part-way through a scan, deterministically. */ #include #include #include #include "dataq.h" #include "netio.h" #include "vtaskmgr.h" static endpoint_t* g_ep1; static endpoint_t* g_ep2; static int g_ep1_checks; static vtask_t* make_io_task(endpoint_t* ep) { vtask_t* t = (vtask_t*)calloc(1, sizeof(*t)); vtask_data_t* d = (vtask_data_t*)calloc(1, sizeof(*d)); TEST_ASSERT_NOT_NULL(t); TEST_ASSERT_NOT_NULL(d); d->vtype = VTASK_IO; d->u.iodata.ep = ep; t->data = d; return t; } static vtask_t* make_generic_task(void) { vtask_t* t = (vtask_t*)calloc(1, sizeof(*t)); vtask_data_t* d = (vtask_data_t*)calloc(1, sizeof(*d)); TEST_ASSERT_NOT_NULL(t); TEST_ASSERT_NOT_NULL(d); d->vtype = VTASK_GENERIC; t->data = d; return t; } static void free_task(vtask_t* t) { if (t != NULL) { free(t->data); free(t); } } /* ep1 is busy on the first check of this scan, then its running task * finishes and it is free for every later check -- the live interleaving */ static int ep1_unblocks_mid_scan(endpoint_t* ep) { if (ep == g_ep1) { return (g_ep1_checks++ == 0); } return 0; } static int ep1_busy(endpoint_t* ep) { return (ep == g_ep1); } static int none_busy(endpoint_t* ep) { (void)ep; return 0; } static void order_setup(dataq_t* q) { dataq_init(q, NULL); g_ep1 = (endpoint_t*)calloc(1, sizeof(endpoint_t)); g_ep2 = (endpoint_t*)calloc(1, sizeof(endpoint_t)); g_ep1_checks = 0; } static void order_teardown(dataq_t* q) { data_item_t* it; while ((it = dataq_deq(q, NULL)) != NULL) { free_task(ITPTR_TO_VTASK(it)); } free(g_ep1); free(g_ep2); } /* the bug: a later message must never overtake an earlier one of the same * endpoint, even if the endpoint frees up while the scan is between them */ static void test_later_task_of_skipped_endpoint_is_not_dequeued(void) { dataq_t q; vtask_t* first; vtask_t* second; vtask_t* got; order_setup(&q); first = make_io_task(g_ep1); second = make_io_task(g_ep1); dataq_enq(&q, &(first->link)); dataq_enq(&q, &(second->link)); got = vtask_deq_eligible(&q, ep1_unblocks_mid_scan); TEST_ASSERT_NULL_MESSAGE(got, "task N was skipped as busy, so task N+1 of the same endpoint must " "not be dequeued ahead of it in the same scan"); /* next scan: the endpoint is free, and N comes out first */ got = vtask_deq_eligible(&q, none_busy); TEST_ASSERT_EQUAL_PTR(first, got); free_task(got); got = vtask_deq_eligible(&q, none_busy); TEST_ASSERT_EQUAL_PTR(second, got); free_task(got); order_teardown(&q); } /* a busy endpoint must not hold up other endpoints' tasks */ static void test_other_endpoint_still_dispatched(void) { dataq_t q; vtask_t* a = NULL; vtask_t* c = NULL; vtask_t* got; order_setup(&q); a = make_io_task(g_ep1); c = make_io_task(g_ep2); dataq_enq(&q, &(a->link)); dataq_enq(&q, &(c->link)); got = vtask_deq_eligible(&q, ep1_busy); TEST_ASSERT_EQUAL_PTR(c, got); free_task(got); order_teardown(&q); } /* FIFO when nothing is busy; non-IO tasks are always dispatchable */ static void test_fifo_and_generic_tasks(void) { dataq_t q; vtask_t* a; vtask_t* b; vtask_t* g; vtask_t* got; order_setup(&q); a = make_io_task(g_ep1); b = make_io_task(g_ep1); dataq_enq(&q, &(a->link)); dataq_enq(&q, &(b->link)); got = vtask_deq_eligible(&q, none_busy); TEST_ASSERT_EQUAL_PTR(a, got); free_task(got); order_teardown(&q); order_setup(&q); a = make_io_task(g_ep1); g = make_generic_task(); dataq_enq(&q, &(a->link)); dataq_enq(&q, &(g->link)); got = vtask_deq_eligible(&q, ep1_busy); TEST_ASSERT_EQUAL_PTR(g, got); free_task(got); order_teardown(&q); } void test_vtask_order(void) { RUN_TEST(test_later_task_of_skipped_endpoint_is_not_dequeued); RUN_TEST(test_other_endpoint_still_dispatched); RUN_TEST(test_fifo_and_generic_tasks); }