Commit ad1ceca2 authored by David Turner's avatar David Turner
Browse files

* src/base/ftdbgmem.c, docs/DEBUG.TXT: added new environment variables

    to control memory debugging with FreeType. See the description of
    "FT2_DEBUG_MEMORY", "FT2_ALLOC_TOTAL_MAX" and "FT2_ALLOC_COUNT_MAX"
    in DEBUG.TXT

    * src/cache/ftccache.c, src/cache/ftccmap.c, src/cache/ftcsbits.c,
    ftlru.c: fixed the cache sub-system to correctly deal with out-of-memory
    conditions.

    * src/pfr/pfrobjs.c, src/pfr/pfrsbits.c: fixing compiler warnings and a
    small memory leak

    * src/psaux/psobjs.c (t1_reallocate_table): fixed a bug (memory leak) that
    only happened when trying to resize an array would end in an OOM.

    * src/smooth/ftgrays.c: removed compiler warnings / volatile bug

    * src/truetype/ttobjs.c: removed segmentation fault that happened in
    tight memory environments.
parent 7a50e764
2003-03-13 David Turner <david@freetype.org>
* src/base/ftdbgmem.c, docs/DEBUG.TXT: added new environment variables
to control memory debugging with FreeType. See the description of
"FT2_DEBUG_MEMORY", "FT2_ALLOC_TOTAL_MAX" and "FT2_ALLOC_COUNT_MAX"
in DEBUG.TXT
* src/cache/ftccache.c, src/cache/ftccmap.c, src/cache/ftcsbits.c,
ftlru.c: fixed the cache sub-system to correctly deal with out-of-memory
conditions.
* src/pfr/pfrobjs.c, src/pfr/pfrsbits.c: fixing compiler warnings and a
small memory leak
* src/psaux/psobjs.c (t1_reallocate_table): fixed a bug (memory leak) that
only happened when trying to resize an array would end in an OOM.
* src/smooth/ftgrays.c: removed compiler warnings / volatile bug
* src/truetype/ttobjs.c: removed segmentation fault that happened in
tight memory environments.
2003-02-28 Pixel <pixel@mandrakesoft.com>
* src/gzip/ftgzip.c (ft_gzip_file_done): fixed memory leak, the ZLib
......
......@@ -159,4 +159,25 @@ behaviour of FreeType at runtime:
ignored in other builds.
FT2_ALLOC_TOTAL_MAX
this variable is ignored if FT2_DEBUG_MEMORY is not defined. It allows
you to specify a maximum heap size for all memory allocations performed
by FreeType. This is very useful to test the robustness of the font
engine and programs that use it in tight memory conditions.
If it is undefined, or if its value is not strictly positive, then no
allocation bounds are checked at runtime.
FT2_ALLOC_COUNT_MAX
this variable is ignored if FT2_DEBUG_MEMORY is not defined. It allows
you to sepcify a maximum number of memory allocations performed by
FreeType before returning the error FT_Err_Out_Of_Memory. This is
useful for debugging and testing the engine's robustness.
If it is undefined, or if its value is not strictly positive, then no
allocation bounsd are checked at runtime.
End of file
......@@ -62,6 +62,13 @@
FT_ULong alloc_total;
FT_ULong alloc_current;
FT_ULong alloc_max;
FT_ULong alloc_count;
FT_Bool bound_total;
FT_ULong alloc_total_max;
FT_Bool bound_count;
FT_ULong alloc_count_max;
const char* file_name;
FT_Long line_no;
......@@ -476,10 +483,22 @@
if ( size <= 0 )
ft_mem_debug_panic( "negative block size allocation (%ld)", size );
/* return NULL if the maximum number of allocations was reached */
if ( table->bound_count &&
table->alloc_count >= table->alloc_count_max )
return NULL;
/* return NULL if this allocation would overflow the maximum heap size */
if ( table->bound_total &&
table->alloc_current + (FT_ULong)size > table->alloc_total_max )
return NULL;
block = (FT_Byte *)ft_mem_table_alloc( table, size );
if ( block )
ft_mem_table_set( table, block, (FT_ULong)size );
table->alloc_count++;
table->file_name = NULL;
table->line_no = 0;
......@@ -570,15 +589,42 @@
FT_Int result = 0;
if ( getenv( "FT_DEBUG_MEMORY" ) )
if ( getenv( "FT2_DEBUG_MEMORY" ) )
{
table = ft_mem_table_new( memory );
if ( table )
{
const char* p;
memory->user = table;
memory->alloc = ft_mem_debug_alloc;
memory->realloc = ft_mem_debug_realloc;
memory->free = ft_mem_debug_free;
p = getenv( "FT2_ALLOC_TOTAL_MAX" );
if ( p != NULL )
{
FT_Long total_max = atol(p);
if ( total_max > 0 )
{
table->bound_total = 1;
table->alloc_total_max = (FT_ULong) total_max;
}
}
p = getenv( "FT2_ALLOC_COUNT_MAX" );
if ( p != NULL )
{
FT_Long total_count = atol(p);
if ( total_count > 0 )
{
table->bound_count = 1;
table->alloc_count_max = (FT_ULong) total_count;
}
}
result = 1;
}
}
......
......@@ -357,10 +357,12 @@
FT_FREE( node );
#if 0
/* check, just in case of general corruption :-) */
if ( manager->num_nodes == 0 )
FT_ERROR(( "ftc_node_destroy: invalid cache node count! = %d\n",
manager->num_nodes ));
#endif
}
......@@ -546,8 +548,10 @@
FTC_Query query,
FTC_Node *anode )
{
FT_Error error = FT_Err_Ok;
FT_LruNode lru;
FT_Error error = FT_Err_Ok;
FTC_Manager manager;
FT_LruNode lru;
FT_UInt free_count = 0;
if ( !cache || !query || !anode )
......@@ -558,152 +562,237 @@
query->hash = 0;
query->family = NULL;
/* XXX: we break encapsulation for the sake of speed! */
{
/* first of all, find the relevant family */
FT_LruList list = cache->families;
FT_LruNode fam, *pfam;
FT_LruNode_CompareFunc compare = list->clazz->node_compare;
manager = cache->manager;
/* here's a small note explaining what's hapenning in the code below.
*
* we need to deal intelligently with out-of-memory (OOM) conditions
* when trying to create a new family or cache node during the lookup.
*
* when an OOM is detected, we'll try to free one or more "old" nodes
* from the cache, then try again. it may be necessary to do that several
* times, so a loop is needed.
*
* the local variable "free_count" holds the number of "old" nodes to
* discard on each attempt. it starts at 1 and doubles on each iteration.
* the loop stops when:
*
* - a non-OOM error is detected
* - a succesful lookup is performed
* - there are no more unused nodes in the cache
*
* for the record, remember that all used nodes appear _before_
* unused ones in the manager's MRU node list.
*/
pfam = &list->nodes;
for (;;)
for (;;)
{
{
fam = *pfam;
if ( fam == NULL )
/* first of all, find the relevant family */
FT_LruList list = cache->families;
FT_LruNode fam, *pfam;
FT_LruNode_CompareFunc compare = list->clazz->node_compare;
pfam = &list->nodes;
for (;;)
{
error = FT_LruList_Lookup( list, query, &lru );
if ( error )
goto Exit;
goto Skip;
fam = *pfam;
if ( fam == NULL )
{
error = FT_LruList_Lookup( list, query, &lru );
if ( error )
goto Fail;
goto Skip;
}
if ( compare( fam, query, list->data ) )
break;
pfam = &fam->next;
}
if ( compare( fam, query, list->data ) )
break;
pfam = &fam->next;
}
FT_ASSERT( fam != NULL );
/* move to top of list when needed */
if ( fam != list->nodes )
{
*pfam = fam->next;
fam->next = list->nodes;
list->nodes = fam;
}
lru = fam;
Skip:
;
}
{
FTC_Family family = (FTC_Family) lru;
FT_UFast hash = query->hash;
FTC_Node* bucket;
FT_UInt idx;
idx = hash & cache->mask;
if ( idx < cache->p )
idx = hash & ( cache->mask * 2 + 1 );
bucket = cache->buckets + idx;
if ( query->family != family ||
family->fam_index >= cache->manager->families.size )
{
FT_ERROR((
"ftc_cache_lookup: invalid query (bad 'family' field)\n" ));
return FTC_Err_Invalid_Argument;
FT_ASSERT( fam != NULL );
/* move to top of list when needed */
if ( fam != list->nodes )
{
*pfam = fam->next;
fam->next = list->nodes;
list->nodes = fam;
}
lru = fam;
Skip:
;
}
if ( *bucket )
{
FTC_Node* pnode = bucket;
FTC_Node_CompareFunc compare = cache->clazz->node_compare;
for ( ;; )
FTC_Manager manager = cache->manager;
FTC_Family family = (FTC_Family) lru;
FT_UFast hash = query->hash;
FTC_Node* bucket;
FT_UInt idx;
idx = hash & cache->mask;
if ( idx < cache->p )
idx = hash & ( cache->mask * 2 + 1 );
bucket = cache->buckets + idx;
if ( query->family != family ||
family->fam_index >= manager->families.size )
{
FTC_Node node;
node = *pnode;
if ( node == NULL )
break;
FT_ERROR((
"ftc_cache_lookup: invalid query (bad 'family' field)\n" ));
error = FTC_Err_Invalid_Argument;
goto Exit;
}
if ( *bucket )
{
FTC_Node* pnode = bucket;
FTC_Node_CompareFunc compare = cache->clazz->node_compare;
if ( node->hash == hash &&
(FT_UInt)node->fam_index == family->fam_index &&
compare( node, query, cache ) )
for ( ;; )
{
/* move to head of bucket list */
if ( pnode != bucket )
FTC_Node node;
node = *pnode;
if ( node == NULL )
break;
if ( node->hash == hash &&
(FT_UInt)node->fam_index == family->fam_index &&
compare( node, query, cache ) )
{
*pnode = node->link;
node->link = *bucket;
*bucket = node;
/* move to head of bucket list */
if ( pnode != bucket )
{
*pnode = node->link;
node->link = *bucket;
*bucket = node;
}
/* move to head of MRU list */
if ( node != manager->nodes_list )
ftc_node_mru_up( node, manager );
*anode = node;
goto Exit;
}
/* move to head of MRU list */
if ( node != cache->manager->nodes_list )
ftc_node_mru_up( node, cache->manager );
*anode = node;
goto Exit;
pnode = &node->link;
}
pnode = &node->link;
}
/* didn't find a node, create a new one */
{
FTC_Cache_Class clazz = cache->clazz;
FT_Memory memory = cache->memory;
FTC_Node node;
if ( FT_ALLOC( node, clazz->node_size ) )
goto Fail;
node->fam_index = (FT_UShort) family->fam_index;
node->hash = query->hash;
node->ref_count = 0;
error = clazz->node_init( node, query, cache );
if ( error )
{
FT_FREE( node );
goto Fail;
}
error = ftc_node_hash_link( node, cache );
if ( error )
{
clazz->node_done( node, cache );
FT_FREE( node );
goto Fail;
}
ftc_node_mru_link( node, cache->manager );
cache->manager->cur_weight += clazz->node_weight( node, cache );
/* now try to compress the node pool when necessary */
if ( manager->cur_weight >= manager->max_weight )
{
node->ref_count++;
FTC_Manager_Compress( manager );
node->ref_count--;
}
*anode = node;
}
/* all is well, exit now
*/
goto Exit;
}
/* didn't find a node, create a new one */
Fail:
if ( error != FT_Err_Out_Of_Memory )
goto Exit;
/* there is not enough memory, try to release some unused nodes
* from the cache to make room for a new one.
*/
{
FTC_Cache_Class clazz = cache->clazz;
FTC_Manager manager = cache->manager;
FT_Memory memory = cache->memory;
FTC_Node node;
FT_UInt new_count;
new_count = 1 + free_count*2;
if ( FT_ALLOC( node, clazz->node_size ) )
/* check overflow and bounds */
if ( new_count < free_count || free_count > manager->num_nodes )
goto Exit;
node->fam_index = (FT_UShort) family->fam_index;
node->hash = query->hash;
node->ref_count = 0;
error = clazz->node_init( node, query, cache );
if ( error )
free_count = new_count;
/* try to remove "new_count" nodes from the list */
{
FT_FREE( node );
goto Exit;
}
FTC_Node first = manager->nodes_list;
FTC_Node node;
error = ftc_node_hash_link( node, cache );
if ( error )
{
clazz->node_done( node, cache );
FT_FREE( node );
goto Exit;
}
if ( first == NULL ) /* empty list ! */
goto Exit;
ftc_node_mru_link( node, cache->manager );
/* go to last node - it's a circular list */
node = first->mru_prev;
for ( ; node && new_count > 0; new_count-- )
{
FTC_Node prev = node->mru_prev;
cache->manager->cur_weight += clazz->node_weight( node, cache );
/* used nodes always appear before unused one in the MRU
* list. if we find one here, we'd better stop right now
* our iteration
*/
if ( node->ref_count > 0 )
{
/* if there are no unused nodes in the list, we'd better exit */
if ( new_count == free_count )
goto Exit;
break;
}
/* now try to compress the node pool when necessary */
if ( manager->cur_weight >= manager->max_weight )
{
node->ref_count++;
FTC_Manager_Compress( manager );
node->ref_count--;
}
ftc_node_destroy( node, manager );
if ( node == first )
break;
*anode = node;
node = prev;
}
}
}
}
......
......@@ -27,6 +27,9 @@
#include "ftcerror.h"
#undef FT_COMPONENT
#define FT_COMPONENT trace_cache
/*************************************************************************/
/* */
/* Each FTC_CMapNode contains a simple array to map a range of character */
......
......@@ -216,7 +216,7 @@
/* we mark unloaded glyphs with `sbit.buffer == 0' */
/* and 'width == 255', 'height == 0' */
/* */
if ( error )
if ( error && error != FT_Err_Out_Of_Memory )
{
sbit->width = 255;
error = 0;
......
......@@ -21,6 +21,7 @@
#include FT_CACHE_INTERNAL_LRU_H
#include FT_LIST_H
#include FT_INTERNAL_OBJECTS_H
#include FT_INTERNAL_DEBUG_H
#include "ftcerror.h"
......@@ -187,80 +188,135 @@
goto Exit;
}
/* we haven't found the relevant element. We will now try */
/* to create a new one. */
/* */
/* first, check if our list if full, when appropriate */
if ( list->max_nodes > 0 && list->num_nodes >= list->max_nodes )
/* since we haven't found the relevant element in our LRU list,
* we're going to "create" a new one.
*
* the following code is a bit special, because it tries to handle
* out-of-memory conditions (OOM) in an intelligent way.
*
* more precisely, if not enough memory is available to create a
* new node or "flush" an old one, we need to remove the oldest
* elements from our list, and try again. since several tries may
* be necessary, a loop is needed
*
* this loop will only exit when:
*
* - a new node was succesfully created, or an old node flushed
* - an error other than FT_Err_Out_Of_Memory is detected
* - the list of nodes is empty, and it isn't possible to create
* new nodes
*
* on each unsucesful attempt, one node will be removed from the list
*
*/
{
/* this list list is full; we will now flush */
/* the oldest node, if there's one! */
FT_LruNode last = *plast;
FT_Int drop_last = ( list->max_nodes > 0 &&
list->num_nodes >= list->max_nodes );
if ( last )
for (;;)
{
if ( clazz->node_flush )
node = NULL;
/* when "drop_last" is true, we should free the last node in
* the list to make room for a new one. note that we re-use
* its memory block to save allocation calls.
*/
if ( drop_last )
{
error = clazz->node_flush( last, key, list->data );
/* find the last node in the list
*/
pnode = &list->nodes;
node = *pnode;
if ( node == NULL )
{
FT_ASSERT( list->nodes == 0 );
error = FT_Err_Out_Of_Memory;
goto Exit;
}
FT_ASSERT( list->nodes > 0 );
while ( node->next )
{
pnode = &node->next;
node = *pnode;
}
/* remove it from the list, and try to "flush" it. doing this will
* save a significant number of dynamic allocations compared to
* a classic destroy/create cycle
*/
*pnode = NULL;
list->num_nodes -= 1;
if ( clazz->node_flush )
{
error = clazz->node_flush( node, key, list->data );
if ( !error )
goto Success;
/* note that if an error occured during the flush, we need to