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