Exemple #1
0
/* 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);
    }
}
Exemple #2
0
/* 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);
}
Exemple #3
0
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;
}
Exemple #4
0
/* 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);
}
Exemple #5
0
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));
}
Exemple #6
0
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);
}
Exemple #7
0
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;
}
Exemple #8
0
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);
}
Exemple #9
0
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
}
Exemple #10
0
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);
    }
}
Exemple #11
0
/* 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);
        }
    }
}
Exemple #12
0
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;
}
Exemple #13
0
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);
}
Exemple #14
0
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);
}
Exemple #15
0
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);
}
Exemple #16
0
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
}