/*
* Copyright 1995, 2010 Perforce Software. All rights reserved.
*
* This file is part of Perforce - the FAST SCM System.
*/
/*
* shhandler.cc -- Smart Heap error handler.
*/
# if defined(USE_SMARTHEAP) && !defined(OS_NTIA64)
# define NEED_SMARTHEAP
# ifdef OS_NT
# define NEED_DBGBREAK
# endif
# include <stdhdrs.h>
# include <ctype.h>
# include <strbuf.h>
# include <shhandler.h>
# include <debug.h>
# include <tunable.h>
# if defined( OS_NTX86 ) || defined( OS_LINUXX86 )
# define FMT_U "%lu"
# define FMT_X "0x%lx"
# else
# define FMT_U "%llu"
# define FMT_X "0x%llx"
# endif
// Smart Heap error handler.
// Mostly concerned about out of memory conditions.
//
// If SmartHeap is not able to allocate memory from the normal
// heap lists, it will invoke this error handler. The problem
// is that SmartHeap will then perform a second chance allocation
// from heaps which are meant for other purposes.
//
// In essence this error handler can be invoked when there is no
// real error. At least we know we are close to no memory left.
//
// This seems to occur mostly when the Perforce Server is doing
// test memory probes while in critical sections of meta data
// handling.
//
# ifndef OS_NT
// The following two functions are used for Linux instrumentation.
int
IsDebuggerPresent()
{
# ifdef MEM_DEBUG
return 1;
# else
// This tunable is only for Linux, "sys.memory.debug".
return p4tunable.Get( P4TUNE_SYS_MEMORY_DEBUG );
# endif
}
void
OutputDebugString(
const char *msg
)
{
// On Linux, Smart Heap output goes to stderr.
fprintf(stderr, "%s", msg);
fflush( stderr );
}
# endif // !OS_NT
MEM_BOOL MEM_CALLBACK
SHHandleError(
MEM_ERROR_INFO *errorInfo
)
{
char msg[128];
# ifdef OS_NT
// On Windows, we catch Smart Heap errors.
//
if( IsDebuggerPresent() )
{
// Post the Smart Heap Error address to p4diag.
// P4diag will read the structure from process memory
// and display pertinent information.
//
sprintf (msg, "display MEM_ERROR_INFO: 0x%p\n", errorInfo);
OutputDebugString(msg);
# ifdef HAVE_DBGBREAK
// If under p4diag, this break point will create a stack
// trace and optionally a mini dump.
DebugBreak();
# endif // HAVE_DBGBREAK
}
# else
// On Linux just report the SH errorCode.
//
if( IsDebuggerPresent() )
{
sprintf (msg, "display MEM_ERROR_CODE: 0x%x\n", errorInfo->errorCode);
OutputDebugString(msg);
}
# endif // OS_NT
return (0);
}
SHHandler::SHHandler()
{
char msg[128];
// Initial values are only used when an unset is requested.
// If we know the previous value, we put it back.
initial_procfree = (MEM_SIZET)0;
initial_poolfree = (MEM_SIZET)0;
initial_ceiling = (MEM_SIZET)0;
initial_procgrowinc = (MEM_SIZET)0;
initial_poolgrowinc = (MEM_SIZET)0;
// The default checkpoint value is 1, we start above that.
cur_ckpt = 1;
max_ckpt = 1;
# ifdef OS_NT
// On Linux let Smart Heap format the error message.
if( MemSetErrorHandler( SHHandleError ) == NULL)
OutputDebugString ("note: MemSetErrorHandler(): failed.\n");
# else
// On Linux we can not do error output in the constructor.
MemSetErrorHandler( SHHandleError );
# endif
MemRegisterTask();
// Smart Heap instrumented memory checks. Three levels possible.
//
# ifdef SMARTHEAP_CHECKS
# if (SMARTHEAP_CHECKS == 1)
// O(c)
dbgMemSetSafetyLevel( MEM_SAFETY_SOME );
# endif
# if (SMARTHEAP_CHECKS == 2)
// O(n)
dbgMemSetSafetyLevel( MEM_SAFETY_FULL );
# endif
# if (SMARTHEAP_CHECKS == 3)
// O(n*n)
dbgMemSetSafetyLevel( MEM_SAFETY_DEBUG );
# endif
# if (SMARTHEAP_CHECKS == 4)
// O(n*n) plus the thorough checking
dbgMemSetSafetyLevel( MEM_SAFETY_DEBUG );
// Enable thorough checking, this might take a while.
dbgMemSetCheckFrequency( 1 );
dbgMemDeferFreeing( 1 );
dbgMemSetDeferQueueLen( ULONG_MAX-1 );
dbgMemSetDeferSizeThreshold( (MEM_SIZET)ULONG_MAX-1 );
# endif
# ifdef OS_NT
// On Windows, Debug Smart Heap output goes to the debug console.
// Use p4diag to view these messages.
if( !dbgMemSetDefaultErrorOutput( DBGMEM_OUTPUT_CONSOLE, NULL))
OutputDebugString ("note: dbgMemSetDefaultErrorOutput(): failed.\n");
# else // Linux
// On Linux, Debug Smart Heap output goes to stderr.
// On Linux we can not do error output in the constructor.
dbgMemSetDefaultErrorOutput( DBGMEM_OUTPUT_CONSOLE, NULL);
# endif // OS_NT
# endif // SMARTHEAP_CHECKS
# ifdef OS_NT
InitializeCriticalSection( §ion );
if( IsDebuggerPresent() )
{
MEM_VERSION vers;
vers = MemVersion( );
sprintf (msg, "control: set sh_version=%d.%d.%d\n",
MEM_MAJOR_VERSION(vers), MEM_MINOR_VERSION(vers), MEM_UPDATE_VERSION(vers));
OutputDebugString( msg );
# ifdef MEM_DEBUG
sprintf (msg, "control: set sh_mode=mem_debug\n");
OutputDebugString( msg );
# endif
}
# endif // OS_NT
}
// Set all Smart Heap tunables.
void
SHHandler::Tunables()
{
unsigned int value;
// sys.memory.procfree
// Controls how much free space is in the large heap block.
//
value = (MEM_SIZET)p4tunable.Get( P4TUNE_SYS_MEMORY_PROCFREE );
SetTunable( P4TUNE_SYS_MEMORY_PROCFREE, &value );
// sys.memory.poolfree
// Controls how much free space is in a pool.
//
value = (MEM_SIZET)p4tunable.Get( P4TUNE_SYS_MEMORY_POOLFREE );
SetTunable( P4TUNE_SYS_MEMORY_POOLFREE, &value );
// sys.memory.procgrowinc
// Limit a process to a specific growth increment.
//
value = (MEM_SIZET)p4tunable.Get( P4TUNE_SYS_MEMORY_PROCGROWINC );
SetTunable( P4TUNE_SYS_MEMORY_PROCGROWINC, &value );
// sys.memory.poolgrowinc
// Limit a pool to a specific growth increment.
//
value = (MEM_SIZET)p4tunable.Get( P4TUNE_SYS_MEMORY_POOLGROWINC );
SetTunable( P4TUNE_SYS_MEMORY_POOLGROWINC, &value );
// sys.memory.subpools
// Limit the process to a specific number of sub pools.
//
value = (MEM_SIZET)p4tunable.Get( P4TUNE_SYS_MEMORY_SUBPOOLS );
SetTunable( P4TUNE_SYS_MEMORY_SUBPOOLS, &value );
// sys.memory.limit
// Limit the process to a specific amount of memory.
//
value = (MEM_SIZET)p4tunable.Get( P4TUNE_SYS_MEMORY_LIMIT );
SetTunable( P4TUNE_SYS_MEMORY_LIMIT, &value );
}
// Internal function for SH setting tunables.
void
SHHandler::SetTunable(
int index,
unsigned int *value
)
{
MEM_SIZET membytes = 0;
MEM_SIZET prevbytes = 0;
# ifdef OS_NT
// A value of 0 is only allowed for poolgrowinc.
//
if( *value == 0 && index != P4TUNE_SYS_MEMORY_POOLGROWINC )
return;
# else
// No values of 0 allowed on Linux.
// Revisit this if we move to SH 11.0 on Linux.
//
if( *value == 0 )
return;
# endif
membytes = (MEM_SIZET)*value;
// Smart Heap requires a process free low limit of 64.
// Smart Heap also requires a quantization of 64.
if( index == P4TUNE_SYS_MEMORY_PROCFREE ||
index == P4TUNE_CMD_MEMORY_PROCFREE )
{
if( membytes < 64 )
membytes = 64;
// Quantize up to the next multiple of 64.
membytes = (membytes+63) - ((membytes+63)%64);
}
// Smart Heap will quantize the grow increment.
// Pool grow increment is allowed to be 0.
// For now block a 0 process grow increment above.
if( index == P4TUNE_SYS_MEMORY_PROCGROWINC ||
index == P4TUNE_SYS_MEMORY_POOLGROWINC )
{
if( membytes != 0 )
{
// Quantize up to the next multiple of 64.
membytes = (membytes+63) - ((membytes+63)%64);
}
}
// Smart Heap limit checks instituted by Perforce.
if( index == P4TUNE_SYS_MEMORY_POOLFREE ||
index == P4TUNE_CMD_MEMORY_POOLFREE )
{
// Arbitrary pool free low limit of 1K.
if( membytes < 1 )
membytes = (MEM_SIZET)1;
// Protect from non linear Smart Heap issues.
// Cap at 256 Mbytes, 262144.
if( membytes > (MEM_SIZET)262144 )
membytes = (MEM_SIZET)262144;
}
if( (index == P4TUNE_SYS_MEMORY_LIMIT ||
index == P4TUNE_CMD_MEMORY_LIMIT ) && membytes < 1024 )
{
// Force a minimum memory limit of 1M.
// The Server will not start otherwise.
membytes = (MEM_SIZET)1024;
}
// Let the caller know the final non scaled value.
*value = (unsigned int)membytes;
# if defined( OS_NTX86 ) || defined( OS_LINUXX86 )
// Prevent 32bit rounding back to zero.
if( membytes == 0 && index != P4TUNE_SYS_MEMORY_POOLGROWINC )
membytes = (MEM_SIZET)ULONG_MAX;
# endif
# ifdef OS_NT
// Smart Heap has not confirmed safety of their APIs.
EnterCriticalSection( §ion );
# endif
switch( index )
{
case P4TUNE_SYS_MEMORY_POOLFREE:
membytes *= 1024;
prevbytes = MemPoolSetFreeBytes( MemDefaultPool, membytes );
if( initial_poolfree == 0 )
initial_poolfree = prevbytes;
break;
case P4TUNE_SYS_MEMORY_PROCFREE:
membytes *= 1024;
prevbytes = MemProcessSetFreeBytes( membytes );
if( initial_procfree == 0 )
initial_procfree = prevbytes;
break;
case P4TUNE_SYS_MEMORY_POOLGROWINC:
membytes *= 1024;
prevbytes = MemPoolSetGrowIncrement( MemDefaultPool, membytes);
if( initial_poolgrowinc == 0 )
initial_poolgrowinc = prevbytes;
break;
case P4TUNE_SYS_MEMORY_PROCGROWINC:
membytes *= 1024;
prevbytes = MemProcessSetGrowIncrement( membytes);
if( initial_procgrowinc == 0 )
initial_procgrowinc = prevbytes;
break;
case P4TUNE_SYS_MEMORY_SUBPOOLS:
# ifdef OS_NT
# ifndef MEM_DEBUG
// Smart Heap bug, debug library doesn't have this export.
// Check this again in SH 11.3
prevbytes = MemPoolSetMaxSubpools( MemDefaultPool, membytes);
# endif
# endif
break;
case P4TUNE_SYS_MEMORY_LIMIT:
membytes *= 1024;
prevbytes = MemPoolSetCeiling( MemDefaultPool, membytes );
if( initial_ceiling == 0 )
initial_ceiling = prevbytes;
break;
case P4TUNE_CMD_MEMORY_POOLFREE:
membytes *= 1024;
prevbytes = MemPoolSetFreeBytes( MemDefaultPool, membytes );
break;
case P4TUNE_CMD_MEMORY_PROCFREE:
membytes *= 1024;
prevbytes = MemProcessSetFreeBytes( membytes );
break;
case P4TUNE_CMD_MEMORY_LIMIT:
membytes *= 1024;
prevbytes = MemPoolSetCeiling( MemDefaultPool, membytes );
break;
case P4TUNE_CMD_MEMORY_FLUSHPOOL:
prevbytes = FlushPool( MemDefaultPool );
break;
}
# ifdef OS_NT
LeaveCriticalSection( §ion );
# endif
// These traversal functions will take some time. Do these
// outside of the critical section since the pool will be
// locked during the listing.
switch( index )
{
case P4TUNE_CMD_MEMORY_LISTPOOLS:
// ListAllPools (tag, detail)
ListAllPools( (const char *)"cmd.memory.listpools", *value );
break;
case P4TUNE_CMD_MEMORY_CHKPT:
Checkpoint( (const char *)"cmd.memory.chkpt", *value );
break;
}
// Emit a trace message for p4diag.
if( IsDebuggerPresent() )
{
const char *name = p4tunable.GetName( index );
char fmt[128];
char msg[128];
switch( index )
{
case P4TUNE_CMD_MEMORY_FLUSHPOOL:
sprintf(fmt, "note: sys.memory.flushpool released %s bytes\n",
FMT_U);
sprintf(msg, fmt, prevbytes);
break;
case P4TUNE_SYS_MEMORY_SUBPOOLS:
sprintf (fmt, "tunable: set %%s to %s\n", FMT_U);
sprintf (msg, fmt, name, membytes);
break;
case P4TUNE_CMD_MEMORY_LISTPOOLS:
case P4TUNE_CMD_MEMORY_CHKPT:
default:
sprintf (fmt, "tunable: set %%s from %s to %s\n", FMT_U, FMT_U);
sprintf (msg, fmt, name, prevbytes, membytes);
break;
}
OutputDebugString( msg );
}
}
// External function for setting SH tunables.
int
SHHandler::SetTunable(
StrBuf &name,
StrBuf &value
)
{
int idx;
unsigned int setting;
if ((idx = p4tunable.GetIndex( name.Text() )) < 0)
return (1);
// Only Smart Heap tunables get through.
// Otherwise return 1 to allow caller to continue.
if( idx < P4TUNE_SYS_MEMORY_POOLFREE ||
idx > P4TUNE_CMD_MEMORY_CHKPT )
return (1);
setting = Config2Membytes( value.Text() );
SetTunable( idx, &setting );
// The setting may have been modified in SetTunable().
if( setting )
value.Set( StrNum( (int)setting ) );
// If setting a cmd.memory tunable, return 0 to ensure it is
// not permanently stored.
if( idx >= P4TUNE_CMD_MEMORY_POOLFREE &&
idx <= P4TUNE_CMD_MEMORY_CHKPT )
return (0);
return (1);
}
void
SHHandler::UnsetTunable( int index )
{
MEM_SIZET membytes = 0;
MEM_SIZET prevbytes = 0;
# ifdef OS_NT
// Smart Heap has not confirmed safety of their APIs.
EnterCriticalSection( §ion );
# endif
switch( index )
{
case P4TUNE_SYS_MEMORY_POOLFREE:
if( initial_poolfree != 0 )
{
membytes = initial_poolfree;
prevbytes = MemPoolSetFreeBytes( MemDefaultPool, membytes );
}
break;
case P4TUNE_SYS_MEMORY_PROCFREE:
if( initial_procfree != 0 )
{
membytes = initial_procfree;
prevbytes = MemProcessSetFreeBytes( membytes );
}
break;
case P4TUNE_SYS_MEMORY_POOLGROWINC:
if( initial_poolgrowinc != 0 )
{
membytes = initial_poolgrowinc;
prevbytes = MemPoolSetGrowIncrement( MemDefaultPool, membytes);
}
break;
case P4TUNE_SYS_MEMORY_PROCGROWINC:
if( initial_procgrowinc != 0 )
{
membytes = initial_procgrowinc;
prevbytes = MemProcessSetGrowIncrement( membytes);
}
break;
case P4TUNE_SYS_MEMORY_SUBPOOLS:
// Can not unset sub pools.
break;
case P4TUNE_SYS_MEMORY_LIMIT:
if( initial_ceiling != 0 )
{
membytes = initial_ceiling;
prevbytes = MemPoolSetCeiling( MemDefaultPool, membytes );
}
break;
}
# ifdef OS_NT
LeaveCriticalSection( §ion );
// Emit a trace message for p4diag.
if( IsDebuggerPresent() && prevbytes != membytes )
{
const char *name = p4tunable.GetName( index );
char fmt[128];
char msg[128];
sprintf (fmt, "tunable: unset %%s to %s\n", FMT_U);
sprintf (msg, fmt, name, membytes);
OutputDebugString(msg);
}
# endif
}
int
SHHandler::UnsetTunable( const char *name )
{
int idx;
if ((idx = p4tunable.GetIndex( name )) < 0)
return (1);
// Only Smart Heap tunables get through.
// Otherwise return 1 to allow caller to continue.
if( idx < P4TUNE_SYS_MEMORY_POOLFREE || idx > P4TUNE_CMD_MEMORY_CHKPT )
return (1);
// If setting a cmd.memory tunable, return now as we can not
// unset these.
if(idx >= P4TUNE_CMD_MEMORY_POOLFREE && idx <= P4TUNE_CMD_MEMORY_CHKPT)
return (0);
UnsetTunable( idx );
return (1);
}
void
SHHandler::Close()
{
# ifdef OS_NT
DeleteCriticalSection( §ion );
# endif
}
void
SHHandler::ListEntry (
const MEM_POOL_ENTRY *entry,
int show_unused
)
{
char fmt[128];
char frame[128];
char msg[8192];
if( !IsDebuggerPresent() )
return;
if( entry->isInUse )
{
# ifdef MEM_DEBUG
DBGMEM_PTR_INFO info;
if( !dbgMemPtrInfo (entry->entry, &info) )
{
OutputDebugString ("note: dbgMemPtrInfo(): failed.\n");
return;
}
// Information about the memory block.
//
const char *srcfile = info.createFile;
if (srcfile == NULL)
srcfile = "unknown";
// entry sz/asz thrd cp srcfile line
// "lentry: 0x0000000001491BD0 24/32 3036 1 'unknown' 0"
sprintf (fmt, "lentry: 0x%%p %s/%s %%lu %%d '%%s' %%d\n",
FMT_U, FMT_U);
sprintf (msg, fmt,
entry->entry, entry->size, info.argSize, info.threadID,
info.checkpoint, srcfile, info.createLine);
OutputDebugString (msg);
// Callstack for the memory block.
//
if( info.callStack[0] != 0 )
{
register int i;
sprintf (msg, "frames: ");
// MEM_MAXCALLSTACK is from smrtheap.h
for (i=0; i<MEM_MAXCALLSTACK && info.callStack[i]!=0; i++)
{
sprintf (frame, "0x%p ", info.callStack[i]);
strcat (msg, frame);
}
strcat (msg, "\n");
OutputDebugString (msg);
}
# else // MEM_DEBUG
// Information about the memory block.
//
sprintf (fmt, "sentry: 0x%%p %s\n", FMT_U);
sprintf (msg, fmt, entry->entry, entry->size);
OutputDebugString (msg);
# endif // MEM_DEBUG
}
else if( show_unused )
{
sprintf (fmt, "uentry: 0x%%p %s\n", FMT_U);
sprintf (msg, fmt, entry->entry, entry->size);
OutputDebugString (msg);
}
}
MEM_BOOL
SHHandler::ValidatePool (
MEM_POOL pool
)
{
MEM_POOL_INFO info;
MEM_POOL_STATUS status;
status = MemPoolFirst (&info, 0);
while (status == MEM_POOL_OK)
{
if( pool == info.pool )
return (MEM_BOOL)1;
status = MemPoolNext (&info, 0);
}
return (MEM_BOOL)0;
}
void
SHHandler::ListPool (
MEM_POOL pool,
const char *tag,
unsigned int detail
)
{
MEM_POOL_ENTRY entry;
char fmt[1024];
char msg[1024];
int show_unused=0;
if( !IsDebuggerPresent() )
return;
if( pool == (MEM_POOL)1 )
pool = MemDefaultPool;
if( !ValidatePool(pool) )
{
OutputDebugString ("note: ListPool(): Invalid Pool\n");
return;
}
// Show heap entries currently not in use.
if( detail >= 3 )
show_unused = 1;
sprintf (fmt, "poolbegin: %s '%%s' %s %s\n", FMT_X, FMT_U, FMT_U);
sprintf (msg, fmt, pool, tag, MemPoolCount(pool), MemPoolSize(pool));
OutputDebugString (msg);
// Show only pool related information.
if( detail < 2 )
goto end;
OutputDebugString ("control: set log2file=off\n");
OutputDebugString ("note: See p4diag log file for heap entries.\n");
OutputDebugString ("control: set log2file=on\n");
// Tell p4diag to disable the terminal window logging.
//
OutputDebugString ("control: set log2term=off\n");
entry.entry = NULL;
MemPoolLock (pool);
while (MemPoolWalk (pool, &entry) == MEM_POOL_OK)
{
ListEntry (&entry, show_unused);
}
MemPoolUnlock (pool);
// Tell p4diag to enable the terminal window logging.
//
OutputDebugString ("control: set log2term=on\n");
end:
sprintf (fmt, "poolend: %s '%%s'\n", FMT_X);
sprintf (msg, fmt, pool, tag);
OutputDebugString (msg);
}
void
SHHandler::ListAllPools (
const char *tag,
unsigned int detail
)
{
MEM_POOL_INFO info;
MEM_POOL_STATUS status;
MEM_POOL_ENTRY entry;
MEM_SIZET size, s;
char msg[1024];
if( !IsDebuggerPresent() )
return;
sprintf (msg, "listbegin: '%s' %d\n", tag, detail);
OutputDebugString (msg);
status = MemPoolFirst (&info, 0);
while (status == MEM_POOL_OK)
{
ListPool( info.pool, tag, detail );
status = MemPoolNext (&info, 0);
}
sprintf (msg, "listend: '%s'\n", tag);
OutputDebugString (msg);
}
MEM_SIZET
SHHandler::FlushPool( MEM_POOL pool )
{
MEM_SIZET membytes;
if( pool == (MEM_POOL)1 )
pool = MemDefaultPool;
membytes = MemPoolShrink( pool );
if ( membytes == MEM_ERROR_RET )
{
if( IsDebuggerPresent() )
OutputDebugString("note: MemPoolShrink() failed\n");
return (0);
}
return (membytes);
}
int
SHHandler::Config2Membytes( const char *value )
{
int membytes=0;
const char *ptr;
// Atoi()
ptr = value;
while( *ptr != '\0' && isdigit( *ptr ) )
membytes = membytes * 10 + *ptr++ - '0';
// k = *1024, m = *1048576
if( *ptr == 'k' || *ptr == 'K' )
membytes *= 1024;
else if( *ptr == 'm' || *ptr == 'M' )
membytes *= 1048576;
return (membytes);
}
int
SHHandler::Checkpoint (
const char *tag,
int ckpt
)
{
# ifdef MEM_DEBUG
int prev_ckpt;
int new_ckpt;
char msg[256];
if( ckpt == 0 )
new_ckpt = ++cur_ckpt;
else
new_ckpt = cur_ckpt = ckpt;
if( max_ckpt < new_ckpt )
max_ckpt = new_ckpt;
prev_ckpt = dbgMemSetCheckpoint( new_ckpt );
if( IsDebuggerPresent() )
{
sprintf (msg, "shchkpt: '%s' %u %u\n", tag, prev_ckpt, new_ckpt);
OutputDebugString (msg);
}
return (new_ckpt);
# else
return (0);
# endif
}
void
SHHandler::ReportLeakage (
int ckpt1,
int ckpt2
)
{
# ifdef MEM_DEBUG
MEM_BOOL ret;
char msg[256];
if( !IsDebuggerPresent() )
return;
OutputDebugString ("note: ReportLeakage() begin\n");
ret = dbgMemReportLeakage( MemDefaultPool, ckpt1, ckpt2 );
if( !ret )
{
OutputDebugString ("note: ReportLeakage() failed.\n");
}
else
{
sprintf (msg, "note: ReportLeakage() ckpt1=%u ckpt1=%u\n",
ckpt1, ckpt2);
OutputDebugString (msg);
}
OutputDebugString ("note: ReportLeakage() end\n");
# endif
}
void
SHHandler::MemCheckAll ()
{
# ifdef MEM_DEBUG
char msg[256];
if( !IsDebuggerPresent() )
return;
OutputDebugString ("note: MemCheckAll() begin\n");
if( !dbgMemCheckAll() )
OutputDebugString ("note: MemCheckAll() failed.\n");
// After the check, free the deferred memory. Otherwise
// it can pile up and potentially exhaust memory.
//
dbgMemFreeDeferred();
OutputDebugString ("note: MemCheckAll() end\n");
# endif
}
# endif // USE_SMARTHEAP && !OS_NTIA64