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 <assert.h>
18
19#include "apu.h"
20#include "apr_reslist.h"
21#include "apr_errno.h"
22#include "apr_strings.h"
23#include "apr_thread_mutex.h"
24#include "apr_thread_cond.h"
25#include "apr_ring.h"
26
27#if APR_HAS_THREADS
28
29/**
30 * A single resource element.
31 */
32struct apr_res_t {
33    apr_time_t freed;
34    void *opaque;
35    APR_RING_ENTRY(apr_res_t) link;
36};
37typedef struct apr_res_t apr_res_t;
38
39/**
40 * A ring of resources representing the list of available resources.
41 */
42APR_RING_HEAD(apr_resring_t, apr_res_t);
43typedef struct apr_resring_t apr_resring_t;
44
45struct apr_reslist_t {
46    apr_pool_t *pool; /* the pool used in constructor and destructor calls */
47    int ntotal;     /* total number of resources managed by this list */
48    int nidle;      /* number of available resources */
49    int min;  /* desired minimum number of available resources */
50    int smax; /* soft maximum on the total number of resources */
51    int hmax; /* hard maximum on the total number of resources */
52    apr_interval_time_t ttl; /* TTL when we have too many resources */
53    apr_interval_time_t timeout; /* Timeout for waiting on resource */
54    apr_reslist_constructor constructor;
55    apr_reslist_destructor destructor;
56    void *params; /* opaque data passed to constructor and destructor calls */
57    apr_resring_t avail_list;
58    apr_resring_t free_list;
59    apr_thread_mutex_t *listlock;
60    apr_thread_cond_t *avail;
61};
62
63/**
64 * Grab a resource from the front of the resource list.
65 * Assumes: that the reslist is locked.
66 */
67static apr_res_t *pop_resource(apr_reslist_t *reslist)
68{
69    apr_res_t *res;
70    res = APR_RING_FIRST(&reslist->avail_list);
71    APR_RING_REMOVE(res, link);
72    reslist->nidle--;
73    return res;
74}
75
76/**
77 * Add a resource to the beginning of the list, set the time at which
78 * it was added to the list.
79 * Assumes: that the reslist is locked.
80 */
81static void push_resource(apr_reslist_t *reslist, apr_res_t *resource)
82{
83    APR_RING_INSERT_HEAD(&reslist->avail_list, resource, apr_res_t, link);
84    resource->freed = apr_time_now();
85    reslist->nidle++;
86}
87
88/**
89 * Get an resource container from the free list or create a new one.
90 */
91static apr_res_t *get_container(apr_reslist_t *reslist)
92{
93    apr_res_t *res;
94
95    if (!APR_RING_EMPTY(&reslist->free_list, apr_res_t, link)) {
96        res = APR_RING_FIRST(&reslist->free_list);
97        APR_RING_REMOVE(res, link);
98    }
99    else
100        res = apr_pcalloc(reslist->pool, sizeof(*res));
101    return res;
102}
103
104/**
105 * Free up a resource container by placing it on the free list.
106 */
107static void free_container(apr_reslist_t *reslist, apr_res_t *container)
108{
109    APR_RING_INSERT_TAIL(&reslist->free_list, container, apr_res_t, link);
110}
111
112/**
113 * Create a new resource and return it.
114 * Assumes: that the reslist is locked.
115 */
116static apr_status_t create_resource(apr_reslist_t *reslist, apr_res_t **ret_res)
117{
118    apr_status_t rv;
119    apr_res_t *res;
120
121    res = get_container(reslist);
122
123    rv = reslist->constructor(&res->opaque, reslist->params, reslist->pool);
124
125    *ret_res = res;
126    return rv;
127}
128
129/**
130 * Destroy a single idle resource.
131 * Assumes: that the reslist is locked.
132 */
133static apr_status_t destroy_resource(apr_reslist_t *reslist, apr_res_t *res)
134{
135    return reslist->destructor(res->opaque, reslist->params, reslist->pool);
136}
137
138static apr_status_t reslist_cleanup(void *data_)
139{
140    apr_status_t rv = APR_SUCCESS;
141    apr_reslist_t *rl = data_;
142    apr_res_t *res;
143
144    apr_thread_mutex_lock(rl->listlock);
145
146    while (rl->nidle > 0) {
147        apr_status_t rv1;
148        res = pop_resource(rl);
149        rl->ntotal--;
150        rv1 = destroy_resource(rl, res);
151        if (rv1 != APR_SUCCESS) {
152            rv = rv1;  /* loses info in the unlikely event of
153                        * multiple *different* failures */
154        }
155        free_container(rl, res);
156    }
157
158    assert(rl->nidle == 0);
159    assert(rl->ntotal == 0);
160
161    apr_thread_mutex_unlock(rl->listlock);
162    apr_thread_mutex_destroy(rl->listlock);
163    apr_thread_cond_destroy(rl->avail);
164
165    return rv;
166}
167
168/**
169 * Perform routine maintenance on the resource list. This call
170 * may instantiate new resources or expire old resources.
171 */
172static apr_status_t reslist_maint(apr_reslist_t *reslist)
173{
174    apr_time_t now;
175    apr_status_t rv;
176    apr_res_t *res;
177    int created_one = 0;
178
179    apr_thread_mutex_lock(reslist->listlock);
180
181    /* Check if we need to create more resources, and if we are allowed to. */
182    while (reslist->nidle < reslist->min && reslist->ntotal < reslist->hmax) {
183        /* Create the resource */
184        rv = create_resource(reslist, &res);
185        if (rv != APR_SUCCESS) {
186            free_container(reslist, res);
187            apr_thread_mutex_unlock(reslist->listlock);
188            return rv;
189        }
190        /* Add it to the list */
191        push_resource(reslist, res);
192        /* Update our counters */
193        reslist->ntotal++;
194        /* If someone is waiting on that guy, wake them up. */
195        rv = apr_thread_cond_signal(reslist->avail);
196        if (rv != APR_SUCCESS) {
197            apr_thread_mutex_unlock(reslist->listlock);
198            return rv;
199        }
200        created_one++;
201    }
202
203    /* We don't need to see if we're over the max if we were under it before */
204    if (created_one) {
205        apr_thread_mutex_unlock(reslist->listlock);
206        return APR_SUCCESS;
207    }
208
209    /* Check if we need to expire old resources */
210    now = apr_time_now();
211    while (reslist->nidle > reslist->smax && reslist->nidle > 0) {
212        /* Peak at the last resource in the list */
213        res = APR_RING_LAST(&reslist->avail_list);
214        /* See if the oldest entry should be expired */
215        if (now - res->freed < reslist->ttl) {
216            /* If this entry is too young, none of the others
217             * will be ready to be expired either, so we are done. */
218            break;
219        }
220        APR_RING_REMOVE(res, link);
221        reslist->nidle--;
222        reslist->ntotal--;
223        rv = destroy_resource(reslist, res);
224        free_container(reslist, res);
225        if (rv != APR_SUCCESS) {
226            apr_thread_mutex_unlock(reslist->listlock);
227            return rv;
228        }
229    }
230
231    apr_thread_mutex_unlock(reslist->listlock);
232    return APR_SUCCESS;
233}
234
235APU_DECLARE(apr_status_t) apr_reslist_create(apr_reslist_t **reslist,
236                                             int min, int smax, int hmax,
237                                             apr_interval_time_t ttl,
238                                             apr_reslist_constructor con,
239                                             apr_reslist_destructor de,
240                                             void *params,
241                                             apr_pool_t *pool)
242{
243    apr_status_t rv;
244    apr_reslist_t *rl;
245
246    /* Do some sanity checks so we don't thrash around in the
247     * maintenance routine later. */
248    if (min < 0 || min > smax || min > hmax || smax > hmax || hmax == 0 ||
249        ttl < 0) {
250        return APR_EINVAL;
251    }
252
253    rl = apr_pcalloc(pool, sizeof(*rl));
254    rl->pool = pool;
255    rl->min = min;
256    rl->smax = smax;
257    rl->hmax = hmax;
258    rl->ttl = ttl;
259    rl->constructor = con;
260    rl->destructor = de;
261    rl->params = params;
262
263    APR_RING_INIT(&rl->avail_list, apr_res_t, link);
264    APR_RING_INIT(&rl->free_list, apr_res_t, link);
265
266    rv = apr_thread_mutex_create(&rl->listlock, APR_THREAD_MUTEX_DEFAULT,
267                                 pool);
268    if (rv != APR_SUCCESS) {
269        return rv;
270    }
271    rv = apr_thread_cond_create(&rl->avail, pool);
272    if (rv != APR_SUCCESS) {
273        return rv;
274    }
275
276    rv = reslist_maint(rl);
277    if (rv != APR_SUCCESS) {
278        /* Destroy what we've created so far.
279         */
280        reslist_cleanup(rl);
281        return rv;
282    }
283
284    apr_pool_cleanup_register(rl->pool, rl, reslist_cleanup,
285                              apr_pool_cleanup_null);
286
287    *reslist = rl;
288
289    return APR_SUCCESS;
290}
291
292APU_DECLARE(apr_status_t) apr_reslist_destroy(apr_reslist_t *reslist)
293{
294    return apr_pool_cleanup_run(reslist->pool, reslist, reslist_cleanup);
295}
296
297APU_DECLARE(apr_status_t) apr_reslist_acquire(apr_reslist_t *reslist,
298                                              void **resource)
299{
300    apr_status_t rv;
301    apr_res_t *res;
302    apr_time_t now;
303
304    apr_thread_mutex_lock(reslist->listlock);
305    /* If there are idle resources on the available list, use
306     * them right away. */
307    now = apr_time_now();
308    while (reslist->nidle > 0) {
309        /* Pop off the first resource */
310        res = pop_resource(reslist);
311        if (reslist->ttl && (now - res->freed >= reslist->ttl)) {
312            /* this res is expired - kill it */
313            reslist->ntotal--;
314            rv = destroy_resource(reslist, res);
315            free_container(reslist, res);
316            if (rv != APR_SUCCESS) {
317                apr_thread_mutex_unlock(reslist->listlock);
318                return rv;  /* FIXME: this might cause unnecessary fails */
319            }
320            continue;
321        }
322        *resource = res->opaque;
323        free_container(reslist, res);
324        apr_thread_mutex_unlock(reslist->listlock);
325        return APR_SUCCESS;
326    }
327    /* If we've hit our max, block until we're allowed to create
328     * a new one, or something becomes free. */
329    while (reslist->ntotal >= reslist->hmax && reslist->nidle <= 0) {
330        if (reslist->timeout) {
331            if ((rv = apr_thread_cond_timedwait(reslist->avail,
332                reslist->listlock, reslist->timeout)) != APR_SUCCESS) {
333                apr_thread_mutex_unlock(reslist->listlock);
334                return rv;
335            }
336        }
337        else {
338            apr_thread_cond_wait(reslist->avail, reslist->listlock);
339        }
340    }
341    /* If we popped out of the loop, first try to see if there
342     * are new resources available for immediate use. */
343    if (reslist->nidle > 0) {
344        res = pop_resource(reslist);
345        *resource = res->opaque;
346        free_container(reslist, res);
347        apr_thread_mutex_unlock(reslist->listlock);
348        return APR_SUCCESS;
349    }
350    /* Otherwise the reason we dropped out of the loop
351     * was because there is a new slot available, so create
352     * a resource to fill the slot and use it. */
353    else {
354        rv = create_resource(reslist, &res);
355        if (rv == APR_SUCCESS) {
356            reslist->ntotal++;
357            *resource = res->opaque;
358        }
359        free_container(reslist, res);
360        apr_thread_mutex_unlock(reslist->listlock);
361        return rv;
362    }
363}
364
365APU_DECLARE(apr_status_t) apr_reslist_release(apr_reslist_t *reslist,
366                                              void *resource)
367{
368    apr_res_t *res;
369
370    apr_thread_mutex_lock(reslist->listlock);
371    res = get_container(reslist);
372    res->opaque = resource;
373    push_resource(reslist, res);
374    apr_thread_cond_signal(reslist->avail);
375    apr_thread_mutex_unlock(reslist->listlock);
376
377    return reslist_maint(reslist);
378}
379
380APU_DECLARE(void) apr_reslist_timeout_set(apr_reslist_t *reslist,
381                                          apr_interval_time_t timeout)
382{
383    reslist->timeout = timeout;
384}
385
386APU_DECLARE(apr_uint32_t) apr_reslist_acquired_count(apr_reslist_t *reslist)
387{
388    apr_uint32_t count;
389
390    apr_thread_mutex_lock(reslist->listlock);
391    count = reslist->ntotal - reslist->nidle;
392    apr_thread_mutex_unlock(reslist->listlock);
393
394    return count;
395}
396
397APU_DECLARE(apr_status_t) apr_reslist_invalidate(apr_reslist_t *reslist,
398                                                 void *resource)
399{
400    apr_status_t ret;
401    apr_thread_mutex_lock(reslist->listlock);
402    ret = reslist->destructor(resource, reslist->params, reslist->pool);
403    reslist->ntotal--;
404    apr_thread_cond_signal(reslist->avail);
405    apr_thread_mutex_unlock(reslist->listlock);
406    return ret;
407}
408
409#endif  /* APR_HAS_THREADS */
410