Skip to content

Commit 5332ce0

Browse files
committed
Fixed teardown race condition w/static loader
Signed-off-by: Russell McGuire <russell.w.mcguire@intel.com>
1 parent 25af48a commit 5332ce0

5 files changed

Lines changed: 33 additions & 18 deletions

File tree

source/layers/validation/ze_validation_layer.cpp

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,11 @@ namespace validation_layer
2525
enableThreadingValidation = getenv_tobool( "ZE_ENABLE_THREADING_VALIDATION" );
2626
verboseLogging = getenv_tobool( "ZEL_LOADER_LOGGING_ENABLE_SUCCESS_PRINT" );
2727

28-
// Initialize logger to a no-op sentinel (level=off, no file/console I/O, no banner).
29-
// This is purely crash protection: in normal operation the loader calls
30-
// zelLoaderSetLogger() immediately after dlopen — before the DDI tables
31-
// go live — so no real log call ever hits this sentinel.
32-
//
33-
// Thread-safety note: zelLoaderSetLogger() writes this field once, on the
34-
// init thread, before zeDdiTable.exchange() makes the validation layer
35-
// reachable from other threads. The non-atomic shared_ptr assignment is
36-
// therefore safe in practice; no mutex is needed here.
37-
logger = std::shared_ptr<loader::ZeLogger>(new loader::ZeLogger()); // no-op sentinel: no sink, no mutex, no syscalls
28+
// Point at the process-lifetime no-op logger until the loader calls
29+
// zelLoaderSetLogger(). This is never null, so call sites need no null check.
30+
// Thread-safety: zelLoaderSetLogger() writes this field exactly once on the
31+
// init thread before zeDdiTable.exchange() makes the layer reachable.
32+
logger = loader::noopLogger();
3833
}
3934

4035
///////////////////////////////////////////////////////////////////////////////
@@ -64,13 +59,13 @@ zelLoaderGetVersion(zel_component_version_t *version)
6459
}
6560

6661
/// @brief Called by the loader immediately after dlopen to share its logger.
67-
/// Replaces the fallback logger created in the constructor so that
68-
/// validation-layer messages flow through the same sink as the loader.
62+
/// Replaces the no-op default so that validation-layer messages flow
63+
/// through the same sink as the loader.
6964
ZE_DLLEXPORT void ZE_APICALL
70-
zelLoaderSetLogger(std::shared_ptr<loader::ZeLogger> *loaderLogger)
65+
zelLoaderSetLogger(loader::ZeLogger *loaderLogger)
7166
{
72-
if (loaderLogger && *loaderLogger) {
73-
validation_layer::context_t::getInstance().logger = *loaderLogger;
67+
if (loaderLogger) {
68+
validation_layer::context_t::getInstance().logger = loaderLogger;
7469
}
7570
}
7671

source/layers/validation/ze_validation_layer.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,12 @@ namespace validation_layer
5757
std::vector<validationChecker *> validationHandlers;
5858
std::unique_ptr<HandleLifetimeValidation> handleLifetime;
5959

60-
std::shared_ptr<loader::ZeLogger> logger;
60+
// Raw pointer — the loader owns the ZeLogger and guarantees it outlives
61+
// the validation layer during normal operation (dlclose happens before
62+
// zel_logger is destroyed in context_t::~context_t()). Using a raw pointer
63+
// (rather than shared_ptr) avoids _Sp_counted_base::_M_release() being
64+
// called during _dl_call_fini after the control block has been freed.
65+
loader::ZeLogger *logger;
6166

6267
static context_t& getInstance() {
6368
static context_t instance;

source/loader/ze_loader.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -780,11 +780,11 @@ namespace loader
780780
}
781781
// Inject this loader instance's logger into the validation layer
782782
// so both share a single file handle and mutex.
783-
using SetLoggerFn = void (*)(std::shared_ptr<loader::ZeLogger> *);
783+
using SetLoggerFn = void (*)(loader::ZeLogger *);
784784
auto setLogger = reinterpret_cast<SetLoggerFn>(
785785
GET_FUNCTION_PTR(validationLayer, "zelLoaderSetLogger"));
786786
if (setLogger) {
787-
setLogger(&zel_logger);
787+
setLogger(zel_logger.get());
788788
}
789789

790790
auto getVersion = reinterpret_cast<getVersion_t>(

source/utils/ze_logger.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,4 +600,13 @@ std::shared_ptr<ZeLogger> createLogger(const std::string &caller) {
600600
return logger;
601601
}
602602

603+
// Returns a pointer to a process-lifetime no-op ZeLogger.
604+
// Using a function-local static here gives STB_LOCAL linkage (non-inline, non-template .cpp
605+
// function), avoiding STB_GNU_UNIQUE. The instance is never destroyed, which is intentional:
606+
// components holding a raw pointer to this object are safe regardless of shutdown order.
607+
ZeLogger *noopLogger() {
608+
static ZeLogger instance; // default constructor: level=off, _sink=nullptr
609+
return &instance;
610+
}
611+
603612
} // namespace loader

source/utils/ze_logger.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ std::string to_string(ze_result_t result);
9595
// Factory: reads ZEL_* env vars and constructs an appropriately configured logger.
9696
std::shared_ptr<ZeLogger> createLogger(const std::string &caller = "Loader");
9797

98+
// A permanently-alive no-op logger instance suitable for use as a raw-pointer
99+
// default in components (e.g. the validation layer) that must never hold a
100+
// shared_ptr across dlclose/process-exit boundaries.
101+
// Level=off, no sink, no I/O. Safe to call from any thread at any time.
102+
ZeLogger *noopLogger();
103+
98104
} // namespace loader
99105

100106
#endif // ZE_LOGGER_H

0 commit comments

Comments
 (0)