1/*
2 * Copyright 2010-2018 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/thread.h>
17
18#include <aws/common/clock.h>
19
20#include <errno.h>
21#include <limits.h>
22#include <time.h>
23
24static struct aws_thread_options s_default_options = {
25 /* this will make sure platform default stack size is used. */
26 .stack_size = 0};
27
28struct thread_atexit_callback {
29 aws_thread_atexit_fn *callback;
30 void *user_data;
31 struct thread_atexit_callback *next;
32};
33
34struct thread_wrapper {
35 struct aws_allocator *allocator;
36 void (*func)(void *arg);
37 void *arg;
38 struct thread_atexit_callback *atexit;
39 void (*call_once)(void *);
40 void *once_arg;
41};
42
43static AWS_THREAD_LOCAL struct thread_wrapper *tl_wrapper = NULL;
44
45static void *thread_fn(void *arg) {
46 struct thread_wrapper wrapper = *(struct thread_wrapper *)arg;
47 struct aws_allocator *allocator = wrapper.allocator;
48 tl_wrapper = &wrapper;
49 wrapper.func(wrapper.arg);
50
51 struct thread_atexit_callback *exit_callback_data = wrapper.atexit;
52 aws_mem_release(allocator, arg);
53
54 while (exit_callback_data) {
55 aws_thread_atexit_fn *exit_callback = exit_callback_data->callback;
56 void *exit_callback_user_data = exit_callback_data->user_data;
57 struct thread_atexit_callback *next_exit_callback_data = exit_callback_data->next;
58
59 aws_mem_release(allocator, exit_callback_data);
60
61 exit_callback(exit_callback_user_data);
62 exit_callback_data = next_exit_callback_data;
63 }
64 tl_wrapper = NULL;
65
66 return NULL;
67}
68
69const struct aws_thread_options *aws_default_thread_options(void) {
70 return &s_default_options;
71}
72
73void aws_thread_clean_up(struct aws_thread *thread) {
74 if (thread->detach_state == AWS_THREAD_JOINABLE) {
75 pthread_detach(thread->thread_id);
76 }
77}
78
79static void s_call_once(void) {
80 tl_wrapper->call_once(tl_wrapper->once_arg);
81}
82
83void aws_thread_call_once(aws_thread_once *flag, void (*call_once)(void *), void *user_data) {
84 // If this is a non-aws_thread, then gin up a temp thread wrapper
85 struct thread_wrapper temp_wrapper;
86 if (!tl_wrapper) {
87 tl_wrapper = &temp_wrapper;
88 }
89
90 tl_wrapper->call_once = call_once;
91 tl_wrapper->once_arg = user_data;
92 pthread_once(flag, s_call_once);
93
94 if (tl_wrapper == &temp_wrapper) {
95 tl_wrapper = NULL;
96 }
97}
98
99int aws_thread_init(struct aws_thread *thread, struct aws_allocator *allocator) {
100 thread->allocator = allocator;
101 thread->thread_id = 0;
102 thread->detach_state = AWS_THREAD_NOT_CREATED;
103
104 return AWS_OP_SUCCESS;
105}
106
107int aws_thread_launch(
108 struct aws_thread *thread,
109 void (*func)(void *arg),
110 void *arg,
111 const struct aws_thread_options *options) {
112
113 pthread_attr_t attributes;
114 pthread_attr_t *attributes_ptr = NULL;
115 int attr_return = 0;
116 int allocation_failed = 0;
117
118 if (options) {
119 attr_return = pthread_attr_init(&attributes);
120
121 if (attr_return) {
122 goto cleanup;
123 }
124
125 attributes_ptr = &attributes;
126
127 if (options->stack_size > PTHREAD_STACK_MIN) {
128 attr_return = pthread_attr_setstacksize(attributes_ptr, options->stack_size);
129
130 if (attr_return) {
131 goto cleanup;
132 }
133 }
134 }
135
136 struct thread_wrapper *wrapper =
137 (struct thread_wrapper *)aws_mem_calloc(thread->allocator, 1, sizeof(struct thread_wrapper));
138
139 if (!wrapper) {
140 allocation_failed = 1;
141 goto cleanup;
142 }
143
144 wrapper->allocator = thread->allocator;
145 wrapper->func = func;
146 wrapper->arg = arg;
147 attr_return = pthread_create(&thread->thread_id, attributes_ptr, thread_fn, (void *)wrapper);
148
149 if (attr_return) {
150 goto cleanup;
151 }
152
153 thread->detach_state = AWS_THREAD_JOINABLE;
154
155cleanup:
156 if (attributes_ptr) {
157 pthread_attr_destroy(attributes_ptr);
158 }
159
160 if (attr_return == EINVAL) {
161 return aws_raise_error(AWS_ERROR_THREAD_INVALID_SETTINGS);
162 }
163
164 if (attr_return == EAGAIN) {
165 return aws_raise_error(AWS_ERROR_THREAD_INSUFFICIENT_RESOURCE);
166 }
167
168 if (attr_return == EPERM) {
169 return aws_raise_error(AWS_ERROR_THREAD_NO_PERMISSIONS);
170 }
171
172 if (allocation_failed || attr_return == ENOMEM) {
173 return aws_raise_error(AWS_ERROR_OOM);
174 }
175
176 return AWS_OP_SUCCESS;
177}
178
179uint64_t aws_thread_get_id(struct aws_thread *thread) {
180 return (uintptr_t)thread->thread_id;
181}
182
183enum aws_thread_detach_state aws_thread_get_detach_state(struct aws_thread *thread) {
184 return thread->detach_state;
185}
186
187int aws_thread_join(struct aws_thread *thread) {
188 if (thread->detach_state == AWS_THREAD_JOINABLE) {
189 int err_no = pthread_join(thread->thread_id, 0);
190
191 if (err_no) {
192 if (err_no == EINVAL) {
193 return aws_raise_error(AWS_ERROR_THREAD_NOT_JOINABLE);
194 }
195 if (err_no == ESRCH) {
196 return aws_raise_error(AWS_ERROR_THREAD_NO_SUCH_THREAD_ID);
197 }
198 if (err_no == EDEADLK) {
199 return aws_raise_error(AWS_ERROR_THREAD_DEADLOCK_DETECTED);
200 }
201 }
202
203 thread->detach_state = AWS_THREAD_JOIN_COMPLETED;
204 }
205
206 return AWS_OP_SUCCESS;
207}
208
209uint64_t aws_thread_current_thread_id(void) {
210 return (uintptr_t)pthread_self();
211}
212
213void aws_thread_current_sleep(uint64_t nanos) {
214 uint64_t nano = 0;
215 time_t seconds = (time_t)aws_timestamp_convert(nanos, AWS_TIMESTAMP_NANOS, AWS_TIMESTAMP_SECS, &nano);
216
217 struct timespec tm = {
218 .tv_sec = seconds,
219 .tv_nsec = (long)nano,
220 };
221 struct timespec output;
222
223 nanosleep(&tm, &output);
224}
225
226int aws_thread_current_at_exit(aws_thread_atexit_fn *callback, void *user_data) {
227 if (!tl_wrapper) {
228 return aws_raise_error(AWS_ERROR_THREAD_NOT_JOINABLE);
229 }
230
231 struct thread_atexit_callback *cb = aws_mem_calloc(tl_wrapper->allocator, 1, sizeof(struct thread_atexit_callback));
232 if (!cb) {
233 return AWS_OP_ERR;
234 }
235 cb->callback = callback;
236 cb->user_data = user_data;
237 cb->next = tl_wrapper->atexit;
238 tl_wrapper->atexit = cb;
239 return AWS_OP_SUCCESS;
240}
241