mirror of
https://github.com/wolfpld/tracy.git
synced 2026-09-11 21:08:20 +00:00
Recover main thread producer orphaned by cross-module init order.
ELF init_priority only orders constructors within a single module. All of a shared object's initializers run before any of the executable's, so an instrumented dependency .so emitting a zone from its static initializer creates the main thread producer token against the zero-initialized s_queue. The queue constructor then resets the producer list, orphaning that producer: every zone emitted on the main thread from that point on is enqueued into blocks no consumer ever iterates and silently lost, while sampling (worker thread producer) keeps working. Re-link such a producer right after the queue is constructed. In the common case, where nothing was emitted during shared object init, this merely constructs the main thread token eagerly.
This commit is contained in:
@@ -1425,6 +1425,22 @@ std::atomic<int> init_order(102) RpInitLock( 0 );
|
||||
thread_local bool RpThreadInitDone = false;
|
||||
thread_local bool RpThreadShutdown = false;
|
||||
moodycamel::ConcurrentQueue<QueueItem> init_order(103) s_queue( QueuePrealloc );
|
||||
|
||||
# ifndef _MSC_VER
|
||||
// An instrumented shared object may emit zones from its static initializers, which the
|
||||
// dynamic loader runs before any of the executable's constructors, including the
|
||||
// priority-ordered constructor of s_queue above. The main thread producer token (s_token)
|
||||
// is then lazily created against the zero-initialized queue memory, and the queue
|
||||
// constructor subsequently orphans it, making all zones emitted on the main thread
|
||||
// invisible to the consumer. Re-adopt such a producer here. If no zones were emitted up
|
||||
// to this point, this only triggers construction of s_token, which is a no-op repair.
|
||||
struct EarlyMainThreadTokenRepair
|
||||
{
|
||||
EarlyMainThreadTokenRepair() { if( s_token.ptr ) s_queue.readopt_orphaned_producer( s_token.ptr ); }
|
||||
};
|
||||
static EarlyMainThreadTokenRepair init_order(104) s_earlyMainThreadTokenRepair;
|
||||
# endif
|
||||
|
||||
std::atomic<uint32_t> init_order(104) s_lockCounter( 0 );
|
||||
std::atomic<uint8_t> init_order(104) s_gpuCtxCounter( 0 );
|
||||
|
||||
|
||||
@@ -1210,6 +1210,21 @@ private:
|
||||
return static_cast<ExplicitProducer*>(token.producer);
|
||||
}
|
||||
|
||||
// If a producer token is created before the constructor of a statically allocated
|
||||
// queue runs (which may happen due to the undefined order of static initialization
|
||||
// across module boundaries), the constructor will orphan it by resetting the
|
||||
// producer list. Such a producer is functional, as producer creation works on the
|
||||
// zero-initialized queue memory, but the consumer is not able to see the data it
|
||||
// enqueues. This method links the producer back into the list.
|
||||
bool readopt_orphaned_producer(ExplicitProducer* producer)
|
||||
{
|
||||
for (auto ptr = producerListTail.load(std::memory_order_relaxed); ptr != nullptr; ptr = ptr->next_prod()) {
|
||||
if (ptr == static_cast<ProducerBase*>(producer)) return false;
|
||||
}
|
||||
add_producer(static_cast<ProducerBase*>(producer));
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
//////////////////////////////////
|
||||
|
||||
Reference in New Issue
Block a user