/** * Copyright (C) Mellanox Technologies Ltd. 2001-2015. ALL RIGHTS RESERVED. * * See file LICENSE for terms. */ #include #include extern "C" { #include #include } #include class test_arbiter : public ucs::test { protected: static ucs_arbiter_cb_result_t resched_groups(ucs_arbiter_t *arbitrer, ucs_arbiter_group_t *group, ucs_arbiter_elem_t *elem, void *arg) { int *counter = (int*)arg; if (*counter == 0) { return UCS_ARBITER_CB_RESULT_REMOVE_ELEM; } else { --(*counter); return UCS_ARBITER_CB_RESULT_RESCHED_GROUP; } } struct arb_elem { unsigned group_idx; unsigned elem_idx; int count; bool last; bool release; ucs_arbiter_elem_t elem; }; void skip_empty_groups() { while (m_empty_groups.find(m_expected_group_idx) != m_empty_groups.end()) { advance_expected_group(); } } void advance_expected_group() { ++m_expected_group_idx; if (m_expected_group_idx >= m_num_groups) { m_expected_group_idx = 0; } } static void release_element(arb_elem *e) { memset(e, 0xCC, sizeof(*e)); /* Invalidate memory to catch use-after-free bugs */ delete e; } void prepare_groups(ucs_arbiter_group_t *groups, ucs_arbiter_elem_t *elems, const int N, const int nelems_per_group, bool push_head) { int i, j; for (i = 0; i < N; i++) { ucs_arbiter_group_init(&groups[i]); for (j = 0; j < nelems_per_group; j++) { ucs_arbiter_elem_init(&elems[nelems_per_group*i+j]); } for (j = 0; j < nelems_per_group; j++) { if (push_head) { int rev_j = nelems_per_group - 1 - j; ucs_arbiter_group_push_head_elem(&groups[i], &elems[nelems_per_group*i+rev_j]); } else { ucs_arbiter_group_push_elem(&groups[i], &elems[nelems_per_group*i+j]); } } ucs_arbiter_group_schedule(&m_arb1, &groups[i]); } } ucs_arbiter_cb_result_t dispatch(ucs_arbiter_t *arbiter, ucs_arbiter_elem_t *elem) { arb_elem *e = ucs_container_of(elem, arb_elem, elem); skip_empty_groups(); EXPECT_EQ(m_expected_group_idx, e->group_idx); EXPECT_EQ(m_expected_elem_idx[e->group_idx], e->elem_idx); advance_expected_group(); /* Sometimes we just move to the next group */ if ((ucs::rand() % 5) == 0) { return UCS_ARBITER_CB_RESULT_NEXT_GROUP; } /* Sometimes we want to detach the whole group */ if ((ucs::rand() % 10) == 0) { m_empty_groups.insert(e->group_idx); m_detached_groups.insert(e->group_idx); return UCS_ARBITER_CB_RESULT_DESCHED_GROUP; } ++m_expected_elem_idx[e->group_idx]; if (e->last) { m_empty_groups.insert(e->group_idx); } release_element(e); return UCS_ARBITER_CB_RESULT_REMOVE_ELEM; } static ucs_arbiter_cb_result_t dispatch_cb(ucs_arbiter_t *arbiter, ucs_arbiter_group_t *group, ucs_arbiter_elem_t *elem, void *arg) { test_arbiter *self = (test_arbiter *)arg; return self->dispatch(arbiter, elem); } static ucs_arbiter_cb_result_t dispatch_dummy_cb(ucs_arbiter_t *arbiter, ucs_arbiter_group_t *group, ucs_arbiter_elem_t *elem, void *arg) { return UCS_ARBITER_CB_RESULT_REMOVE_ELEM; } ucs_arbiter_cb_result_t desched_group(ucs_arbiter_group_t *group, ucs_arbiter_elem_t *elem) { m_count++; ucs_arbiter_group_schedule(&m_arb2, group); return UCS_ARBITER_CB_RESULT_DESCHED_GROUP; } ucs_arbiter_cb_result_t remove_elem(ucs_arbiter_elem_t *elem) { m_count++; return UCS_ARBITER_CB_RESULT_REMOVE_ELEM; } static ucs_arbiter_cb_result_t desched_cb(ucs_arbiter_t *arbiter, ucs_arbiter_group_t *group, ucs_arbiter_elem_t *elem, void *arg) { test_arbiter *self = (test_arbiter *)arg; return self->desched_group(group, elem); } static ucs_arbiter_cb_result_t remove_cb(ucs_arbiter_t *arbiter, ucs_arbiter_group_t *group, ucs_arbiter_elem_t *elem, void *arg) { test_arbiter *self = (test_arbiter *)arg; return self->remove_elem(elem); } static ucs_arbiter_cb_result_t stop_cb(ucs_arbiter_t *arbiter, ucs_arbiter_group_t *group, ucs_arbiter_elem_t *elem, void *arg) { return UCS_ARBITER_CB_RESULT_STOP; } static ucs_arbiter_cb_result_t purge_cb(ucs_arbiter_t *arbiter, ucs_arbiter_group_t *group, ucs_arbiter_elem_t *elem, void *arg) { arb_elem *e = ucs_container_of(elem, arb_elem, elem); release_element(e); return UCS_ARBITER_CB_RESULT_REMOVE_ELEM; } static ucs_arbiter_cb_result_t count_cb(ucs_arbiter_t *arbiter, ucs_arbiter_group_t *group, ucs_arbiter_elem_t *elem, void *arg) { test_arbiter *self = static_cast(arg); arb_elem *e = ucs_container_of(elem, arb_elem, elem); ++e->count; ++self->m_count; return UCS_ARBITER_CB_RESULT_RESCHED_GROUP; } static ucs_arbiter_cb_result_t purge_cond_cb(ucs_arbiter_t *arbiter, ucs_arbiter_group_t *group, ucs_arbiter_elem_t *elem, void *arg) { test_arbiter *self = static_cast(arg); arb_elem *e = ucs_container_of(elem, arb_elem, elem); if (e->release) { ++self->m_count; return UCS_ARBITER_CB_RESULT_REMOVE_ELEM; } return UCS_ARBITER_CB_RESULT_NEXT_GROUP; } static ucs_arbiter_cb_result_t purge_dummy_cb(ucs_arbiter_t *arbiter, ucs_arbiter_group_t *group, ucs_arbiter_elem_t *elem, void *arg) { test_arbiter *self = static_cast(arg); ++self->m_count; return UCS_ARBITER_CB_RESULT_REMOVE_ELEM; } void test_move_groups(int N, int nelems, bool push_head = false) { ucs_arbiter_group_t *groups; ucs_arbiter_elem_t *elems; ucs_arbiter_init(&m_arb1); ucs_arbiter_init(&m_arb2); groups = new ucs_arbiter_group_t [N]; elems = new ucs_arbiter_elem_t [nelems*N]; prepare_groups(groups, elems, N, nelems, push_head); m_count = 0; ucs_arbiter_dispatch(&m_arb1, 1, desched_cb, this); EXPECT_EQ(N, m_count); m_count = 0; ucs_arbiter_dispatch(&m_arb2, 1, remove_cb, this); EXPECT_EQ(nelems*N, m_count); m_count = 0; ucs_arbiter_dispatch(&m_arb1, 1, remove_cb, this); EXPECT_EQ(0, m_count); delete [] groups; delete [] elems; ucs_arbiter_cleanup(&m_arb1); ucs_arbiter_cleanup(&m_arb2); } protected: std::set m_empty_groups; std::set m_detached_groups; std::vector m_expected_elem_idx; unsigned m_expected_group_idx; unsigned m_num_groups; ucs_arbiter_t m_arb1; ucs_arbiter_t m_arb2; int m_count; }; UCS_TEST_F(test_arbiter, add_purge) { ucs_arbiter_t arbiter; ucs_arbiter_group_t group1; ucs_arbiter_group_t group2; ucs_arbiter_init(&arbiter); ucs_arbiter_group_init(&group1); ucs_arbiter_group_init(&group2); ucs_arbiter_elem_t elem1; ucs_arbiter_elem_t elem2_1; ucs_arbiter_elem_t elem2_2; /* test internal function */ ucs_arbiter_group_push_elem_always(&group1, &elem1); ucs_arbiter_group_push_elem_always(&group2, &elem2_1); ucs_arbiter_group_push_elem_always(&group2, &elem2_2); ucs_arbiter_group_schedule(&arbiter, &group1); ucs_arbiter_group_schedule(&arbiter, &group2); m_count = 0; ucs_arbiter_dispatch_nonempty(&arbiter, 3, remove_cb, this); EXPECT_EQ(3, m_count); ucs_arbiter_group_cleanup(&group2); ucs_arbiter_group_cleanup(&group1); ucs_arbiter_cleanup(&arbiter); } UCS_TEST_F(test_arbiter, purge_cond) { int num_elems = m_num_groups = 25; ucs_arbiter_group_t groups[m_num_groups]; int purged_count[m_num_groups]; ucs::ptr_vector elems; ucs_arbiter_t arbiter; ucs_arbiter_init(&arbiter); memset(purged_count, 0, sizeof(int) * m_num_groups); for (unsigned i = 0; i < m_num_groups; ++i) { ucs_arbiter_group_init(&groups[i]); for (int j = 0; j < num_elems; ++j) { arb_elem *e = new arb_elem; if ((ucs::rand() % 2) == 0) { e->release = true; ++purged_count[i]; } else { e->release = false; } ucs_arbiter_elem_init(&e->elem); elems.push_back(e); ucs_arbiter_group_push_elem(&groups[i], &e->elem); /* coverity[leaked_storage] */ } ucs_arbiter_group_schedule(&arbiter, &groups[i]); } // All groups are scheduled, start purging them from some non-current group // (purge just half of the groups, the rest will be dispatched) unsigned start = ucs::rand() % m_num_groups; for (unsigned i = 0; i < m_num_groups / 2; ++i) { unsigned idx = (start + i) % m_num_groups; m_count = 0; ucs_arbiter_group_purge(&arbiter, &groups[idx], purge_cond_cb, this); EXPECT_EQ(m_count, purged_count[idx]); m_count = 0; ucs_arbiter_group_purge(&arbiter, &groups[idx], purge_dummy_cb, this); EXPECT_EQ(m_count, num_elems - purged_count[idx]); ucs_arbiter_group_cleanup(&groups[idx]); } ucs_arbiter_dispatch(&arbiter, 1, dispatch_dummy_cb, NULL); ucs_arbiter_cleanup(&arbiter); } UCS_TEST_F(test_arbiter, purge_corner) { ucs_arbiter_t arbiter; ucs_arbiter_group_t group; arb_elem elems[2]; ucs_arbiter_init(&arbiter); ucs_arbiter_group_init(&group); for (int i = 0; i < 2; ++i) { ucs_arbiter_elem_init(&elems[i].elem); elems[i].release = !i; // try to purge first ucs_arbiter_group_push_elem(&group, &elems[i].elem); } m_count = 0; ucs_arbiter_group_purge(&arbiter, &group, purge_cond_cb, this); EXPECT_EQ(1, m_count); EXPECT_FALSE(ucs_arbiter_group_is_empty(&group)); EXPECT_FALSE(ucs_arbiter_elem_is_scheduled(&elems[0].elem)); EXPECT_TRUE(ucs_arbiter_elem_is_scheduled(&elems[1].elem)); // try to reuse 0-th element and purge it (now it is last element) ucs_arbiter_group_push_elem(&group, &elems[0].elem); EXPECT_EQ(true, elems[0].release); m_count = 0; ucs_arbiter_group_purge(&arbiter, &group, purge_cond_cb, this); EXPECT_EQ(1, m_count); EXPECT_FALSE(ucs_arbiter_elem_is_scheduled(&elems[0].elem)); EXPECT_TRUE(ucs_arbiter_elem_is_scheduled(&elems[1].elem)); // clear the group ucs_arbiter_group_purge(&arbiter, &group, purge_dummy_cb, this); EXPECT_FALSE(ucs_arbiter_elem_is_scheduled(&elems[0].elem)); EXPECT_FALSE(ucs_arbiter_elem_is_scheduled(&elems[1].elem)); ucs_arbiter_group_cleanup(&group); ucs_arbiter_cleanup(&arbiter); } UCS_TEST_F(test_arbiter, multiple_dispatch) { m_num_groups = 20; ucs_arbiter_t arbiter; ucs_arbiter_init(&arbiter); std::vector groups(m_num_groups); for (unsigned i = 0; i < m_num_groups; ++i) { ucs_arbiter_group_init(&groups[i]); unsigned num_elems = ucs::rand() % 9; for (unsigned j = 0; j < num_elems; ++j) { arb_elem *e = new arb_elem; e->group_idx = i; e->elem_idx = j; e->release = true; e->last = (j == num_elems - 1); ucs_arbiter_elem_init(&e->elem); ucs_arbiter_group_push_elem(&groups[i], &e->elem); /* coverity[leaked_storage] */ } if (num_elems == 0) { m_empty_groups.insert(i); } ucs_arbiter_group_schedule(&arbiter, &groups[i]); } m_expected_group_idx = 0; m_expected_elem_idx.resize(m_num_groups, 0); std::fill(m_expected_elem_idx.begin(), m_expected_elem_idx.end(), 0); ucs_arbiter_dispatch(&arbiter, 1, dispatch_cb, this); ASSERT_TRUE(ucs_arbiter_is_empty(&arbiter)); /* Release detached groups */ for (unsigned i = 0; i < m_num_groups; ++i) { if (m_detached_groups.find(i) != m_detached_groups.end()) { ucs_arbiter_group_purge(&arbiter, &groups[i], purge_cb, NULL); } ucs_arbiter_group_cleanup(&groups[i]); } ucs_arbiter_cleanup(&arbiter); } UCS_TEST_F(test_arbiter, resched) { ucs_arbiter_t arbiter; ucs_arbiter_group_t group1; ucs_arbiter_group_t group2; ucs_arbiter_init(&arbiter); ucs_arbiter_group_init(&group1); ucs_arbiter_group_init(&group2); ucs_arbiter_elem_t elem1; ucs_arbiter_elem_t elem2_1; ucs_arbiter_elem_init(&elem1); ucs_arbiter_elem_init(&elem2_1); ucs_arbiter_group_push_elem(&group1, &elem1); ucs_arbiter_group_push_elem(&group2, &elem2_1); ucs_arbiter_group_schedule(&arbiter, &group1); ucs_arbiter_group_schedule(&arbiter, &group2); int count = 2; ucs_arbiter_dispatch_nonempty(&arbiter, 1, resched_groups, &count); EXPECT_EQ(0, count); count = 1; ucs_arbiter_dispatch_nonempty(&arbiter, 1, resched_groups, &count); EXPECT_EQ(0, count); /* one group with one elem should be there */ m_count = 0; ucs_arbiter_dispatch_nonempty(&arbiter, 3, remove_cb, this); EXPECT_EQ(1, m_count); ASSERT_TRUE(ucs_arbiter_is_empty(&arbiter)); ucs_arbiter_group_cleanup(&group2); ucs_arbiter_group_cleanup(&group1); ucs_arbiter_cleanup(&arbiter); } /* check that it is possible to reuse removed * element */ UCS_TEST_F(test_arbiter, reuse_elem) { int i; ucs_arbiter_group_t group1; ucs_arbiter_elem_t elem1; ucs_arbiter_elem_t elem2; ucs_arbiter_init(&m_arb1); ucs_arbiter_group_init(&group1); ucs_arbiter_elem_init(&elem1); ucs_arbiter_elem_init(&elem2); for (i = 0; i < 3; i++) { ucs_arbiter_group_push_elem(&group1, &elem1); ucs_arbiter_group_push_elem(&group1, &elem2); ucs_arbiter_group_schedule(&m_arb1, &group1); m_count = 0; ucs_arbiter_dispatch(&m_arb1, 1, remove_cb, this); EXPECT_EQ(2, m_count); } for (i = 0; i < 3; i++) { ucs_arbiter_group_push_elem(&group1, &elem1); ucs_arbiter_group_schedule(&m_arb1, &group1); m_count = 0; ucs_arbiter_dispatch(&m_arb1, 1, remove_cb, this); EXPECT_EQ(1, m_count); } } UCS_TEST_F(test_arbiter, move_group) { ucs_arbiter_group_t group1; ucs_arbiter_elem_t elem1; ucs_arbiter_elem_t elem2; ucs_arbiter_init(&m_arb1); ucs_arbiter_init(&m_arb2); ucs_arbiter_group_init(&group1); ucs_arbiter_elem_init(&elem1); ucs_arbiter_elem_init(&elem2); ucs_arbiter_group_push_elem(&group1, &elem1); ucs_arbiter_group_push_elem(&group1, &elem2); ucs_arbiter_group_schedule(&m_arb1, &group1); m_count = 0; ucs_arbiter_dispatch(&m_arb1, 1, desched_cb, this); EXPECT_EQ(1, m_count); m_count = 0; ucs_arbiter_dispatch(&m_arb2, 1, remove_cb, this); EXPECT_EQ(2, m_count); m_count = 0; ucs_arbiter_dispatch(&m_arb1, 1, remove_cb, this); EXPECT_EQ(0, m_count); ucs_arbiter_cleanup(&m_arb1); ucs_arbiter_cleanup(&m_arb2); } /* Simulates a bug fixed in UCX GH issue #5382 * (https://github.com/openucx/ucx/issues/5382). * The failing flow (with DC transport) is: * - DCI waiting arbiter is being dispatched * - In the dispatch callback group is scheduled to the TX waiting arbiter and * UCS_ARBITER_CB_RESULT_DESCHED_GROUP is returned from the callback * - Now the group is scheduled on TX waiting arbiter * - ucs_arbiter_group_desched is called in uct_dc_mlx5_iface_dci_put * - ARBITER_CHECK assert fails */ UCS_TEST_F(test_arbiter, move_group_and_desched) { ucs_arbiter_group_t group1; ucs_arbiter_elem_t elem1; ucs_arbiter_init(&m_arb1); ucs_arbiter_init(&m_arb2); ucs_arbiter_group_init(&group1); ucs_arbiter_elem_init(&elem1); ucs_arbiter_group_push_elem(&group1, &elem1); ucs_arbiter_group_schedule(&m_arb1, &group1); m_count = 0; ucs_arbiter_dispatch(&m_arb1, 1, desched_cb, this); EXPECT_EQ(1, m_count); ucs_arbiter_group_desched(&m_arb2, &group1); ucs_arbiter_cleanup(&m_arb1); ucs_arbiter_cleanup(&m_arb2); } UCS_TEST_F(test_arbiter, push_head_scheduled) { ucs_arbiter_group_t group1; ucs_arbiter_group_t group2; arb_elem elem1; arb_elem elem2; arb_elem elem3; ucs_arbiter_init(&m_arb1); ucs_arbiter_group_init(&group1); ucs_arbiter_group_init(&group2); ucs_arbiter_elem_init(&elem1.elem); ucs_arbiter_elem_init(&elem2.elem); ucs_arbiter_elem_init(&elem3.elem); elem1.count = elem2.count = elem3.count = 0; ucs_arbiter_group_push_head_elem(&group1, &elem1.elem); ucs_arbiter_group_push_head_elem(&group2, &elem2.elem); ucs_arbiter_group_schedule(&m_arb1, &group1); ucs_arbiter_group_schedule(&m_arb1, &group2); m_count = 0; ucs_arbiter_dispatch(&m_arb1, 1, count_cb, this); EXPECT_EQ(2, m_count); EXPECT_EQ(1, elem1.count); EXPECT_EQ(1, elem2.count); EXPECT_EQ(0, elem3.count); /* Adding new head elem to group2 */ ucs_arbiter_group_push_head_elem(&group2, &elem3.elem); m_count = 0; ucs_arbiter_dispatch(&m_arb1, 1, count_cb, this); EXPECT_EQ(2, m_count); EXPECT_EQ(2, elem1.count); EXPECT_EQ(1, elem2.count); EXPECT_EQ(1, elem3.count); m_count = 0; ucs_arbiter_dispatch(&m_arb1, 2, remove_cb, this); EXPECT_EQ(3, m_count); /* Add to single scheduled group */ ucs_arbiter_group_push_head_elem(&group2, &elem2.elem); ucs_arbiter_group_schedule(&m_arb1, &group2); ucs_arbiter_group_push_head_elem(&group2, &elem3.elem); m_count = 0; elem2.count = elem3.count = 0; ucs_arbiter_dispatch(&m_arb1, 2, count_cb, this); EXPECT_EQ(0, elem2.count); EXPECT_EQ(1, elem3.count); EXPECT_EQ(1, m_count); m_count = 0; ucs_arbiter_dispatch(&m_arb1, 2, remove_cb, this); EXPECT_EQ(2, m_count); ucs_arbiter_cleanup(&m_arb1); } UCS_TEST_F(test_arbiter, move_groups1) { test_move_groups(42, 3); } UCS_TEST_F(test_arbiter, move_groups2) { test_move_groups(42, 1); } UCS_TEST_F(test_arbiter, move_groups1_push_head) { test_move_groups(42, 3, true); } UCS_TEST_F(test_arbiter, move_groups2_push_head) { test_move_groups(42, 1, true); } UCS_TEST_F(test_arbiter, desched_group) { ucs_arbiter_group_t group1; ucs_arbiter_elem_t elem1; ucs_arbiter_elem_t elem2; ucs_arbiter_init(&m_arb1); ucs_arbiter_group_init(&group1); ucs_arbiter_elem_init(&elem1); ucs_arbiter_elem_init(&elem2); ucs_arbiter_group_push_elem(&group1, &elem1); ucs_arbiter_group_push_elem(&group1, &elem2); /* should do nothing */ ucs_arbiter_group_desched(&m_arb1, &group1); ucs_arbiter_group_schedule(&m_arb1, &group1); /* arbiter will be empty */ ucs_arbiter_group_desched(&m_arb1, &group1); m_count = 0; ucs_arbiter_dispatch(&m_arb1, 1, remove_cb, this); EXPECT_EQ(0, m_count); /* group must still have 2 elements */ ucs_arbiter_group_schedule(&m_arb1, &group1); m_count = 0; ucs_arbiter_dispatch(&m_arb1, 1, remove_cb, this); EXPECT_EQ(2, m_count); ucs_arbiter_cleanup(&m_arb1); } UCS_TEST_F(test_arbiter, desched_groups) { ucs_arbiter_group_t *groups; ucs_arbiter_elem_t *elems; const int N = 17; ucs_arbiter_init(&m_arb1); ucs_arbiter_init(&m_arb2); groups = new ucs_arbiter_group_t [N]; elems = new ucs_arbiter_elem_t [3*N]; prepare_groups(groups, elems, N, 3, false); ucs_arbiter_group_desched(&m_arb1, &groups[N-1]); ucs_arbiter_group_desched(&m_arb1, &groups[0]); ucs_arbiter_group_desched(&m_arb1, &groups[5]); ucs_arbiter_group_desched(&m_arb1, &groups[11]); m_count = 0; ucs_arbiter_dispatch(&m_arb1, 1, remove_cb, this); /* 4 groups with 3 elems each were descheduled */ EXPECT_EQ(3*(N-4), m_count); ucs_arbiter_group_schedule(&m_arb1, &groups[N-1]); ucs_arbiter_group_schedule(&m_arb1, &groups[0]); ucs_arbiter_group_schedule(&m_arb1, &groups[5]); ucs_arbiter_group_schedule(&m_arb1, &groups[11]); m_count = 0; ucs_arbiter_dispatch(&m_arb1, 1, remove_cb, this); /* 4 groups with 3 elems each were scheduled */ EXPECT_EQ(4*3, m_count); delete [] groups; delete [] elems; ucs_arbiter_cleanup(&m_arb1); } /* make sure that next arbiter dispatch * continues from the group that stopped */ UCS_TEST_F(test_arbiter, result_stop) { const int N = 5; const int nelems = 1; ucs_arbiter_group_t *groups; ucs_arbiter_elem_t *elems; groups = new ucs_arbiter_group_t [N]; elems = new ucs_arbiter_elem_t [nelems*N]; ucs_arbiter_init(&m_arb1); prepare_groups(groups, elems, N, nelems, false); for (int i = 0; i < N + 3; i++) { ucs_arbiter_dispatch(&m_arb1, 1, stop_cb, this); /* arbiter current position must not change on STOP */ EXPECT_EQ(m_arb1.list.next, &groups[0].tail->next->list); } m_count = 0; ucs_arbiter_dispatch(&m_arb1, 1, remove_cb, this); EXPECT_EQ(N*nelems, m_count); ucs_arbiter_cleanup(&m_arb1); delete [] groups; delete [] elems; } class test_arbiter_resched_from_dispatch : public ucs::test { public: virtual void init() { ucs::test::init(); ucs_arbiter_init(&m_arb); ucs_arbiter_group_init(&m_group1); ucs_arbiter_group_init(&m_group2); ucs_arbiter_elem_init(&m_elem); } virtual void cleanup() { ucs_arbiter_cleanup(&m_arb); ucs::test::cleanup(); } protected: /* the callback pushes the elem on group2 and schedules it */ virtual ucs_arbiter_cb_result_t dispatch(ucs_arbiter_group_t *group, ucs_arbiter_elem_t *elem) { if (m_moved) { return UCS_ARBITER_CB_RESULT_STOP; } else { EXPECT_EQ(&m_elem, elem); ucs_arbiter_group_push_elem(&m_group2, elem); ucs_arbiter_group_schedule(&m_arb, &m_group2); m_moved = true; } return UCS_ARBITER_CB_RESULT_REMOVE_ELEM; } static ucs_arbiter_cb_result_t purge_cb(ucs_arbiter_t *arbiter, ucs_arbiter_group_t *group, ucs_arbiter_elem_t *elem, void *arg) { return UCS_ARBITER_CB_RESULT_REMOVE_ELEM; } static ucs_arbiter_cb_result_t dispatch_cb(ucs_arbiter_t *arbiter, ucs_arbiter_group_t *group, ucs_arbiter_elem_t *elem, void *arg) { test_arbiter_resched_from_dispatch *self = reinterpret_cast(arg); EXPECT_EQ(&self->m_arb, arbiter); return self->dispatch(group, elem); } void check_group_state(ucs_arbiter_group_t *group, bool is_scheduled) { EXPECT_EQ(is_scheduled, ucs_arbiter_group_is_scheduled(group)); EXPECT_EQ(is_scheduled, !ucs_arbiter_group_is_empty(group)); } ucs_arbiter_t m_arb; ucs_arbiter_group_t m_group1, m_group2; ucs_arbiter_elem_t m_elem; bool m_moved; }; /* from the arbiter dispatch callback, reschedule the element on another group, * and remove it from current group */ UCS_TEST_F(test_arbiter_resched_from_dispatch, remove_and_resched) { m_moved = false; ucs_arbiter_group_push_elem(&m_group1, &m_elem); ucs_arbiter_group_schedule(&m_arb, &m_group1); /* group1 should be scheduled, group2 not */ check_group_state(&m_group1, true); check_group_state(&m_group2, false); ucs_arbiter_dispatch(&m_arb, 1, dispatch_cb, this); /* the dispatch should deschedule group1 and schedule group2 instead */ check_group_state(&m_group1, false); check_group_state(&m_group2, true); ucs_arbiter_group_purge(&m_arb, &m_group2, purge_cb, NULL); } class test_arbiter_random_resched : public test_arbiter_resched_from_dispatch { public: test_arbiter_random_resched(); protected: virtual ucs_arbiter_cb_result_t dispatch(ucs_arbiter_group_t *_group, ucs_arbiter_elem_t *elem); void do_test_loop(unsigned num_groups, unsigned elems_per_group, unsigned dispatch_per_group); private: typedef struct { ucs_arbiter_group_t super; unsigned num_elems; } arb_group_t; void reset_counters(); void add_new_elem(arb_group_t *group); void do_test(unsigned iteration_num, unsigned num_groups, unsigned elems_per_group, unsigned dispatch_per_group); std::vector m_groups; unsigned m_num_dispatch; unsigned m_num_only; unsigned m_num_added; unsigned m_num_removed; unsigned m_num_push_self; unsigned m_num_push_another; unsigned m_num_next_group; unsigned m_num_desched; unsigned m_num_resched; }; test_arbiter_random_resched::test_arbiter_random_resched() { reset_counters(); } void test_arbiter_random_resched::reset_counters() { m_num_dispatch = 0; m_num_only = 0; m_num_added = 0; m_num_removed = 0; m_num_push_self = 0; m_num_push_another = 0; m_num_next_group = 0; m_num_desched = 0; m_num_resched = 0; } void test_arbiter_random_resched::add_new_elem(arb_group_t *group) { ucs_arbiter_elem_t *elem = new ucs_arbiter_elem_t; ucs_arbiter_elem_init(elem); ucs_arbiter_group_push_elem(&group->super, elem); ++group->num_elems; ++m_num_added; } ucs_arbiter_cb_result_t test_arbiter_random_resched::dispatch(ucs_arbiter_group_t *_group, ucs_arbiter_elem_t *elem) { arb_group_t *group = ucs_derived_of(_group, arb_group_t); arb_group_t *new_group; ++m_num_dispatch; /* Test ucs_arbiter_group_num_elems() */ EXPECT_EQ(group->num_elems, ucs_arbiter_group_num_elems(&group->super)); /* We should be able to reschedule this group to another place */ EXPECT_FALSE(ucs_arbiter_group_is_scheduled(&group->super)); /* Test ucs_arbiter_elem_is_only() */ if (group->num_elems == 1) { ++m_num_only; EXPECT_TRUE(ucs_arbiter_elem_is_only(elem)); } /* Randomly add few more elements to same group */ while ((ucs::rand() % 4) == 0) { add_new_elem(group); if ((ucs::rand() % 2) == 0) { ucs_arbiter_group_schedule(&m_arb, &group->super); } } if ((ucs::rand() % 2) == 0) { /* Remove the current element. * Must remove elements with higher probability than adding to avoid * infinite loop. */ if ((ucs::rand() % 4) == 0) { /* push the removed element to a random group. It could be either * the current group or a new group, both cases should work. */ new_group = &m_groups[ucs::rand() % m_groups.size()]; ucs_arbiter_group_push_elem(&new_group->super, elem); if (new_group == group) { ++m_num_push_self; if ((ucs::rand() % 2) == 0) { ucs_arbiter_group_schedule(&m_arb, &new_group->super); } } else { /* schedule the new group if it's now the current one */ ++m_num_push_another; ucs_arbiter_group_schedule(&m_arb, &new_group->super); } ++new_group->num_elems; } else { /* Element is removed permanently, so invalidate and delete it */ ++m_num_removed; memset(elem, 0xBB, sizeof(*elem)); delete elem; } --group->num_elems; return UCS_ARBITER_CB_RESULT_REMOVE_ELEM; } else { /* Don't remove the current element, do some other random group action * instead. */ int action = ucs::rand() % 3; switch (action) { case 0: ++m_num_next_group; return UCS_ARBITER_CB_RESULT_NEXT_GROUP; case 1: /* Reschedule the group on same arbiter to keep it going */ ucs_arbiter_group_schedule(&m_arb, &group->super); ++m_num_desched; return UCS_ARBITER_CB_RESULT_DESCHED_GROUP; case 2: default: ++m_num_resched; return UCS_ARBITER_CB_RESULT_RESCHED_GROUP; } } } void test_arbiter_random_resched::do_test(unsigned iteration_num, unsigned num_groups, unsigned elems_per_group, unsigned dispatch_per_group) { arb_group_t* group; UCS_TEST_MESSAGE << "Iteration " << iteration_num << ": " << num_groups << " m_groups, " << elems_per_group << " elements each"; /* Add elements and groups */ m_groups.resize(num_groups); for (unsigned group_index = 0; group_index < num_groups; ++group_index) { group = &m_groups[group_index]; ucs_arbiter_group_init(&group->super); group->num_elems = 0; for (unsigned i = 0; i < elems_per_group; ++i) { add_new_elem(group); } ucs_arbiter_group_schedule(&m_arb, &group->super); /* Test arbiter helper functions */ EXPECT_EQ(elems_per_group, ucs_arbiter_group_num_elems(&group->super)); if (elems_per_group == 0) { EXPECT_TRUE(ucs_arbiter_group_is_empty(&group->super)); } } EXPECT_EQ(num_groups * elems_per_group, m_num_added); /* Dispatch arbiter until it becomes empty */ do { ucs_arbiter_dispatch(&m_arb, dispatch_per_group, dispatch_cb, reinterpret_cast(this)); } while (!ucs_arbiter_is_empty(&m_arb)); /* Show counters */ UCS_TEST_MESSAGE << " added: " << m_num_added << " removed: " << m_num_removed; UCS_TEST_MESSAGE << " dispatch: " << m_num_dispatch << " only: " << m_num_only << " push self: " << m_num_push_self << " push another: " << m_num_push_another; UCS_TEST_MESSAGE << " desched: " << m_num_desched << " resched: " << m_num_resched << " next_group: " << m_num_next_group; /* Check counters */ EXPECT_EQ(m_num_added, m_num_removed); /* Make sure all is removed */ for (unsigned group_index = 0; group_index < num_groups; ++group_index) { group = &m_groups[group_index]; EXPECT_EQ(0u, group->num_elems); EXPECT_TRUE(ucs_arbiter_group_is_empty(&group->super)); } } void test_arbiter_random_resched::do_test_loop(unsigned num_groups, unsigned elems_per_group, unsigned dispatch_per_group) { for (unsigned i = 0; i < 5; ++i) { reset_counters(); do_test(i, num_groups, elems_per_group, dispatch_per_group); } } UCS_TEST_F(test_arbiter_random_resched, one_elem_one_group) { do_test_loop(1, 1, 1); } UCS_TEST_F(test_arbiter_random_resched, one_elem_many_groups) { do_test_loop(42, 1, 1); } UCS_TEST_F(test_arbiter_random_resched, many_elems_many_groups) { do_test_loop(42, 10, 4); }