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/private/thread_shared.h>
7
8 #include <aws/common/clock.h>
9 #include <aws/common/condition_variable.h>
10 #include <aws/common/linked_list.h>
11 #include <aws/common/mutex.h>
12
13 /*
14 * lock guarding the unjoined thread count and pending join list
15 */
16 static struct aws_mutex s_managed_thread_lock = AWS_MUTEX_INIT;
17 static struct aws_condition_variable s_managed_thread_signal = AWS_CONDITION_VARIABLE_INIT;
18 static uint64_t s_default_managed_join_timeout_ns = 0;
19
20 /*
21 * The number of successfully launched managed threads (or event loop threads which participate by inc/dec) that
22 * have not been joined yet.
23 */
24 static uint32_t s_unjoined_thread_count = 0;
25
26 /*
27 * A list of thread_wrapper structs for threads whose thread function has finished but join has not been called
28 * yet for the thread.
29 *
30 * This list is only ever at most length one.
31 */
32 static struct aws_linked_list s_pending_join_managed_threads;
33
aws_thread_increment_unjoined_count(void)34 void aws_thread_increment_unjoined_count(void) {
35 aws_mutex_lock(&s_managed_thread_lock);
36 ++s_unjoined_thread_count;
37 aws_mutex_unlock(&s_managed_thread_lock);
38 }
39
aws_thread_decrement_unjoined_count(void)40 void aws_thread_decrement_unjoined_count(void) {
41 aws_mutex_lock(&s_managed_thread_lock);
42 --s_unjoined_thread_count;
43 aws_mutex_unlock(&s_managed_thread_lock);
44 aws_condition_variable_notify_one(&s_managed_thread_signal);
45 }
46
aws_thread_get_managed_thread_count(void)47 size_t aws_thread_get_managed_thread_count(void) {
48 size_t thread_count = 0;
49 aws_mutex_lock(&s_managed_thread_lock);
50 thread_count = s_unjoined_thread_count;
51 aws_mutex_unlock(&s_managed_thread_lock);
52
53 return thread_count;
54 }
55
s_one_or_fewer_managed_threads_unjoined(void * context)56 static bool s_one_or_fewer_managed_threads_unjoined(void *context) {
57 (void)context;
58 return s_unjoined_thread_count <= 1;
59 }
60
aws_thread_set_managed_join_timeout_ns(uint64_t timeout_in_ns)61 void aws_thread_set_managed_join_timeout_ns(uint64_t timeout_in_ns) {
62 aws_mutex_lock(&s_managed_thread_lock);
63 s_default_managed_join_timeout_ns = timeout_in_ns;
64 aws_mutex_unlock(&s_managed_thread_lock);
65 }
66
aws_thread_join_all_managed(void)67 int aws_thread_join_all_managed(void) {
68 struct aws_linked_list join_list;
69
70 aws_mutex_lock(&s_managed_thread_lock);
71 uint64_t timeout_in_ns = s_default_managed_join_timeout_ns;
72 aws_mutex_unlock(&s_managed_thread_lock);
73
74 uint64_t now_in_ns = 0;
75 uint64_t timeout_timestamp_ns = 0;
76 if (timeout_in_ns > 0) {
77 aws_sys_clock_get_ticks(&now_in_ns);
78 timeout_timestamp_ns = now_in_ns + timeout_in_ns;
79 }
80
81 bool successful = true;
82 bool done = false;
83 while (!done) {
84 aws_mutex_lock(&s_managed_thread_lock);
85
86 /*
87 * We lazily join old threads as newer ones finish their thread function. This means that when called from
88 * the main thread, there will always be one last thread (whichever completion serialized last) that is our
89 * responsibility to join (as long as at least one managed thread was created). So we wait for a count <= 1
90 * rather than what you'd normally expect (0).
91 *
92 * Absent a timeout, we only terminate if there are no threads left so it is possible to spin-wait a while
93 * if there is a single thread still running.
94 */
95 if (timeout_timestamp_ns > 0) {
96 uint64_t wait_ns = 0;
97
98 /*
99 * now_in_ns is always refreshed right before this either outside the loop before the first iteration or
100 * after the previous wait when the overall timeout was checked.
101 */
102 if (now_in_ns <= timeout_timestamp_ns) {
103 wait_ns = timeout_timestamp_ns - now_in_ns;
104 }
105
106 aws_condition_variable_wait_for_pred(
107 &s_managed_thread_signal,
108 &s_managed_thread_lock,
109 wait_ns,
110 s_one_or_fewer_managed_threads_unjoined,
111 NULL);
112 } else {
113 aws_condition_variable_wait_pred(
114 &s_managed_thread_signal, &s_managed_thread_lock, s_one_or_fewer_managed_threads_unjoined, NULL);
115 }
116
117 done = s_unjoined_thread_count == 0;
118
119 aws_sys_clock_get_ticks(&now_in_ns);
120 if (timeout_timestamp_ns != 0 && now_in_ns >= timeout_timestamp_ns) {
121 done = true;
122 successful = false;
123 }
124
125 aws_linked_list_init(&join_list);
126
127 aws_linked_list_swap_contents(&join_list, &s_pending_join_managed_threads);
128
129 aws_mutex_unlock(&s_managed_thread_lock);
130
131 /*
132 * Join against any finished threads. These threads are guaranteed to:
133 * (1) Not be the current thread
134 * (2) Have already ran to user thread_function completion
135 *
136 * The number of finished threads on any iteration is at most one.
137 */
138 aws_thread_join_and_free_wrapper_list(&join_list);
139 }
140
141 return successful ? AWS_OP_SUCCESS : AWS_OP_ERR;
142 }
143
aws_thread_pending_join_add(struct aws_linked_list_node * node)144 void aws_thread_pending_join_add(struct aws_linked_list_node *node) {
145 struct aws_linked_list join_list;
146 aws_linked_list_init(&join_list);
147
148 aws_mutex_lock(&s_managed_thread_lock);
149 /*
150 * Swap out the pending join threads before adding this, otherwise we'd join against ourselves which won't work
151 */
152 aws_linked_list_swap_contents(&join_list, &s_pending_join_managed_threads);
153 aws_linked_list_push_back(&s_pending_join_managed_threads, node);
154 aws_mutex_unlock(&s_managed_thread_lock);
155
156 /*
157 * Join against any finished threads. This thread (it's only ever going to be at most one)
158 * is guaranteed to:
159 * (1) Not be the current thread
160 * (2) Has already ran to user thread_function completion
161 */
162 aws_thread_join_and_free_wrapper_list(&join_list);
163 }
164
aws_thread_initialize_thread_management(void)165 void aws_thread_initialize_thread_management(void) {
166 aws_linked_list_init(&s_pending_join_managed_threads);
167 }
168