1 | /* Copyright (C) 2001-2023 Free Software Foundation, Inc. |
2 | This file is part of the GNU C Library. |
3 | |
4 | The GNU C Library is free software; you can redistribute it and/or |
5 | modify it under the terms of the GNU Lesser General Public |
6 | License as published by the Free Software Foundation; either |
7 | version 2.1 of the License, or (at your option) any later version. |
8 | |
9 | The GNU C Library is distributed in the hope that it will be useful, |
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | Lesser General Public License for more details. |
13 | |
14 | You should have received a copy of the GNU Lesser General Public |
15 | License along with the GNU C Library; if not, see |
16 | <https://www.gnu.org/licenses/>. */ |
17 | |
18 | #include <assert.h> |
19 | #include <errno.h> |
20 | #include <pthread.h> |
21 | #include <stdlib.h> |
22 | #include <sys/time.h> |
23 | |
24 | #include <gai_misc.h> |
25 | |
26 | #if !PTHREAD_IN_LIBC |
27 | /* The available function names differ outside of libc. (In libc, we |
28 | need to use hidden aliases to avoid the PLT.) */ |
29 | #define __pthread_attr_init pthread_attr_init |
30 | #define __pthread_attr_setdetachstate pthread_attr_setdetachstate |
31 | #define __pthread_cond_signal pthread_cond_signal |
32 | #define __pthread_cond_timedwait pthread_cond_timedwait |
33 | #define __pthread_create pthread_create |
34 | #define __pthread_exit pthread_exit |
35 | #endif |
36 | |
37 | #ifndef gai_create_helper_thread |
38 | # define gai_create_helper_thread __gai_create_helper_thread |
39 | |
40 | extern inline int |
41 | __gai_create_helper_thread (pthread_t *threadp, void *(*tf) (void *), |
42 | void *arg) |
43 | { |
44 | pthread_attr_t attr; |
45 | |
46 | /* Make sure the thread is created detached. */ |
47 | __pthread_attr_init (&attr); |
48 | __pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); |
49 | |
50 | int ret = __pthread_create (threadp, &attr, tf, arg); |
51 | |
52 | (void) __pthread_attr_destroy (&attr); |
53 | return ret; |
54 | } |
55 | #endif |
56 | |
57 | |
58 | /* Pool of request list entries. */ |
59 | static struct requestlist **pool; |
60 | |
61 | /* Number of total and allocated pool entries. */ |
62 | static size_t pool_max_size; |
63 | static size_t pool_size; |
64 | |
65 | /* We implement a two dimensional array but allocate each row separately. |
66 | The macro below determines how many entries should be used per row. |
67 | It should better be a power of two. */ |
68 | #define ENTRIES_PER_ROW 32 |
69 | |
70 | /* How many rows we allocate at once. */ |
71 | #define ROWS_STEP 8 |
72 | |
73 | /* List of available entries. */ |
74 | static struct requestlist *freelist; |
75 | |
76 | /* Structure list of all currently processed requests. */ |
77 | static struct requestlist *requests; |
78 | static struct requestlist *requests_tail; |
79 | |
80 | /* Number of threads currently running. */ |
81 | static int nthreads; |
82 | |
83 | /* Number of threads waiting for work to arrive. */ |
84 | static int idle_thread_count; |
85 | |
86 | |
87 | /* These are the values used for optimization. We will probably |
88 | create a funcion to set these values. */ |
89 | static struct gaiinit optim = |
90 | { |
91 | 20, /* int gai_threads; Maximal number of threads. */ |
92 | 64, /* int gai_num; Number of expected simultanious requests. */ |
93 | 0, |
94 | 0, |
95 | 0, |
96 | 0, |
97 | 1, |
98 | 0 |
99 | }; |
100 | |
101 | |
102 | /* Since the list is global we need a mutex protecting it. */ |
103 | pthread_mutex_t __gai_requests_mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; |
104 | |
105 | /* When you add a request to the list and there are idle threads present, |
106 | you signal this condition variable. When a thread finishes work, it waits |
107 | on this condition variable for a time before it actually exits. */ |
108 | pthread_cond_t __gai_new_request_notification = PTHREAD_COND_INITIALIZER; |
109 | |
110 | |
111 | /* Functions to handle request list pool. */ |
112 | static struct requestlist * |
113 | get_elem (void) |
114 | { |
115 | struct requestlist *result; |
116 | |
117 | if (freelist == NULL) |
118 | { |
119 | struct requestlist *new_row; |
120 | int cnt; |
121 | |
122 | if (pool_size + 1 >= pool_max_size) |
123 | { |
124 | size_t new_max_size = pool_max_size + ROWS_STEP; |
125 | struct requestlist **new_tab; |
126 | |
127 | new_tab = (struct requestlist **) |
128 | realloc (pool, new_max_size * sizeof (struct requestlist *)); |
129 | |
130 | if (new_tab == NULL) |
131 | return NULL; |
132 | |
133 | pool_max_size = new_max_size; |
134 | pool = new_tab; |
135 | } |
136 | |
137 | /* Allocate the new row. */ |
138 | cnt = pool_size == 0 ? optim.gai_num : ENTRIES_PER_ROW; |
139 | new_row = (struct requestlist *) calloc (cnt, |
140 | sizeof (struct requestlist)); |
141 | if (new_row == NULL) |
142 | return NULL; |
143 | |
144 | pool[pool_size++] = new_row; |
145 | |
146 | /* Put all the new entries in the freelist. */ |
147 | do |
148 | { |
149 | new_row->next = freelist; |
150 | freelist = new_row++; |
151 | } |
152 | while (--cnt > 0); |
153 | } |
154 | |
155 | result = freelist; |
156 | freelist = freelist->next; |
157 | |
158 | return result; |
159 | } |
160 | |
161 | |
162 | struct requestlist * |
163 | __gai_find_request (const struct gaicb *gaicbp) |
164 | { |
165 | struct requestlist *runp; |
166 | |
167 | runp = requests; |
168 | while (runp != NULL) |
169 | if (runp->gaicbp == gaicbp) |
170 | return runp; |
171 | else |
172 | runp = runp->next; |
173 | |
174 | return NULL; |
175 | } |
176 | |
177 | |
178 | int |
179 | __gai_remove_request (struct gaicb *gaicbp) |
180 | { |
181 | struct requestlist *runp; |
182 | struct requestlist *lastp; |
183 | |
184 | runp = requests; |
185 | lastp = NULL; |
186 | while (runp != NULL) |
187 | if (runp->gaicbp == gaicbp) |
188 | break; |
189 | else |
190 | { |
191 | lastp = runp; |
192 | runp = runp->next; |
193 | } |
194 | |
195 | if (runp == NULL) |
196 | /* Not known. */ |
197 | return -1; |
198 | if (runp->running != 0) |
199 | /* Currently handled. */ |
200 | return 1; |
201 | |
202 | /* Dequeue the request. */ |
203 | if (lastp == NULL) |
204 | requests = runp->next; |
205 | else |
206 | lastp->next = runp->next; |
207 | if (runp == requests_tail) |
208 | requests_tail = lastp; |
209 | |
210 | return 0; |
211 | } |
212 | |
213 | |
214 | /* The thread handler. */ |
215 | static void *handle_requests (void *arg); |
216 | |
217 | |
218 | /* The main function of the async I/O handling. It enqueues requests |
219 | and if necessary starts and handles threads. */ |
220 | struct requestlist * |
221 | __gai_enqueue_request (struct gaicb *gaicbp) |
222 | { |
223 | struct requestlist *newp; |
224 | struct requestlist *lastp; |
225 | |
226 | /* Get the mutex. */ |
227 | __pthread_mutex_lock (&__gai_requests_mutex); |
228 | |
229 | /* Get a new element for the waiting list. */ |
230 | newp = get_elem (); |
231 | if (newp == NULL) |
232 | { |
233 | __pthread_mutex_unlock (&__gai_requests_mutex); |
234 | __set_errno (EAGAIN); |
235 | return NULL; |
236 | } |
237 | newp->running = 0; |
238 | newp->gaicbp = gaicbp; |
239 | newp->waiting = NULL; |
240 | newp->next = NULL; |
241 | |
242 | lastp = requests_tail; |
243 | if (requests_tail == NULL) |
244 | requests = requests_tail = newp; |
245 | else |
246 | { |
247 | requests_tail->next = newp; |
248 | requests_tail = newp; |
249 | } |
250 | |
251 | gaicbp->__return = EAI_INPROGRESS; |
252 | |
253 | /* See if we need to and are able to create a thread. */ |
254 | if (nthreads < optim.gai_threads && idle_thread_count == 0) |
255 | { |
256 | pthread_t thid; |
257 | |
258 | newp->running = 1; |
259 | |
260 | /* Now try to start a thread. */ |
261 | if (gai_create_helper_thread (&thid, handle_requests, newp) == 0) |
262 | /* We managed to enqueue the request. All errors which can |
263 | happen now can be recognized by calls to `gai_error'. */ |
264 | ++nthreads; |
265 | else |
266 | { |
267 | if (nthreads == 0) |
268 | { |
269 | /* We cannot create a thread in the moment and there is |
270 | also no thread running. This is a problem. `errno' is |
271 | set to EAGAIN if this is only a temporary problem. */ |
272 | assert (requests == newp || lastp->next == newp); |
273 | if (lastp != NULL) |
274 | lastp->next = NULL; |
275 | else |
276 | requests = NULL; |
277 | requests_tail = lastp; |
278 | |
279 | newp->next = freelist; |
280 | freelist = newp; |
281 | |
282 | newp = NULL; |
283 | } |
284 | else |
285 | /* We are not handling the request after all. */ |
286 | newp->running = 0; |
287 | } |
288 | } |
289 | |
290 | /* Enqueue the request in the request queue. */ |
291 | if (newp != NULL) |
292 | { |
293 | /* If there is a thread waiting for work, then let it know that we |
294 | have just given it something to do. */ |
295 | if (idle_thread_count > 0) |
296 | __pthread_cond_signal (&__gai_new_request_notification); |
297 | } |
298 | |
299 | /* Release the mutex. */ |
300 | __pthread_mutex_unlock (&__gai_requests_mutex); |
301 | |
302 | return newp; |
303 | } |
304 | |
305 | |
306 | static void * |
307 | __attribute__ ((noreturn)) |
308 | handle_requests (void *arg) |
309 | { |
310 | struct requestlist *runp = (struct requestlist *) arg; |
311 | |
312 | do |
313 | { |
314 | /* If runp is NULL, then we were created to service the work queue |
315 | in general, not to handle any particular request. In that case we |
316 | skip the "do work" stuff on the first pass, and go directly to the |
317 | "get work off the work queue" part of this loop, which is near the |
318 | end. */ |
319 | if (runp == NULL) |
320 | __pthread_mutex_lock (&__gai_requests_mutex); |
321 | else |
322 | { |
323 | /* Make the request. */ |
324 | struct gaicb *req = runp->gaicbp; |
325 | struct requestlist *srchp; |
326 | struct requestlist *lastp; |
327 | |
328 | req->__return = getaddrinfo (req->ar_name, req->ar_service, |
329 | req->ar_request, &req->ar_result); |
330 | |
331 | /* Get the mutex. */ |
332 | __pthread_mutex_lock (&__gai_requests_mutex); |
333 | |
334 | /* Send the signal to notify about finished processing of the |
335 | request. */ |
336 | __gai_notify (runp); |
337 | |
338 | /* Now dequeue the current request. */ |
339 | lastp = NULL; |
340 | srchp = requests; |
341 | while (srchp != runp) |
342 | { |
343 | lastp = srchp; |
344 | srchp = srchp->next; |
345 | } |
346 | assert (runp->running == 1); |
347 | |
348 | if (requests_tail == runp) |
349 | requests_tail = lastp; |
350 | if (lastp == NULL) |
351 | requests = requests->next; |
352 | else |
353 | lastp->next = runp->next; |
354 | |
355 | /* Free the old element. */ |
356 | runp->next = freelist; |
357 | freelist = runp; |
358 | } |
359 | |
360 | runp = requests; |
361 | while (runp != NULL && runp->running != 0) |
362 | runp = runp->next; |
363 | |
364 | /* If the runlist is empty, then we sleep for a while, waiting for |
365 | something to arrive in it. */ |
366 | if (runp == NULL && optim.gai_idle_time >= 0) |
367 | { |
368 | struct timespec now; |
369 | struct timespec wakeup_time; |
370 | |
371 | ++idle_thread_count; |
372 | __clock_gettime (CLOCK_REALTIME, &now); |
373 | wakeup_time.tv_sec = now.tv_sec + optim.gai_idle_time; |
374 | wakeup_time.tv_nsec = now.tv_nsec; |
375 | if (wakeup_time.tv_nsec >= 1000000000) |
376 | { |
377 | wakeup_time.tv_nsec -= 1000000000; |
378 | ++wakeup_time.tv_sec; |
379 | } |
380 | __pthread_cond_timedwait (&__gai_new_request_notification, |
381 | &__gai_requests_mutex, &wakeup_time); |
382 | --idle_thread_count; |
383 | runp = requests; |
384 | while (runp != NULL && runp->running != 0) |
385 | runp = runp->next; |
386 | } |
387 | |
388 | if (runp == NULL) |
389 | --nthreads; |
390 | else |
391 | { |
392 | /* Mark the request as being worked on. */ |
393 | assert (runp->running == 0); |
394 | runp->running = 1; |
395 | |
396 | /* If we have a request to process, and there's still another in |
397 | the run list, then we need to either wake up or create a new |
398 | thread to service the request that is still in the run list. */ |
399 | if (requests != NULL) |
400 | { |
401 | /* There are at least two items in the work queue to work on. |
402 | If there are other idle threads, then we should wake them |
403 | up for these other work elements; otherwise, we should try |
404 | to create a new thread. */ |
405 | if (idle_thread_count > 0) |
406 | __pthread_cond_signal (&__gai_new_request_notification); |
407 | else if (nthreads < optim.gai_threads) |
408 | { |
409 | pthread_t thid; |
410 | pthread_attr_t attr; |
411 | |
412 | /* Make sure the thread is created detached. */ |
413 | __pthread_attr_init (&attr); |
414 | __pthread_attr_setdetachstate (&attr, |
415 | PTHREAD_CREATE_DETACHED); |
416 | |
417 | /* Now try to start a thread. If we fail, no big deal, |
418 | because we know that there is at least one thread (us) |
419 | that is working on lookup operations. */ |
420 | if (__pthread_create (&thid, &attr, handle_requests, NULL) |
421 | == 0) |
422 | ++nthreads; |
423 | } |
424 | } |
425 | } |
426 | |
427 | /* Release the mutex. */ |
428 | __pthread_mutex_unlock (&__gai_requests_mutex); |
429 | } |
430 | while (runp != NULL); |
431 | |
432 | __pthread_exit (NULL); |
433 | } |
434 | |
435 | |
436 | /* Free allocated resources. */ |
437 | libc_freeres_fn (free_res) |
438 | { |
439 | size_t row; |
440 | |
441 | for (row = 0; row < pool_max_size; ++row) |
442 | free (pool[row]); |
443 | |
444 | free (pool); |
445 | } |
446 | |