shithub: choc

Download patch

ref: 3f8b7b23999b672f2aac922c9c1ba04e32628396
parent: e4681dd485de5a393070e3fcb91505116405cc1d
author: Simon Howard <[email protected]>
date: Sun Jul 29 20:31:33 EDT 2007

Fix bug in z_native linked list logic. Clear out all PU_CACHE blocks
when out of memory.

Subversion-branch: /trunk/chocolate-doom
Subversion-revision: 940

--- a/src/z_native.c
+++ b/src/z_native.c
@@ -42,6 +42,7 @@
 {
     int id; // = ZONEID
     int tag;
+    int size;
     void **user;
     memblock_t *prev;
     memblock_t *next;
@@ -51,6 +52,45 @@
  
 static memblock_t *allocated_blocks[PU_NUM_TAGS];
 
+#ifdef TESTING
+
+static int test_malloced = 0;
+
+void *test_malloc(size_t size)
+{
+    int *result;
+
+    if (test_malloced + size > 2 * 1024 * 1024)
+    {
+        return NULL;
+    }
+
+    test_malloced += size;
+
+    result = malloc(size + sizeof(int));
+
+    *result = size;
+
+    return result + 1;
+}
+
+void test_free(void *data)
+{
+    int *i;
+
+    i = ((int *) data) - 1;
+
+    test_malloced -= *i;
+
+    free(i);
+}
+
+#define malloc test_malloc
+#define free test_free
+
+#endif /* #ifdef TESTING */
+
+
 // Add a block into the linked list for its type.
 
 static void Z_InsertBlock(memblock_t *block)
@@ -57,7 +97,7 @@
 {
     block->prev = NULL;
     block->next = allocated_blocks[block->tag];
-    allocated_blocks[block->tag] = block->next;
+    allocated_blocks[block->tag] = block;
     
     if (block->next != NULL)
     {
@@ -108,7 +148,9 @@
     block = (memblock_t *) ((byte *)ptr - sizeof(memblock_t));
 
     if (block->id != ZONEID)
+    {
         I_Error ("Z_Free: freed a pointer without ZONEID");
+    }
 		
     if (block->tag != PU_FREE && block->user != NULL)
     {
@@ -124,8 +166,70 @@
     free(block);
 }
 
+// Empty data from the cache list to allocate enough data of the size
+// required.
+//
+// Returns true if any blocks were freed.
 
+static boolean ClearCache(int size)
+{
+    memblock_t *block;
+    memblock_t *next_block;
+    int remaining;
 
+    block = allocated_blocks[PU_CACHE];
+
+    if (block == NULL)
+    {
+        // Cache is already empty.
+
+        return false;
+    }
+
+    // Search to the end of the PU_CACHE list.  The blocks at the end
+    // of the list are the ones that have been free for longer and
+    // are more likely to be unneeded now.
+
+    while (block->next != NULL)
+    {
+        block = block->next;
+    }
+
+    //printf("out of memory; cleaning out the cache: %i\n", test_malloced);
+
+    // Search backwards through the list freeing blocks until we have
+    // freed the amount of memory required.
+
+    remaining = size;
+
+    while (remaining > 0)
+    {
+        if (block == NULL)
+        {
+            // No blocks left to free; we've done our best.
+  
+            break;
+        }
+
+        next_block = block->prev;
+
+        Z_RemoveBlock(block);
+
+        remaining -= block->size;
+
+        if (block->user)
+        {
+            *block->user = NULL;
+        }
+
+        free(block);
+
+        block = next_block;
+    }
+
+    return true;
+}
+
 //
 // Z_Malloc
 // You can pass a NULL user if the tag is < PU_PURGELEVEL.
@@ -144,9 +248,27 @@
     }
 
     if (user == NULL && tag >= PU_PURGELEVEL)
+    {
         I_Error ("Z_Malloc: an owner is required for purgable blocks");
+    }
+
+    // Malloc a block of the required size
     
-    newblock = (memblock_t *) malloc(sizeof(memblock_t) + size);
+    newblock = NULL;
+
+    while (newblock == NULL)
+    {
+        newblock = (memblock_t *) malloc(sizeof(memblock_t) + size);
+
+        if (newblock == NULL)
+        {
+            if (!ClearCache(sizeof(memblock_t) + size))
+            {
+                I_Error("Out of memory!");
+            }
+        }
+    }
+
     newblock->tag = tag;
     
     // Hook into the linked list for this tag type
@@ -153,6 +275,7 @@
 
     newblock->id = ZONEID;
     newblock->user = user;
+    newblock->size = size;
 
     Z_InsertBlock(newblock);