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_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(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); }
/* 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); } } }
/* IN OUR TASK THREAD */ static void notify_if_done(HioOutputStream *stream) { HRT_ASSERT_IN_TASK_THREAD(stream->task); if (g_atomic_int_get(&stream->done_notified) == 0 && hio_output_stream_is_done(stream)) { g_atomic_int_inc(&stream->done_notified); /* allow task to complete, assuming it has no other watchers. * * Another side effect: this should mean the task can complete * even if no watcher was ever added, which happens if the * stream is closed with no bytes ever written. */ hrt_task_unblock_completion(stream->task); g_mutex_lock(stream->done_notify_lock); if (stream->done_notify_func != NULL) { HioOutputStreamDoneNotify func = stream->done_notify_func; void *data = stream->done_notify_data; GDestroyNotify dnotify = stream->done_notify_dnotify; stream->done_notify_func = NULL; stream->done_notify_data = NULL; stream->done_notify_dnotify = NULL; g_mutex_unlock(stream->done_notify_lock); g_object_ref(stream); (* func) (stream, data); if (dnotify != NULL) { (* dnotify) (data); } g_object_unref(stream); } else { g_mutex_unlock(stream->done_notify_lock); } } }