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 | |
30 | struct aws_log_foreground_channel { |
31 | struct aws_mutex sync; |
32 | }; |
33 | |
34 | static 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 | |
54 | static 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 | |
62 | static 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 | |
67 | int 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 | |
89 | struct 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 | |
97 | static 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 | |
109 | static 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 | |
126 | static 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 | |
131 | static 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 | |
140 | static 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 | |
195 | int 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 | |
239 | clean_up_background_thread_init_fail: |
240 | aws_condition_variable_clean_up(&impl->pending_line_signal); |
241 | |
242 | clean_up_pending_line_signal_init_fail: |
243 | aws_array_list_clean_up(&impl->pending_log_lines); |
244 | |
245 | clean_up_pending_log_lines_init_fail: |
246 | aws_mutex_clean_up(&impl->sync); |
247 | |
248 | clean_up_sync_init_fail: |
249 | aws_mem_release(allocator, impl); |
250 | |
251 | return AWS_OP_ERR; |
252 | } |
253 | |
254 | void 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 | |