Debugging AMX error 3

AMX Error 3 occurs when your plugin runs out of memory for its local variables. However, before we get into that there are two other causes to rule out due to bugs in the AMX engine:

Checking your memory usage

The heapsize() native function can be used to check how much memory you have remaining at any point in your code. e.g.
    new heapsize[MAX_TEXT_LENGTH];
    snprintf(heapsize,MAX_TEXT_LENGTH,"[%s] %i free bytes in the plugin heap.",context,heapspace());
    plugin_message(heapsize);

There are two possible causes of the error. You could have a leak somewhere in your code (although the leak is usually a fault in Admin Mod/ Small, not a fault in your code - your code is just highlighting it). Or you could be using too much memory in local variables.

By default a plugin has around 8K of memory to hold local variables. Now remember each array element takes 4 bytes, so each array of MAX_TEXT_LENGTH or MAX_DATA_LENGTH takes 800 bytes. Therefore the following function would case AMX Error 3:

public plugin_init() {
  new a[MAX_TEXT_LENGTH];
  new b[MAX_TEXT_LENGTH];
  new c[MAX_TEXT_LENGTH];
  new d[MAX_TEXT_LENGTH];
  new e[MAX_TEXT_LENGTH];
  new f[MAX_TEXT_LENGTH];
  new g[MAX_TEXT_LENGTH];
  new h[MAX_TEXT_LENGTH];
  new i[MAX_TEXT_LENGTH];
  new j[MAX_TEXT_LENGTH];
  return PLUGIN_CONTINUE;
}

When declaring an array, don't automatically make it of size MAX_TEXT_LENGTH - think about what the maximum possible size is of the data you will be putting the array. You can increase the memory available to your plugin by putting a #pragma dynamic instruction somewhere in the file - by convention, this is normally at the top of the file, after the initial comment. e.g.

#pragma dynamic 4096

The size is specified in cells, so the above statement creates as 16K stack, not a 4K stack.

If you suspect a memory leak

If you have a memory leak in your code rather than just attempting to use too many local variables, then the #pragma statement will only delay the time it takes for your plugin to crash - it won't fix the problem. To test if you have a memory leak, start a regular timer in your plugin_init function and log the heapsize free from the timer handler function. It should remain constant; if it starts decreasing work out what bits of your code executed during the time period it decreased.

Below are the routines I use for debugging such problems - simply paste the bits of code into the appropriate places in your plugin:

/*******************************************************
 * This plugin should never be used on a server!       *
 *******************************************************
 *                                                     *
 * If is intended that plugin authors copy this code   *
 * into their plugins to help debug memory leak issues *
 * both in the plugin and in Admin Mod itself.         *
 *                                                     *
 * Each plugin is allocated 8K of heap/stack space.    *
 * If you log the remaining space from a timer, it     *
 * should consistently return the same value - if it   *
 * does not, you have a leak.                          *
 *                                                     *
 * If you log the value from your plugin commands or   *
 * timer handlers you can detect if your code has less *
 * memory available on each subsequent call.           *
 *                                                     *
 * To prevent needless log bloating, it only reports   *
 * when admin debug is greater than 1.                 *
 *******************************************************/


/* Step 1: Ensure these includes are in your plugin */
#include <core>
#include <string>
#include <admin>
#include <plugin>

/* Step 2: Declare this global variable in your plugin */
new g_DebugLevel = 0;

public plugin_init() {
  plugin_registerinfo("Bugblatter's Heap Debug Example Plugin","Tests for memory leaks","1.0");
  plugin_registercmd("admin_sample","SampleCommand",ACCESS_ALL,
                     "A sample command to illustrate where to call DebugHeap");

  /* Step 3: Call this in your plugin_init */
  DebugHeapInit();

  return PLUGIN_CONTINUE;
}


public SampleCommand(HLCommand,HLData,HLUserName,UserIndex) {
  /* Example: call DebugHeap at the start and end of your admin commands to see if
   * they are leaking memory */
  DebugHeap("SampleCommand Start");

  new strData[100];

  DebugHeap("SampleCommand Allocate");

  convert_string(HLData,strData,MAX_DATA_LENGTH);

  DebugHeap("SampleCommand Convert");

  say(strData);

  DebugHeap("SampleCommand End");
}


/* Step 4: Include these three functions in your plugin */

public DebugHeapInit() {
  g_DebugLevel = getvar("admin_debug");
  if (g_DebugLevel >= 2) {
    set_timer("DebugHeapTimer",2,99999);
  }
  return 0;
}

public DebugHeapTimer() {
  return DebugHeap("IDLE");
}

DebugHeap(context[]) {
  if (g_DebugLevel >= 2) {
    new heapsize[MAX_TEXT_LENGTH];
    snprintf(heapsize,MAX_TEXT_LENGTH,"[%s] %i free bytes in the plugin heap.",context,heapspace());
    plugin_message(heapsize);
  }
  return 0;
}