Fix potential spinlock timeout for heap disable callbacks.
If the disabled callback for one heap that was enabled triggered
operations on another one, we could re-enter AHeapProfile_report*
while holding the spinlock.
Change-Id: I43d6f9d7676c3a57ed62eedf615db8fe1c3d71ac
diff --git a/src/profiling/memory/client_api.cc b/src/profiling/memory/client_api.cc
index bdede0d..9c3f6d1 100644
--- a/src/profiling/memory/client_api.cc
+++ b/src/profiling/memory/client_api.cc
@@ -133,32 +133,49 @@
std::atomic<uint32_t> g_next_heap_id{kMinHeapId};
+// This can get called while holding the spinlock (in normal operation), or
+// without holding the spinlock (from OnSpinlockTimeout).
void DisableAllHeaps() {
- for (uint32_t i = kMinHeapId; i < g_next_heap_id.load(); ++i) {
+ bool disabled[perfetto::base::ArraySize(g_heaps)] = {};
+ uint32_t max_heap = g_next_heap_id.load();
+ // This has to be done in two passes, in case the disabled_callback for one
+ // enabled heap uses another. In that case, the callbacks for the other heap
+ // would time out trying to acquire the spinlock, which we hold here.
+ for (uint32_t i = kMinHeapId; i < max_heap; ++i) {
AHeapInfo& info = GetHeap(i);
if (!info.ready.load(std::memory_order_acquire))
continue;
- if (info.enabled.load(std::memory_order_acquire)) {
- info.enabled.store(false, std::memory_order_release);
- if (info.disabled_callback) {
- AHeapProfileDisableCallbackInfo disable_info;
- info.disabled_callback(info.disabled_callback_data, &disable_info);
- }
+ disabled[i] = info.enabled.exchange(false, std::memory_order_acq_rel);
+ }
+ for (uint32_t i = kMinHeapId; i < max_heap; ++i) {
+ if (!disabled[i]) {
+ continue;
+ }
+ AHeapInfo& info = GetHeap(i);
+ if (info.disabled_callback) {
+ AHeapProfileDisableCallbackInfo disable_info;
+ info.disabled_callback(info.disabled_callback_data, &disable_info);
}
}
}
+#pragma GCC diagnostic push
+#if PERFETTO_DCHECK_IS_ON()
+#pragma GCC diagnostic ignored "-Wmissing-noreturn"
+#endif
+
void OnSpinlockTimeout() {
// Give up on profiling the process but leave it running.
// The process enters into a poisoned state and will reject all
// subsequent profiling requests. The current session is kept
// running but no samples are reported to it.
- PERFETTO_ELOG(
+ PERFETTO_DFATAL_OR_ELOG(
"Timed out on the spinlock - something is horribly wrong. "
"Leaking heapprofd client.");
DisableAllHeaps();
perfetto::profiling::PoisonSpinlock(&g_client_lock);
}
+#pragma GCC diagnostic pop
// Note: g_client can be reset by AHeapProfile_initSession without calling this
// function.
diff --git a/src/profiling/memory/heapprofd_end_to_end_test.cc b/src/profiling/memory/heapprofd_end_to_end_test.cc
index d406ace..525d865 100644
--- a/src/profiling/memory/heapprofd_end_to_end_test.cc
+++ b/src/profiling/memory/heapprofd_end_to_end_test.cc
@@ -19,6 +19,7 @@
#include <vector>
#include <fcntl.h>
+#include <stdint.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
@@ -454,6 +455,7 @@
static std::atomic<bool> disabled{false};
static std::atomic<uint64_t> sampling_interval;
+ static uint32_t other_heap_id = 0;
auto enabled_callback = [](void*,
const AHeapProfileEnableCallbackInfo* info) {
sampling_interval =
@@ -461,6 +463,8 @@
initialized = true;
};
auto disabled_callback = [](void*, const AHeapProfileDisableCallbackInfo*) {
+ PERFETTO_CHECK(other_heap_id);
+ AHeapProfile_reportFree(other_heap_id, 0);
disabled = true;
};
static uint32_t heap_id =
@@ -469,6 +473,7 @@
enabled_callback, nullptr),
disabled_callback, nullptr));
+ other_heap_id = AHeapProfile_registerHeap(AHeapInfo_create("othertest"));
ChildFinishHandshake();
// heapprofd_client needs malloc to see the signal.
@@ -1136,6 +1141,7 @@
cfg->set_sampling_interval_bytes(1000000);
cfg->add_pid(pid);
cfg->add_heaps("test");
+ cfg->add_heaps("othertest");
});
auto helper = Trace(trace_config);