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