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/** 28 * A single resource element. 29 */ 30struct apr_res_t { 31 apr_time_t freed; 32 void *opaque; 33 APR_RING_ENTRY(apr_res_t) link; 34}; 35typedef struct apr_res_t apr_res_t; 36 37/** 38 * A ring of resources representing the list of available resources. 39 */ 40APR_RING_HEAD(apr_resring_t, apr_res_t); 41typedef struct apr_resring_t apr_resring_t; 42 43struct apr_reslist_t { 44 apr_pool_t *pool; /* the pool used in constructor and destructor calls */ 45 int ntotal; /* total number of resources managed by this list */ 46 int nidle; /* number of available resources */ 47 int min; /* desired minimum number of available resources */ 48 int smax; /* soft maximum on the total number of resources */ 49 int hmax; /* hard maximum on the total number of resources */ 50 apr_interval_time_t ttl; /* TTL when we have too many resources */ 51 apr_interval_time_t timeout; /* Timeout for waiting on resource */ 52 apr_reslist_constructor constructor; 53 apr_reslist_destructor destructor; 54 void *params; /* opaque data passed to constructor and destructor calls */ 55 apr_resring_t avail_list; 56 apr_resring_t free_list; 57#if APR_HAS_THREADS 58 apr_thread_mutex_t *listlock; 59 apr_thread_cond_t *avail; 60#endif 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#if APR_HAS_THREADS 145 apr_thread_mutex_lock(rl->listlock); 146#endif 147 148 while (rl->nidle > 0) { 149 apr_status_t rv1; 150 res = pop_resource(rl); 151 rl->ntotal--; 152 rv1 = destroy_resource(rl, res); 153 if (rv1 != APR_SUCCESS) { 154 rv = rv1; /* loses info in the unlikely event of 155 * multiple *different* failures */ 156 } 157 free_container(rl, res); 158 } 159 160 assert(rl->nidle == 0); 161 assert(rl->ntotal == 0); 162 163#if APR_HAS_THREADS 164 apr_thread_mutex_unlock(rl->listlock); 165 apr_thread_mutex_destroy(rl->listlock); 166 apr_thread_cond_destroy(rl->avail); 167#endif 168 169 return rv; 170} 171 172/** 173 * Perform routine maintenance on the resource list. This call 174 * may instantiate new resources or expire old resources. 175 */ 176APU_DECLARE(apr_status_t) apr_reslist_maintain(apr_reslist_t *reslist) 177{ 178 apr_time_t now; 179 apr_status_t rv; 180 apr_res_t *res; 181 int created_one = 0; 182 183#if APR_HAS_THREADS 184 apr_thread_mutex_lock(reslist->listlock); 185#endif 186 187 /* Check if we need to create more resources, and if we are allowed to. */ 188 while (reslist->nidle < reslist->min && reslist->ntotal < reslist->hmax) { 189 /* Create the resource */ 190 rv = create_resource(reslist, &res); 191 if (rv != APR_SUCCESS) { 192 free_container(reslist, res); 193#if APR_HAS_THREADS 194 apr_thread_mutex_unlock(reslist->listlock); 195#endif 196 return rv; 197 } 198 /* Add it to the list */ 199 push_resource(reslist, res); 200 /* Update our counters */ 201 reslist->ntotal++; 202 /* If someone is waiting on that guy, wake them up. */ 203#if APR_HAS_THREADS 204 rv = apr_thread_cond_signal(reslist->avail); 205 if (rv != APR_SUCCESS) { 206 apr_thread_mutex_unlock(reslist->listlock); 207 return rv; 208 } 209#endif 210 created_one++; 211 } 212 213 /* We don't need to see if we're over the max if we were under it before */ 214 if (created_one) { 215#if APR_HAS_THREADS 216 apr_thread_mutex_unlock(reslist->listlock); 217#endif 218 return APR_SUCCESS; 219 } 220 221 /* Check if we need to expire old resources */ 222 now = apr_time_now(); 223 while (reslist->nidle > reslist->smax && reslist->nidle > 0) { 224 /* Peak at the last resource in the list */ 225 res = APR_RING_LAST(&reslist->avail_list); 226 /* See if the oldest entry should be expired */ 227 if (now - res->freed < reslist->ttl) { 228 /* If this entry is too young, none of the others 229 * will be ready to be expired either, so we are done. */ 230 break; 231 } 232 APR_RING_REMOVE(res, link); 233 reslist->nidle--; 234 reslist->ntotal--; 235 rv = destroy_resource(reslist, res); 236 free_container(reslist, res); 237 if (rv != APR_SUCCESS) { 238#if APR_HAS_THREADS 239 apr_thread_mutex_unlock(reslist->listlock); 240#endif 241 return rv; 242 } 243 } 244 245#if APR_HAS_THREADS 246 apr_thread_mutex_unlock(reslist->listlock); 247#endif 248 return APR_SUCCESS; 249} 250 251APU_DECLARE(apr_status_t) apr_reslist_create(apr_reslist_t **reslist, 252 int min, int smax, int hmax, 253 apr_interval_time_t ttl, 254 apr_reslist_constructor con, 255 apr_reslist_destructor de, 256 void *params, 257 apr_pool_t *pool) 258{ 259 apr_status_t rv; 260 apr_reslist_t *rl; 261 262 /* Do some sanity checks so we don't thrash around in the 263 * maintenance routine later. */ 264 if (min < 0 || min > smax || min > hmax || smax > hmax || hmax == 0 || 265 ttl < 0) { 266 return APR_EINVAL; 267 } 268 269#if !APR_HAS_THREADS 270 /* There can be only one resource when we have no threads. */ 271 if (min > 0) { 272 min = 1; 273 } 274 if (smax > 0) { 275 smax = 1; 276 } 277 hmax = 1; 278#endif 279 280 rl = apr_pcalloc(pool, sizeof(*rl)); 281 rl->pool = pool; 282 rl->min = min; 283 rl->smax = smax; 284 rl->hmax = hmax; 285 rl->ttl = ttl; 286 rl->constructor = con; 287 rl->destructor = de; 288 rl->params = params; 289 290 APR_RING_INIT(&rl->avail_list, apr_res_t, link); 291 APR_RING_INIT(&rl->free_list, apr_res_t, link); 292 293#if APR_HAS_THREADS 294 rv = apr_thread_mutex_create(&rl->listlock, APR_THREAD_MUTEX_DEFAULT, 295 pool); 296 if (rv != APR_SUCCESS) { 297 return rv; 298 } 299 rv = apr_thread_cond_create(&rl->avail, pool); 300 if (rv != APR_SUCCESS) { 301 return rv; 302 } 303#endif 304 305 rv = apr_reslist_maintain(rl); 306 if (rv != APR_SUCCESS) { 307 /* Destroy what we've created so far. 308 */ 309 reslist_cleanup(rl); 310 return rv; 311 } 312 313 apr_pool_cleanup_register(rl->pool, rl, reslist_cleanup, 314 apr_pool_cleanup_null); 315 316 *reslist = rl; 317 318 return APR_SUCCESS; 319} 320 321APU_DECLARE(apr_status_t) apr_reslist_destroy(apr_reslist_t *reslist) 322{ 323 return apr_pool_cleanup_run(reslist->pool, reslist, reslist_cleanup); 324} 325 326APU_DECLARE(apr_status_t) apr_reslist_acquire(apr_reslist_t *reslist, 327 void **resource) 328{ 329 apr_status_t rv; 330 apr_res_t *res; 331 apr_time_t now; 332 333#if APR_HAS_THREADS 334 apr_thread_mutex_lock(reslist->listlock); 335#endif 336 /* If there are idle resources on the available list, use 337 * them right away. */ 338 now = apr_time_now(); 339 while (reslist->nidle > 0) { 340 /* Pop off the first resource */ 341 res = pop_resource(reslist); 342 if (reslist->ttl && (now - res->freed >= reslist->ttl)) { 343 /* this res is expired - kill it */ 344 reslist->ntotal--; 345 rv = destroy_resource(reslist, res); 346 free_container(reslist, res); 347 if (rv != APR_SUCCESS) { 348#if APR_HAS_THREADS 349 apr_thread_mutex_unlock(reslist->listlock); 350#endif 351 return rv; /* FIXME: this might cause unnecessary fails */ 352 } 353 continue; 354 } 355 *resource = res->opaque; 356 free_container(reslist, res); 357#if APR_HAS_THREADS 358 apr_thread_mutex_unlock(reslist->listlock); 359#endif 360 return APR_SUCCESS; 361 } 362 /* If we've hit our max, block until we're allowed to create 363 * a new one, or something becomes free. */ 364 while (reslist->ntotal >= reslist->hmax && reslist->nidle <= 0) { 365#if APR_HAS_THREADS 366 if (reslist->timeout) { 367 if ((rv = apr_thread_cond_timedwait(reslist->avail, 368 reslist->listlock, reslist->timeout)) != APR_SUCCESS) { 369 apr_thread_mutex_unlock(reslist->listlock); 370 return rv; 371 } 372 } 373 else { 374 apr_thread_cond_wait(reslist->avail, reslist->listlock); 375 } 376#else 377 return APR_EAGAIN; 378#endif 379 } 380 /* If we popped out of the loop, first try to see if there 381 * are new resources available for immediate use. */ 382 if (reslist->nidle > 0) { 383 res = pop_resource(reslist); 384 *resource = res->opaque; 385 free_container(reslist, res); 386#if APR_HAS_THREADS 387 apr_thread_mutex_unlock(reslist->listlock); 388#endif 389 return APR_SUCCESS; 390 } 391 /* Otherwise the reason we dropped out of the loop 392 * was because there is a new slot available, so create 393 * a resource to fill the slot and use it. */ 394 else { 395 rv = create_resource(reslist, &res); 396 if (rv == APR_SUCCESS) { 397 reslist->ntotal++; 398 *resource = res->opaque; 399 } 400 free_container(reslist, res); 401#if APR_HAS_THREADS 402 apr_thread_mutex_unlock(reslist->listlock); 403#endif 404 return rv; 405 } 406} 407 408APU_DECLARE(apr_status_t) apr_reslist_release(apr_reslist_t *reslist, 409 void *resource) 410{ 411 apr_res_t *res; 412 413#if APR_HAS_THREADS 414 apr_thread_mutex_lock(reslist->listlock); 415#endif 416 res = get_container(reslist); 417 res->opaque = resource; 418 push_resource(reslist, res); 419#if APR_HAS_THREADS 420 apr_thread_cond_signal(reslist->avail); 421 apr_thread_mutex_unlock(reslist->listlock); 422#endif 423 424 return apr_reslist_maintain(reslist); 425} 426 427APU_DECLARE(void) apr_reslist_timeout_set(apr_reslist_t *reslist, 428 apr_interval_time_t timeout) 429{ 430 reslist->timeout = timeout; 431} 432 433APU_DECLARE(apr_uint32_t) apr_reslist_acquired_count(apr_reslist_t *reslist) 434{ 435 apr_uint32_t count; 436 437#if APR_HAS_THREADS 438 apr_thread_mutex_lock(reslist->listlock); 439#endif 440 count = reslist->ntotal - reslist->nidle; 441#if APR_HAS_THREADS 442 apr_thread_mutex_unlock(reslist->listlock); 443#endif 444 445 return count; 446} 447 448APU_DECLARE(apr_status_t) apr_reslist_invalidate(apr_reslist_t *reslist, 449 void *resource) 450{ 451 apr_status_t ret; 452#if APR_HAS_THREADS 453 apr_thread_mutex_lock(reslist->listlock); 454#endif 455 ret = reslist->destructor(resource, reslist->params, reslist->pool); 456 reslist->ntotal--; 457#if APR_HAS_THREADS 458 apr_thread_cond_signal(reslist->avail); 459 apr_thread_mutex_unlock(reslist->listlock); 460#endif 461 return ret; 462} 463 464APU_DECLARE(void) apr_reslist_cleanup_order_set(apr_reslist_t *rl, 465 apr_uint32_t mode) 466{ 467 apr_pool_cleanup_kill(rl->pool, rl, reslist_cleanup); 468 if (mode == APR_RESLIST_CLEANUP_FIRST) 469 apr_pool_pre_cleanup_register(rl->pool, rl, reslist_cleanup); 470 else 471 apr_pool_cleanup_register(rl->pool, rl, reslist_cleanup, 472 apr_pool_cleanup_null); 473} 474