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