1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2  * contributor license agreements.  See the NOTICE file distributed with
3  * this work for additional information regarding copyright ownership.
4  * The ASF licenses this file to You under the Apache License, Version 2.0
5  * (the "License"); you may not use this file except in compliance with
6  * the License.  You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "mod_perl.h"
18 
19 #ifdef USE_ITHREADS
20 
21 /*
22  * tipool == "thread item pool"
23  * this module is intended to provide generic stuctures/functions
24  * for managing a "pool" of a given items (data structures) within a threaded
25  * process.  at the moment, mod_perl uses this module to manage a pool
26  * of PerlInterpreter objects.  it should be quite easy to reuse for
27  * other data, such as database connection handles and the like.
28  * while it is "generic" it is also tuned for Apache, making use of
29  * apr_pool_t and the like, and implementing start/max/{min,max}_spare/
30  * max_requests configuration.
31  * this is another "proof-of-concept", plenty of room for improvement here
32  */
33 
modperl_list_new()34 modperl_list_t *modperl_list_new()
35 {
36     modperl_list_t *listp =
37         (modperl_list_t *)malloc(sizeof(*listp));
38     memset(listp, '\0', sizeof(*listp));
39     return listp;
40 }
41 
modperl_list_last(modperl_list_t * list)42 modperl_list_t *modperl_list_last(modperl_list_t *list)
43 {
44     while (list->next) {
45         list = list->next;
46     }
47 
48     return list;
49 }
50 
modperl_list_first(modperl_list_t * list)51 modperl_list_t *modperl_list_first(modperl_list_t *list)
52 {
53     while (list->prev) {
54         list = list->prev;
55     }
56 
57     return list;
58 }
59 
modperl_list_append(modperl_list_t * list,modperl_list_t * new_list)60 modperl_list_t *modperl_list_append(modperl_list_t *list,
61                                     modperl_list_t *new_list)
62 {
63     modperl_list_t *last;
64 
65     new_list->prev = new_list->next = NULL;
66 
67     if (!list) {
68         return new_list;
69     }
70 
71     last = modperl_list_last(list);
72 
73     last->next = new_list;
74     new_list->prev = last;
75 
76     return list;
77 }
78 
modperl_list_prepend(modperl_list_t * list,modperl_list_t * new_list)79 modperl_list_t *modperl_list_prepend(modperl_list_t *list,
80                                      modperl_list_t *new_list)
81 {
82     new_list->prev = new_list->next = NULL;
83 
84     if (!list) {
85         return new_list;
86     }
87 
88     if (list->prev) {
89         list->prev->next = new_list;
90         new_list->prev = list->prev;
91     }
92 
93     list->prev = new_list;
94     new_list->next = list;
95 
96     return new_list;
97 }
98 
modperl_list_remove(modperl_list_t * list,modperl_list_t * rlist)99 modperl_list_t *modperl_list_remove(modperl_list_t *list,
100                                     modperl_list_t *rlist)
101 {
102     modperl_list_t *tmp = list;
103 
104     while (tmp) {
105         if (tmp != rlist) {
106             tmp = tmp->next;
107         }
108         else {
109             if (tmp->prev) {
110                 tmp->prev->next = tmp->next;
111             }
112             if (tmp->next) {
113                 tmp->next->prev = tmp->prev;
114             }
115             if (list == tmp) {
116                 list = list->next;
117             }
118 
119             break;
120         }
121     }
122 
123 #ifdef MP_TRACE
124     if (!tmp) {
125         /* should never happen */
126         MP_TRACE_i(MP_FUNC, "failed to find 0x%lx in list 0x%lx",
127                    (unsigned long)rlist, (unsigned long)list);
128     }
129 #endif
130 
131     return list;
132 }
133 
modperl_list_remove_data(modperl_list_t * list,void * data,modperl_list_t ** listp)134 modperl_list_t *modperl_list_remove_data(modperl_list_t *list,
135                                          void *data,
136                                          modperl_list_t **listp)
137 {
138     modperl_list_t *tmp = list;
139 
140     while (tmp) {
141         if (tmp->data != data) {
142             tmp = tmp->next;
143         }
144         else {
145             *listp = tmp;
146             if (tmp->prev) {
147                 tmp->prev->next = tmp->next;
148             }
149             if (tmp->next) {
150                 tmp->next->prev = tmp->prev;
151             }
152             if (list == tmp) {
153                 list = list->next;
154             }
155 
156             break;
157         }
158     }
159 
160     return list;
161 }
162 
modperl_tipool_new(apr_pool_t * p,modperl_tipool_config_t * cfg,modperl_tipool_vtbl_t * func,void * data)163 modperl_tipool_t *modperl_tipool_new(apr_pool_t *p,
164                                      modperl_tipool_config_t *cfg,
165                                      modperl_tipool_vtbl_t *func,
166                                      void *data)
167 {
168     modperl_tipool_t *tipool =
169         (modperl_tipool_t *)apr_pcalloc(p, sizeof(*tipool));
170 
171     tipool->cfg = cfg;
172     tipool->func = func;
173     tipool->data = data;
174 
175     MUTEX_INIT(&tipool->tiplock);
176     COND_INIT(&tipool->available);
177 
178     return tipool;
179 }
180 
modperl_tipool_init(modperl_tipool_t * tipool)181 void modperl_tipool_init(modperl_tipool_t *tipool)
182 {
183     int i;
184 
185     for (i=0; i<tipool->cfg->start; i++) {
186         void *item =
187             (*tipool->func->tipool_sgrow)(tipool, tipool->data);
188 
189         modperl_tipool_add(tipool, item);
190     }
191 
192     MP_TRACE_i(MP_FUNC, "start=%d, max=%d, min_spare=%d, max_spare=%d",
193                tipool->cfg->start, tipool->cfg->max,
194                tipool->cfg->min_spare, tipool->cfg->max_spare);
195 
196 }
197 
modperl_tipool_destroy(modperl_tipool_t * tipool)198 void modperl_tipool_destroy(modperl_tipool_t *tipool)
199 {
200     while (tipool->idle) {
201         modperl_list_t *listp;
202 
203         if (tipool->func->tipool_destroy) {
204             (*tipool->func->tipool_destroy)(tipool, tipool->data,
205                                             tipool->idle->data);
206         }
207         tipool->size--;
208         listp = tipool->idle->next;
209         free(tipool->idle);
210         tipool->idle = listp;
211     }
212 
213     if (tipool->busy) {
214         MP_TRACE_i(MP_FUNC, "ERROR: %d items still in use",
215                    tipool->in_use);
216     }
217 
218     MUTEX_DESTROY(&tipool->tiplock);
219     COND_DESTROY(&tipool->available);
220 }
221 
modperl_tipool_add(modperl_tipool_t * tipool,void * data)222 void modperl_tipool_add(modperl_tipool_t *tipool, void *data)
223 {
224     modperl_list_t *listp = modperl_list_new();
225 
226     listp->data = data;
227 
228     /* assuming tipool->tiplock has already been acquired */
229 
230     tipool->idle = modperl_list_append(tipool->idle, listp);
231 
232     tipool->size++;
233 
234     MP_TRACE_i(MP_FUNC, "added 0x%lx (size=%d)",
235                (unsigned long)listp, tipool->size);
236 }
237 
modperl_tipool_remove(modperl_tipool_t * tipool,modperl_list_t * listp)238 void modperl_tipool_remove(modperl_tipool_t *tipool, modperl_list_t *listp)
239 {
240     /* assuming tipool->tiplock has already been acquired */
241 
242     tipool->idle = modperl_list_remove(tipool->idle, listp);
243 
244     tipool->size--;
245     MP_TRACE_i(MP_FUNC, "removed 0x%lx (size=%d)",
246                (unsigned long)listp, tipool->size);
247 }
248 
modperl_tipool_pop(modperl_tipool_t * tipool)249 modperl_list_t *modperl_tipool_pop(modperl_tipool_t *tipool)
250 {
251     modperl_list_t *head;
252 
253     modperl_tipool_lock(tipool);
254 
255     if (tipool->size == tipool->in_use) {
256         if (tipool->size < tipool->cfg->max) {
257             MP_TRACE_i(MP_FUNC,
258                        "no idle items, size %d < %d max",
259                        tipool->size, tipool->cfg->max);
260             if (tipool->func->tipool_rgrow) {
261                 void * item =
262                     (*tipool->func->tipool_rgrow)(tipool, tipool->data);
263 
264                 modperl_tipool_add(tipool, item);
265             }
266         }
267         /* block until an item becomes available */
268         modperl_tipool_wait(tipool);
269     }
270 
271     head = tipool->idle;
272 
273     tipool->idle = modperl_list_remove(tipool->idle, head);
274     tipool->busy = modperl_list_append(tipool->busy, head);
275 
276     tipool->in_use++;
277 
278     /* XXX: this should never happen */
279     if (!head) {
280         MP_TRACE_i(MP_FUNC, "PANIC: no items available, %d of %d in use",
281                    tipool->in_use, tipool->size);
282         abort();
283     }
284 
285     modperl_tipool_unlock(tipool);
286 
287     return head;
288 }
289 
modperl_tipool_putback_base(modperl_tipool_t * tipool,modperl_list_t * listp,void * data,int num_requests)290 static void modperl_tipool_putback_base(modperl_tipool_t *tipool,
291                                         modperl_list_t *listp,
292                                         void *data,
293                                         int num_requests)
294 {
295     int max_spare, max_requests;
296 
297     modperl_tipool_lock(tipool);
298 
299     /* remove from busy list, add back to idle */
300     /* XXX: option to sort list, e.g. on num_requests */
301 
302     if (listp) {
303         tipool->busy = modperl_list_remove(tipool->busy, listp);
304     }
305     else {
306         tipool->busy = modperl_list_remove_data(tipool->busy, data, &listp);
307     }
308 
309     if (!listp) {
310         /* XXX: Attempt to putback something that was never there */
311         modperl_tipool_unlock(tipool);
312         return;
313     }
314 
315     tipool->idle = modperl_list_prepend(tipool->idle, listp);
316 
317     tipool->in_use--;
318 
319 #ifdef MP_TRACE
320     if (!tipool->busy && tipool->func->tipool_dump) {
321         MP_TRACE_i(MP_FUNC, "all items idle:");
322         MP_TRACE_i_do((*tipool->func->tipool_dump)(tipool,
323                                                    tipool->data,
324                                                    tipool->idle));
325     }
326 #endif
327 
328     MP_TRACE_i(MP_FUNC, "0x%lx now available (%d in use, %d running)",
329                (unsigned long)listp->data, tipool->in_use, tipool->size);
330 
331     modperl_tipool_broadcast(tipool);
332     if (tipool->in_use == (tipool->cfg->max - 1)) {
333         /* hurry up, another thread may be blocking */
334         modperl_tipool_unlock(tipool);
335         return;
336     }
337 
338     max_spare = ((tipool->size - tipool->in_use) > tipool->cfg->max_spare);
339     max_requests = ((num_requests > 0) &&
340                     (num_requests > tipool->cfg->max_requests));
341 
342     if (max_spare) {
343         MP_TRACE_i(MP_FUNC,
344                    "shrinking pool: max_spare=%d, only %d of %d in use",
345                    tipool->cfg->max_spare, tipool->in_use, tipool->size);
346     }
347     else if (max_requests) {
348         MP_TRACE_i(MP_FUNC, "shrinking pool: max requests %d reached",
349                    tipool->cfg->max_requests);
350     }
351 
352     /* XXX: this management should probably be happening elsewhere
353      * like in a thread spawned at startup
354      */
355     if (max_spare || max_requests) {
356         modperl_tipool_remove(tipool, listp);
357 
358         if (tipool->func->tipool_destroy) {
359             (*tipool->func->tipool_destroy)(tipool, tipool->data,
360                                             listp->data);
361         }
362 
363         free(listp); /* gone for good */
364 
365         if (max_requests && ((tipool->size - tipool->in_use) <
366                              tipool->cfg->min_spare)) {
367             if (tipool->func->tipool_rgrow) {
368                 void *item =
369                     (*tipool->func->tipool_rgrow)(tipool,
370                                                   tipool->data);
371 
372                 MP_TRACE_i(MP_FUNC,
373                            "growing pool: min_spare=%d, %d of %d in use",
374                            tipool->cfg->min_spare, tipool->in_use,
375                            tipool->size);
376 
377                 modperl_tipool_add(tipool, item);
378             }
379         }
380     }
381 
382     modperl_tipool_unlock(tipool);
383 }
384 
385 /* _data functions are so structures (e.g. modperl_interp_t) don't
386  * need to maintain a pointer back to the modperl_list_t
387  */
388 
modperl_tipool_putback_data(modperl_tipool_t * tipool,void * data,int num_requests)389 void modperl_tipool_putback_data(modperl_tipool_t *tipool,
390                                  void *data,
391                                  int num_requests)
392 {
393     modperl_tipool_putback_base(tipool, NULL, data, num_requests);
394 }
395 
modperl_tipool_putback(modperl_tipool_t * tipool,modperl_list_t * listp,int num_requests)396 void modperl_tipool_putback(modperl_tipool_t *tipool,
397                             modperl_list_t *listp,
398                             int num_requests)
399 {
400     modperl_tipool_putback_base(tipool, listp, NULL, num_requests);
401 }
402 
403 #endif /* USE_ITHREADS */
404 
405 /*
406  * Local Variables:
407  * c-basic-offset: 4
408  * indent-tabs-mode: nil
409  * End:
410  */
411