Foundation Classes, Standard_ErrorHandler - use thread_local stack instead of global mutex lock (#980)

Refactored Standard_ErrorHandler to use thread_local storage for the error handler stack
instead of a global list protected by mutex. This eliminates locking overhead entirely
since each thread only accesses its own error handlers.

Changes:
- Replaced global mutex-protected stack with thread_local Top pointer
- Simplified FindHandler() to directly return the thread-local Top
- Removed Catches() and LastCaughtError() methods (no longer needed)
- Added Raise() method for re-throwing caught exceptions
- Removed obsolete member variables: myStatus, myThread
- Deleted unused headers: Standard_HandlerStatus.hxx, Standard_JmpBuf.hxx, Standard_PErrorHandler.hxx
- Updated OCC_CATCH_SIGNALS macro to use new Raise() method
This commit is contained in:
Kirill Gavrilov
2026-01-05 17:03:00 +00:00
committed by dpasukhi
parent b9980c3c39
commit 078dfc44ae
9 changed files with 53 additions and 456 deletions
-71
View File
@@ -2352,76 +2352,6 @@ static int OCC6143(Draw_Interpretor& di, int argc, const char** argv)
return 0;
}
//! Auxiliary functor.
struct TestParallelFunctor
{
TestParallelFunctor()
: myNbNotRaised(0),
myNbSigSegv(0),
myNbUnknown(0)
{
}
int NbNotRaised() const { return myNbNotRaised; }
int NbSigSegv() const { return myNbSigSegv; }
int NbUnknown() const { return myNbUnknown; }
void operator()(int theThreadId, int theTaskId) const
{
(void)theThreadId;
(void)theTaskId;
// Test Access Violation
{
try
{
OCC_CATCH_SIGNALS
int* pint = nullptr;
*pint = 4;
++myNbNotRaised;
}
#ifdef _WIN32
catch (OSD_Exception_ACCESS_VIOLATION const&)
#else
catch (OSD_SIGSEGV const&)
#endif
{
++myNbSigSegv;
}
catch (Standard_Failure const&)
{
++myNbUnknown;
}
}
}
private:
mutable std::atomic<int> myNbNotRaised;
mutable std::atomic<int> myNbSigSegv;
mutable std::atomic<int> myNbUnknown;
};
static int OCC30775(Draw_Interpretor& theDI, int theNbArgs, const char**)
{
if (theNbArgs != 1)
{
std::cout << "Syntax error: wrong number of arguments\n";
return 1;
}
occ::handle<OSD_ThreadPool> aPool = new OSD_ThreadPool(4);
OSD_ThreadPool::Launcher aLauncher(*aPool, 4);
TestParallelFunctor aFunctor;
aLauncher.Perform(0, 100, aFunctor);
theDI << "NbRaised: " << (aFunctor.NbSigSegv() + aFunctor.NbUnknown()) << "\n"
<< "NbNotRaised: " << aFunctor.NbNotRaised() << "\n"
<< "NbSigSeg: " << aFunctor.NbSigSegv() << "\n"
<< "NbUnknown: " << aFunctor.NbUnknown() << "\n";
return 0;
}
#if defined(_MSC_VER) && !defined(__clang__)
#pragma optimize("", on)
#endif
@@ -5051,7 +4981,6 @@ void QABugs::Commands_11(Draw_Interpretor& theCommands)
theCommands.Add("OCC6046", "OCC6046 nb_of_vectors size", __FILE__, OCC6046, group);
theCommands.Add("OCC5698", "OCC5698 wire", __FILE__, OCC5698, group);
theCommands.Add("OCC6143", "OCC6143 catching signals", __FILE__, OCC6143, group);
theCommands.Add("OCC30775", "OCC30775 catching signals in threads", __FILE__, OCC30775, group);
theCommands.Add("OCC30762", "OCC30762 printing backtrace", __FILE__, OCC30762, group);
theCommands.Add("OCC7141", "OCC7141 [nCount] aPath", __FILE__, OCC7141, group);
theCommands.Add("OCC7372", "OCC7372", __FILE__, OCC7372, group);
@@ -37,13 +37,11 @@ set(OCCT_Standard_FILES
Standard_GUID.cxx
Standard_GUID.hxx
Standard_Handle.hxx
Standard_HandlerStatus.hxx
Standard_HashUtils.hxx
Standard_HashUtils.lxx
Standard_ImmutableObject.hxx
Standard_Integer.hxx
Standard_IStream.hxx
Standard_JmpBuf.hxx
Standard_LicenseError.hxx
Standard_LicenseNotFound.hxx
Standard_Macro.hxx
@@ -69,7 +67,6 @@ set(OCCT_Standard_FILES
Standard_Overflow.hxx
Standard_PByte.hxx
Standard_PCharacter.hxx
Standard_PErrorHandler.hxx
Standard_Persistent.cxx
Standard_Persistent.hxx
Standard_PExtCharacter.hxx
@@ -12,125 +12,51 @@
// Alternatively, this file may be used under the terms of Open CASCADE
// commercial license or contractual agreement.
//============================================================================
//==== Title: Standard_ErrorHandler.cxx
//==== Role : class "Standard_ErrorHandler" implementation.
//============================================================================
#include <Standard_ErrorHandler.hxx>
#include <Standard_Failure.hxx>
#include <Standard.hxx>
#include <mutex>
#include <shared_mutex>
#ifndef _WIN32
#include <pthread.h>
#else
#include <windows.h>
#endif
// ===========================================================================
// The class "Standard_ErrorHandler" variables
// ===========================================================================
// During [sig]setjmp()/[sig]longjmp() K_SETJMP is non zero (try)
// So if there is an abort request and if K_SETJMP is non zero, the abort
// request will be ignored. If the abort request do a raise during a setjmp
// or a longjmp, there will be a "terminating SEGV" impossible to handle.
//==== The top of the Errors Stack ===========================================
static Standard_ErrorHandler* Top = nullptr;
// The top of the Errors Stack
//=================================================================================================
namespace
{
// Using std::shared_mutex to allow concurrent read access from multiple threads.
// Most FindHandler calls (theUnlink=false) use shared_lock for concurrent searches.
// Only write operations (constructor, Unlink, FindHandler with theUnlink=true) use unique_lock.
//
// TODO: Long-term optimization - use thread_local storage to eliminate locking entirely.
// Since each thread only accesses its own error handlers (filtered by thread ID),
// this global list could be replaced with thread_local Top pointers.
// This would eliminate all mutex overhead for thousands of try blocks in multi-threaded code.
static std::shared_mutex THE_GLOBAL_MUTEX;
static inline Standard_ThreadId GetThreadID()
{
#ifndef _WIN32
return (Standard_ThreadId)pthread_self();
#else
return GetCurrentThreadId();
#endif
}
} // namespace
//============================================================================
//==== Constructor : Create a ErrorHandler structure. And add it at the
//==== 'Top' of "ErrorHandler's stack".
//============================================================================
static thread_local Standard_ErrorHandler* Top = nullptr;
Standard_ErrorHandler::Standard_ErrorHandler()
: myStatus(Standard_HandlerVoid),
myCallbackPtr(nullptr)
{
myThread = GetThreadID();
memset(&myLabel, 0, sizeof(myLabel));
std::unique_lock<std::shared_mutex> aLock(THE_GLOBAL_MUTEX);
myPrevious = Top;
Top = this;
}
//============================================================================
//==== Destructor : Delete the ErrorHandler and Abort if there is a 'Error'.
//============================================================================
//=================================================================================================
void Standard_ErrorHandler::Destroy()
{
Unlink();
if (myStatus == Standard_HandlerJumped)
{
// jumped, but not caught
Abort(myCaughtError);
}
}
//=================================================================================================
void Standard_ErrorHandler::Unlink()
{
// put a lock on the stack
std::unique_lock<std::shared_mutex> aLock(THE_GLOBAL_MUTEX);
Standard_ErrorHandler* aPrevious = nullptr;
Standard_ErrorHandler* aCurrent = Top;
// locate this handler in the stack
while (aCurrent != nullptr && this != aCurrent)
// Unlink handlers that were created after this one (shouldn't happen in normal usage)
while (Top != nullptr && Top != this)
{
aPrevious = aCurrent;
aCurrent = aCurrent->myPrevious;
Top->Unlink();
}
if (aCurrent == nullptr)
if (Top == this)
{
return;
Top = myPrevious;
}
if (aPrevious == nullptr)
{
// a top exception taken
Top = aCurrent->myPrevious;
}
else
{
aPrevious->myPrevious = aCurrent->myPrevious;
}
myPrevious = nullptr;
// unlink and destroy all registered callbacks
void* aPtr = aCurrent->myCallbackPtr;
void* aPtr = myCallbackPtr;
myCallbackPtr = nullptr;
while (aPtr)
{
@@ -141,27 +67,20 @@ void Standard_ErrorHandler::Unlink()
}
}
//=======================================================================
// function : IsInTryBlock
// purpose : test if the code is currently running in
//=======================================================================
//=================================================================================================
bool Standard_ErrorHandler::IsInTryBlock()
{
Standard_ErrorHandler* anActive = FindHandler(Standard_HandlerVoid, false);
Standard_ErrorHandler* anActive = FindHandler();
return anActive != nullptr;
}
//============================================================================
//==== Abort: make a longjmp to the saved Context.
//==== Abort if there is a non null 'Error'
//============================================================================
//=================================================================================================
void Standard_ErrorHandler::Abort(const occ::handle<Standard_Failure>& theError)
{
Standard_ErrorHandler* anActive = FindHandler(Standard_HandlerVoid, true);
Standard_ErrorHandler* anActive = FindHandler();
//==== Check if can do the "longjmp" =======================================
if (anActive == nullptr)
{
std::cerr << "*** Abort *** an exception was raised, but no catch was found." << std::endl;
@@ -170,151 +89,34 @@ void Standard_ErrorHandler::Abort(const occ::handle<Standard_Failure>& theError)
exit(1);
}
anActive->myStatus = Standard_HandlerJumped;
anActive->myCaughtError = theError;
longjmp(anActive->myLabel, true);
}
//============================================================================
//==== Catches: If there is a 'Error', and it is in good type
//==== returns True and clean 'Error', else returns False.
//============================================================================
//=================================================================================================
bool Standard_ErrorHandler::Catches(const occ::handle<Standard_Type>& AType)
void Standard_ErrorHandler::Raise()
{
Standard_ErrorHandler* anActive = FindHandler(Standard_HandlerJumped, false);
if (anActive == nullptr)
return false;
if (anActive->myCaughtError.IsNull())
return false;
if (anActive->myCaughtError->IsKind(AType))
if (myCaughtError.IsNull())
{
myStatus = Standard_HandlerProcessed;
return true;
}
else
{
return false;
std::cerr << "*** Abort *** an exception handler was called, but not exception object is set."
<< std::endl;
exit(1);
}
myCaughtError->Reraise();
}
occ::handle<Standard_Failure> Standard_ErrorHandler::LastCaughtError()
//=================================================================================================
Standard_ErrorHandler* Standard_ErrorHandler::FindHandler()
{
occ::handle<Standard_Failure> aHandle;
Standard_ErrorHandler* anActive = FindHandler(Standard_HandlerProcessed, false);
if (anActive != nullptr)
aHandle = anActive->myCaughtError;
return aHandle;
}
occ::handle<Standard_Failure> Standard_ErrorHandler::Error() const
{
return myCaughtError;
}
void Standard_ErrorHandler::Error(const occ::handle<Standard_Failure>& theError)
{
Standard_ErrorHandler* anActive = FindHandler(Standard_HandlerVoid, false);
if (anActive == nullptr)
Abort(theError);
anActive->myCaughtError = theError;
}
Standard_ErrorHandler* Standard_ErrorHandler::FindHandler(const Standard_HandlerStatus theStatus,
const bool theUnlink)
{
// Use shared lock for read-only access (most common case), exclusive lock only when modifying
if (!theUnlink)
{
// Read-only path - use shared lock to allow concurrent searches from multiple threads
std::shared_lock<std::shared_mutex> aLock(THE_GLOBAL_MUTEX);
// Find the current ErrorHandler according to thread
Standard_ErrorHandler* aCurrent = Top;
Standard_ThreadId aTreadId = GetThreadID();
// searching an exception with correct ID number
while (aCurrent != nullptr)
{
if (aTreadId == aCurrent->myThread && theStatus == aCurrent->myStatus)
{
// found one
return aCurrent;
}
aCurrent = aCurrent->myPrevious;
}
return nullptr;
}
else
{
// Modifying path - use exclusive lock
std::unique_lock<std::shared_mutex> aLock(THE_GLOBAL_MUTEX);
// Find the current ErrorHandler according to thread
Standard_ErrorHandler* aPrevious = nullptr;
Standard_ErrorHandler* aCurrent = Top;
Standard_ErrorHandler* anActive = nullptr;
bool aStop = false;
Standard_ThreadId aTreadId = GetThreadID();
// searching an exception with correct ID number
// which is not processed for the moment
while (!aStop)
{
while (aCurrent != nullptr && aTreadId != aCurrent->myThread)
{
aPrevious = aCurrent;
aCurrent = aCurrent->myPrevious;
}
if (aCurrent != nullptr)
{
if (theStatus != aCurrent->myStatus)
{
// unlink current
if (aPrevious == nullptr)
{
// a top exception taken
Top = aCurrent->myPrevious;
}
else
{
aPrevious->myPrevious = aCurrent->myPrevious;
}
// shift
aCurrent = aCurrent->myPrevious;
}
else
{
// found one
anActive = aCurrent;
aStop = true;
}
}
else
{
// Current is NULL, means that no handles
aStop = true;
}
}
return anActive;
}
return Top;
}
#if defined(OCC_CONVERT_SIGNALS)
Standard_ErrorHandler::Callback::Callback()
: myHandler(nullptr),
myPrev(nullptr),
myNext(nullptr)
{
}
Standard_ErrorHandler::Callback::Callback() {}
Standard_ErrorHandler::Callback::~Callback()
{
@@ -327,7 +129,7 @@ void Standard_ErrorHandler::Callback::RegisterCallback()
return; // already registered
// find current active exception handler
Standard_ErrorHandler* aHandler = Standard_ErrorHandler::FindHandler(Standard_HandlerVoid, false);
Standard_ErrorHandler* aHandler = Standard_ErrorHandler::FindHandler();
// if found, add this callback object first to the list
if (aHandler)
@@ -19,14 +19,9 @@
#include <Standard.hxx>
#include <Standard_Handle.hxx>
#include <Standard_PErrorHandler.hxx>
#include <Standard_JmpBuf.hxx>
#include <Standard_HandlerStatus.hxx>
#include <Standard_ThreadId.hxx>
#include <Standard_Type.hxx>
#include <mutex>
#include <setjmp.h>
//! @file
//! Support of handling of C signals as C++-style exceptions, and implementation
@@ -49,14 +44,13 @@
#if defined(OCC_CONVERT_SIGNALS)
// Exceptions are raied as usual, signal cause jumps in the nearest
// Exceptions are raised as usual, signal cause jumps in the nearest
// OCC_CATCH_SIGNALS and then thrown as exceptions.
#define OCC_CATCH_SIGNALS \
Standard_ErrorHandler _aHandler; \
if (setjmp(_aHandler.Label())) \
{ \
_aHandler.Catches(STANDARD_TYPE(Standard_Failure)); \
_aHandler.Error()->Reraise(); \
_aHandler.Raise(); \
}
// Suppress GCC warning "variable ... might be clobbered by 'longjmp' or 'vfork'"
@@ -75,12 +69,11 @@ class Standard_Failure;
//! Class implementing mechanics of conversion of signals to exceptions.
//!
//! Each instance of it stores data for jump placement, thread id,
//! Each instance of it stores data for jump placement,
//! and callbacks to be called during jump (for proper resource release).
//!
//! The active handlers are stored in the global stack, which is used
//! to find appropriate handler when signal is raised.
class Standard_ErrorHandler
{
public:
@@ -96,39 +89,30 @@ public:
//! Destructor
~Standard_ErrorHandler() { Destroy(); }
//! Removes handler from the handlers list
Standard_EXPORT void Unlink();
//! Returns "True" if the caught exception has the same type
//! or inherits from "aType"
Standard_EXPORT bool Catches(const occ::handle<Standard_Type>& aType);
//! Throws C++ exception if exception object set,
//! otherwise prints error and terminates program.
Standard_EXPORT void Raise();
//! Returns label for jump
Standard_JmpBuf& Label() { return myLabel; }
jmp_buf& Label() { return myLabel; }
//! Returns the current Error.
Standard_EXPORT occ::handle<Standard_Failure> Error() const;
//! Returns the caught exception.
Standard_EXPORT static occ::handle<Standard_Failure> LastCaughtError();
const occ::handle<Standard_Failure>& Error() const { return myCaughtError; }
//! Test if the code is currently running in a try block
Standard_EXPORT static bool IsInTryBlock();
private:
//! A exception is raised but it is not yet caught.
//! So Abort the current function and transmit the exception
//! to "calling routines".
//! Warning: If no catch is prepared for this exception, it displays the
//! exception name and calls "exit(1)".
Standard_EXPORT static void Abort(const occ::handle<Standard_Failure>& theError);
//! Removes handler from the list.
void Unlink();
//! Set the Error which will be transmitted to "calling routines".
Standard_EXPORT static void Error(const occ::handle<Standard_Failure>& aError);
//! Finds nearest error handler in the stack and sets its exception object to @p theError
//! and long jump which then throw normal C++ exception.
//! If handler not found, prints error and exit program with error code @c 1.
static void Abort(const occ::handle<Standard_Failure>& theError);
//! Returns the current handler (closest in the stack in the current execution thread)
Standard_EXPORT static Standard_PErrorHandler FindHandler(const Standard_HandlerStatus theStatus,
const bool theUnlink);
static Standard_ErrorHandler* FindHandler();
public:
//! Defines a base class for callback objects that can be registered
@@ -186,20 +170,18 @@ public:
Callback();
private:
void* myHandler;
void* myPrev;
void* myNext;
void* myHandler = nullptr;
void* myPrev = nullptr;
void* myNext = nullptr;
friend class Standard_ErrorHandler;
};
private:
Standard_PErrorHandler myPrevious;
Standard_ErrorHandler* myPrevious = nullptr;
occ::handle<Standard_Failure> myCaughtError;
Standard_JmpBuf myLabel;
Standard_HandlerStatus myStatus;
Standard_ThreadId myThread;
Callback* myCallbackPtr;
jmp_buf myLabel = {};
Callback* myCallbackPtr = nullptr;
friend class Standard_Failure;
};
@@ -207,18 +189,9 @@ private:
// If OCC_CONVERT_SIGNALS is not defined,
// provide empty inline implementation
#if !defined(OCC_CONVERT_SIGNALS)
inline Standard_ErrorHandler::Callback::Callback()
: myHandler(0),
myPrev(0),
myNext(0)
{
}
inline Standard_ErrorHandler::Callback::Callback() {}
inline Standard_ErrorHandler::Callback::~Callback()
{
(void)myHandler;
(void)myPrev;
}
inline Standard_ErrorHandler::Callback::~Callback() {}
inline void Standard_ErrorHandler::Callback::RegisterCallback() {}
@@ -233,7 +233,6 @@ void Standard_Failure::Reraise()
void Standard_Failure::Jump()
{
#if defined(OCC_CONVERT_SIGNALS)
Standard_ErrorHandler::Error(this);
Standard_ErrorHandler::Abort(this);
#else
Throw();
@@ -1,27 +0,0 @@
// Created on: 1991-09-05
// Created by: J.P. TIRAUlt
// Copyright (c) 1991-1999 Matra Datavision
// Copyright (c) 1999-2014 OPEN CASCADE SAS
//
// This file is part of Open CASCADE Technology software library.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License version 2.1 as published
// by the Free Software Foundation, with special exception defined in the file
// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
// distribution for complete text of the license and disclaimer of any warranty.
//
// Alternatively, this file may be used under the terms of Open CASCADE
// commercial license or contractual agreement.
#ifndef _Standard_HandlerStatus_HeaderFile
#define _Standard_HandlerStatus_HeaderFile
enum Standard_HandlerStatus
{
Standard_HandlerVoid,
Standard_HandlerJumped,
Standard_HandlerProcessed
};
#endif // _Standard_HandlerStatus_HeaderFile
@@ -1,29 +0,0 @@
// Copyright (c) 1999-2014 OPEN CASCADE SAS
//
// This file is part of Open CASCADE Technology software library.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License version 2.1 as published
// by the Free Software Foundation, with special exception defined in the file
// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
// distribution for complete text of the license and disclaimer of any warranty.
//
// Alternatively, this file may be used under the terms of Open CASCADE
// commercial license or contractual agreement.
#ifndef _Standard_JmpBuf_HeaderFile
#define _Standard_JmpBuf_HeaderFile
#include <setjmp.h>
typedef
#ifdef SOLARIS
sigjmp_buf
#elif defined(IRIX)
sigjmp_buf
#else
jmp_buf
#endif
Standard_JmpBuf;
#endif
@@ -1,23 +0,0 @@
// Created on: 1991-09-05
// Created by: J.P. TIRAUlt
// Copyright (c) 1991-1999 Matra Datavision
// Copyright (c) 1999-2014 OPEN CASCADE SAS
//
// This file is part of Open CASCADE Technology software library.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License version 2.1 as published
// by the Free Software Foundation, with special exception defined in the file
// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
// distribution for complete text of the license and disclaimer of any warranty.
//
// Alternatively, this file may be used under the terms of Open CASCADE
// commercial license or contractual agreement.
#ifndef _Standard_PErrorHandler_HeaderFile
#define _Standard_PErrorHandler_HeaderFile
class Standard_ErrorHandler;
typedef Standard_ErrorHandler* Standard_PErrorHandler;
#endif // _Standard_PErrorHandler_HeaderFile
-24
View File
@@ -1,24 +0,0 @@
puts "================"
puts "0030775: OSD::SetSignal() within OSD_ThreadPool should not override global handlers"
puts "================"
puts ""
pload QAcommands
dsetsignal set
set IsDone [catch {set aResult [OCC30775]} result]
if { ${IsDone} != 0 } {
puts "result = ${result}"
puts "Error: command raised exception"
} else {
if { [string first "NbRaised: 100" $aResult] != -1 } {
puts "OK test case"
} else {
puts "Error: expected to have 100 raised exceptions"
}
}
# restore defaults
dsetsignal