in source/h2_connection.c [631:739]
static void s_write_outgoing_frames(struct aws_h2_connection *connection, bool first_try) {
AWS_PRECONDITION(aws_channel_thread_is_callers_thread(connection->base.channel_slot->channel));
AWS_PRECONDITION(connection->thread_data.is_outgoing_frames_task_active);
struct aws_channel_slot *channel_slot = connection->base.channel_slot;
struct aws_linked_list *outgoing_frames_queue = &connection->thread_data.outgoing_frames_queue;
struct aws_linked_list *outgoing_streams_list = &connection->thread_data.outgoing_streams_list;
if (connection->thread_data.is_writing_stopped) {
return;
}
/* Determine whether there's work to do, and end task immediately if there's not.
* Note that we stop writing DATA frames if the channel is trying to shut down */
bool has_control_frames = !aws_linked_list_empty(outgoing_frames_queue);
bool has_data_frames = !aws_linked_list_empty(outgoing_streams_list);
bool may_write_data_frames = (connection->thread_data.window_size_peer > AWS_H2_MIN_WINDOW_SIZE) &&
!connection->thread_data.channel_shutdown_waiting_for_goaway_to_be_written;
bool will_write = has_control_frames || (has_data_frames && may_write_data_frames);
if (!will_write) {
if (!first_try) {
CONNECTION_LOGF(
TRACE,
connection,
"Outgoing frames task stopped. has_control_frames:%d has_data_frames:%d may_write_data_frames:%d",
has_control_frames,
has_data_frames,
may_write_data_frames);
}
connection->thread_data.is_outgoing_frames_task_active = false;
if (connection->thread_data.channel_shutdown_waiting_for_goaway_to_be_written) {
s_finish_shutdown(connection);
}
return;
}
if (first_try) {
CONNECTION_LOG(TRACE, connection, "Starting outgoing frames task");
}
/* Acquire aws_io_message, that we will attempt to fill up */
struct aws_io_message *msg = aws_channel_slot_acquire_max_message_for_write(channel_slot);
if (AWS_UNLIKELY(!msg)) {
CONNECTION_LOG(ERROR, connection, "Failed to acquire message from pool, closing connection.");
goto error;
}
/* Set up callback so we can send another message when this one completes */
msg->on_completion = s_on_channel_write_complete;
msg->user_data = connection;
CONNECTION_LOGF(
TRACE,
connection,
"Outgoing frames task acquired message with %zu bytes available",
msg->message_data.capacity - msg->message_data.len);
/* Write as many frames from outgoing_frames_queue as possible. */
if (s_encode_outgoing_frames_queue(connection, &msg->message_data)) {
goto error;
}
/* If outgoing_frames_queue emptied, and connection is running normally,
* then write as many DATA frames from outgoing_streams_list as possible. */
if (aws_linked_list_empty(outgoing_frames_queue) && may_write_data_frames) {
if (s_encode_data_from_outgoing_streams(connection, &msg->message_data)) {
goto error;
}
}
if (msg->message_data.len) {
/* Write message to channel.
* outgoing_frames_task will resume when message completes. */
CONNECTION_LOGF(TRACE, connection, "Outgoing frames task sending message of size %zu", msg->message_data.len);
if (aws_channel_slot_send_message(channel_slot, msg, AWS_CHANNEL_DIR_WRITE)) {
CONNECTION_LOGF(
ERROR,
connection,
"Failed to send channel message: %s. Closing connection.",
aws_error_name(aws_last_error()));
goto error;
}
} else {
/* Message is empty, warn that no work is being done and reschedule the task to try again next tick.
* It's likely that body isn't ready, so body streaming function has no data to write yet.
* If this scenario turns out to be common we should implement a "pause" feature. */
CONNECTION_LOG(WARN, connection, "Outgoing frames task sent no data, will try again next tick.");
aws_mem_release(msg->allocator, msg);
aws_channel_schedule_task_now(channel_slot->channel, &connection->outgoing_frames_task);
}
return;
error:;
int error_code = aws_last_error();
if (msg) {
aws_mem_release(msg->allocator, msg);
}
aws_h2_connection_shutdown_due_to_write_err(connection, error_code);
}