blob: 730b84fbf0ee8aa70b8b2400e1b3474cd000f8d1 [file] [log] [blame]
/*
* simex.c
* Implementation of interface for simulating Exceptional Situations (ES).
*
* The Simulator of Exceptions (SimEx) framework controls when an ES will be injected
* and what ES subclass will be used.
*
* The ES class to be simulated is set at startup and is associated with a set of ES
* subclasses. SimEx uses bit vectors (one per ES subclass) to track which stacks have
* been examined. On each call, a hash value is calculating over the frame addresses in
* the stack. Then, the corresponding bit is checked in all bit vectors. The first bit
* vector that has the bit unset gives the ES subclass to be injected.
*
* After identifying the ES subclass, SimEx logs the prominent ES injection. Logged
* information includes the ES class-subclass names, the file and the line where
* injection will occur and the current stack trace.
*
* Bit vectors are stored in a shared memory segment; the postmaster is responsible
* for initializing, reseting and deleting it. All backends access the same bit
* vectors through this shmem segment.
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
#include <dlfcn.h>
#include "postgres.h"
#include "lib/stringinfo.h"
#include "utils/elog.h"
#include "utils/simex.h"
#ifdef USE_TEST_UTILS
/* associate an ES subclass with its class */
typedef struct SimExESClassAssociate
{
SimExESClass class;
SimExESSubClass subclass;
const char *ESName; // format: "class_subclass"
} SimExESClassAssociate;
/*
* Table holding ES class/subclass associations
*
* NOTICE: This table needs to be updated when a new ES class or subclass
* is introduced, see simex.h.
*/
const SimExESClassAssociate SimExESClassAssociateTable[] =
{
{SimExESClass_OOM, SimExESSubClass_OOM_ReturnNull, "OOM_ReturnNull"},
{SimExESClass_Cancel, SimExESSubClass_Cancel_QueryCancel, "Cancel_QueryCancel"},
{SimExESClass_Cancel, SimExESSubClass_Cancel_ProcDie, "Cancel_ProcDie"},
{SimExESClass_CacheInvalidation, SimExESSubClass_CacheInvalidation, "Cache_Invalidation"},
{SimExESClass_SysError, SimExESSubClass_SysError_Net, "SysError_Net"},
{SimExESClass_ProcKill, SimExESSubClass_ProcKill_BgWriter, "Send SIGKILL to writer process"},
};
#define SimExESClassAssociateTable_Size (sizeof(SimExESClassAssociateTable) / sizeof(SimExESClassAssociate))
#define SIMEX_ES_SUBCLASS_MAX_COUNT (SYNC_BITVECTOR_MAX_COUNT) /* one bit vector per ES subclass */
#define SIMEX_MAX_FRAME_DEPTH 64 /* number of frames used to compute a stack's hash value,
* set to low value to prevent multiple ES injection for a
* repeating function sequence.
*/
#define OFFSET_NOT_FOUND -1
#define PERCENT 100
#define RAND_ACCURACY 1000 /* percentage with three digits accuracy */
/* SimEx bit vector container */
static SyncBitvectorContainer bitvectorContainer = NULL;
/* maps bit vector offsets to ES subclasses */
static int16 SimExESSubclassOffset[SIMEX_ES_SUBCLASS_MAX_COUNT];
static int32 SimExESSubclassCount = 0;
/* holds return addresses of frames in stack */
static void *SimExStackAddresses[SIMEX_MAX_FRAME_DEPTH];
/* stack depth */
static uint32 SimExStackDepth = 0;
/* static functions */
static void check_associate_table(void);
static uint64 simex_get_fingerprint(void);
static int32 simex_check_fingerprint(uint64 fingerprint);
static SimExESSubClass
simex_log_and_return(int32 associateOffset, const char *file, int32 line, uint64 fingerprint);
/*
* Initializes SimEx. This must run before initializing the shared memory segment.
*
* Checks if the ES class (as set by GUC gp_simex_class) is valid and if there
* are valid associated ES subclasses to it.
* Stores the used offsets of SimExESClassAssociateTable in SimExESSubclassOffset
* and sets the number of ES subclasses to be examined.
*/
void simex_init(void)
{
check_associate_table();
/* check if the build allows SimEx */
#ifndef USE_TEST_UTILS
gp_simex_init = false;
#endif
/* only x86 is currently supported */
#if !defined(__i386) && !defined(__x86_64__)
gp_simex_init = false;
#endif
if (!gp_simex_init)
{
return;
}
SimExESClass class = SimExESClass_Base;
int32 i;
const int32 size = SimExESClassAssociateTable_Size;
SimExESClass_CheckValid(gp_simex_class);
class = (SimExESClass) gp_simex_class;
SimExESSubclassCount = 0;
for (i = 0; i < size; i++)
{
if (SimExESClassAssociateTable[i].class == class)
{
SimExESSubclassOffset[SimExESSubclassCount] = i;
SimExESSubclassCount++;
}
}
SimExStackDepth = 0;
}
/*
* Checks if the entries in the ES class/subclass association table
* are consistent with the enum values for ES class/subclass.
*/
static
void check_associate_table()
{
/* counts ES subclasses per class */
int32 subclassCount[SimExESClass_Max];
int32 i = 0;
/* true is a subclass is associated to a class */
bool subclassAssociated[SimExESSubClass_Max];
/* reset */
for (i = 0; i < SimExESClass_Max; i++)
{
subclassCount[i] = 0;
}
for (i = 0; i < SimExESSubClass_Max; i++)
{
subclassAssociated[i] = false;
}
/* check association table */
for (i = 0; i < SimExESClassAssociateTable_Size; i++)
{
SimExESClassAssociate associate = SimExESClassAssociateTable[i];
Assert(SimExESClass_IsValid(associate.class));
Assert(SimExESSubClass_IsValid(associate.subclass));
subclassCount[associate.class]++;
Assert(subclassCount[associate.class] <= SIMEX_ES_SUBCLASS_MAX_COUNT);
subclassAssociated[associate.subclass] = true;
}
/* check if all classes are associated to subclasses */
for (i = 0; i < SimExESClass_Max; i++)
{
Assert(subclassCount[i] > 0);
}
/* check if all subclasses are associated to classes */
for (i = 1; i < SimExESSubClass_Max; i++)
{
Assert(subclassAssociated[i]);
}
}
/*
* Returns the number of ES subclasses associated to the specified ES class.
*/
int32 simex_get_subclass_count(void)
{
if (SimExESSubclassCount == 0)
{
simex_init();
}
Assert(SimExESSubclassCount > 0);
return SimExESSubclassCount;
}
/*
* Sets the SimEx bit vector container.
*/
void simex_set_sync_bitvector_container(SyncBitvectorContainer container)
{
bitvectorContainer = container;
}
/*
* Checks which ES subclass will be injected (if any).
* Logs ES injection to follow.
*/
SimExESSubClass simex_check(const char *file, int32 line)
{
/* check if the build allows SimEx */
#ifndef USE_TEST_UTILS
return SimExESSubClass_OK;
#endif
if (!gp_simex_init || !gp_simex_run)
{
return SimExESSubClass_OK;
}
Assert(file && "No source file is specified");
Assert(line > 0 && "Line number is invalid");
Assert(bitvectorContainer && "Bit vector container is NULL");
Assert(SimExESSubclassCount > 0 && "No ES subclass has been set");
/* disable SimEx to prevent infinitive loops */
gp_simex_run = false;
int64 fingerprint = simex_get_fingerprint();
int32 associateOffset = simex_check_fingerprint(fingerprint);
SimExESSubClass subclass =
simex_log_and_return(associateOffset, file, line, fingerprint);
/* re-enable SimEx before returning */
gp_simex_run = true;
return subclass;
}
/*
* Returns a hash value as the fingerprint of the current stack.
* The hash value is calculated by iterating over the frame
* addresses in the stack.
*/
static
uint64 simex_get_fingerprint(void)
{
uint64 hash = 0;
/* get frame addresses */
SimExStackDepth = gp_backtrace(SimExStackAddresses, SIMEX_MAX_FRAME_DEPTH);
Assert(SIMEX_MAX_FRAME_DEPTH >= SimExStackDepth && "Stack size exceeds maximum");
/* consider the first SIMEX_MAX_FRAME_DEPTH frames only, below the stack base pointer */
for (uint32 depth = 0; depth < SimExStackDepth; depth++)
{
/*
* compute a hash -- xor with multiple of height of stack to make sure
* multiple recursive calls are distinguished from each other.
*/
hash ^= (*(uintptr_t*)SimExStackAddresses[depth]) ^ (depth * 23);
}
return hash;
}
/*
* Checks if a ES subclass has not been examined for the
* specific stack fingerprint. Returns the ES subclass offset
* in SimExESClassAssociateTable if such a subclass is found,
* else OFFSET_NOT_FOUND.
*/
static
int32 simex_check_fingerprint(uint64 fingerprint)
{
// check for randomized operation
if (gp_simex_rand < PERCENT)
{
Assert(gp_simex_rand * RAND_ACCURACY >= 1);
uint64_t rand = random();
/* check if random number between 0.001 and 100 is less than the threshold percentage */
if (rand % (PERCENT * RAND_ACCURACY) < (gp_simex_rand * RAND_ACCURACY))
{
/* randomly pick one of the ES subclasses to inject */
return SimExESSubclassOffset[rand % SimExESSubclassCount];
}
/* no injection */
return OFFSET_NOT_FOUND;
}
int32 i = 0;
for (i = 0; i < SimExESSubclassCount; i++)
{
if (SyncBitVector_TestTestSetBit(bitvectorContainer, i, fingerprint))
{
int32 offset = SimExESSubclassOffset[i];
Assert(offset >= 0);
return offset;
}
}
return OFFSET_NOT_FOUND;
}
/*
* Logs prominent ES injection and returns the specified ES subclass.
* Log information includes ES class-subclass names, file, line and current stack trace.
*/
static
SimExESSubClass simex_log_and_return(int32 associateOffset, const char *file, int32 line, uint64 fingerprint)
{
if (associateOffset == OFFSET_NOT_FOUND)
{
return SimExESSubClass_OK;
}
Assert(associateOffset < SimExESClassAssociateTable_Size && "Invalid association offset");
char *stackTrace = gp_stacktrace(SimExStackAddresses, SimExStackDepth);
/* log stack trace */
ereport(LOG,
(errmsg("SimEx inject: class='%s', file='%s', line=%d, hash=%lu, stack trace: \n%s\n",
SimExESClassAssociateTable[associateOffset].ESName,
file,
line,
(unsigned long int) fingerprint,
stackTrace)));
return SimExESClassAssociateTable[associateOffset].subclass;
}
#endif /* USE_TEST_UTILS */