@ -94,6 +94,10 @@
* worth trying to avoid sending such inval traffic in the future , if those
* problems can be overcome cheaply .
*
* When making a nontransactional change to a cacheable object , we must
* likewise send the invalidation immediately , before ending the change ' s
* critical section . This includes inplace heap updates , relmap , and smgr .
*
* When wal_level = logical , write invalidations into WAL at each command end to
* support the decoding of the in - progress transactions . See
* CommandEndInvalidationMessages .
@ -130,13 +134,15 @@
/*
* Pending requests are stored as ready - to - send SharedInvalidationMessages .
* We keep the messages themselves in arrays in TopTransactionContext
* ( there are separate arrays for catcache and relcache messages ) . Control
* information is kept in a chain of TransInvalidationInfo structs , also
* allocated in TopTransactionContext . ( We could keep a subtransaction ' s
* TransInvalidationInfo in its CurTransactionContext ; but that ' s more
* wasteful not less so , since in very many scenarios it ' d be the only
* allocation in the subtransaction ' s CurTransactionContext . )
* We keep the messages themselves in arrays in TopTransactionContext ( there
* are separate arrays for catcache and relcache messages ) . For transactional
* messages , control information is kept in a chain of TransInvalidationInfo
* structs , also allocated in TopTransactionContext . ( We could keep a
* subtransaction ' s TransInvalidationInfo in its CurTransactionContext ; but
* that ' s more wasteful not less so , since in very many scenarios it ' d be the
* only allocation in the subtransaction ' s CurTransactionContext . ) For
* inplace update messages , control information appears in an
* InvalidationInfo , allocated in CurrentMemoryContext .
*
* We can store the message arrays densely , and yet avoid moving data around
* within an array , because within any one subtransaction we need only
@ -147,7 +153,9 @@
* struct . Similarly , we need distinguish messages of prior subtransactions
* from those of the current subtransaction only until the subtransaction
* completes , after which we adjust the array indexes in the parent ' s
* TransInvalidationInfo to include the subtransaction ' s messages .
* TransInvalidationInfo to include the subtransaction ' s messages . Inplace
* invalidations don ' t need a concept of command or subtransaction boundaries ,
* since we send them during the WAL insertion critical section .
*
* The ordering of the individual messages within a command ' s or
* subtransaction ' s output is not considered significant , although this
@ -200,7 +208,7 @@ typedef struct InvalidationMsgsGroup
/*----------------
* I nvalidation messages are divided into two groups :
* Transactional i nvalidation messages are divided into two groups :
* 1 ) events so far in current command , not yet reflected to caches .
* 2 ) events in previous commands of current transaction ; these have
* been reflected to local caches , and must be either broadcast to
@ -216,26 +224,36 @@ typedef struct InvalidationMsgsGroup
* - - - - - - - - - - - - - - - -
*/
typedef struct TransInvalidationInfo
/* fields common to both transactional and inplace invalidation */
typedef struct InvalidationInfo
{
/* Back link to parent transaction's info */
struct TransInvalidationInfo * parent ;
/* Subtransaction nesting depth */
int my_level ;
/* Events emitted by current command */
InvalidationMsgsGroup CurrentCmdInvalidMsgs ;
/* init file must be invalidated? */
bool RelcacheInitFileInval ;
} InvalidationInfo ;
/* subclass adding fields specific to transactional invalidation */
typedef struct TransInvalidationInfo
{
/* Base class */
struct InvalidationInfo ii ;
/* Events emitted by previous commands of this (sub)transaction */
InvalidationMsgsGroup PriorCmdInvalidMsgs ;
/* init file must be invalidated? */
bool RelcacheInitFileInval ;
/* Back link to parent transaction's info */
struct TransInvalidationInfo * parent ;
/* Subtransaction nesting depth */
int my_level ;
} TransInvalidationInfo ;
static TransInvalidationInfo * transInvalInfo = NULL ;
static InvalidationInfo * inplaceInvalInfo = NULL ;
/* GUC storage */
int debug_discard_caches = 0 ;
@ -543,9 +561,12 @@ ProcessInvalidationMessagesMulti(InvalidationMsgsGroup *group,
static void
RegisterCatcacheInvalidation ( int cacheId ,
uint32 hashValue ,
Oid dbId )
Oid dbId ,
void * context )
{
AddCatcacheInvalidationMessage ( & transInvalInfo - > CurrentCmdInvalidMsgs ,
InvalidationInfo * info = ( InvalidationInfo * ) context ;
AddCatcacheInvalidationMessage ( & info - > CurrentCmdInvalidMsgs ,
cacheId , hashValue , dbId ) ;
}
@ -555,10 +576,9 @@ RegisterCatcacheInvalidation(int cacheId,
* Register an invalidation event for all catcache entries from a catalog .
*/
static void
RegisterCatalogInvalidation ( Oid dbId , Oid catId )
RegisterCatalogInvalidation ( InvalidationInfo * info , Oid dbId , Oid catId )
{
AddCatalogInvalidationMessage ( & transInvalInfo - > CurrentCmdInvalidMsgs ,
dbId , catId ) ;
AddCatalogInvalidationMessage ( & info - > CurrentCmdInvalidMsgs , dbId , catId ) ;
}
/*
@ -567,10 +587,9 @@ RegisterCatalogInvalidation(Oid dbId, Oid catId)
* As above , but register a relcache invalidation event .
*/
static void
RegisterRelcacheInvalidation ( Oid dbId , Oid relId )
RegisterRelcacheInvalidation ( InvalidationInfo * info , Oid dbId , Oid relId )
{
AddRelcacheInvalidationMessage ( & transInvalInfo - > CurrentCmdInvalidMsgs ,
dbId , relId ) ;
AddRelcacheInvalidationMessage ( & info - > CurrentCmdInvalidMsgs , dbId , relId ) ;
/*
* Most of the time , relcache invalidation is associated with system
@ -587,7 +606,7 @@ RegisterRelcacheInvalidation(Oid dbId, Oid relId)
* as well . Also zap when we are invalidating whole relcache .
*/
if ( relId = = InvalidOid | | RelationIdIsInInitFile ( relId ) )
transInvalI nfo- > RelcacheInitFileInval = true ;
i nfo- > RelcacheInitFileInval = true ;
}
/*
@ -597,24 +616,27 @@ RegisterRelcacheInvalidation(Oid dbId, Oid relId)
* Only needed for catalogs that don ' t have catcaches .
*/
static void
RegisterSnapshotInvalidation ( Oid dbId , Oid relId )
RegisterSnapshotInvalidation ( InvalidationInfo * info , Oid dbId , Oid relId )
{
AddSnapshotInvalidationMessage ( & transInvalInfo - > CurrentCmdInvalidMsgs ,
dbId , relId ) ;
AddSnapshotInvalidationMessage ( & info - > CurrentCmdInvalidMsgs , dbId , relId ) ;
}
/*
* PrepareInvalidationState
* Initialize inval data for the current ( sub ) transaction .
*/
static void
static InvalidationInfo *
PrepareInvalidationState ( void )
{
TransInvalidationInfo * myInfo ;
Assert ( IsTransactionState ( ) ) ;
/* Can't queue transactional message while collecting inplace messages. */
Assert ( inplaceInvalInfo = = NULL ) ;
if ( transInvalInfo ! = NULL & &
transInvalInfo - > my_level = = GetCurrentTransactionNestLevel ( ) )
return ;
return ( InvalidationInfo * ) transInvalInfo ;
myInfo = ( TransInvalidationInfo * )
MemoryContextAllocZero ( TopTransactionContext ,
@ -637,7 +659,7 @@ PrepareInvalidationState(void)
* counter . This is a convenient place to check for that , as well as
* being important to keep management of the message arrays simple .
*/
if ( NumMessagesInGroup ( & transInvalInfo - > CurrentCmdInvalidMsgs ) ! = 0 )
if ( NumMessagesInGroup ( & transInvalInfo - > ii . CurrentCmdInvalidMsgs ) ! = 0 )
elog ( ERROR , " cannot start a subtransaction when there are unprocessed inval messages " ) ;
/*
@ -646,8 +668,8 @@ PrepareInvalidationState(void)
* to update them to follow whatever is already in the arrays .
*/
SetGroupToFollow ( & myInfo - > PriorCmdInvalidMsgs ,
& transInvalInfo - > CurrentCmdInvalidMsgs ) ;
SetGroupToFollow ( & myInfo - > CurrentCmdInvalidMsgs ,
& transInvalInfo - > ii . CurrentCmdInvalidMsgs ) ;
SetGroupToFollow ( & myInfo - > ii . CurrentCmdInvalidMsgs ,
& myInfo - > PriorCmdInvalidMsgs ) ;
}
else
@ -663,6 +685,41 @@ PrepareInvalidationState(void)
}
transInvalInfo = myInfo ;
return ( InvalidationInfo * ) myInfo ;
}
/*
* PrepareInplaceInvalidationState
* Initialize inval data for an inplace update .
*
* See previous function for more background .
*/
static InvalidationInfo *
PrepareInplaceInvalidationState ( void )
{
InvalidationInfo * myInfo ;
Assert ( IsTransactionState ( ) ) ;
/* limit of one inplace update under assembly */
Assert ( inplaceInvalInfo = = NULL ) ;
/* gone after WAL insertion CritSection ends, so use current context */
myInfo = ( InvalidationInfo * ) palloc0 ( sizeof ( InvalidationInfo ) ) ;
/* Stash our messages past end of the transactional messages, if any. */
if ( transInvalInfo ! = NULL )
SetGroupToFollow ( & myInfo - > CurrentCmdInvalidMsgs ,
& transInvalInfo - > ii . CurrentCmdInvalidMsgs ) ;
else
{
InvalMessageArrays [ CatCacheMsgs ] . msgs = NULL ;
InvalMessageArrays [ CatCacheMsgs ] . maxmsgs = 0 ;
InvalMessageArrays [ RelCacheMsgs ] . msgs = NULL ;
InvalMessageArrays [ RelCacheMsgs ] . maxmsgs = 0 ;
}
inplaceInvalInfo = myInfo ;
return myInfo ;
}
/* ----------------------------------------------------------------
@ -902,7 +959,7 @@ xactGetCommittedInvalidationMessages(SharedInvalidationMessage **msgs,
* after we send the SI messages . However , we need not do anything unless
* we committed .
*/
* RelcacheInitFileInval = transInvalInfo - > RelcacheInitFileInval ;
* RelcacheInitFileInval = transInvalInfo - > ii . RelcacheInitFileInval ;
/*
* Collect all the pending messages into a single contiguous array of
@ -913,7 +970,7 @@ xactGetCommittedInvalidationMessages(SharedInvalidationMessage **msgs,
* not new ones .
*/
nummsgs = NumMessagesInGroup ( & transInvalInfo - > PriorCmdInvalidMsgs ) +
NumMessagesInGroup ( & transInvalInfo - > CurrentCmdInvalidMsgs ) ;
NumMessagesInGroup ( & transInvalInfo - > ii . CurrentCmdInvalidMsgs ) ;
* msgs = msgarray = ( SharedInvalidationMessage * )
MemoryContextAlloc ( CurTransactionContext ,
@ -926,7 +983,7 @@ xactGetCommittedInvalidationMessages(SharedInvalidationMessage **msgs,
msgs ,
n * sizeof ( SharedInvalidationMessage ) ) ,
nmsgs + = n ) ) ;
ProcessMessageSubGroupMulti ( & transInvalInfo - > CurrentCmdInvalidMsgs ,
ProcessMessageSubGroupMulti ( & transInvalInfo - > ii . CurrentCmdInvalidMsgs ,
CatCacheMsgs ,
( memcpy ( msgarray + nmsgs ,
msgs ,
@ -938,7 +995,51 @@ xactGetCommittedInvalidationMessages(SharedInvalidationMessage **msgs,
msgs ,
n * sizeof ( SharedInvalidationMessage ) ) ,
nmsgs + = n ) ) ;
ProcessMessageSubGroupMulti ( & transInvalInfo - > CurrentCmdInvalidMsgs ,
ProcessMessageSubGroupMulti ( & transInvalInfo - > ii . CurrentCmdInvalidMsgs ,
RelCacheMsgs ,
( memcpy ( msgarray + nmsgs ,
msgs ,
n * sizeof ( SharedInvalidationMessage ) ) ,
nmsgs + = n ) ) ;
Assert ( nmsgs = = nummsgs ) ;
return nmsgs ;
}
/*
* inplaceGetInvalidationMessages ( ) is called by the inplace update to collect
* invalidation messages to add to its WAL record . Like the previous
* function , we might still fail .
*/
int
inplaceGetInvalidationMessages ( SharedInvalidationMessage * * msgs ,
bool * RelcacheInitFileInval )
{
SharedInvalidationMessage * msgarray ;
int nummsgs ;
int nmsgs ;
/* Quick exit if we haven't done anything with invalidation messages. */
if ( inplaceInvalInfo = = NULL )
{
* RelcacheInitFileInval = false ;
* msgs = NULL ;
return 0 ;
}
* RelcacheInitFileInval = inplaceInvalInfo - > RelcacheInitFileInval ;
nummsgs = NumMessagesInGroup ( & inplaceInvalInfo - > CurrentCmdInvalidMsgs ) ;
* msgs = msgarray = ( SharedInvalidationMessage * )
palloc ( nummsgs * sizeof ( SharedInvalidationMessage ) ) ;
nmsgs = 0 ;
ProcessMessageSubGroupMulti ( & inplaceInvalInfo - > CurrentCmdInvalidMsgs ,
CatCacheMsgs ,
( memcpy ( msgarray + nmsgs ,
msgs ,
n * sizeof ( SharedInvalidationMessage ) ) ,
nmsgs + = n ) ) ;
ProcessMessageSubGroupMulti ( & inplaceInvalInfo - > CurrentCmdInvalidMsgs ,
RelCacheMsgs ,
( memcpy ( msgarray + nmsgs ,
msgs ,
@ -1024,7 +1125,9 @@ ProcessCommittedInvalidationMessages(SharedInvalidationMessage *msgs,
void
AtEOXact_Inval ( bool isCommit )
{
/* Quick exit if no messages */
inplaceInvalInfo = NULL ;
/* Quick exit if no transactional messages */
if ( transInvalInfo = = NULL )
return ;
@ -1038,16 +1141,16 @@ AtEOXact_Inval(bool isCommit)
* after we send the SI messages . However , we need not do anything
* unless we committed .
*/
if ( transInvalInfo - > RelcacheInitFileInval )
if ( transInvalInfo - > ii . RelcacheInitFileInval )
RelationCacheInitFilePreInvalidate ( ) ;
AppendInvalidationMessages ( & transInvalInfo - > PriorCmdInvalidMsgs ,
& transInvalInfo - > CurrentCmdInvalidMsgs ) ;
& transInvalInfo - > ii . CurrentCmdInvalidMsgs ) ;
ProcessInvalidationMessagesMulti ( & transInvalInfo - > PriorCmdInvalidMsgs ,
SendSharedInvalidMessages ) ;
if ( transInvalInfo - > RelcacheInitFileInval )
if ( transInvalInfo - > ii . RelcacheInitFileInval )
RelationCacheInitFilePostInvalidate ( ) ;
}
else
@ -1060,6 +1163,44 @@ AtEOXact_Inval(bool isCommit)
transInvalInfo = NULL ;
}
/*
* PreInplace_Inval
* Process queued - up invalidation before inplace update critical section .
*
* Tasks belong here if they are safe even if the inplace update does not
* complete . Currently , this just unlinks a cache file , which can fail . The
* sum of this and AtInplace_Inval ( ) mirrors AtEOXact_Inval ( isCommit = true ) .
*/
void
PreInplace_Inval ( void )
{
Assert ( CritSectionCount = = 0 ) ;
if ( inplaceInvalInfo & & inplaceInvalInfo - > RelcacheInitFileInval )
RelationCacheInitFilePreInvalidate ( ) ;
}
/*
* AtInplace_Inval
* Process queued - up invalidations after inplace update buffer mutation .
*/
void
AtInplace_Inval ( void )
{
Assert ( CritSectionCount > 0 ) ;
if ( inplaceInvalInfo = = NULL )
return ;
ProcessInvalidationMessagesMulti ( & inplaceInvalInfo - > CurrentCmdInvalidMsgs ,
SendSharedInvalidMessages ) ;
if ( inplaceInvalInfo - > RelcacheInitFileInval )
RelationCacheInitFilePostInvalidate ( ) ;
inplaceInvalInfo = NULL ;
}
/*
* AtEOSubXact_Inval
* Process queued - up invalidation messages at end of subtransaction .
@ -1082,9 +1223,20 @@ void
AtEOSubXact_Inval ( bool isCommit )
{
int my_level ;
TransInvalidationInfo * myInfo = transInvalInfo ;
TransInvalidationInfo * myInfo ;
/*
* Successful inplace update must clear this , but we clear it on abort .
* Inplace updates allocate this in CurrentMemoryContext , which has
* lifespan < = subtransaction lifespan . Hence , don ' t free it explicitly .
*/
if ( isCommit )
Assert ( inplaceInvalInfo = = NULL ) ;
else
inplaceInvalInfo = NULL ;
/* Quick exit if no messages. */
/* Quick exit if no transactional messages. */
myInfo = transInvalInfo ;
if ( myInfo = = NULL )
return ;
@ -1125,12 +1277,12 @@ AtEOSubXact_Inval(bool isCommit)
& myInfo - > PriorCmdInvalidMsgs ) ;
/* Must readjust parent's CurrentCmdInvalidMsgs indexes now */
SetGroupToFollow ( & myInfo - > parent - > CurrentCmdInvalidMsgs ,
SetGroupToFollow ( & myInfo - > parent - > ii . CurrentCmdInvalidMsgs ,
& myInfo - > parent - > PriorCmdInvalidMsgs ) ;
/* Pending relcache inval becomes parent's problem too */
if ( myInfo - > RelcacheInitFileInval )
myInfo - > parent - > RelcacheInitFileInval = true ;
if ( myInfo - > ii . RelcacheInitFileInval )
myInfo - > parent - > ii . RelcacheInitFileInval = true ;
/* Pop the transaction state stack */
transInvalInfo = myInfo - > parent ;
@ -1177,7 +1329,7 @@ CommandEndInvalidationMessages(void)
if ( transInvalInfo = = NULL )
return ;
ProcessInvalidationMessages ( & transInvalInfo - > CurrentCmdInvalidMsgs ,
ProcessInvalidationMessages ( & transInvalInfo - > ii . CurrentCmdInvalidMsgs ,
LocalExecuteInvalidationMessage ) ;
/* WAL Log per-command invalidation messages for wal_level=logical */
@ -1185,26 +1337,21 @@ CommandEndInvalidationMessages(void)
LogLogicalInvalidations ( ) ;
AppendInvalidationMessages ( & transInvalInfo - > PriorCmdInvalidMsgs ,
& transInvalInfo - > CurrentCmdInvalidMsgs ) ;
& transInvalInfo - > ii . CurrentCmdInvalidMsgs ) ;
}
/*
* CacheInvalidateHeapTuple
* Register the given tuple for invalidation at end of command
* ( ie , current command is creating or outdating this tuple ) .
* Also , detect whether a relcache invalidation is implied .
*
* For an insert or delete , tuple is the target tuple and newtuple is NULL .
* For an update , we are called just once , with tuple being the old tuple
* version and newtuple the new version . This allows avoidance of duplicate
* effort during an update .
* CacheInvalidateHeapTupleCommon
* Common logic for end - of - command and inplace variants .
*/
void
CacheInvalidateHeapTuple ( Relation relation ,
HeapTuple tuple ,
HeapTuple newtuple )
static void
CacheInvalidateHeapTupleCommon ( Relation relation ,
HeapTuple tuple ,
HeapTuple newtuple ,
InvalidationInfo * ( * prepare_callback ) ( void ) )
{
InvalidationInfo * info ;
Oid tupleRelId ;
Oid databaseId ;
Oid relationId ;
@ -1228,11 +1375,8 @@ CacheInvalidateHeapTuple(Relation relation,
if ( IsToastRelation ( relation ) )
return ;
/*
* If we ' re not prepared to queue invalidation messages for this
* subtransaction level , get ready now .
*/
PrepareInvalidationState ( ) ;
/* Allocate any required resources. */
info = prepare_callback ( ) ;
/*
* First let the catcache do its thing
@ -1241,11 +1385,12 @@ CacheInvalidateHeapTuple(Relation relation,
if ( RelationInvalidatesSnapshotsOnly ( tupleRelId ) )
{
databaseId = IsSharedRelation ( tupleRelId ) ? InvalidOid : MyDatabaseId ;
RegisterSnapshotInvalidation ( databaseId , tupleRelId ) ;
RegisterSnapshotInvalidation ( info , databaseId , tupleRelId ) ;
}
else
PrepareToInvalidateCacheTuple ( relation , tuple , newtuple ,
RegisterCatcacheInvalidation ) ;
RegisterCatcacheInvalidation ,
( void * ) info ) ;
/*
* Now , is this tuple one of the primary definers of a relcache entry ? See
@ -1318,7 +1463,44 @@ CacheInvalidateHeapTuple(Relation relation,
/*
* Yes . We need to register a relcache invalidation event .
*/
RegisterRelcacheInvalidation ( databaseId , relationId ) ;
RegisterRelcacheInvalidation ( info , databaseId , relationId ) ;
}
/*
* CacheInvalidateHeapTuple
* Register the given tuple for invalidation at end of command
* ( ie , current command is creating or outdating this tuple ) and end of
* transaction . Also , detect whether a relcache invalidation is implied .
*
* For an insert or delete , tuple is the target tuple and newtuple is NULL .
* For an update , we are called just once , with tuple being the old tuple
* version and newtuple the new version . This allows avoidance of duplicate
* effort during an update .
*/
void
CacheInvalidateHeapTuple ( Relation relation ,
HeapTuple tuple ,
HeapTuple newtuple )
{
CacheInvalidateHeapTupleCommon ( relation , tuple , newtuple ,
PrepareInvalidationState ) ;
}
/*
* CacheInvalidateHeapTupleInplace
* Register the given tuple for nontransactional invalidation pertaining
* to an inplace update . Also , detect whether a relcache invalidation is
* implied .
*
* Like CacheInvalidateHeapTuple ( ) , but for inplace updates .
*/
void
CacheInvalidateHeapTupleInplace ( Relation relation ,
HeapTuple tuple ,
HeapTuple newtuple )
{
CacheInvalidateHeapTupleCommon ( relation , tuple , newtuple ,
PrepareInplaceInvalidationState ) ;
}
/*
@ -1337,14 +1519,13 @@ CacheInvalidateCatalog(Oid catalogId)
{
Oid databaseId ;
PrepareInvalidationState ( ) ;
if ( IsSharedRelation ( catalogId ) )
databaseId = InvalidOid ;
else
databaseId = MyDatabaseId ;
RegisterCatalogInvalidation ( databaseId , catalogId ) ;
RegisterCatalogInvalidation ( PrepareInvalidationState ( ) ,
databaseId , catalogId ) ;
}
/*
@ -1362,15 +1543,14 @@ CacheInvalidateRelcache(Relation relation)
Oid databaseId ;
Oid relationId ;
PrepareInvalidationState ( ) ;
relationId = RelationGetRelid ( relation ) ;
if ( relation - > rd_rel - > relisshared )
databaseId = InvalidOid ;
else
databaseId = MyDatabaseId ;
RegisterRelcacheInvalidation ( databaseId , relationId ) ;
RegisterRelcacheInvalidation ( PrepareInvalidationState ( ) ,
databaseId , relationId ) ;
}
/*
@ -1383,9 +1563,8 @@ CacheInvalidateRelcache(Relation relation)
void
CacheInvalidateRelcacheAll ( void )
{
PrepareInvalidationState ( ) ;
RegisterRelcacheInvalidation ( InvalidOid , InvalidOid ) ;
RegisterRelcacheInvalidation ( PrepareInvalidationState ( ) ,
InvalidOid , InvalidOid ) ;
}
/*
@ -1399,14 +1578,13 @@ CacheInvalidateRelcacheByTuple(HeapTuple classTuple)
Oid databaseId ;
Oid relationId ;
PrepareInvalidationState ( ) ;
relationId = classtup - > oid ;
if ( classtup - > relisshared )
databaseId = InvalidOid ;
else
databaseId = MyDatabaseId ;
RegisterRelcacheInvalidation ( databaseId , relationId ) ;
RegisterRelcacheInvalidation ( PrepareInvalidationState ( ) ,
databaseId , relationId ) ;
}
/*
@ -1420,8 +1598,6 @@ CacheInvalidateRelcacheByRelid(Oid relid)
{
HeapTuple tup ;
PrepareInvalidationState ( ) ;
tup = SearchSysCache1 ( RELOID , ObjectIdGetDatum ( relid ) ) ;
if ( ! HeapTupleIsValid ( tup ) )
elog ( ERROR , " cache lookup failed for relation %u " , relid ) ;
@ -1611,7 +1787,7 @@ LogLogicalInvalidations(void)
if ( transInvalInfo = = NULL )
return ;
group = & transInvalInfo - > CurrentCmdInvalidMsgs ;
group = & transInvalInfo - > ii . CurrentCmdInvalidMsgs ;
nmsgs = NumMessagesInGroup ( group ) ;
if ( nmsgs > 0 )