/* IN ANY THREAD */ void hio_output_stream_error(HioOutputStream *stream) { /* incrementing error and closing will make * hio_output_stream_is_done() return TRUE. At that point we need * to be sure done-notification happens if it hasn't. */ g_atomic_int_inc(&stream->errored); /* close to ignore any further writes. we want to * silently eat them, so errors don't have to be handled * by the writer. (Though writer could do so, if desired.) */ hio_output_stream_close(stream); /* Delete any leftover buffers, in our task thread. This would not * work if we were already done-notified because completion would * be unblocked and we can't add watchers to a completed * task. There's no buffers if we're already done, anyhow. */ if (g_atomic_int_get(&stream->done_notified) == 0) { hrt_task_add_immediate(stream->task, on_error_drop_all_buffers, g_object_ref(stream), g_object_unref); } }
/* Called when headers are complete but body may not be. * Request is thus in use from another thread once headers * are complete. */ static void hwf_connection_container_on_incoming_message(HioConnection *connection, HioIncoming *incoming) { HrtTask *task; g_assert(HWF_IS_REQUEST_CONTAINER(incoming)); task = hrt_task_create_task(connection->task); hrt_debug("Created task %p for incoming request %p", task, incoming); /* execute the request handler */ hrt_task_add_immediate(task, on_request_run, g_object_ref(incoming), (GDestroyNotify) g_object_unref); /* don't quit the connection task while request tasks are pending */ hrt_task_add_subtask(connection->task, task, on_request_completed, g_object_ref(incoming), (GDestroyNotify) g_object_unref); g_object_unref(task); }
static void test_chain_with_error(OutputTestFixture *fixture, const void *data) { g_assert(fixture->chain_task != NULL); /* make writing to write_fd error */ close(fixture->read_fd); /* it isn't allowed to do stuff with chains except in the chain's * task. So setup all the streams in the chain task. */ hrt_task_add_immediate(fixture->chain_task, on_start_streams_in_chain, fixture, NULL); /* run main loop to collect the tasks */ g_main_loop_run(fixture->loop); g_assert(hio_output_chain_got_error(fixture->chain)); g_assert(hio_output_chain_is_empty(fixture->chain)); /* so we don't close it again in teardown */ fixture->read_fd = -1; }
/* the main point of this test is that the immediates run in serial * and each runs exactly once */ static void test_one_task_many_immediates(TestFixture *fixture, const void *data) { HrtTask *task; int i; task = hrt_task_runner_create_task(fixture->runner); fixture->tasks_started_count += 1; /* note that we serialize all immediates for a task so making this * a large number will take forever */ #define NUM_IMMEDIATES 7 for (i = 0; i < NUM_IMMEDIATES; ++i) { hrt_task_add_immediate(task, on_immediate_many_for_one_task, fixture, on_dnotify_bump_count); } g_main_loop_run(fixture->loop); g_assert_cmpint(fixture->tasks_completed_count, ==, 1); g_assert_cmpint(fixture->tasks_completed_count, ==, fixture->tasks_started_count); g_assert_cmpint(fixture->dnotify_count, ==, NUM_IMMEDIATES); /* we should have run each immediate exactly once */ g_assert_cmpint(fixture->tasks[0].immediates_run_count, ==, NUM_IMMEDIATES); }
static void test_chain(OutputTestFixture *fixture, const void *data) { int i; g_assert(fixture->chain_task != NULL); /* it isn't allowed to do stuff with chains except in the chain's * task. So setup all the streams in the chain task. */ hrt_task_add_immediate(fixture->chain_task, on_start_streams_in_chain, fixture, NULL); /* Read all streams in order */ for (i = 0; i < N_STREAMS_IN_CHAIN; ++i) { read_and_verify_stream(fixture->read_fd, &fixture->stream_descs[i % fixture->n_stream_descs]); } /* run main loop to collect the tasks */ g_main_loop_run(fixture->loop); g_assert(!hio_output_chain_got_error(fixture->chain)); g_assert(hio_output_chain_is_empty(fixture->chain)); }
static void test_stream_with_initial_error(OutputTestFixture *fixture, const void *data) { HioOutputStream *stream; g_assert(fixture->chain_task == NULL); /* error before we even set an fd on it */ stream = hio_output_stream_new(fixture->stream_tasks[0]); hio_output_stream_error(stream); hrt_task_add_immediate(fixture->write_tasks[0], on_write_stream_task, write_task_data_new(stream, fixture->stream_descs), write_task_data_free); /* run main loop to collect the tasks */ g_main_loop_run(fixture->loop); g_assert(hio_output_stream_got_error(stream)); g_assert(hio_output_stream_is_done(stream)); g_assert(hio_output_stream_is_closed(stream)); g_object_unref(stream); }
static void test_stream_with_error(OutputTestFixture *fixture, const void *data) { HioOutputStream *stream; g_assert(fixture->chain_task == NULL); close(fixture->read_fd); /* should error the writes */ stream = hio_output_stream_new(fixture->stream_tasks[0]); hio_output_stream_set_fd(stream, fixture->write_fd); hrt_task_add_immediate(fixture->write_tasks[0], on_write_stream_task, write_task_data_new(stream, fixture->stream_descs), write_task_data_free); /* run main loop to collect the tasks */ g_main_loop_run(fixture->loop); /* if the stream is zero-length we'll never try to write and never get an error */ g_assert(fixture->stream_descs->length == 0 || hio_output_stream_got_error(stream)); g_assert(hio_output_stream_is_done(stream)); g_assert(hio_output_stream_is_closed(stream)); g_object_unref(stream); /* so we don't close it again in teardown */ fixture->read_fd = -1; }
static void test_stream(OutputTestFixture *fixture, const void *data) { HioOutputStream *stream; g_assert(fixture->chain_task == NULL); stream = hio_output_stream_new(fixture->stream_tasks[0]); hio_output_stream_set_fd(stream, fixture->write_fd); hrt_task_add_immediate(fixture->write_tasks[0], on_write_stream_task, write_task_data_new(stream, fixture->stream_descs), write_task_data_free); read_and_verify_stream(fixture->read_fd, fixture->stream_descs); /* run main loop to collect the task */ g_main_loop_run(fixture->loop); g_assert(!hio_output_stream_got_error(stream)); g_assert(hio_output_stream_is_done(stream)); g_assert(hio_output_stream_is_closed(stream)); g_object_unref(stream); }
static void test_immediate_performance_n_tasks(TestFixture *fixture, const void *data, int n_tasks) { int i, j; if (!g_test_perf()) return; /* this has to be set up front of there's a race in using it to * decide to quit mainloop, because task runner starts running * tasks right away, doesn't wait for our local mainloop */ fixture->tasks_started_count = n_tasks; /* start here, to include task creation. Also, immediates can start * running right away, before we block in main loop. */ g_test_timer_start(); for (i = 0; i < n_tasks; ++i) { HrtTask *task; task = hrt_task_runner_create_task(fixture->runner); #define NUM_IMMEDIATES 4 for (j = 0; j < NUM_IMMEDIATES; ++j) { hrt_task_add_immediate(task, on_immediate_for_performance_many_tasks, fixture, on_dnotify_bump_count); } } g_main_loop_run(fixture->loop); g_test_minimized_result(g_test_timer_elapsed(), "Run %d tasks with %d immediates each", n_tasks, NUM_IMMEDIATES); g_assert_cmpint(fixture->tasks_completed_count, ==, n_tasks); g_assert_cmpint(fixture->tasks_completed_count, ==, fixture->tasks_started_count); g_assert_cmpint(fixture->dnotify_count, ==, NUM_IMMEDIATES * n_tasks); #undef NUM_IMMEDIATES }
static void test_immediate_block_completion(TestFixture *fixture, const void *data) { HrtTask *task; task = hrt_task_runner_create_task(fixture->runner); fixture->tasks[0].task = task; fixture->tasks_started_count += 1; if (fixture->completion_should_be_blocked) { hrt_task_block_completion(task); } fixture->tasks[0].watcher = hrt_task_add_immediate(task, on_immediate_for_block_completion_test, fixture, on_dnotify_bump_count); /* When the task is completed, the main loop quits. From the * task, we add a timeout to the main loop. If the main loop quits * before the timeout runs, then we completed without waiting to * unblock. */ g_main_loop_run(fixture->loop); g_source_remove(fixture->completion_check_timeout_id); g_assert_cmpint(fixture->tasks_completed_count, ==, 1); g_assert_cmpint(fixture->tasks_completed_count, ==, fixture->tasks_started_count); g_assert_cmpint(fixture->dnotify_count, ==, 1); g_assert_cmpint(fixture->tasks[0].immediates_run_count, ==, 1); if (fixture->completion_should_be_blocked) { g_assert(fixture->completion_check_timeout_ran); } else { g_assert(!fixture->completion_check_timeout_ran); } }
/* TYPICALLY CALLED FROM ANOTHER THREAD */ void hio_output_stream_close(HioOutputStream *stream) { if (g_atomic_int_exchange_and_add(&stream->closed, 1) == 0) { /* If we just went from 0 (not closed) to 1 (closed) and that * makes us done, we need to notify done-ness in the task * thread. If we still aren't done, we'll notify done-ness * once we flush so don't need to add a handler here. * * It's important not to add a watcher if we've already * notified done-ness because we'll have unblocked completion * and you can't add watchers to completed tasks. */ if (hio_output_stream_is_done(stream)) { hrt_task_add_immediate(stream->task, on_notify_done_after_close, g_object_ref(stream), g_object_unref); } } }
static gboolean on_start_streams_in_chain(HrtTask *task, HrtWatcherFlags flags, void *data) { OutputTestFixture *fixture = data; int i; /* Fire up all streams */ for (i = 0; i < N_STREAMS_IN_CHAIN; ++i) { HioOutputStream *stream; stream = hio_output_stream_new(fixture->stream_tasks[i]); hrt_task_add_immediate(fixture->write_tasks[i], on_write_stream_task, write_task_data_new(stream, &fixture->stream_descs[i % fixture->n_stream_descs]), write_task_data_free); hio_output_chain_add_stream(fixture->chain, stream); g_object_unref(stream); } hio_output_chain_set_empty_notify(fixture->chain, on_chain_empty, /* reference cycle we'll * break by unsetting the * chain when notified */ g_object_ref(fixture->chain), g_object_unref); /* start the writing! */ hio_output_chain_set_fd(fixture->chain, fixture->write_fd); return FALSE; }
static void test_immediate_runs_several_times(TestFixture *fixture, const void *data) { HrtTask *task; task = hrt_task_runner_create_task(fixture->runner); fixture->tasks_started_count += 1; hrt_task_add_immediate(task, on_immediate_runs_several_times, fixture, on_dnotify_bump_count); g_main_loop_run(fixture->loop); g_assert_cmpint(fixture->tasks_completed_count, ==, 1); g_assert_cmpint(fixture->tasks_completed_count, ==, fixture->tasks_started_count); g_assert_cmpint(fixture->dnotify_count, ==, 1); g_assert_cmpint(fixture->times_run, ==, SEVERAL_TIMES); }
static void test_immediate_that_sleeps_return_false(TestFixture *fixture, const void *data) { HrtTask *task; task = hrt_task_runner_create_task(fixture->runner); fixture->tasks_started_count += 1; hrt_task_add_immediate(task, on_immediate_sleep_return_false, fixture, on_dnotify_bump_count); g_main_loop_run(fixture->loop); g_assert_cmpint(fixture->tasks_completed_count, ==, 1); g_assert_cmpint(fixture->tasks_completed_count, ==, fixture->tasks_started_count); g_assert_cmpint(fixture->dnotify_count, ==, 1); g_assert_cmpint(fixture->tasks[0].immediates_run_count, ==, 1); }
static void test_immediate_performance_n_watchers(TestFixture *fixture, const void *data, int n_watchers) { int i, j; if (!g_test_perf()) return; /* this has to be set up front of there's a race in using it to * decide to quit mainloop, because task runner starts running * tasks right away, doesn't wait for our local mainloop */ fixture->tasks_started_count = NUM_TASKS; /* start here, to include task creation. Also, immediates can start * running right away, before we block in main loop. */ g_test_timer_start(); for (i = 0; i < NUM_TASKS; ++i) { HrtTask *task; task = hrt_task_runner_create_task(fixture->runner); fixture->tasks[i].task = task; } /* If we added n_watchers immediates to task 0, then task 1, then 2, * etc. then we'd never use any parallelism because we'd just * have one task active at a time using only one thread. By doing * the loop this way we get some use of multiple threads in * theory. Also this is more "real world" in that most likely * tasks do some work, add an event loop source, do some work, * etc. instead of just adding a pile of sources from the * same task all at once. This more "real world" scenario is * less efficient and slows down the benchmark. */ for (j = 0; j < n_watchers; ++j) { for (i = 0; i < NUM_TASKS; ++i) { HrtTask *task = fixture->tasks[i].task; hrt_task_add_immediate(task, on_immediate_for_performance_many_watchers, fixture, on_dnotify_bump_count); } } g_main_loop_run(fixture->loop); g_test_minimized_result(g_test_timer_elapsed(), "Run %d tasks with %d immediates each", NUM_TASKS, n_watchers); g_assert_cmpint(fixture->tasks_completed_count, ==, NUM_TASKS); g_assert_cmpint(fixture->tasks_completed_count, ==, fixture->tasks_started_count); g_assert_cmpint(fixture->dnotify_count, ==, n_watchers * NUM_TASKS); }
static void test_many_tasks_many_immediates(TestFixture *fixture, const void *data) { int i, j; gboolean some_overlap; GTimer *timer; GString *overlap_report; /* this has to be set up front of there's a race in using it to * decide to quit mainloop, because task runner starts running * tasks right away, doesn't wait for our local mainloop */ fixture->tasks_started_count = NUM_TASKS; for (i = 0; i < NUM_TASKS; ++i) { HrtTask *task; task = hrt_task_runner_create_task(fixture->runner); fixture->tasks[i].task = task; /* note that we serialize all immediates for a task so making this * a large number will take forever */ #define NUM_IMMEDIATES 7 for (j = 0; j < NUM_IMMEDIATES; ++j) { hrt_task_add_immediate(task, on_immediate_for_many_tasks, fixture, on_dnotify_bump_count); } } timer = g_timer_new(); g_main_loop_run(fixture->loop); /* we don't want an assertion based on timing, will fail too often, * but print it for manual checking sometimes. */ g_test_message("%g seconds elapsed to run lots of tasks that should have each taken 0.7 seconds\n", g_timer_elapsed(timer, NULL)); g_timer_destroy(timer); g_assert_cmpint(fixture->tasks_completed_count, ==, NUM_TASKS); g_assert_cmpint(fixture->tasks_completed_count, ==, fixture->tasks_started_count); g_assert_cmpint(fixture->dnotify_count, ==, NUM_IMMEDIATES * NUM_TASKS); /* we should have run each immediate exactly once */ for (i = 0; i < NUM_TASKS; ++i) { g_assert(fixture->tasks[i].immediates_run_count == NUM_IMMEDIATES); } /* unfortunately this isn't strictly guaranteed, but it should be * nearly certain. If it keeps failing, increase number of immediates * and number of tasks. */ some_overlap = FALSE; for (i = 0; i < NUM_TASKS; ++i) { if (fixture->tasks[i].saw_another_immediate_in_an_immediate_count > 0) some_overlap = TRUE; } g_assert(some_overlap); overlap_report = g_string_new(NULL); for (i = 0; i < NUM_TASKS; ++i) { g_string_append_printf(overlap_report, " %d", fixture->tasks[i].saw_another_immediate_in_an_immediate_count); } g_test_message("# of immediates of %d run during at least one other task's immediate:\n %s\n", NUM_IMMEDIATES, overlap_report->str); g_string_free(overlap_report, TRUE); #undef NUM_IMMEDIATES }