00001 /* 00002 FILE: rpool.c 00003 HEADER: rpool.h 00004 00005 --GNU LGPL 00006 This library is free software; you can redistribute it and/or 00007 modify it under the terms of the GNU Lesser General Public 00008 License as published by the Free Software Foundation; either 00009 version 2.1 of the License, or (at your option) any later version. 00010 00011 This library is distributed in the hope that it will be useful, 00012 but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00014 Lesser General Public License for more details. 00015 00016 You should have received a copy of the GNU Lesser General Public 00017 License along with this library; if not, write to the Free Software 00018 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 00019 00020 TO_HEADER: 00021 00022 #include "../basext.h" 00023 #include "../thread.h" 00024 00025 typedef struct _rpm_resource_t { 00026 void *handle; // handle to the resouce 00027 unsigned long lBurn; // the current burn level 00028 unsigned long lCreateTime; // when the resource was created 00029 unsigned long lUseCounter; // how many thread has used the resource 00030 struct _rpm_resource_t *flink,*blink; // link for the linked list 00031 struct _rpm_pool_t *pool; // the pool the resource belongs to 00032 } rpm_resource_t; 00033 00034 typedef struct _rpm_pool_t { 00035 pSupportTable pSt; 00036 unsigned long lMaxBurn; // limit for resource burning 00037 unsigned long lMaxTime; // limit for resource age 00038 unsigned long lMaxUse; // limit for number of use 00039 00040 void *pool; // pool pointer passed to resource handling functions 00041 00042 void *(*fpOpen)(void *); // func pointer to function that opens the resource 00043 void (*fpClose)(void *, void *); // func pointer to function that closes the resource 00044 00045 void *pMemorySegment; // memory segment pointer used by alloc_* 00046 00047 unsigned long (*timefun)(void *);// pointer to the time function 00048 00049 unsigned long nFree; // resources in the list 00050 unsigned long nUsed; // the number of resources used currently 00051 unsigned long nOpening; // the number of resources the RPM is currently opening 00052 00053 unsigned long lMinFree; // the minimum number of free resources in the pool 00054 unsigned long lMaxFree; // the maximum number of free resources in the pool 00055 00056 unsigned long lWaitSleep; // the time the ager thread should sleep in seconds 00057 00058 rpm_resource_t *first_resource; // pointer to the list of resources 00059 rpm_resource_t *first_used; // pointer to the list of currently used resources 00060 MUTEX mxPool; // mutex to lock the whole pool 00061 MUTEX mxRun; // mutex that the worker thread waits for 00062 00063 } rpm_pool_t; 00064 00065 */ 00066 #include <stdio.h> 00067 #include <stdlib.h> 00068 #include <time.h> 00069 00070 #include "rpool.h" 00071 00072 #define rpmALLOC(X) RPool->pSt->Alloc((X),RPool->pMemorySegment) 00073 #define rpmFREE(X) RPool->pSt->Free((X),RPool->pMemorySegment) 00074 /*POD 00075 00076 This file implements resource pool handling routines. This helps multi-thread programs 00077 to maintain a pool of resources. If it makes you problem to think about a resource as 00078 an abstract entity then replace in your mind to database connection. This is especially 00079 true because the very first version of this program is developed to help multi-thread 00080 variation of ScriptBasic to efficiently handle DB connections to MySQL, PostgreSQL and 00081 ORACLE. (Well, at the time we only have MySQL, but we plan the future.) 00082 00083 In an abstract way a resource is something that has to be opened, used and closed. Opening 00084 a resource give a handle to a resource in the form of a pointer. This pointer is used to 00085 reference this resource whenever it is used or is closed. 00086 00087 The resource pool (RP) contains resources. The resource pool manager (RPM) manages the pool. 00088 00089 The RPM has opened resources. Whenever a thread needs a resource it retrieves one from 00090 the RP calling RPM function. When the thread does not need the resource anymore if passes 00091 it back to the RPM. 00092 00093 Between getting the resource and passing it back the thread uses the resource. We say 00094 that the thread burns the resource. When a resource is used a lot it is burnt. The RPM 00095 keeps track of the burn level of the resources and when a 00096 resource is burnt out it is closed it and removed from the pool. 00097 00098 To keep track the burn level of the resource the thread using it can call the 00099 RPM burning function telling that the actual resource was used. 00100 00101 Assume the following example: 00102 00103 The resource is a database connection. There is a server process in the database for each connection. 00104 The server is bugous and looses memory. You experience that a database connection used a lot of times 00105 causes huge memory eating processes on the DB server. Thus you decide that the DB connection should not 00106 be used more than a 100 queries. 00107 00108 The RPM also counts the number of times a resource was passed to a thread. 00109 If this number reaches a limit the RPM removes the resource from the pool. 00110 00111 There is a third way of aging a resource. The RPM keeps track of the absolute age of the resource. 00112 If this age reaches a limit the resource is closed. 00113 00114 A resource is never closed while used by a thread. 00115 00116 If there is any module that uses this module the module T<myalloc.c> has to be compiled in multi-threaded 00117 mode defining T<-DMTHREAD=1> when compiling. 00118 00119 CUT*/ 00120 00121 /*POD 00122 =H rpm_open 00123 00124 This function is called by the R<rpm_thread> function to open a new resource. This function is 00125 started as a separate thread. This allocates memory to store a new resource, calls the resource 00126 opening function pointed by the function pointer T<fpOpen> and intializes the resource. This 00127 means setting the burn level, use level to zero and the resource create time to the current time. 00128 00129 When the resource is ready the function locks the resource pool and links the resource into the 00130 free list. While linking the resource into the free list the function decrements the field T<nOpening> 00131 and increments the field T<nFree> as the resource is not in the opening state anymore but free and available for 00132 use. 00133 00134 =verbatim 00135 */ 00136 static void rpm_open(void *p){ 00137 /*noverbatim 00138 CUT*/ 00139 pSupportTable pSt; 00140 rpm_pool_t *RPool; 00141 rpm_resource_t *pR; 00142 00143 RPool = p; 00144 /* copy it to use the besXXX macros safely */ 00145 pSt = RPool->pSt; 00146 00147 pR = rpmALLOC(sizeof(rpm_resource_t)); 00148 /* if there is no memory enough then */ 00149 if( pR == NULL ){ 00150 besLockMutex( &(RPool->mxPool) ); 00151 RPool->nOpening--; 00152 besUnlockMutex( &(RPool->mxPool) ); 00153 return; 00154 } 00155 00156 /* Open the resource */ 00157 pR->handle = RPool->fpOpen(RPool->pool); 00158 if( pR->handle == NULL ){ 00159 besLockMutex( &(RPool->mxPool) ); 00160 RPool->nOpening--; 00161 besUnlockMutex( &(RPool->mxPool) ); 00162 return; 00163 } 00164 00165 pR->lBurn = 0; /* it is not burnt so far */ 00166 pR->lCreateTime = RPool->timefun(RPool->pool); /* get the creation time for aging */ 00167 pR->lUseCounter = 0; /* it is not used so far */ 00168 pR->pool = RPool; /* this pointer is needed when closing the resource */ 00169 00170 /* lock the pool for the time we insert the opened resource into the list */ 00171 besLockMutex( &(RPool->mxPool) ); 00172 RPool->nOpening--; /* it is not opening */ 00173 RPool->nFree++; /* rather it is opened and free */ 00174 00175 /* link the resource into the freelist */ 00176 pR->blink = NULL; /* no previous, this is the first */ 00177 pR->flink = RPool->first_resource; 00178 if( RPool->first_resource )/* the one that was the first so far becomes the second and points to this one */ 00179 RPool->first_resource->blink = pR; 00180 RPool->first_resource = pR; 00181 00182 /* we are done the resource can be used, the pool is released */ 00183 besUnlockMutex( &(RPool->mxPool) ); 00184 return; 00185 } 00186 00187 /*POD 00188 =H rpm_close 00189 00190 This function closes a resource. This function is started in separate thread thus no other 00191 thread waits for it to close the resource in case the closing takes too long time. The function 00192 R<rpm_close_excess> call this function in its own thread but that function is also started as 00193 a separate thread. 00194 00195 When this function is called the resource is already unlinked from the free list and no-one can 00196 access it. 00197 00198 The argument is a pointer pointing to the resource record. 00199 00200 The resource record has a pointer to the resource pool, but does not access the pool more than 00201 acessing the memory segment pointer and close function. 00202 00203 =verbatim 00204 */ 00205 void rpm_close(void *p){ 00206 /*noverbatim 00207 CUT*/ 00208 rpm_resource_t *pR; 00209 rpm_pool_t *RPool; 00210 00211 pR = p; 00212 RPool = pR->pool; 00213 /* Note that RPool->pool is a pointer that points to a structure that we never touch. That is 00214 the responsibility of the caller what it stores there and how uses this pointer. */ 00215 RPool->fpClose(RPool->pool,pR->handle); 00216 rpmFREE(pR); 00217 } 00218 00219 /*POD 00220 =H rpm_close_excess 00221 00222 This function is called to close the excess resources when there are too many free resources. 00223 This function should close no more than exactly one resource. 00224 00225 The argument is a pointer to the resource pool. 00226 00227 =verbatim 00228 */ 00229 void rpm_close_excess(void *p){ 00230 /*noverbatim 00231 CUT*/ 00232 rpm_pool_t *RPool; 00233 pSupportTable pSt; 00234 rpm_resource_t *pR; 00235 00236 RPool = p; 00237 pSt = RPool->pSt; 00238 besLockMutex( &(RPool->mxPool) ); 00239 /* it may happen that usage grew in the meantime and thus there is no need to close resource anymore */ 00240 if( RPool->nFree <= RPool->lMaxFree ){ 00241 besUnlockMutex( &(RPool->mxPool) ); 00242 return; 00243 } 00244 00245 /* select a free resource to close Later version may be more complex. Now we select the first. */ 00246 pR = RPool->first_resource; 00247 /* ulink the resource */ 00248 if( pR->flink )pR->flink->blink = pR->blink; 00249 if( pR->blink )pR->blink->flink = pR->flink; 00250 pR->blink = pR->flink = NULL; 00251 besUnlockMutex( &(RPool->mxPool) ); 00252 /* close the resource in syncronous mode */ 00253 rpm_close( (void *)pR); 00254 } 00255 00256 /*POD 00257 =H rpm_thread 00258 00259 This function implements the worker thread for the resource pool. For each resource pool there is 00260 a worker thread that continously runs and maintains the pool. 00261 00262 =verbatim 00263 */ 00264 static void rpm_thread(void *p){ 00265 /*noverbatim 00266 CUT*/ 00267 pSupportTable pSt; 00268 rpm_pool_t *RPool; 00269 rpm_resource_t *pR,*pRn; 00270 THREADHANDLE T; 00271 unsigned long nClosing; 00272 unsigned long lTimeNow; 00273 int fActive; 00274 00275 RPool = p; 00276 /* copy it to use the besXXX macros safely */ 00277 pSt = RPool->pSt; 00278 while( 1 ){ 00279 /* this mutex is locked so long as long there is no need to run this */ 00280 besLockMutex( &(RPool->mxRun) ); 00281 00282 do{ 00283 fActive = 0; 00284 besLockMutex( &(RPool->mxPool) ); 00285 00286 /* if there are not enough free resources */ 00287 while( RPool->nFree + RPool->nOpening < RPool->lMinFree ){ 00288 fActive = 1; 00289 besCreateThread(&T,rpm_open,RPool); 00290 RPool->nOpening++; 00291 besUnlockMutex( &(RPool->mxPool) ); 00292 /* here is a small chanche for other threads to get hold of the resource pool */ 00293 besLockMutex( &(RPool->mxPool) ); 00294 } 00295 00296 /* if there are too many free resources */ 00297 if( RPool->nFree > RPool->lMaxFree ){ 00298 fActive = 1; 00299 nClosing = RPool->nFree -RPool->lMaxFree; 00300 /* start nClose threads to close free resources */ 00301 while( nClosing ){ 00302 besCreateThread(&T,rpm_close_excess,RPool); 00303 nClosing ++;; 00304 } 00305 besUnlockMutex( &(RPool->mxPool) ); 00306 /* here is a small chanche for other threads to get hold of the resource pool */ 00307 besLockMutex( &(RPool->mxPool) ); 00308 } 00309 00310 /* check if there are aged resources */ 00311 if( RPool->lMaxTime ){/* if we age resources at all */ 00312 pR = RPool->first_resource; 00313 lTimeNow = RPool->timefun(RPool->pool); 00314 /* go through all the free list */ 00315 while( pR ){ 00316 pRn = pR->flink; 00317 /* if the actual resource is aged */ 00318 if( pR->lCreateTime + RPool->lMaxTime > lTimeNow ){ 00319 /* ulink the resource */ 00320 if( pR->flink )pR->flink->blink = pR->blink; 00321 if( pR->blink )pR->blink->flink = pR->flink; 00322 pR->blink = pR->flink = NULL; 00323 /* close the resource */ 00324 besCreateThread(&T,rpm_close,pR); 00325 } 00326 pR = pRn; 00327 } 00328 } 00329 besUnlockMutex( &(RPool->mxPool) ); 00330 }while(fActive); 00331 } 00332 00333 } 00334 00335 /*POD 00336 =H rpm_ager 00337 00338 This function is started as a separate thread during resource pool initialization only 00339 if resource aging is active. In other words if there is T<lMaxTime> is not zero for a 00340 resource pool then this function as a thread is started. This thread runs infinitely. 00341 00342 The function periodically checks if there is any resource aged in the free list. If there is 00343 then it triggers the RPM worker thread that will close these ages items. 00344 00345 =verbatim 00346 */ 00347 static void rpm_ager(void *p){ 00348 /*noverbatim 00349 CUT*/ 00350 rpm_pool_t *RPool; 00351 rpm_resource_t *pR; 00352 pSupportTable pSt; 00353 unsigned long lTimeNow; 00354 00355 RPool = p; 00356 pSt = RPool->pSt; 00357 lTimeNow = RPool->timefun(RPool->pool); 00358 while( 1 ){ 00359 besLockMutex( &(RPool->mxPool) ); 00360 for( pR = RPool->first_resource ; pR ; pR = pR->flink ){ 00361 /* if we find a resource that is already over age */ 00362 if( pR->lCreateTime+RPool->lMaxTime < lTimeNow ){ 00363 besUnlockMutex( &(RPool->mxRun) );/* trigger the RPM thread */ 00364 break; 00365 } 00366 } 00367 besUnlockMutex( &(RPool->mxPool) ); 00368 besSLEEP(RPool->lWaitSleep); 00369 } 00370 } 00371 00372 /*POD 00373 =H rpm_NewPool 00374 00375 This function creates and initializes a new resource pool. 00376 00377 /*FUNCTION*/ 00378 void *rpm_NewPool( 00379 pSupportTable pSt, 00380 unsigned long lMaxBurn, 00381 unsigned long lMaxTime, 00382 unsigned long lMaxUse, 00383 00384 unsigned long lMinFree, 00385 unsigned long lMaxFree, 00386 00387 unsigned long lWaitSleep, 00388 00389 void *pool, 00390 00391 void *(*fpOpen)(void *), 00392 void (*fpClose)(void *, void *), 00393 00394 void *(*myalloc)(size_t), 00395 void (*myfree)(void *), 00396 unsigned long (*timefun)(void *) 00397 ){ 00398 /*noverbatim 00399 The arguments: 00400 00401 =itemize 00402 =item T<pSt> should point to the ScriptBasic support function table. This is needed to call the functions available 00403 in the ScriptBasic core code and callable by the extensions. 00404 00405 =item T<lMaxBurn> the maximum number of burn value that a resource can get. When a resource is used the thread 00406 using it may call the burning function to increase the burn level of the resource. If the burn level 00407 gets higher than this limit the resource is not used anymore and is closed by the RPM when the thread 00408 passes it back to the RPM. If this value is zero then burn values are not calculated. 00409 00410 =item T<lMaxTime> the maximum number of seconds (or other time base) that a resource can be used. When a resource 00411 gets older than this value it is not given to a thread anymore but closed by the RPM. If this value is zero any 00412 age of resource is used. The time is calculated calling the function T<timefun> which may not return the actual 00413 time but any value that is appropriate to age resources. 00414 00415 =item T<lMaxUse> the maximum number of times a resource is passed to a thread. When a resource is passed to a thread 00416 an internal counter is increased. When the counter reaches this limit the resource is not used any more but 00417 closed. If this limit is zero a reasource can be passed to threads any times. 00418 00419 =item T<lMinFree> this argument gives the number of free resources that the RPM tries to keep available. When the number 00420 of resources gets lower than this the RPM starts to allocate more free resources smaking them available by the 00421 time when a thread asks for it. This value should be positive. 00422 00423 =item T<lMaxFree> this argument gives the number of free resources that the RPM keeps at most. If the number of free 00424 resources gets higher than this number the RPM starts to close some resources. This value should be no smaller 00425 than T<lMinFree>. 00426 00427 =item T<lWaitSleep> should specify the time in seconds to sleep when a thread waits for something. This is the 00428 interval the R<rpm_ager> sleeps between checking that there are ages resources and this is the interval the resource 00429 allocator sleeps waiting for free resource when there is no available free resource. 00430 00431 =item T<pool> is a pointer to a resource type handle. This pointer is not used by the RPM but is passed to the 00432 T<pfOpen()>, T<fpClose()> and T<timefun> functions. 00433 00434 =item T<fpOpen> pointer to a function that opens a resource. This function should accept a T<void *> pointer. Ther 00435 RPM will pass the T<pool> to this pointer. The function should return the handle T<void *> pointer to the resource 00436 or T<NULL> if the resource is not openable. 00437 00438 =item T<fpClose> pointer to a function that closes a resource. This function shoudl accept two T<void *> pointer arguments. 00439 The first one is the T<pool> pointer, the second is the pointer that was passed back by the function T<fpOpen>. 00440 00441 =item T<myalloc> function to a T<malloc> like function. If this argument is T<NULL> then T<malloc> is used. 00442 00443 =item T<myfree> function to a T<free> like function. If this argument is T<NULL> then T<free> is used. 00444 00445 =item T<timefun> function to a function that returns the actual time in terms of resource age. 00446 This can be the number of seconds since the epoch, like returned by the system function T<time()> or 00447 some other increasing value that correspnds with resource age. The function should accept a T<void *> pointer 00448 as argument. The RPM passes the pointer T<pool> in this argument. If this argument is T<NULL> then the 00449 system function T<time()> is used. 00450 00451 =noitemize 00452 00453 Return value is a pointer to the new pool or T<NULL> if there is memory allocation error or if the arguments 00454 are erroneous. 00455 00456 CUT*/ 00457 void *pMemorySegment; 00458 rpm_pool_t *RPool; 00459 THREADHANDLE T; 00460 00461 if( lMinFree == 0 || 00462 lMaxFree < lMinFree || 00463 fpOpen == NULL || 00464 fpClose == NULL )return NULL; 00465 00466 if( myalloc == NULL )myalloc = malloc; 00467 if( myfree == NULL )myfree = free; 00468 if( timefun == NULL )timefun = (void *)time; 00469 00470 pMemorySegment = besINIT_SEGMENT(myalloc,myfree); 00471 if( pMemorySegment == NULL )return NULL; 00472 00473 RPool = pSt->Alloc(sizeof(rpm_pool_t),pMemorySegment); 00474 if( RPool == NULL )return NULL; 00475 RPool->pMemorySegment = pMemorySegment; 00476 RPool->pSt = pSt; 00477 00478 RPool->lMaxBurn = lMaxBurn; 00479 RPool->lMaxTime = lMaxTime; 00480 RPool->lMaxUse = lMaxUse; 00481 00482 RPool->lMinFree = lMinFree; 00483 RPool->lMaxFree = lMaxFree; 00484 00485 RPool->pool = pool; 00486 00487 RPool->fpOpen = fpOpen; 00488 RPool->fpClose = fpClose; 00489 00490 RPool->timefun = timefun; 00491 00492 RPool->nFree = 0; 00493 RPool->nUsed = 0; 00494 RPool->nOpening = 0; 00495 00496 RPool->first_resource = NULL; 00497 RPool->first_used = NULL; 00498 00499 RPool->lWaitSleep = lWaitSleep; 00500 if( RPool->lMaxTime ) 00501 besCreateThread(&T,rpm_ager,RPool); 00502 00503 besInitMutex(&(RPool->mxPool)); 00504 besInitMutex(&(RPool->mxRun)); 00505 besCreateThread(&T,rpm_thread,RPool); 00506 00507 return RPool; 00508 } 00509 00510 /*POD 00511 =H rpm_GetResource 00512 00513 Get a resource from a pool. Arguments: 00514 00515 =itemize 00516 =item T<RPool> is the resource pool to get the resource from. 00517 =item T<lMaxWait> is the maximum number of seconds the function waits to have a free resource. 00518 If this argument is zero the function waits infinitely until there is a free resource. 00519 =noitemize 00520 00521 /*FUNCTION*/ 00522 void *rpm_GetResource( 00523 rpm_pool_t *RPool, 00524 unsigned long lMaxWait 00525 ){ 00526 /*noverbatim 00527 If there is a free resource available the function unlinks it from the used list and links it into the used list. 00528 In this case the function returns the resource pointer. 00529 00530 If there is no available resource the function sleeps T<RPool->lWaitSleep> seconds and tries again. If the repetitive 00531 sleep time exceeds the argument T<lMaxWait> the function returns T<NULL>. 00532 00533 Note that this is a simple v1.0 implementation that does not guarantee first arrived first served. 00534 00535 CUT*/ 00536 pSupportTable pSt; 00537 rpm_resource_t *pR; 00538 unsigned long lSlept; 00539 00540 pSt = RPool->pSt; 00541 lSlept = 0; 00542 while( 1 ){ 00543 besLockMutex( &(RPool->mxPool) ); 00544 /* if there is free resource to be used */ 00545 if( RPool->first_resource ){ 00546 /* get the resource */ 00547 pR = RPool->first_resource; 00548 00549 /* unlink the resource from the free list */ 00550 RPool->first_resource = RPool->first_resource->flink; 00551 if( RPool->first_resource )RPool->first_resource->blink = NULL; 00552 00553 /* link the resource to the used list */ 00554 pR->flink = RPool->first_used; 00555 pR->blink = NULL; 00556 if( pR->flink )pR->flink->blink = pR; 00557 RPool->first_used = pR; 00558 besUnlockMutex( &(RPool->mxPool) ); 00559 return (void *)pR; 00560 } 00561 besUnlockMutex( &(RPool->mxPool) ); 00562 /* if there was no available resource */ 00563 if( lMaxWait && lSlept + RPool->lWaitSleep > lMaxWait )return NULL; 00564 besSLEEP(RPool->lWaitSleep); 00565 } 00566 } 00567 00568 /*POD 00569 =H rpm_PutResource 00570 00571 Get a resource from a pool. Arguments: 00572 00573 =itemize 00574 =item T<RPool> is the resource pool to put the resource back. 00575 =item T<p> is the resource 00576 =noitemize 00577 00578 /*FUNCTION*/ 00579 void rpm_PutResource( 00580 rpm_pool_t *RPool, 00581 void *p 00582 ){ 00583 /*noverbatim 00584 This function should be used to put a resource back into the pool. When a thread does not need the 00585 resource anymore it puts it back to the resource pool handling the resource to the resource pool manager 00586 calling this function. 00587 CUT*/ 00588 pSupportTable pSt; 00589 rpm_resource_t *pR; 00590 00591 pR = p; 00592 pSt = RPool->pSt; 00593 pR->lUseCounter ++; 00594 if( (RPool->lMaxUse && pR->lUseCounter > RPool->lMaxUse) || 00595 (RPool->lMaxBurn && pR->lBurn > RPool->lMaxBurn) 00596 ){ 00597 /* The resource is not put back to the free list, rather closed. */ 00598 rpm_close( (void *)pR); 00599 return; 00600 } 00601 /* lock the pool to handle the used and the free list */ 00602 besLockMutex( &(RPool->mxPool) ); 00603 00604 /* unlink the resource from the used list */ 00605 if( pR->blink ) 00606 pR->blink->flink = pR->flink; 00607 else 00608 RPool->first_used = pR->flink; 00609 if( pR->flink ) 00610 pR->flink->blink = pR->blink; 00611 00612 /* link the resource into the free list */ 00613 pR->flink = RPool->first_resource; 00614 pR->blink = NULL; 00615 if( pR->flink )pR->flink->blink = pR; 00616 RPool->first_resource = pR; 00617 00618 /* release the pool */ 00619 besUnlockMutex( &(RPool->mxPool) ); 00620 }