blob: 9f134e2caa06e55b2cf2dbed0248d2487c60b86c [file] [log] [blame]
/**********************************************************************
// @@@ START COPYRIGHT @@@
//
// 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.
//
// @@@ END COPYRIGHT @@@
**********************************************************************/
/* -*-C++-*-
*****************************************************************************
*
* File: timeout_data.cpp
* Description: Methods for TimeoutData, an encapsulation of all the
* dynamically set timeout values
*
* Created: 1/12/2000
* Language: C++
*
*
*
*****************************************************************************
*/
#include "NAStdlib.h" // Redefines strcpy(), strncpy(), strcmp() for SRLs
#include "timeout_data.h"
#include "ex_ex.h" // for ex_assert()
#include "ComTdbRoot.h"
//**********************************************************************
// methods for TimeoutHashTable
//**********************************************************************
// Set the table name (in place for a short one, allocate space for long)
void TimeoutTableEntry::setTableName (char * tableName, CollHeap * heap)
{
ex_assert( heap , "Bad heap given to setTableName" );
heap_ = heap; // keep the heap (for de-allocation)
if ( str_len(tableName) >= MAX_INTERNAL_TN ) {
tableNamePtr_ = (char *) heap_->allocateMemory( str_len(tableName) + 1 );
strcpy( tableNamePtr_ , tableName );
}
else {
strcpy( tableName_, tableName );
tableNamePtr_ = NULL ; // mark it as -- in place
}
}
TimeoutHashTable::TimeoutHashTable(CollHeap * heap,
ULng32 hashTableSize)
: entries_(0),
hashTableSize_(hashTableSize),
hashArray_(NULL),
hashTableOriginalSize_(hashTableSize),
heap_(heap)
{
ex_assert( heap , "Bad heap given to ctor" );
}
TimeoutTableEntry * TimeoutHashTable::allocateHashTable(ULng32 size)
{
// allocate the array
return (TimeoutTableEntry *)
heap_->allocateMemory(size * sizeof(TimeoutTableEntry));
}
// reset back to the empty state
void TimeoutHashTable::clearAll()
{
if ( hashArray_ == NULL ) return; else entries_ = 0 ;
// traverse array, deallocating long names if needed
for ( ULng32 u = 0; u < hashTableSize_ ; u++ ) hashArray_[u].reset();
heap_->deallocateMemory(hashArray_);
hashArray_ = NULL;
hashTableSize_ = hashTableOriginalSize_;
}
// Double hash table size if became %50 full, or resize down if the
// number of entries was significantly reduced
void TimeoutHashTable::resizeHashTableIfNeeded()
{
ULng32 newSize ;
if ( entries_ * 2 >= hashTableSize_ ) { // at least 50 percent full
newSize = 2 * hashTableSize_ ;
} else if ( 0 == entries_ ) { // last entry removed
clearAll();
return;
} else if ( hashTableSize_ > hashTableOriginalSize_ &&
entries_ * 5 < hashTableSize_ ) {
// rare case of significat reduction in the number of entries
// after the table was enlarged from its original size
newSize = hashTableOriginalSize_ ; // start at original size
while ( entries_ * 3 > newSize ) newSize *= 2 ; // aim at %33 max full
}
else return; // no need to resize the hash table !!!
// allocate a new table and insert into it all the values from the old one
TimeoutTableEntry * newHashArray = NULL ;
for ( ULng32 ul = 0; ul < hashTableSize_ ; ul++ )
if ( hashArray_[ul].used )
// this call would also allocate the new array
internalInsert( & newHashArray, newSize, hashArray_[ul].tableName(),
hashArray_[ul].timeoutValue );
// replace old hash array with the new one
ULng32 keepEntries = entries_ ;
clearAll(); // removes the old hash array, alse resets entries_
entries_ = keepEntries;
hashArray_ = newHashArray;
hashTableSize_ = newSize;
}
// internal routine: insert entry into H.T., return TRUE iff entry is new
// The table parameter is a pointer to a pointer to allow allocation
NABoolean TimeoutHashTable::internalInsert( TimeoutTableEntry **hashTable,
ULng32 hashTableSize,
char * tableName,
Lng32 timeoutValue )
{
NABoolean itIsNew ;
// get the hashValue
Lng32 hashValue = getTimeoutHashValue(tableName);
Lng32 originalHashValue = hashValue;
// We may have to initialize the hash table
if ( *hashTable == NULL ) {
// allocate the array
*hashTable = allocateHashTable(hashTableSize);
// initialize each entry to "unused"
for (ULng32 ul=0; ul<hashTableSize; ul++) (*hashTable)[ul].init();
}
// find an available location for this tablename (if new), or
// its current location (if already there)
while ( (*hashTable)[hashValue].used &&
strcmp( (*hashTable)[hashValue].tableName(), tableName ) )
hashValue = ++hashValue % hashTableSize ;
itIsNew = ! (*hashTable)[hashValue].used ; // tableName is new in this H.T
// insert the entry
(*hashTable)[hashValue].used = TRUE;
(*hashTable)[hashValue].hashValue = originalHashValue;
(*hashTable)[hashValue].setTableName( tableName , heap_ ) ;
(*hashTable)[hashValue].timeoutValue = timeoutValue;
return itIsNew;
}
void TimeoutHashTable::insert(char * tableName, Lng32 timeoutValue)
{
if ( internalInsert( &hashArray_, hashTableSize_, tableName, timeoutValue ) )
entries_ ++ ; // if inserted a new table name, increment #entries
resizeHashTableIfNeeded(); // as the name says ...
};
void TimeoutHashTable::remove(char * tableName)
{
Lng32 dummy;
if ( ! getTimeout( tableName, dummy ) ) return; // entry not found
// tempIndex_ was set internally by getTimeout; use it to remove this entry
hashArray_[tempIndex_].reset(); // mark as unused
entries_-- ;
// "fill the hole": In case subsequent array locations are used, and the
// entries there got there by being "pushed ahead" due to collisions, then
// we can not leave the removed location empty, because getTimeout() would
// not be able to find them. Thus, we iteratively check ahead and move back
// those "pushed" entries to fill the holes.
// This is a little tricky, because several "pushed ahead" different chains
// may overlap and mix, and the the whole sequence may wrap the end of the
// array back to the beginning of the array.
// Our solution: We go back in the used-sequence and find a base index,
// thus any index smaller than that base means: this index was wrapped !!
// We fix the wrapping (normalize indices) and compare the entry's hash
// value to the hole index to eliminate the case of a chain that "started
// after the hole" and thus should not be moved back (skip that entry).
ULng32 holeIndex = tempIndex_ ; // index to where the "hole" is
// base index shows where the whole used-sequence begins
ULng32 baseIndex = holeIndex;
while ( 1 ) {
ULng32 prev = ( baseIndex + hashTableSize_ - 1 ) % hashTableSize_ ;
if ( ! hashArray_[prev].used ) break; // previous not used ==> base found
baseIndex = prev;
}
// iterate ahead and move entries back; finish when next is not used
for ( ULng32 nextIndex = ( tempIndex_ + 1 ) % hashTableSize_;
hashArray_[nextIndex].used ;
nextIndex = ( nextIndex + 1 ) % hashTableSize_ ) {
// Normalize the hole and hashValue indices (to simplify comparison)
ULng32 normHashValue = hashArray_[nextIndex].hashValue;
if ( normHashValue < baseIndex ) normHashValue += hashTableSize_ ;
ULng32 normHoleIndex = holeIndex;
if ( normHoleIndex < baseIndex ) normHoleIndex += hashTableSize_ ;
// If this entry was hashed to "no after" than the hole: OK to move
if ( normHashValue <= normHoleIndex ) {
hashArray_[holeIndex] = hashArray_[nextIndex] ; // copy entry
// Now mark next as the "hole" and continue iterating
hashArray_[nextIndex].used = FALSE;
holeIndex = nextIndex;
} // else it was "hashed after" the hole -- just skip it
} // end of for(nextIndex)
resizeHashTableIfNeeded(); // as the name says ...
};
// find and set the timeout value for this table; return FALSE iff not found
NABoolean TimeoutHashTable::getTimeout(char * tableName,
Lng32 & timeoutValue)
{
if (!entries_) return FALSE;
Lng32 hashValue = getTimeoutHashValue(tableName);
while ( hashArray_[hashValue].used )
if ( strcmp( hashArray_[hashValue].tableName(), tableName ) )
hashValue = ++hashValue % hashTableSize_ ; // no match, check next
else { // match !
timeoutValue = hashArray_[hashValue].timeoutValue; // copy the value
tempIndex_ = hashValue; // in case this was called by remove()
return TRUE; // found the entry !
}
return FALSE; // entry not found
}
// The hash-function
Lng32 TimeoutHashTable::getTimeoutHashValue(char * tableName)
{
Lng32 tempVal = 0;
Lng32 primes[4] = {1,3,5,7} ; // a mask to scatter similar names
for ( Lng32 ul = 0; tableName[ul] != '\0'; ul++ )
tempVal += primes[ ul % 4 ] * (char) tableName[ul] ;
return ( tempVal % hashTableSize_ );
}
// Methods for packing/unpacking (used for passing information to ESPs)
IpcMessageObjSize TimeoutHashTable::packedLength()
{
if ( 0 == entries_ ) return 0;
IpcMessageObjSize result = sizeof ( ULng32 ); // #entries slot
ULng32 count = entries_ ; // used to speed-up hash array scanning
for ( ULng32 ul = 0; count > 0 && ul < hashTableSize_ ; ul++ ) {
if ( ! hashArray_[ul].used ) continue; // skip unused array entry
ULng32 nameLen = str_len( hashArray_[ul].tableName() );
// add an alignment (note: When already aligned, add four null bytes).
// This requirement is from the packIntoBuffer method.
nameLen += 4 - nameLen % 4 ; // add alignment size
result += nameLen + sizeof(Lng32); // aligned name length + timeout_value
count-- ; // one less entry to go
}
return result;
}
// pack or unpack, and move the pointer ("buffer") accordingly
void TimeoutHashTable::packIntoBuffer(IpcMessageBufferPtr &buffer)
{
if ( 0 == entries_ ) return;
ULng32 count =
*(ULng32 *) buffer = entries_ ; // #entries is first in buffer
buffer = (char *) buffer +
sizeof(ULng32); // move pointer to next location
for ( ULng32 ul = 0; count > 0 && ul < hashTableSize_ ; ul++ ) {
if ( ! hashArray_[ul].used ) continue; // skip unused array entry
// copy the name of the table
char * tblName = hashArray_[ul].tableName();
ULng32 nameLen = str_len(tblName);
strncpy( buffer, tblName, nameLen );
buffer = (char *) buffer + nameLen ; // advance pointer
// add an alignment (note: When already aligned, add four null bytes)
ULng32 alignedNameLen = 0 ; // align on a 4 byte boundaries
alignedNameLen += 4 - nameLen % 4 ; // Align to one of: 1,2,3,4 chars
strncpy( buffer , "\0\0\0\0", alignedNameLen );
buffer = (char *) buffer + alignedNameLen ; // advance pointer
// now get the timeout value
*(Lng32 *) buffer = hashArray_[ul].timeoutValue ;
buffer = (char *) buffer + sizeof(Lng32);
count-- ; // one less entry to go
}
}
// The following unpack method should ONLY be called when the previously
// packed (into the buffer) hash table had one or more entries (because
// an empty hash table does not pack anything into the buffer.)
void TimeoutHashTable::unpackObj(IpcConstMessageBufferPtr &buffer)
{
ULng32 entries = * (ULng32 *) buffer;
buffer += sizeof(ULng32); // got # entries
for ( ; entries > 0 ; entries-- ) {
char * tblName = (char *) buffer ;
ULng32 nameLen = str_len(tblName);
// add an alignment (note: When already aligned, add four null bytes).
// This requirement is from the packIntoBuffer method.
nameLen += 4 - nameLen % 4 ; // add alignment size
buffer += nameLen ;
Lng32 timeoutVal = *(Lng32 *) buffer;
buffer += sizeof(Lng32);
// insert into the hash array
this->insert( tblName, timeoutVal);
}
}
//**********************************************************************
////////////////////////////////////////////////////////////////////////
//
// Methods for TimeoutData
//
////////////////////////////////////////////////////////////////////////
TimeoutData::TimeoutData(CollHeap *heap, ULng32 estimatedMaxEntries)
: noLockTimeoutsSet_(TRUE),
forAll_(FALSE),
streamTimeoutSet_(FALSE),
timeoutsHT_( heap, estimatedMaxEntries * 2 )
{
ex_assert( heap && estimatedMaxEntries > 0 , "Bad parameters" );
};
void TimeoutData::setAllLockTimeout( Lng32 lockTimeoutValue )
{
noLockTimeoutsSet_ = FALSE;
forAll_ = TRUE;
forAllTimeout_ = lockTimeoutValue ;
// clean up the timeout table
timeoutsHT_.clearAll();
}
void TimeoutData::resetAllLockTimeout()
{
noLockTimeoutsSet_ = TRUE;
forAll_ = FALSE;
forAllTimeout_ = 0 ; // reset this, to allow up-to date checks
// clean up the timeout table
timeoutsHT_.clearAll();
}
void TimeoutData::setTableLockTimeout(char *tableName,Lng32 lockTimeoutValue )
{
noLockTimeoutsSet_ = FALSE;
timeoutsHT_.insert( tableName, lockTimeoutValue ) ;
}
void TimeoutData::resetTableLockTimeout( char * tableName )
{
timeoutsHT_.remove( tableName );
if ( timeoutsHT_.entries() == 0 && ! forAll_ ) noLockTimeoutsSet_ = TRUE;
}
NABoolean TimeoutData::getLockTimeout(char * tableName ,Lng32 & timeoutValue )
{
if ( noLockTimeoutsSet_ ) return FALSE;
// check timeoutsHT_ table, which overrides "set for all" (if set)
if ( timeoutsHT_.getTimeout( tableName, timeoutValue ) ) return TRUE;
// last, check the "set for all" case
if ( ! forAll_ ) return FALSE; // no "for all" (and no "per table") setting
timeoutValue = forAllTimeout_ ; // else -- return "for all" value
return TRUE;
}
void TimeoutData::setStreamTimeout( Lng32 streamTimeoutValue )
{
streamTimeoutSet_ = TRUE;
streamTimeoutValue_ = streamTimeoutValue ;
}
void TimeoutData::resetStreamTimeout()
{
streamTimeoutSet_ = FALSE;
}
// check if this TimeoutData (likely the global one) has any data that
// is relevant to a given execution plan
NABoolean TimeoutData::anyRelevantTimeoutData( ComTdbRoot * rootTdb )
{
// any relevant stream timeout data ?
if ( rootTdb->isStreamScan() && streamTimeoutSet_ ) return TRUE;
LateNameInfoList * lnil = rootTdb->getLateNameInfoList() ;
if ( NULL == lnil ) return FALSE ; // ( can this ever happen ? )
// we assume that "for-all lock timeout" always applies
if ( ! noLockTimeoutsSet_ && forAll_ ) return TRUE;
// now check each individual table used by this statement
for (UInt32 ui = 0; ui < lnil->getNumEntries(); ui++) {
char * tableName = lnil->getLateNameInfo(ui).resolvedPhyName();
Lng32 dummyTimeoutValue;
if ( timeoutsHT_.getTimeout( tableName, dummyTimeoutValue ) ) return TRUE ;
}
return FALSE; // nothing relevant was found
}
// Copy relevant timeout data from this (global) to anotherTD
// (also allocates memory for anotherTD, when there is relevant data to copy)
void TimeoutData::copyData( TimeoutData ** anotherTD,
CollHeap * heap,
ComTdbRoot * rootTdb )
{
// check if need to allocate; i.e., there is some relevant timeout data
if ( ! anyRelevantTimeoutData( rootTdb ) ) return;
*anotherTD = new ( heap ) TimeoutData( heap ); // allocate
// copy all scalar fields
(*anotherTD)->noLockTimeoutsSet_ = noLockTimeoutsSet_ ;
(*anotherTD)->forAll_ = forAll_ ;
(*anotherTD)->forAllTimeout_ = forAllTimeout_ ;
(*anotherTD)->streamTimeoutSet_ = streamTimeoutSet_ ;
(*anotherTD)->streamTimeoutValue_ = streamTimeoutValue_ ;
if ( 0 == timeoutsHT_.entries() ) return ; // no individual settings
LateNameInfoList * lnil = rootTdb->getLateNameInfoList() ;
if ( NULL == lnil ) return ; // ( can this ever happen ? )
// check for each table (used by this stmt) in the global hash array
// if found -- make such an entry in the other TD's array
for (UInt32 ui = 0; ui < lnil->getNumEntries(); ui++) {
char * tableName = lnil->getLateNameInfo(ui).resolvedPhyName();
Lng32 timeoutValue;
if ( timeoutsHT_.getTimeout( tableName, timeoutValue ) )
(*anotherTD)->timeoutsHT_.insert( tableName, timeoutValue ) ;
}
}
// Make a detailed check whether another object is up to date with this/global
// (This method is called after a change-counter missmatch was detected)
NABoolean TimeoutData::isUpToDate( TimeoutData * anotherTD ,
ComTdbRoot * rootTdb )
{
// if no local TD; up-to-date depends on relevancy of global timeout data
if ( NULL == anotherTD ) return ! anyRelevantTimeoutData( rootTdb ) ;
// so anotherTD exists; now do an item by item check ...
if ( rootTdb->isStreamScan() && // streams are used: check change there
( anotherTD->streamTimeoutSet_ != streamTimeoutSet_ ||
! streamTimeoutSet_ || // stream timeouts are not set in both !!
anotherTD->streamTimeoutValue_ != streamTimeoutValue_ ) )
return FALSE; // stream timeout was changed
// The following check is a little too restrictive (e.g., setting for-all
// to the same value as per-table, would unnecessarily fail this check )
if ( anotherTD->forAll_ != forAll_ ||
! forAll_ || // for-all lock timeouts are not set in both !!
anotherTD->forAllTimeout_ != forAllTimeout_ ) return FALSE;
// Run individual check per each table used in this statement
LateNameInfoList * lnil = rootTdb->getLateNameInfoList() ;
if ( NULL == lnil ) return TRUE ; // ( can this ever happen ? )
// check timeout similarity for each table (used by this stmt)
for (UInt32 ui = 0; ui < lnil->getNumEntries(); ui++) {
char * tableName = lnil->getLateNameInfo(ui).resolvedPhyName();
Lng32 globalTimeoutValue = 0 , stmtTimeoutValue = 0 ;
NABoolean globalFound =
timeoutsHT_.getTimeout( tableName, globalTimeoutValue );
NABoolean stmtFound =
anotherTD->timeoutsHT_.getTimeout( tableName, stmtTimeoutValue );
// again a little too restrictive (e.g., for-all may have the same value)
if ( globalFound != stmtFound ) return FALSE;
// compare actual values (both are zero if both not found)
if ( globalTimeoutValue != stmtTimeoutValue ) return FALSE;
}
return TRUE; // if we've gotten so far; otherTD must be up to date
}
//
// Method for doing the (un)packing -- used to transfer this obj to ESPs
//
IpcMessageObjSize TimeoutData::packedLength()
{
IpcMessageObjSize result = sizeof(ULng32) ; // "flags" are always there
if ( streamTimeoutSet_ ) result += sizeof(Lng32) ;
if ( ! noLockTimeoutsSet_ & forAll_ ) result += sizeof(Lng32) ;
result += timeoutsHT_.packedLength() ;
return result;
}
void TimeoutData::packIntoBuffer(IpcMessageBufferPtr &buffer)
{
// pack the flags (must be in sync with unpackObj() )
ULng32 flags = streamTimeoutSet_ ? 0x01 : 0;
if ( ! noLockTimeoutsSet_ ) {
flags |= 0x02 ;
if ( forAll_ ) flags |= 0x04 ;
if ( timeoutsHT_.entries() > 0 ) flags |= 0x08 ; // is timeout HT empty ?
}
*(ULng32 *) buffer = flags ; // store the flags
buffer = (char *) buffer + sizeof(Lng32); // incr buffer
if ( streamTimeoutSet_ ) {
*(Lng32 *) buffer = streamTimeoutValue_ ; // store the stream t/o value
buffer = (char *) buffer + sizeof(Lng32); // incr buffer
}
if ( ! noLockTimeoutsSet_ & forAll_ ) {
*(Lng32 *) buffer = forAllTimeout_ ; // store the for-all value
buffer = (char *) buffer + sizeof(Lng32); // incr buffer
}
timeoutsHT_.packIntoBuffer(buffer);
}
void TimeoutData::unpackObj(IpcConstMessageBufferPtr &buffer)
{
ULng32 flags = *(ULng32 *) buffer;
buffer += sizeof(ULng32);
streamTimeoutSet_ = flags & 0x01 ;
if ( streamTimeoutSet_ ) {
streamTimeoutValue_ = *(Lng32 *) buffer;
buffer += sizeof(Lng32);
}
noLockTimeoutsSet_ = ! ( flags & 0x02 );
forAll_ = flags & 0x04 ;
if ( ! noLockTimeoutsSet_ && forAll_ ) {
forAllTimeout_ = *(Lng32 *) buffer;
buffer += sizeof(Lng32);
}
// only if hash table existed then unpack it!
if ( flags & 0x08 ) // individual lock-timeout settings exist
timeoutsHT_.unpackObj(buffer);
}