tclPreserve.c

Go to the documentation of this file.
00001 /*
00002  * tclPreserve.c --
00003  *
00004  *      This file contains a collection of functions that are used to make
00005  *      sure that widget records and other data structures aren't reallocated
00006  *      when there are nested functions that depend on their existence.
00007  *
00008  * Copyright (c) 1991-1994 The Regents of the University of California.
00009  * Copyright (c) 1994-1998 Sun Microsystems, Inc.
00010  *
00011  * See the file "license.terms" for information on usage and redistribution of
00012  * this file, and for a DISCLAIMER OF ALL WARRANTIES.
00013  *
00014  * RCS: @(#) $Id: tclPreserve.c,v 1.10 2007/03/21 18:02:51 dgp Exp $
00015  */
00016 
00017 #include "tclInt.h"
00018 
00019 /*
00020  * The following data structure is used to keep track of all the Tcl_Preserve
00021  * calls that are still in effect. It grows as needed to accommodate any
00022  * number of calls in effect.
00023  */
00024 
00025 typedef struct {
00026     ClientData clientData;      /* Address of preserved block. */
00027     int refCount;               /* Number of Tcl_Preserve calls in effect for
00028                                  * block. */
00029     int mustFree;               /* Non-zero means Tcl_EventuallyFree was
00030                                  * called while a Tcl_Preserve call was in
00031                                  * effect, so the structure must be freed when
00032                                  * refCount becomes zero. */
00033     Tcl_FreeProc *freeProc;     /* Function to call to free. */
00034 } Reference;
00035 
00036 /*
00037  * Global data structures used to hold the list of preserved data references.
00038  * These variables are protected by "preserveMutex".
00039  */
00040 
00041 static Reference *refArray = NULL;      /* First in array of references. */
00042 static int spaceAvl = 0;        /* Total number of structures available at
00043                                  * *firstRefPtr. */
00044 static int inUse = 0;           /* Count of structures currently in use in
00045                                  * refArray. */
00046 TCL_DECLARE_MUTEX(preserveMutex)/* To protect the above statics */
00047 
00048 #define INITIAL_SIZE    2       /* Initial number of reference slots to make */
00049 
00050 /*
00051  * The following data structure is used to keep track of whether an arbitrary
00052  * block of memory has been deleted. This is used by the TclHandle code to
00053  * avoid the more time-expensive algorithm of Tcl_Preserve(). This mechanism
00054  * is mainly used when we have lots of references to a few big, expensive
00055  * objects that we don't want to live any longer than necessary.
00056  */
00057 
00058 typedef struct HandleStruct {
00059     void *ptr;                  /* Pointer to the memory block being tracked.
00060                                  * This field will become NULL when the memory
00061                                  * block is deleted. This field must be the
00062                                  * first in the structure. */
00063 #ifdef TCL_MEM_DEBUG
00064     void *ptr2;                 /* Backup copy of the above pointer used to
00065                                  * ensure that the contents of the handle are
00066                                  * not changed by anyone else. */
00067 #endif
00068     int refCount;               /* Number of TclHandlePreserve() calls in
00069                                  * effect on this handle. */
00070 } HandleStruct;
00071 
00072 /*
00073  *----------------------------------------------------------------------
00074  *
00075  * TclFinalizePreserve --
00076  *
00077  *      Called during exit processing to clean up the reference array.
00078  *
00079  * Results:
00080  *      None.
00081  *
00082  * Side effects:
00083  *      Frees the storage of the reference array.
00084  *
00085  *----------------------------------------------------------------------
00086  */
00087 
00088         /* ARGSUSED */
00089 void
00090 TclFinalizePreserve(void)
00091 {
00092     Tcl_MutexLock(&preserveMutex);
00093     if (spaceAvl != 0) {
00094         ckfree((char *) refArray);
00095         refArray = NULL;
00096         inUse = 0;
00097         spaceAvl = 0;
00098     }
00099     Tcl_MutexUnlock(&preserveMutex);
00100 }
00101 
00102 /*
00103  *----------------------------------------------------------------------
00104  *
00105  * Tcl_Preserve --
00106  *
00107  *      This function is used by a function to declare its interest in a
00108  *      particular block of memory, so that the block will not be reallocated
00109  *      until a matching call to Tcl_Release has been made.
00110  *
00111  * Results:
00112  *      None.
00113  *
00114  * Side effects:
00115  *      Information is retained so that the block of memory will not be freed
00116  *      until at least the matching call to Tcl_Release.
00117  *
00118  *----------------------------------------------------------------------
00119  */
00120 
00121 void
00122 Tcl_Preserve(
00123     ClientData clientData)      /* Pointer to malloc'ed block of memory. */
00124 {
00125     Reference *refPtr;
00126     int i;
00127 
00128     /*
00129      * See if there is already a reference for this pointer. If so, just
00130      * increment its reference count.
00131      */
00132 
00133     Tcl_MutexLock(&preserveMutex);
00134     for (i=0, refPtr=refArray ; i<inUse ; i++, refPtr++) {
00135         if (refPtr->clientData == clientData) {
00136             refPtr->refCount++;
00137             Tcl_MutexUnlock(&preserveMutex);
00138             return;
00139         }
00140     }
00141 
00142     /*
00143      * Make a reference array if it doesn't already exist, or make it bigger
00144      * if it is full.
00145      */
00146 
00147     if (inUse == spaceAvl) {
00148         spaceAvl = spaceAvl ? 2*spaceAvl : INITIAL_SIZE;
00149         refArray = (Reference *) ckrealloc((char *) refArray,
00150                 spaceAvl * sizeof(Reference));
00151     }
00152 
00153     /*
00154      * Make a new entry for the new reference.
00155      */
00156 
00157     refPtr = &refArray[inUse];
00158     refPtr->clientData = clientData;
00159     refPtr->refCount = 1;
00160     refPtr->mustFree = 0;
00161     refPtr->freeProc = TCL_STATIC;
00162     inUse += 1;
00163     Tcl_MutexUnlock(&preserveMutex);
00164 }
00165 
00166 /*
00167  *----------------------------------------------------------------------
00168  *
00169  * Tcl_Release --
00170  *
00171  *      This function is called to cancel a previous call to Tcl_Preserve,
00172  *      thereby allowing a block of memory to be freed (if no one else cares
00173  *      about it).
00174  *
00175  * Results:
00176  *      None.
00177  *
00178  * Side effects:
00179  *      If Tcl_EventuallyFree has been called for clientData, and if no other
00180  *      call to Tcl_Preserve is still in effect, the block of memory is freed.
00181  *
00182  *----------------------------------------------------------------------
00183  */
00184 
00185 void
00186 Tcl_Release(
00187     ClientData clientData)      /* Pointer to malloc'ed block of memory. */
00188 {
00189     Reference *refPtr;
00190     int i;
00191 
00192     Tcl_MutexLock(&preserveMutex);
00193     for (i=0, refPtr=refArray ; i<inUse ; i++, refPtr++) {
00194         int mustFree;
00195         Tcl_FreeProc *freeProc;
00196 
00197         if (refPtr->clientData != clientData) {
00198             continue;
00199         }
00200 
00201         if (--refPtr->refCount != 0) {
00202             Tcl_MutexUnlock(&preserveMutex);
00203             return;
00204         }
00205 
00206         /*
00207          * Must remove information from the slot before calling freeProc to
00208          * avoid reentrancy problems if the freeProc calls Tcl_Preserve on the
00209          * same clientData. Copy down the last reference in the array to
00210          * overwrite the current slot.
00211          */
00212 
00213         freeProc = refPtr->freeProc;
00214         mustFree = refPtr->mustFree;
00215         inUse--;
00216         if (i < inUse) {
00217             refArray[i] = refArray[inUse];
00218         }
00219 
00220         /*
00221          * Now committed to disposing the data. But first, we've patched up
00222          * all the global data structures so we should release the mutex now.
00223          * Only then should we dabble around with potentially-slow memory
00224          * managers...
00225          */
00226 
00227         Tcl_MutexUnlock(&preserveMutex);
00228         if (mustFree) {
00229             if (freeProc == TCL_DYNAMIC) {
00230                 ckfree((char *) clientData);
00231             } else {
00232                 (*freeProc)((char *) clientData);
00233             }
00234         }
00235         return;
00236     }
00237     Tcl_MutexUnlock(&preserveMutex);
00238 
00239     /*
00240      * Reference not found. This is a bug in the caller.
00241      */
00242 
00243     Tcl_Panic("Tcl_Release couldn't find reference for 0x%x", clientData);
00244 }
00245 
00246 /*
00247  *----------------------------------------------------------------------
00248  *
00249  * Tcl_EventuallyFree --
00250  *
00251  *      Free up a block of memory, unless a call to Tcl_Preserve is in effect
00252  *      for that block. In this case, defer the free until all calls to
00253  *      Tcl_Preserve have been undone by matching calls to Tcl_Release.
00254  *
00255  * Results:
00256  *      None.
00257  *
00258  * Side effects:
00259  *      Ptr may be released by calling free().
00260  *
00261  *----------------------------------------------------------------------
00262  */
00263 
00264 void
00265 Tcl_EventuallyFree(
00266     ClientData clientData,      /* Pointer to malloc'ed block of memory. */
00267     Tcl_FreeProc *freeProc)     /* Function to actually do free. */
00268 {
00269     Reference *refPtr;
00270     int i;
00271 
00272     /*
00273      * See if there is a reference for this pointer. If so, set its "mustFree"
00274      * flag (the flag had better not be set already!).
00275      */
00276 
00277     Tcl_MutexLock(&preserveMutex);
00278     for (i = 0, refPtr = refArray; i < inUse; i++, refPtr++) {
00279         if (refPtr->clientData != clientData) {
00280             continue;
00281         }
00282         if (refPtr->mustFree) {
00283             Tcl_Panic("Tcl_EventuallyFree called twice for 0x%x",
00284                     clientData);
00285         }
00286         refPtr->mustFree = 1;
00287         refPtr->freeProc = freeProc;
00288         Tcl_MutexUnlock(&preserveMutex);
00289         return;
00290     }
00291     Tcl_MutexUnlock(&preserveMutex);
00292 
00293     /*
00294      * No reference for this block.  Free it now.
00295      */
00296 
00297     if (freeProc == TCL_DYNAMIC) {
00298         ckfree((char *) clientData);
00299     } else {
00300         (*freeProc)((char *)clientData);
00301     }
00302 }
00303 
00304 /*
00305  *---------------------------------------------------------------------------
00306  *
00307  * TclHandleCreate --
00308  *
00309  *      Allocate a handle that contains enough information to determine if an
00310  *      arbitrary malloc'd block has been deleted. This is used to avoid the
00311  *      more time-expensive algorithm of Tcl_Preserve().
00312  *
00313  * Results:
00314  *      The return value is a TclHandle that refers to the given malloc'd
00315  *      block. Doubly dereferencing the returned handle will give back the
00316  *      pointer to the block, or will give NULL if the block has been deleted.
00317  *
00318  * Side effects:
00319  *      The caller must keep track of this handle (generally by storing it in
00320  *      a field in the malloc'd block) and call TclHandleFree() on this handle
00321  *      when the block is deleted. Everything else that wishes to keep track
00322  *      of whether the malloc'd block has been deleted should use calls to
00323  *      TclHandlePreserve() and TclHandleRelease() on the associated handle.
00324  *
00325  *---------------------------------------------------------------------------
00326  */
00327 
00328 TclHandle
00329 TclHandleCreate(
00330     void *ptr)                  /* Pointer to an arbitrary block of memory to
00331                                  * be tracked for deletion. Must not be
00332                                  * NULL. */
00333 {
00334     HandleStruct *handlePtr;
00335 
00336     handlePtr = (HandleStruct *) ckalloc(sizeof(HandleStruct));
00337     handlePtr->ptr = ptr;
00338 #ifdef TCL_MEM_DEBUG
00339     handlePtr->ptr2 = ptr;
00340 #endif
00341     handlePtr->refCount = 0;
00342     return (TclHandle) handlePtr;
00343 }
00344 
00345 /*
00346  *---------------------------------------------------------------------------
00347  *
00348  * TclHandleFree --
00349  *
00350  *      Called when the arbitrary malloc'd block associated with the handle is
00351  *      being deleted. Modifies the handle so that doubly dereferencing it
00352  *      will give NULL. This informs any user of the handle that the block of
00353  *      memory formerly referenced by the handle has been freed.
00354  *
00355  * Results:
00356  *      None.
00357  *
00358  * Side effects:
00359  *      If nothing is referring to the handle, the handle will be reclaimed.
00360  *
00361  *---------------------------------------------------------------------------
00362  */
00363 
00364 void
00365 TclHandleFree(
00366     TclHandle handle)           /* Previously created handle associated with a
00367                                  * malloc'd block that is being deleted. The
00368                                  * handle is modified so that doubly
00369                                  * dereferencing it will give NULL. */
00370 {
00371     HandleStruct *handlePtr;
00372 
00373     handlePtr = (HandleStruct *) handle;
00374 #ifdef TCL_MEM_DEBUG
00375     if (handlePtr->refCount == 0x61616161) {
00376         Tcl_Panic("using previously disposed TclHandle %x", handlePtr);
00377     }
00378     if (handlePtr->ptr2 != handlePtr->ptr) {
00379         Tcl_Panic("someone has changed the block referenced by the handle %x\nfrom %x to %x",
00380                 handlePtr, handlePtr->ptr2, handlePtr->ptr);
00381     }
00382 #endif
00383     handlePtr->ptr = NULL;
00384     if (handlePtr->refCount == 0) {
00385         ckfree((char *) handlePtr);
00386     }
00387 }
00388 
00389 /*
00390  *---------------------------------------------------------------------------
00391  *
00392  * TclHandlePreserve --
00393  *
00394  *      Declare an interest in the arbitrary malloc'd block associated with
00395  *      the handle.
00396  *
00397  * Results:
00398  *      The return value is the handle argument, with its ref count
00399  *      incremented.
00400  *
00401  * Side effects:
00402  *      For each call to TclHandlePreserve(), there should be a matching call
00403  *      to TclHandleRelease() when the caller is no longer interested in the
00404  *      malloc'd block associated with the handle.
00405  *
00406  *---------------------------------------------------------------------------
00407  */
00408 
00409 TclHandle
00410 TclHandlePreserve(
00411     TclHandle handle)           /* Declare an interest in the block of memory
00412                                  * referenced by this handle. */
00413 {
00414     HandleStruct *handlePtr;
00415 
00416     handlePtr = (HandleStruct *) handle;
00417 #ifdef TCL_MEM_DEBUG
00418     if (handlePtr->refCount == 0x61616161) {
00419         Tcl_Panic("using previously disposed TclHandle %x", handlePtr);
00420     }
00421     if ((handlePtr->ptr != NULL) && (handlePtr->ptr != handlePtr->ptr2)) {
00422         Tcl_Panic("someone has changed the block referenced by the handle %x\nfrom %x to %x",
00423                 handlePtr, handlePtr->ptr2, handlePtr->ptr);
00424     }
00425 #endif
00426     handlePtr->refCount++;
00427 
00428     return handle;
00429 }
00430 
00431 /*
00432  *---------------------------------------------------------------------------
00433  *
00434  * TclHandleRelease --
00435  *
00436  *      This function is called to release an interest in the malloc'd block
00437  *      associated with the handle.
00438  *
00439  * Results:
00440  *      None.
00441  *
00442  * Side effects:
00443  *      The ref count of the handle is decremented. If the malloc'd block has
00444  *      been freed and if no one is using the handle any more, the handle will
00445  *      be reclaimed.
00446  *
00447  *---------------------------------------------------------------------------
00448  */
00449 
00450 void
00451 TclHandleRelease(
00452     TclHandle handle)           /* Unregister interest in the block of memory
00453                                  * referenced by this handle. */
00454 {
00455     HandleStruct *handlePtr;
00456 
00457     handlePtr = (HandleStruct *) handle;
00458 #ifdef TCL_MEM_DEBUG
00459     if (handlePtr->refCount == 0x61616161) {
00460         Tcl_Panic("using previously disposed TclHandle %x", handlePtr);
00461     }
00462     if ((handlePtr->ptr != NULL) && (handlePtr->ptr != handlePtr->ptr2)) {
00463         Tcl_Panic("someone has changed the block referenced by the handle %x\nfrom %x to %x",
00464                 handlePtr, handlePtr->ptr2, handlePtr->ptr);
00465     }
00466 #endif
00467     handlePtr->refCount--;
00468     if ((handlePtr->refCount == 0) && (handlePtr->ptr == NULL)) {
00469         ckfree((char *) handlePtr);
00470     }
00471 }
00472 
00473 /*
00474  * Local Variables:
00475  * mode: c
00476  * c-basic-offset: 4
00477  * fill-column: 78
00478  * End:
00479  */



Generated on Wed Mar 12 12:18:20 2008 by  doxygen 1.5.1