1 /* Copyright (C) 2001-2022 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
__gai_create_helper_thread(pthread_t * threadp,void * (* tf)(void *),void * arg)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 *
get_elem(void)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 *
__gai_find_request(const struct gaicb * gaicbp)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
__gai_remove_request(struct gaicb * gaicbp)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 *
__gai_enqueue_request(struct gaicb * gaicbp)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))
handle_requests(void * arg)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. */
libc_freeres_fn(free_res)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