G:/ScriptBasic/source/extensions/rpool.c

Go to the documentation of this file.
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   }

Generated on Sun Mar 12 23:56:30 2006 for ScriptBasic by  doxygen 1.4.6-NO