1251876Speter/* Licensed to the Apache Software Foundation (ASF) under one or more
2251876Speter * contributor license agreements.  See the NOTICE file distributed with
3251876Speter * this work for additional information regarding copyright ownership.
4251876Speter * The ASF licenses this file to You under the Apache License, Version 2.0
5251876Speter * (the "License"); you may not use this file except in compliance with
6251876Speter * the License.  You may obtain a copy of the License at
7251876Speter *
8251876Speter *     http://www.apache.org/licenses/LICENSE-2.0
9251876Speter *
10251876Speter * Unless required by applicable law or agreed to in writing, software
11251876Speter * distributed under the License is distributed on an "AS IS" BASIS,
12251876Speter * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13251876Speter * See the License for the specific language governing permissions and
14251876Speter * limitations under the License.
15251876Speter */
16251876Speter
17251876Speter#include <assert.h>
18251876Speter
19251876Speter#include "apu.h"
20251876Speter#include "apr_reslist.h"
21251876Speter#include "apr_errno.h"
22251876Speter#include "apr_strings.h"
23251876Speter#include "apr_thread_mutex.h"
24251876Speter#include "apr_thread_cond.h"
25251876Speter#include "apr_ring.h"
26251876Speter
27251876Speter/**
28251876Speter * A single resource element.
29251876Speter */
30251876Speterstruct apr_res_t {
31251876Speter    apr_time_t freed;
32251876Speter    void *opaque;
33251876Speter    APR_RING_ENTRY(apr_res_t) link;
34251876Speter};
35251876Spetertypedef struct apr_res_t apr_res_t;
36251876Speter
37251876Speter/**
38251876Speter * A ring of resources representing the list of available resources.
39251876Speter */
40251876SpeterAPR_RING_HEAD(apr_resring_t, apr_res_t);
41251876Spetertypedef struct apr_resring_t apr_resring_t;
42251876Speter
43251876Speterstruct apr_reslist_t {
44251876Speter    apr_pool_t *pool; /* the pool used in constructor and destructor calls */
45251876Speter    int ntotal;     /* total number of resources managed by this list */
46251876Speter    int nidle;      /* number of available resources */
47251876Speter    int min;  /* desired minimum number of available resources */
48251876Speter    int smax; /* soft maximum on the total number of resources */
49251876Speter    int hmax; /* hard maximum on the total number of resources */
50251876Speter    apr_interval_time_t ttl; /* TTL when we have too many resources */
51251876Speter    apr_interval_time_t timeout; /* Timeout for waiting on resource */
52251876Speter    apr_reslist_constructor constructor;
53251876Speter    apr_reslist_destructor destructor;
54251876Speter    void *params; /* opaque data passed to constructor and destructor calls */
55251876Speter    apr_resring_t avail_list;
56251876Speter    apr_resring_t free_list;
57251876Speter#if APR_HAS_THREADS
58251876Speter    apr_thread_mutex_t *listlock;
59251876Speter    apr_thread_cond_t *avail;
60251876Speter#endif
61251876Speter};
62251876Speter
63251876Speter/**
64251876Speter * Grab a resource from the front of the resource list.
65251876Speter * Assumes: that the reslist is locked.
66251876Speter */
67251876Speterstatic apr_res_t *pop_resource(apr_reslist_t *reslist)
68251876Speter{
69251876Speter    apr_res_t *res;
70251876Speter    res = APR_RING_FIRST(&reslist->avail_list);
71251876Speter    APR_RING_REMOVE(res, link);
72251876Speter    reslist->nidle--;
73251876Speter    return res;
74251876Speter}
75251876Speter
76251876Speter/**
77251876Speter * Add a resource to the beginning of the list, set the time at which
78251876Speter * it was added to the list.
79251876Speter * Assumes: that the reslist is locked.
80251876Speter */
81251876Speterstatic void push_resource(apr_reslist_t *reslist, apr_res_t *resource)
82251876Speter{
83251876Speter    APR_RING_INSERT_HEAD(&reslist->avail_list, resource, apr_res_t, link);
84251876Speter    resource->freed = apr_time_now();
85251876Speter    reslist->nidle++;
86251876Speter}
87251876Speter
88251876Speter/**
89251876Speter * Get an resource container from the free list or create a new one.
90251876Speter */
91251876Speterstatic apr_res_t *get_container(apr_reslist_t *reslist)
92251876Speter{
93251876Speter    apr_res_t *res;
94251876Speter
95251876Speter    if (!APR_RING_EMPTY(&reslist->free_list, apr_res_t, link)) {
96251876Speter        res = APR_RING_FIRST(&reslist->free_list);
97251876Speter        APR_RING_REMOVE(res, link);
98251876Speter    }
99251876Speter    else
100251876Speter        res = apr_pcalloc(reslist->pool, sizeof(*res));
101251876Speter    return res;
102251876Speter}
103251876Speter
104251876Speter/**
105251876Speter * Free up a resource container by placing it on the free list.
106251876Speter */
107251876Speterstatic void free_container(apr_reslist_t *reslist, apr_res_t *container)
108251876Speter{
109251876Speter    APR_RING_INSERT_TAIL(&reslist->free_list, container, apr_res_t, link);
110251876Speter}
111251876Speter
112251876Speter/**
113251876Speter * Create a new resource and return it.
114251876Speter * Assumes: that the reslist is locked.
115251876Speter */
116251876Speterstatic apr_status_t create_resource(apr_reslist_t *reslist, apr_res_t **ret_res)
117251876Speter{
118251876Speter    apr_status_t rv;
119251876Speter    apr_res_t *res;
120251876Speter
121251876Speter    res = get_container(reslist);
122251876Speter
123251876Speter    rv = reslist->constructor(&res->opaque, reslist->params, reslist->pool);
124251876Speter
125251876Speter    *ret_res = res;
126251876Speter    return rv;
127251876Speter}
128251876Speter
129251876Speter/**
130251876Speter * Destroy a single idle resource.
131251876Speter * Assumes: that the reslist is locked.
132251876Speter */
133251876Speterstatic apr_status_t destroy_resource(apr_reslist_t *reslist, apr_res_t *res)
134251876Speter{
135251876Speter    return reslist->destructor(res->opaque, reslist->params, reslist->pool);
136251876Speter}
137251876Speter
138251876Speterstatic apr_status_t reslist_cleanup(void *data_)
139251876Speter{
140251876Speter    apr_status_t rv = APR_SUCCESS;
141251876Speter    apr_reslist_t *rl = data_;
142251876Speter    apr_res_t *res;
143251876Speter
144251876Speter#if APR_HAS_THREADS
145251876Speter    apr_thread_mutex_lock(rl->listlock);
146251876Speter#endif
147251876Speter
148251876Speter    while (rl->nidle > 0) {
149251876Speter        apr_status_t rv1;
150251876Speter        res = pop_resource(rl);
151251876Speter        rl->ntotal--;
152251876Speter        rv1 = destroy_resource(rl, res);
153251876Speter        if (rv1 != APR_SUCCESS) {
154251876Speter            rv = rv1;  /* loses info in the unlikely event of
155251876Speter                        * multiple *different* failures */
156251876Speter        }
157251876Speter        free_container(rl, res);
158251876Speter    }
159251876Speter
160251876Speter    assert(rl->nidle == 0);
161251876Speter    assert(rl->ntotal == 0);
162251876Speter
163251876Speter#if APR_HAS_THREADS
164251876Speter    apr_thread_mutex_unlock(rl->listlock);
165251876Speter    apr_thread_mutex_destroy(rl->listlock);
166251876Speter    apr_thread_cond_destroy(rl->avail);
167251876Speter#endif
168251876Speter
169251876Speter    return rv;
170251876Speter}
171251876Speter
172251876Speter/**
173251876Speter * Perform routine maintenance on the resource list. This call
174251876Speter * may instantiate new resources or expire old resources.
175251876Speter */
176251876SpeterAPU_DECLARE(apr_status_t) apr_reslist_maintain(apr_reslist_t *reslist)
177251876Speter{
178251876Speter    apr_time_t now;
179251876Speter    apr_status_t rv;
180251876Speter    apr_res_t *res;
181251876Speter    int created_one = 0;
182251876Speter
183251876Speter#if APR_HAS_THREADS
184251876Speter    apr_thread_mutex_lock(reslist->listlock);
185251876Speter#endif
186251876Speter
187251876Speter    /* Check if we need to create more resources, and if we are allowed to. */
188251876Speter    while (reslist->nidle < reslist->min && reslist->ntotal < reslist->hmax) {
189251876Speter        /* Create the resource */
190251876Speter        rv = create_resource(reslist, &res);
191251876Speter        if (rv != APR_SUCCESS) {
192251876Speter            free_container(reslist, res);
193251876Speter#if APR_HAS_THREADS
194251876Speter            apr_thread_mutex_unlock(reslist->listlock);
195251876Speter#endif
196251876Speter            return rv;
197251876Speter        }
198251876Speter        /* Add it to the list */
199251876Speter        push_resource(reslist, res);
200251876Speter        /* Update our counters */
201251876Speter        reslist->ntotal++;
202251876Speter        /* If someone is waiting on that guy, wake them up. */
203251876Speter#if APR_HAS_THREADS
204251876Speter        rv = apr_thread_cond_signal(reslist->avail);
205251876Speter        if (rv != APR_SUCCESS) {
206251876Speter            apr_thread_mutex_unlock(reslist->listlock);
207251876Speter            return rv;
208251876Speter        }
209251876Speter#endif
210251876Speter        created_one++;
211251876Speter    }
212251876Speter
213251876Speter    /* We don't need to see if we're over the max if we were under it before */
214251876Speter    if (created_one) {
215251876Speter#if APR_HAS_THREADS
216251876Speter        apr_thread_mutex_unlock(reslist->listlock);
217251876Speter#endif
218251876Speter        return APR_SUCCESS;
219251876Speter    }
220251876Speter
221251876Speter    /* Check if we need to expire old resources */
222251876Speter    now = apr_time_now();
223251876Speter    while (reslist->nidle > reslist->smax && reslist->nidle > 0) {
224251876Speter        /* Peak at the last resource in the list */
225251876Speter        res = APR_RING_LAST(&reslist->avail_list);
226251876Speter        /* See if the oldest entry should be expired */
227251876Speter        if (now - res->freed < reslist->ttl) {
228251876Speter            /* If this entry is too young, none of the others
229251876Speter             * will be ready to be expired either, so we are done. */
230251876Speter            break;
231251876Speter        }
232251876Speter        APR_RING_REMOVE(res, link);
233251876Speter        reslist->nidle--;
234251876Speter        reslist->ntotal--;
235251876Speter        rv = destroy_resource(reslist, res);
236251876Speter        free_container(reslist, res);
237251876Speter        if (rv != APR_SUCCESS) {
238251876Speter#if APR_HAS_THREADS
239251876Speter            apr_thread_mutex_unlock(reslist->listlock);
240251876Speter#endif
241251876Speter            return rv;
242251876Speter        }
243251876Speter    }
244251876Speter
245251876Speter#if APR_HAS_THREADS
246251876Speter    apr_thread_mutex_unlock(reslist->listlock);
247251876Speter#endif
248251876Speter    return APR_SUCCESS;
249251876Speter}
250251876Speter
251251876SpeterAPU_DECLARE(apr_status_t) apr_reslist_create(apr_reslist_t **reslist,
252251876Speter                                             int min, int smax, int hmax,
253251876Speter                                             apr_interval_time_t ttl,
254251876Speter                                             apr_reslist_constructor con,
255251876Speter                                             apr_reslist_destructor de,
256251876Speter                                             void *params,
257251876Speter                                             apr_pool_t *pool)
258251876Speter{
259251876Speter    apr_status_t rv;
260251876Speter    apr_reslist_t *rl;
261251876Speter
262251876Speter    /* Do some sanity checks so we don't thrash around in the
263251876Speter     * maintenance routine later. */
264251876Speter    if (min < 0 || min > smax || min > hmax || smax > hmax || hmax == 0 ||
265251876Speter        ttl < 0) {
266251876Speter        return APR_EINVAL;
267251876Speter    }
268251876Speter
269251876Speter#if !APR_HAS_THREADS
270251876Speter    /* There can be only one resource when we have no threads. */
271251876Speter    if (min > 0) {
272251876Speter        min = 1;
273251876Speter    }
274251876Speter    if (smax > 0) {
275251876Speter        smax = 1;
276251876Speter    }
277251876Speter    hmax = 1;
278251876Speter#endif
279251876Speter
280251876Speter    rl = apr_pcalloc(pool, sizeof(*rl));
281251876Speter    rl->pool = pool;
282251876Speter    rl->min = min;
283251876Speter    rl->smax = smax;
284251876Speter    rl->hmax = hmax;
285251876Speter    rl->ttl = ttl;
286251876Speter    rl->constructor = con;
287251876Speter    rl->destructor = de;
288251876Speter    rl->params = params;
289251876Speter
290251876Speter    APR_RING_INIT(&rl->avail_list, apr_res_t, link);
291251876Speter    APR_RING_INIT(&rl->free_list, apr_res_t, link);
292251876Speter
293251876Speter#if APR_HAS_THREADS
294251876Speter    rv = apr_thread_mutex_create(&rl->listlock, APR_THREAD_MUTEX_DEFAULT,
295251876Speter                                 pool);
296251876Speter    if (rv != APR_SUCCESS) {
297251876Speter        return rv;
298251876Speter    }
299251876Speter    rv = apr_thread_cond_create(&rl->avail, pool);
300251876Speter    if (rv != APR_SUCCESS) {
301251876Speter        return rv;
302251876Speter    }
303251876Speter#endif
304251876Speter
305251876Speter    rv = apr_reslist_maintain(rl);
306251876Speter    if (rv != APR_SUCCESS) {
307251876Speter        /* Destroy what we've created so far.
308251876Speter         */
309251876Speter        reslist_cleanup(rl);
310251876Speter        return rv;
311251876Speter    }
312251876Speter
313251876Speter    apr_pool_cleanup_register(rl->pool, rl, reslist_cleanup,
314251876Speter                              apr_pool_cleanup_null);
315251876Speter
316251876Speter    *reslist = rl;
317251876Speter
318251876Speter    return APR_SUCCESS;
319251876Speter}
320251876Speter
321251876SpeterAPU_DECLARE(apr_status_t) apr_reslist_destroy(apr_reslist_t *reslist)
322251876Speter{
323251876Speter    return apr_pool_cleanup_run(reslist->pool, reslist, reslist_cleanup);
324251876Speter}
325251876Speter
326251876SpeterAPU_DECLARE(apr_status_t) apr_reslist_acquire(apr_reslist_t *reslist,
327251876Speter                                              void **resource)
328251876Speter{
329251876Speter    apr_status_t rv;
330251876Speter    apr_res_t *res;
331251876Speter    apr_time_t now;
332251876Speter
333251876Speter#if APR_HAS_THREADS
334251876Speter    apr_thread_mutex_lock(reslist->listlock);
335251876Speter#endif
336251876Speter    /* If there are idle resources on the available list, use
337251876Speter     * them right away. */
338251876Speter    now = apr_time_now();
339251876Speter    while (reslist->nidle > 0) {
340251876Speter        /* Pop off the first resource */
341251876Speter        res = pop_resource(reslist);
342251876Speter        if (reslist->ttl && (now - res->freed >= reslist->ttl)) {
343251876Speter            /* this res is expired - kill it */
344251876Speter            reslist->ntotal--;
345251876Speter            rv = destroy_resource(reslist, res);
346251876Speter            free_container(reslist, res);
347251876Speter            if (rv != APR_SUCCESS) {
348251876Speter#if APR_HAS_THREADS
349251876Speter                apr_thread_mutex_unlock(reslist->listlock);
350251876Speter#endif
351251876Speter                return rv;  /* FIXME: this might cause unnecessary fails */
352251876Speter            }
353251876Speter            continue;
354251876Speter        }
355251876Speter        *resource = res->opaque;
356251876Speter        free_container(reslist, res);
357251876Speter#if APR_HAS_THREADS
358251876Speter        apr_thread_mutex_unlock(reslist->listlock);
359251876Speter#endif
360251876Speter        return APR_SUCCESS;
361251876Speter    }
362251876Speter    /* If we've hit our max, block until we're allowed to create
363251876Speter     * a new one, or something becomes free. */
364251876Speter    while (reslist->ntotal >= reslist->hmax && reslist->nidle <= 0) {
365251876Speter#if APR_HAS_THREADS
366251876Speter        if (reslist->timeout) {
367251876Speter            if ((rv = apr_thread_cond_timedwait(reslist->avail,
368251876Speter                reslist->listlock, reslist->timeout)) != APR_SUCCESS) {
369251876Speter                apr_thread_mutex_unlock(reslist->listlock);
370251876Speter                return rv;
371251876Speter            }
372251876Speter        }
373251876Speter        else {
374251876Speter            apr_thread_cond_wait(reslist->avail, reslist->listlock);
375251876Speter        }
376251876Speter#else
377251876Speter        return APR_EAGAIN;
378251876Speter#endif
379251876Speter    }
380251876Speter    /* If we popped out of the loop, first try to see if there
381251876Speter     * are new resources available for immediate use. */
382251876Speter    if (reslist->nidle > 0) {
383251876Speter        res = pop_resource(reslist);
384251876Speter        *resource = res->opaque;
385251876Speter        free_container(reslist, res);
386251876Speter#if APR_HAS_THREADS
387251876Speter        apr_thread_mutex_unlock(reslist->listlock);
388251876Speter#endif
389251876Speter        return APR_SUCCESS;
390251876Speter    }
391251876Speter    /* Otherwise the reason we dropped out of the loop
392251876Speter     * was because there is a new slot available, so create
393251876Speter     * a resource to fill the slot and use it. */
394251876Speter    else {
395251876Speter        rv = create_resource(reslist, &res);
396251876Speter        if (rv == APR_SUCCESS) {
397251876Speter            reslist->ntotal++;
398251876Speter            *resource = res->opaque;
399251876Speter        }
400251876Speter        free_container(reslist, res);
401251876Speter#if APR_HAS_THREADS
402251876Speter        apr_thread_mutex_unlock(reslist->listlock);
403251876Speter#endif
404251876Speter        return rv;
405251876Speter    }
406251876Speter}
407251876Speter
408251876SpeterAPU_DECLARE(apr_status_t) apr_reslist_release(apr_reslist_t *reslist,
409251876Speter                                              void *resource)
410251876Speter{
411251876Speter    apr_res_t *res;
412251876Speter
413251876Speter#if APR_HAS_THREADS
414251876Speter    apr_thread_mutex_lock(reslist->listlock);
415251876Speter#endif
416251876Speter    res = get_container(reslist);
417251876Speter    res->opaque = resource;
418251876Speter    push_resource(reslist, res);
419251876Speter#if APR_HAS_THREADS
420251876Speter    apr_thread_cond_signal(reslist->avail);
421251876Speter    apr_thread_mutex_unlock(reslist->listlock);
422251876Speter#endif
423251876Speter
424251876Speter    return apr_reslist_maintain(reslist);
425251876Speter}
426251876Speter
427251876SpeterAPU_DECLARE(void) apr_reslist_timeout_set(apr_reslist_t *reslist,
428251876Speter                                          apr_interval_time_t timeout)
429251876Speter{
430251876Speter    reslist->timeout = timeout;
431251876Speter}
432251876Speter
433251876SpeterAPU_DECLARE(apr_uint32_t) apr_reslist_acquired_count(apr_reslist_t *reslist)
434251876Speter{
435251876Speter    apr_uint32_t count;
436251876Speter
437251876Speter#if APR_HAS_THREADS
438251876Speter    apr_thread_mutex_lock(reslist->listlock);
439251876Speter#endif
440251876Speter    count = reslist->ntotal - reslist->nidle;
441251876Speter#if APR_HAS_THREADS
442251876Speter    apr_thread_mutex_unlock(reslist->listlock);
443251876Speter#endif
444251876Speter
445251876Speter    return count;
446251876Speter}
447251876Speter
448251876SpeterAPU_DECLARE(apr_status_t) apr_reslist_invalidate(apr_reslist_t *reslist,
449251876Speter                                                 void *resource)
450251876Speter{
451251876Speter    apr_status_t ret;
452251876Speter#if APR_HAS_THREADS
453251876Speter    apr_thread_mutex_lock(reslist->listlock);
454251876Speter#endif
455251876Speter    ret = reslist->destructor(resource, reslist->params, reslist->pool);
456251876Speter    reslist->ntotal--;
457251876Speter#if APR_HAS_THREADS
458251876Speter    apr_thread_cond_signal(reslist->avail);
459251876Speter    apr_thread_mutex_unlock(reslist->listlock);
460251876Speter#endif
461251876Speter    return ret;
462251876Speter}
463251876Speter
464251876SpeterAPU_DECLARE(void) apr_reslist_cleanup_order_set(apr_reslist_t *rl,
465251876Speter                                                apr_uint32_t mode)
466251876Speter{
467251876Speter    apr_pool_cleanup_kill(rl->pool, rl, reslist_cleanup);
468251876Speter    if (mode == APR_RESLIST_CLEANUP_FIRST)
469251876Speter        apr_pool_pre_cleanup_register(rl->pool, rl, reslist_cleanup);
470251876Speter    else
471251876Speter        apr_pool_cleanup_register(rl->pool, rl, reslist_cleanup,
472251876Speter                                  apr_pool_cleanup_null);
473251876Speter}
474