|
@@ -1059,6 +1059,271 @@ test_scheduler_ns_changed(void *arg)
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+ * Mocked functions for the kist_pending_list test.
|
|
|
+ */
|
|
|
+
|
|
|
+static int mock_flush_some_cells_num = 1;
|
|
|
+static int mock_more_to_flush = 0;
|
|
|
+static int mock_update_socket_info_limit = 0;
|
|
|
+
|
|
|
+static ssize_t
|
|
|
+channel_flush_some_cells_mock_var(channel_t *chan, ssize_t num_cells)
|
|
|
+{
|
|
|
+ (void) chan;
|
|
|
+ (void) num_cells;
|
|
|
+ return mock_flush_some_cells_num;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+ * fully drained, the wants to write scheduler event is fired back while we
|
|
|
+ * are in the scheduler loop so this mock function does it for us.
|
|
|
+ * Furthermore, the socket limit is set to 0 so once this is triggered, it
|
|
|
+ * informs the scheduler that it can't write on the socket anymore. */
|
|
|
+static void
|
|
|
+channel_write_to_kernel_mock_trigger_24700(channel_t *chan)
|
|
|
+{
|
|
|
+ static int chan_id_seen[2] = {0};
|
|
|
+ if (++chan_id_seen[chan->global_identifier - 1] > 1) {
|
|
|
+ tt_assert(0);
|
|
|
+ }
|
|
|
+
|
|
|
+ scheduler_channel_wants_writes(chan);
|
|
|
+
|
|
|
+ done:
|
|
|
+ return;
|
|
|
+}
|
|
|
+
|
|
|
+static int
|
|
|
+channel_more_to_flush_mock_var(channel_t *chan)
|
|
|
+{
|
|
|
+ (void) chan;
|
|
|
+ return mock_more_to_flush;
|
|
|
+}
|
|
|
+
|
|
|
+static void
|
|
|
+update_socket_info_impl_mock_var(socket_table_ent_t *ent)
|
|
|
+{
|
|
|
+ ent->cwnd = ent->unacked = ent->mss = ent->notsent = 0;
|
|
|
+ ent->limit = mock_update_socket_info_limit;
|
|
|
+}
|
|
|
+
|
|
|
+static void
|
|
|
+test_scheduler_kist_pending_list(void *arg)
|
|
|
+{
|
|
|
+ (void) arg;
|
|
|
+
|
|
|
+#ifndef HAVE_KIST_SUPPORT
|
|
|
+ return;
|
|
|
+#endif
|
|
|
+
|
|
|
+
|
|
|
+ * depending on the channel state, what will be the expected behavior of the
|
|
|
+ * scheduler with that list.
|
|
|
+ *
|
|
|
+ * For instance, we want to catch double channel add or removing a channel
|
|
|
+ * that doesn't exists, or putting a channel in the list in a wrong state.
|
|
|
+ * Essentially, this will articifically test cases of the KIST main loop and
|
|
|
+ * entry point in the channel subsystem.
|
|
|
+ *
|
|
|
+ * In part, this is to also catch things like #24700 and provide a test bed
|
|
|
+ * for more testing in the future like so. */
|
|
|
+
|
|
|
+
|
|
|
+ * scheduler loop to test every use cases and assess the pending list. */
|
|
|
+ MOCK(get_options, mock_get_options);
|
|
|
+ MOCK(channel_flush_some_cells, channel_flush_some_cells_mock_var);
|
|
|
+ MOCK(channel_more_to_flush, channel_more_to_flush_mock_var);
|
|
|
+ MOCK(update_socket_info_impl, update_socket_info_impl_mock_var);
|
|
|
+ MOCK(channel_write_to_kernel, channel_write_to_kernel_mock);
|
|
|
+ MOCK(channel_should_write_to_kernel, channel_should_write_to_kernel_mock);
|
|
|
+
|
|
|
+
|
|
|
+ mocked_options.KISTSchedRunInterval = 10;
|
|
|
+ set_scheduler_options(SCHEDULER_KIST);
|
|
|
+
|
|
|
+
|
|
|
+ scheduler_init();
|
|
|
+
|
|
|
+
|
|
|
+ * test. */
|
|
|
+ channel_t *chan1 = new_fake_channel();
|
|
|
+ channel_t *chan2 = new_fake_channel();
|
|
|
+ tt_assert(chan1);
|
|
|
+ tt_assert(chan2);
|
|
|
+ chan1->magic = chan2->magic = TLS_CHAN_MAGIC;
|
|
|
+ channel_register(chan1);
|
|
|
+ channel_register(chan2);
|
|
|
+ tt_int_op(chan1->scheduler_state, OP_EQ, SCHED_CHAN_IDLE);
|
|
|
+ tt_int_op(chan1->sched_heap_idx, OP_EQ, -1);
|
|
|
+ tt_int_op(chan2->scheduler_state, OP_EQ, SCHED_CHAN_IDLE);
|
|
|
+ tt_int_op(chan2->sched_heap_idx, OP_EQ, -1);
|
|
|
+
|
|
|
+
|
|
|
+ * the scheduler is notified that the channel wants to write so this is the
|
|
|
+ * first step. Might not make sense to you but it is the way it is. */
|
|
|
+ scheduler_channel_wants_writes(chan1);
|
|
|
+ tt_int_op(chan1->scheduler_state, OP_EQ, SCHED_CHAN_WAITING_FOR_CELLS);
|
|
|
+ tt_int_op(smartlist_len(get_channels_pending()), OP_EQ, 0);
|
|
|
+
|
|
|
+
|
|
|
+ * will get scheduled. */
|
|
|
+ scheduler_channel_has_waiting_cells(chan1);
|
|
|
+ tt_int_op(chan1->scheduler_state, OP_EQ, SCHED_CHAN_PENDING);
|
|
|
+ tt_int_op(smartlist_len(get_channels_pending()), OP_EQ, 1);
|
|
|
+
|
|
|
+ * cells in rapid succession before the channel is scheduled. */
|
|
|
+ scheduler_channel_has_waiting_cells(chan1);
|
|
|
+ tt_int_op(chan1->scheduler_state, OP_EQ, SCHED_CHAN_PENDING);
|
|
|
+ tt_int_op(smartlist_len(get_channels_pending()), OP_EQ, 1);
|
|
|
+ scheduler_channel_has_waiting_cells(chan1);
|
|
|
+ tt_int_op(chan1->scheduler_state, OP_EQ, SCHED_CHAN_PENDING);
|
|
|
+ tt_int_op(smartlist_len(get_channels_pending()), OP_EQ, 1);
|
|
|
+
|
|
|
+
|
|
|
+ * flush else we end up in an infinite loop. We expect the channel to be put
|
|
|
+ * in waiting for cells state and the pending list empty. */
|
|
|
+ mock_update_socket_info_limit = INT_MAX;
|
|
|
+ mock_more_to_flush = 0;
|
|
|
+ the_scheduler->run();
|
|
|
+ tt_int_op(smartlist_len(get_channels_pending()), OP_EQ, 0);
|
|
|
+ tt_int_op(chan1->scheduler_state, OP_EQ, SCHED_CHAN_WAITING_FOR_CELLS);
|
|
|
+
|
|
|
+
|
|
|
+ * channel can't write so obviously it has more to flush. We expect the
|
|
|
+ * channel to be back in the pending list. */
|
|
|
+ scheduler_channel_has_waiting_cells(chan1);
|
|
|
+ mock_update_socket_info_limit = 0;
|
|
|
+ mock_more_to_flush = 1;
|
|
|
+ the_scheduler->run();
|
|
|
+ tt_int_op(smartlist_len(get_channels_pending()), OP_EQ, 1);
|
|
|
+ tt_int_op(chan1->scheduler_state, OP_EQ, SCHED_CHAN_PENDING);
|
|
|
+
|
|
|
+
|
|
|
+ * wants to write event because maybe the channel buffers were emptied in
|
|
|
+ * the meantime. This is possible because once the connection outbuf is
|
|
|
+ * flushed down the low watermark, the scheduler is notified.
|
|
|
+ *
|
|
|
+ * We expect the channel to NOT be added in the pending list again and stay
|
|
|
+ * in PENDING state. */
|
|
|
+ scheduler_channel_wants_writes(chan1);
|
|
|
+ tt_int_op(smartlist_len(get_channels_pending()), OP_EQ, 1);
|
|
|
+ tt_int_op(chan1->scheduler_state, OP_EQ, SCHED_CHAN_PENDING);
|
|
|
+
|
|
|
+
|
|
|
+ * expect that it is removed from the pending list and waiting for cells. */
|
|
|
+ mock_update_socket_info_limit = INT_MAX;
|
|
|
+ mock_more_to_flush = 0;
|
|
|
+ the_scheduler->run();
|
|
|
+ tt_int_op(smartlist_len(get_channels_pending()), OP_EQ, 0);
|
|
|
+ tt_int_op(chan1->scheduler_state, OP_EQ, SCHED_CHAN_WAITING_FOR_CELLS);
|
|
|
+
|
|
|
+
|
|
|
+ * the connection outbuf (unlikely that this can happen but let say it
|
|
|
+ * does). We expect the channel to stay in waiting for cells. */
|
|
|
+ scheduler_channel_wants_writes(chan1);
|
|
|
+ tt_int_op(smartlist_len(get_channels_pending()), OP_EQ, 0);
|
|
|
+ tt_int_op(chan1->scheduler_state, OP_EQ, SCHED_CHAN_WAITING_FOR_CELLS);
|
|
|
+
|
|
|
+
|
|
|
+ * cell flushed. We expect that it is put back in waiting for cells. */
|
|
|
+ scheduler_channel_has_waiting_cells(chan1);
|
|
|
+ tt_int_op(smartlist_len(get_channels_pending()), OP_EQ, 1);
|
|
|
+ tt_int_op(chan1->scheduler_state, OP_EQ, SCHED_CHAN_PENDING);
|
|
|
+ mock_flush_some_cells_num = 0;
|
|
|
+ the_scheduler->run();
|
|
|
+ tt_int_op(smartlist_len(get_channels_pending()), OP_EQ, 0);
|
|
|
+ tt_int_op(chan1->scheduler_state, OP_EQ, SCHED_CHAN_WAITING_FOR_CELLS);
|
|
|
+
|
|
|
+
|
|
|
+ * that the channel becomes idle. */
|
|
|
+ scheduler_channel_doesnt_want_writes(chan1);
|
|
|
+ tt_int_op(smartlist_len(get_channels_pending()), OP_EQ, 0);
|
|
|
+ tt_int_op(chan1->scheduler_state, OP_EQ, SCHED_CHAN_IDLE);
|
|
|
+
|
|
|
+
|
|
|
+ * to write. You might wonder why it is not put in the pending list? Because
|
|
|
+ * once the channel becomes OPEN again (the doesn't want to write event only
|
|
|
+ * occurs if the channel goes in MAINT mode), if there are cells in the
|
|
|
+ * channel, the wants to write event is triggered thus putting the channel
|
|
|
+ * in pending mode.
|
|
|
+ *
|
|
|
+ * Else, if no cells, it stays IDLE and then once a cell comes in, it should
|
|
|
+ * go in waiting to write which is a BUG itself because the channel can't be
|
|
|
+ * scheduled until a second cell comes in. Hopefully, #24554 will fix that
|
|
|
+ * for KIST. */
|
|
|
+ scheduler_channel_has_waiting_cells(chan1);
|
|
|
+ tt_int_op(smartlist_len(get_channels_pending()), OP_EQ, 0);
|
|
|
+ tt_int_op(chan1->scheduler_state, OP_EQ, SCHED_CHAN_WAITING_TO_WRITE);
|
|
|
+
|
|
|
+
|
|
|
+ * to write event occurs like described above. */
|
|
|
+ scheduler_channel_has_waiting_cells(chan1);
|
|
|
+ tt_int_op(smartlist_len(get_channels_pending()), OP_EQ, 0);
|
|
|
+ tt_int_op(chan1->scheduler_state, OP_EQ, SCHED_CHAN_WAITING_TO_WRITE);
|
|
|
+
|
|
|
+
|
|
|
+ scheduler_channel_wants_writes(chan1);
|
|
|
+ tt_int_op(smartlist_len(get_channels_pending()), OP_EQ, 1);
|
|
|
+ tt_int_op(chan1->scheduler_state, OP_EQ, SCHED_CHAN_PENDING);
|
|
|
+
|
|
|
+
|
|
|
+ * different channels in the pending list. The first one gets flushed and
|
|
|
+ * bytes are written on the wire which triggers a wants to write event
|
|
|
+ * because the outbuf is below the low watermark. The bug was that this
|
|
|
+ * exact channel was added back in the pending list because its state wasn't
|
|
|
+ * PENDING.
|
|
|
+ *
|
|
|
+ * The following does some ninja-tsu to try to make it happen. We need two
|
|
|
+ * different channels so we create a second one and add it to the pending
|
|
|
+ * list. Then, we have a custom function when we write to kernel that does
|
|
|
+ * two important things:
|
|
|
+ *
|
|
|
+ * 1) Calls scheduler_channel_wants_writes(chan) on the channel.
|
|
|
+ * 2) Keeps track of how many times it sees the channel going through. If
|
|
|
+ * that limit goes > 1, it means we've added the channel twice in the
|
|
|
+ * pending list.
|
|
|
+ *
|
|
|
+ * In the end, we expect both channels to be in the pending list after this
|
|
|
+ * scheduler run. */
|
|
|
+
|
|
|
+
|
|
|
+ scheduler_channel_wants_writes(chan2);
|
|
|
+ scheduler_channel_has_waiting_cells(chan2);
|
|
|
+ tt_int_op(smartlist_len(get_channels_pending()), OP_EQ, 2);
|
|
|
+ tt_int_op(chan2->scheduler_state, OP_EQ, SCHED_CHAN_PENDING);
|
|
|
+
|
|
|
+
|
|
|
+ * then when a single cell is flushed (514 + 29 bytes), the second call to
|
|
|
+ * socket_can_write() will be false. If it wasn't sending back false on the
|
|
|
+ * second run, we end up in an infinite loop of the scheduler. */
|
|
|
+ mock_update_socket_info_limit = 600;
|
|
|
+
|
|
|
+ * true but socket_can_write() has to be false on the second check on the
|
|
|
+ * channel. */
|
|
|
+ mock_more_to_flush = 1;
|
|
|
+ mock_flush_some_cells_num = 1;
|
|
|
+ MOCK(channel_write_to_kernel, channel_write_to_kernel_mock_trigger_24700);
|
|
|
+ the_scheduler->run();
|
|
|
+ tt_int_op(smartlist_len(get_channels_pending()), OP_EQ, 2);
|
|
|
+ tt_int_op(chan1->scheduler_state, OP_EQ, SCHED_CHAN_PENDING);
|
|
|
+ tt_int_op(chan2->scheduler_state, OP_EQ, SCHED_CHAN_PENDING);
|
|
|
+
|
|
|
+ done:
|
|
|
+ chan1->state = chan2->state = CHANNEL_STATE_CLOSED;
|
|
|
+ chan1->registered = chan2->registered = 0;
|
|
|
+ channel_free(chan1);
|
|
|
+ channel_free(chan2);
|
|
|
+ scheduler_free_all();
|
|
|
+
|
|
|
+ UNMOCK(get_options);
|
|
|
+ UNMOCK(channel_flush_some_cells);
|
|
|
+ UNMOCK(channel_more_to_flush);
|
|
|
+ UNMOCK(update_socket_info_impl);
|
|
|
+ UNMOCK(channel_write_to_kernel);
|
|
|
+ UNMOCK(channel_should_write_to_kernel);
|
|
|
+}
|
|
|
+
|
|
|
struct testcase_t scheduler_tests[] = {
|
|
|
{ "compare_channels", test_scheduler_compare_channels,
|
|
|
TT_FORK, NULL, NULL },
|
|
@@ -1068,6 +1333,8 @@ struct testcase_t scheduler_tests[] = {
|
|
|
{ "loop_kist", test_scheduler_loop_kist, TT_FORK, NULL, NULL },
|
|
|
{ "ns_changed", test_scheduler_ns_changed, TT_FORK, NULL, NULL},
|
|
|
{ "should_use_kist", test_scheduler_can_use_kist, TT_FORK, NULL, NULL },
|
|
|
+ { "kist_pending_list", test_scheduler_kist_pending_list, TT_FORK,
|
|
|
+ NULL, NULL },
|
|
|
END_OF_TESTCASES
|
|
|
};
|
|
|
|