1/*
2 * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License").
5 * You may not use this file except in compliance with the License.
6 * A copy of the License is located at
7 *
8 * http://aws.amazon.com/apache2.0
9 *
10 * or in the "license" file accompanying this file. This file is distributed
11 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 * express or implied. See the License for the specific language governing
13 * permissions and limitations under the License.
14 */
15
16#include <aws/common/log_channel.h>
17
18#include <aws/common/condition_variable.h>
19#include <aws/common/log_writer.h>
20#include <aws/common/mutex.h>
21#include <aws/common/string.h>
22#include <aws/common/thread.h>
23
24#include <stdio.h>
25
26/*
27 * Basic channel implementations - synchronized foreground, synchronized background
28 */
29
30struct aws_log_foreground_channel {
31 struct aws_mutex sync;
32};
33
34static int s_foreground_channel_send(struct aws_log_channel *channel, struct aws_string *log_line) {
35
36 struct aws_log_foreground_channel *impl = (struct aws_log_foreground_channel *)channel->impl;
37
38 AWS_ASSERT(channel->writer->vtable->write);
39
40 aws_mutex_lock(&impl->sync);
41 (channel->writer->vtable->write)(channel->writer, log_line);
42 aws_mutex_unlock(&impl->sync);
43
44 /*
45 * send is considered a transfer of ownership. write is not a transfer of ownership.
46 * So it's always the channel's responsibility to clean up all log lines that enter
47 * it as soon as they are no longer needed.
48 */
49 aws_string_destroy(log_line);
50
51 return AWS_OP_SUCCESS;
52}
53
54static void s_foreground_channel_clean_up(struct aws_log_channel *channel) {
55 struct aws_log_foreground_channel *impl = (struct aws_log_foreground_channel *)channel->impl;
56
57 aws_mutex_clean_up(&impl->sync);
58
59 aws_mem_release(channel->allocator, impl);
60}
61
62static struct aws_log_channel_vtable s_foreground_channel_vtable = {
63 .send = s_foreground_channel_send,
64 .clean_up = s_foreground_channel_clean_up,
65};
66
67int aws_log_channel_init_foreground(
68 struct aws_log_channel *channel,
69 struct aws_allocator *allocator,
70 struct aws_log_writer *writer) {
71 struct aws_log_foreground_channel *impl = aws_mem_calloc(allocator, 1, sizeof(struct aws_log_foreground_channel));
72 if (impl == NULL) {
73 return AWS_OP_ERR;
74 }
75
76 if (aws_mutex_init(&impl->sync)) {
77 aws_mem_release(allocator, impl);
78 return AWS_OP_ERR;
79 }
80
81 channel->vtable = &s_foreground_channel_vtable;
82 channel->allocator = allocator;
83 channel->writer = writer;
84 channel->impl = impl;
85
86 return AWS_OP_SUCCESS;
87}
88
89struct aws_log_background_channel {
90 struct aws_mutex sync;
91 struct aws_thread background_thread;
92 struct aws_array_list pending_log_lines;
93 struct aws_condition_variable pending_line_signal;
94 bool finished;
95};
96
97static int s_background_channel_send(struct aws_log_channel *channel, struct aws_string *log_line) {
98
99 struct aws_log_background_channel *impl = (struct aws_log_background_channel *)channel->impl;
100
101 aws_mutex_lock(&impl->sync);
102 aws_array_list_push_back(&impl->pending_log_lines, &log_line);
103 aws_condition_variable_notify_one(&impl->pending_line_signal);
104 aws_mutex_unlock(&impl->sync);
105
106 return AWS_OP_SUCCESS;
107}
108
109static void s_background_channel_clean_up(struct aws_log_channel *channel) {
110 struct aws_log_background_channel *impl = (struct aws_log_background_channel *)channel->impl;
111
112 aws_mutex_lock(&impl->sync);
113 impl->finished = true;
114 aws_condition_variable_notify_one(&impl->pending_line_signal);
115 aws_mutex_unlock(&impl->sync);
116
117 aws_thread_join(&impl->background_thread);
118
119 aws_thread_clean_up(&impl->background_thread);
120 aws_condition_variable_clean_up(&impl->pending_line_signal);
121 aws_array_list_clean_up(&impl->pending_log_lines);
122 aws_mutex_clean_up(&impl->sync);
123 aws_mem_release(channel->allocator, impl);
124}
125
126static struct aws_log_channel_vtable s_background_channel_vtable = {
127 .send = s_background_channel_send,
128 .clean_up = s_background_channel_clean_up,
129};
130
131static bool s_background_wait(void *context) {
132 struct aws_log_background_channel *impl = (struct aws_log_background_channel *)context;
133
134 /*
135 * Condition variable predicates are checked under mutex protection
136 */
137 return impl->finished || aws_array_list_length(&impl->pending_log_lines) > 0;
138}
139
140static void s_background_thread_writer(void *thread_data) {
141 (void)thread_data;
142
143 struct aws_log_channel *channel = (struct aws_log_channel *)thread_data;
144 AWS_ASSERT(channel->writer->vtable->write);
145
146 struct aws_log_background_channel *impl = (struct aws_log_background_channel *)channel->impl;
147
148 struct aws_array_list log_lines;
149
150 AWS_FATAL_ASSERT(aws_array_list_init_dynamic(&log_lines, channel->allocator, 10, sizeof(struct aws_string *)) == 0);
151
152 while (true) {
153 aws_mutex_lock(&impl->sync);
154 aws_condition_variable_wait_pred(&impl->pending_line_signal, &impl->sync, s_background_wait, impl);
155
156 size_t line_count = aws_array_list_length(&impl->pending_log_lines);
157 bool finished = impl->finished;
158
159 if (line_count == 0) {
160 aws_mutex_unlock(&impl->sync);
161 if (finished) {
162 break;
163 }
164 continue;
165 }
166
167 aws_array_list_swap_contents(&impl->pending_log_lines, &log_lines);
168 aws_mutex_unlock(&impl->sync);
169
170 /*
171 * Consider copying these into a page-sized stack buffer (string) and then making the write calls
172 * against it rather than the individual strings. Might be a savings when > 1 lines (cut down on
173 * write calls).
174 */
175 for (size_t i = 0; i < line_count; ++i) {
176 struct aws_string *log_line = NULL;
177 AWS_FATAL_ASSERT(aws_array_list_get_at(&log_lines, &log_line, i) == AWS_OP_SUCCESS);
178
179 (channel->writer->vtable->write)(channel->writer, log_line);
180
181 /*
182 * send is considered a transfer of ownership. write is not a transfer of ownership.
183 * So it's always the channel's responsibility to clean up all log lines that enter
184 * it as soon as they are no longer needed.
185 */
186 aws_string_destroy(log_line);
187 }
188
189 aws_array_list_clear(&log_lines);
190 }
191
192 aws_array_list_clean_up(&log_lines);
193}
194
195int aws_log_channel_init_background(
196 struct aws_log_channel *channel,
197 struct aws_allocator *allocator,
198 struct aws_log_writer *writer) {
199 struct aws_log_background_channel *impl = aws_mem_calloc(allocator, 1, sizeof(struct aws_log_background_channel));
200 if (impl == NULL) {
201 return AWS_OP_ERR;
202 }
203
204 impl->finished = false;
205
206 if (aws_mutex_init(&impl->sync)) {
207 goto clean_up_sync_init_fail;
208 }
209
210 if (aws_array_list_init_dynamic(&impl->pending_log_lines, allocator, 10, sizeof(struct aws_string *))) {
211 goto clean_up_pending_log_lines_init_fail;
212 }
213
214 if (aws_condition_variable_init(&impl->pending_line_signal)) {
215 goto clean_up_pending_line_signal_init_fail;
216 }
217
218 if (aws_thread_init(&impl->background_thread, allocator)) {
219 goto clean_up_background_thread_init_fail;
220 }
221
222 channel->vtable = &s_background_channel_vtable;
223 channel->allocator = allocator;
224 channel->impl = impl;
225 channel->writer = writer;
226
227 /*
228 * Logging thread should need very little stack, but let's defer this to later
229 */
230 struct aws_thread_options thread_options = {.stack_size = 0};
231
232 if (aws_thread_launch(&impl->background_thread, s_background_thread_writer, channel, &thread_options) ==
233 AWS_OP_SUCCESS) {
234 return AWS_OP_SUCCESS;
235 }
236
237 aws_thread_clean_up(&impl->background_thread);
238
239clean_up_background_thread_init_fail:
240 aws_condition_variable_clean_up(&impl->pending_line_signal);
241
242clean_up_pending_line_signal_init_fail:
243 aws_array_list_clean_up(&impl->pending_log_lines);
244
245clean_up_pending_log_lines_init_fail:
246 aws_mutex_clean_up(&impl->sync);
247
248clean_up_sync_init_fail:
249 aws_mem_release(allocator, impl);
250
251 return AWS_OP_ERR;
252}
253
254void aws_log_channel_clean_up(struct aws_log_channel *channel) {
255 AWS_ASSERT(channel->vtable->clean_up);
256 (channel->vtable->clean_up)(channel);
257}
258