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