[11382:11382:0403/000335.346785:ERROR:main_thread_scheduler_impl.cc:1037] POC: force-shutdown type=14 (only 2 future-delayed tasks remain, no immediate work) [11382:11382:0403/000335.347247:ERROR:main_thread_task_queue.cc:208] POC: ShutdownTaskQueue queue=0x7c9d0346cb80 type=14 [11382:11405:0403/000335.833803:ERROR:rtc_stats.cc:108] POC: OnStatsDelivered posted=0 on_main=0 runner=0x7bbd03494640 [11382:11405:0403/000335.834362:FATAL:rtc_stats.cc:84] Check failed: main_thread_->BelongsToCurrentThread(). RTCStatsCollectorCallbackImpl destroyed on signaling thread with live callback_: cppgc::Persistent will be freed off-main-thread, racing PersistentRegion::free_list_head_ pid 11382, tid 11382 = renderer main thread pid 11382, tid 11405 = WebRTC signaling thread Symbolized stack (signaling thread, top-to-bottom): #7 rtc_stats.cc:84 ~RTCStatsCollectorCallbackImpl <-- CHECK fires #8 ref_counted_object.h:56 webrtc::RefCountedObject::Release -> delete this #9 ref_counted_object.h:42 ~RefCountedObject<> #10 scoped_refptr.h:105 ~scoped_refptr <-- last ref drops on signaling thread #11 rtc_stats_collector.cc:1482 lambda inside ProducePartialResultsOnNetworkThread -> OnNetworkReportReady -> requests_ scope-end #12 any_invocable.h:774 absl::AnyInvocable invoke #13 bind_internal.h:740 base::BindOnce invoke #14 callback.h:155 base::OnceCallback::Run #15 task_annotator.h:112 TaskAnnotator::RunTask ... (signaling thread message loop) In a release build (dcheck_always_on=false, no POC CHECK), execution would continue past frame #7: ~RTCStatsCollectorCallbackImpl -> ~RTCStatsReportCallback (callback_, a base::OnceCallback) -> ~BindState -> ~Persistent [SIGNALING THREAD] -> PersistentRegion::FreeNode(node_) [SIGNALING THREAD] node->InitializeAsFreeNode(free_list_head_); free_list_head_ = node; <-- UNLOCKED WRITE --nodes_in_use_; Concurrently on the main thread, any WrapPersistent(...) call: PersistentRegion::TryAllocateNodeFromFreeList(...) PersistentNode* node = free_list_head_; <-- UNLOCKED READ free_list_head_ = node->FreeListNext(); <-- reads node->next_ from union node->InitializeAsUsedNode(owner, trace); <-- writes node->owner_ over union ++nodes_in_use_; PersistentNode is a union { void* owner_; PersistentNode* next_; }. When the signaling-thread FreeNode interleaves with main-thread Allocate, free_list_head_ walks into a node whose `next_` field has been overwritten with an arbitrary heap pointer (the owner_ of a live Persistent). The next allocation returns that arbitrary pointer as a "free" PersistentNode, corrupting the GC root set.