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