sdk: Fail gracefully when attempting to trace on an invalid backend
Adds a TracingBackendFake instance to TracingMuxerImpl, which
is used as a fallback backend for new tracing sessions in case they
specify a backend type that isn't available, e.g., because it wasn't
registered in Tracing::Initialize().
This backend instance simply disconnects consumers when they attempt to
connect. It still allows the SDK to register a producer with the
backend (in order to avoid a lot of connection retries or special
casing of the backend in the muxer), but ignores any data source
registrations.
Also fixes a few races during session callback registration.
Bug: 179761322
Change-Id: I1580461aad3711fb21f99db8e735d84ffe8cebda
diff --git a/Android.bp b/Android.bp
index 3352769..82338ee 100644
--- a/Android.bp
+++ b/Android.bp
@@ -8260,6 +8260,7 @@
"src/tracing/interceptor.cc",
"src/tracing/internal/checked_scope.cc",
"src/tracing/internal/interceptor_trace_writer.cc",
+ "src/tracing/internal/tracing_backend_fake.cc",
"src/tracing/internal/tracing_muxer_fake.cc",
"src/tracing/internal/tracing_muxer_impl.cc",
"src/tracing/internal/track_event_internal.cc",
diff --git a/BUILD b/BUILD
index 525a525..f151b0d 100644
--- a/BUILD
+++ b/BUILD
@@ -520,6 +520,7 @@
"include/perfetto/tracing/internal/in_process_tracing_backend.h",
"include/perfetto/tracing/internal/interceptor_trace_writer.h",
"include/perfetto/tracing/internal/system_tracing_backend.h",
+ "include/perfetto/tracing/internal/tracing_backend_fake.h",
"include/perfetto/tracing/internal/tracing_muxer.h",
"include/perfetto/tracing/internal/tracing_tls.h",
"include/perfetto/tracing/internal/track_event_data_source.h",
@@ -1594,6 +1595,7 @@
"src/tracing/interceptor.cc",
"src/tracing/internal/checked_scope.cc",
"src/tracing/internal/interceptor_trace_writer.cc",
+ "src/tracing/internal/tracing_backend_fake.cc",
"src/tracing/internal/tracing_muxer_fake.cc",
"src/tracing/internal/tracing_muxer_fake.h",
"src/tracing/internal/tracing_muxer_impl.cc",
diff --git a/include/perfetto/tracing/BUILD.gn b/include/perfetto/tracing/BUILD.gn
index 414e40f..39d1250 100644
--- a/include/perfetto/tracing/BUILD.gn
+++ b/include/perfetto/tracing/BUILD.gn
@@ -39,6 +39,7 @@
"internal/in_process_tracing_backend.h",
"internal/interceptor_trace_writer.h",
"internal/system_tracing_backend.h",
+ "internal/tracing_backend_fake.h",
"internal/tracing_muxer.h",
"internal/tracing_tls.h",
"internal/track_event_data_source.h",
diff --git a/include/perfetto/tracing/internal/tracing_backend_fake.h b/include/perfetto/tracing/internal/tracing_backend_fake.h
new file mode 100644
index 0000000..92c8ea1
--- /dev/null
+++ b/include/perfetto/tracing/internal/tracing_backend_fake.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef INCLUDE_PERFETTO_TRACING_INTERNAL_TRACING_BACKEND_FAKE_H_
+#define INCLUDE_PERFETTO_TRACING_INTERNAL_TRACING_BACKEND_FAKE_H_
+
+#include "perfetto/base/export.h"
+#include "perfetto/tracing/tracing_backend.h"
+
+namespace perfetto {
+namespace internal {
+
+// A built-in implementation of TracingBackend that fails any attempt to create
+// a tracing session.
+class PERFETTO_EXPORT TracingBackendFake : public TracingBackend {
+ public:
+ static TracingBackend* GetInstance();
+
+ // TracingBackend implementation.
+ std::unique_ptr<ProducerEndpoint> ConnectProducer(
+ const ConnectProducerArgs&) override;
+ std::unique_ptr<ConsumerEndpoint> ConnectConsumer(
+ const ConnectConsumerArgs&) override;
+
+ private:
+ TracingBackendFake();
+};
+
+} // namespace internal
+} // namespace perfetto
+
+#endif // INCLUDE_PERFETTO_TRACING_INTERNAL_TRACING_BACKEND_FAKE_H_
diff --git a/src/tracing/BUILD.gn b/src/tracing/BUILD.gn
index f4b0b79..43ce774 100644
--- a/src/tracing/BUILD.gn
+++ b/src/tracing/BUILD.gn
@@ -82,7 +82,8 @@
sources = [ "trace_writer_base.cc" ]
}
-# Base target for the client API. On its own doesn't provide any backend.
+# Base target for the client API. On its own doesn't provide any backend other
+# than the unsupported one.
source_set("client_api_without_backends") {
deps = [
"../../include/perfetto/tracing/core",
@@ -105,6 +106,7 @@
"interceptor.cc",
"internal/checked_scope.cc",
"internal/interceptor_trace_writer.cc",
+ "internal/tracing_backend_fake.cc",
"internal/tracing_muxer_fake.cc",
"internal/tracing_muxer_fake.h",
"internal/tracing_muxer_impl.cc",
diff --git a/src/tracing/internal/tracing_backend_fake.cc b/src/tracing/internal/tracing_backend_fake.cc
new file mode 100644
index 0000000..3c64fed
--- /dev/null
+++ b/src/tracing/internal/tracing_backend_fake.cc
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "perfetto/tracing/internal/tracing_backend_fake.h"
+
+#include "perfetto/base/task_runner.h"
+#include "perfetto/ext/base/weak_ptr.h"
+#include "perfetto/ext/tracing/core/consumer.h"
+#include "perfetto/ext/tracing/core/producer.h"
+#include "perfetto/ext/tracing/core/trace_writer.h"
+#include "perfetto/ext/tracing/core/tracing_service.h"
+
+namespace perfetto {
+namespace internal {
+
+namespace {
+
+class UnsupportedProducerEndpoint : public ProducerEndpoint {
+ public:
+ UnsupportedProducerEndpoint(Producer* producer, base::TaskRunner* task_runner)
+ : producer_(producer), task_runner_(task_runner) {
+ // The SDK will attempt to reconnect the producer, so instead we allow it
+ // to connect successfully, but never start any sessions.
+ auto weak_ptr = weak_ptr_factory_.GetWeakPtr();
+ task_runner_->PostTask([weak_ptr] {
+ if (weak_ptr)
+ weak_ptr->producer_->OnConnect();
+ });
+ }
+ ~UnsupportedProducerEndpoint() override { producer_->OnDisconnect(); }
+
+ void RegisterDataSource(const DataSourceDescriptor&) override {}
+ void UnregisterDataSource(const std::string& /*name*/) override {}
+
+ void RegisterTraceWriter(uint32_t /*writer_id*/,
+ uint32_t /*target_buffer*/) override {}
+ void UnregisterTraceWriter(uint32_t /*writer_id*/) override {}
+
+ void CommitData(const CommitDataRequest&,
+ CommitDataCallback callback = {}) override {
+ callback();
+ }
+
+ SharedMemory* shared_memory() const override { return nullptr; }
+ size_t shared_buffer_page_size_kb() const override { return 0; }
+
+ std::unique_ptr<TraceWriter> CreateTraceWriter(
+ BufferID /*target_buffer*/,
+ BufferExhaustedPolicy = BufferExhaustedPolicy::kDefault) override {
+ return nullptr;
+ }
+
+ SharedMemoryArbiter* MaybeSharedMemoryArbiter() override { return nullptr; }
+ bool IsShmemProvidedByProducer() const override { return false; }
+
+ void NotifyFlushComplete(FlushRequestID) override {}
+ void NotifyDataSourceStarted(DataSourceInstanceID) override {}
+ void NotifyDataSourceStopped(DataSourceInstanceID) override {}
+ void ActivateTriggers(const std::vector<std::string>&) override {}
+
+ void Sync(std::function<void()> callback) override { callback(); }
+
+ private:
+ Producer* const producer_;
+ base::TaskRunner* const task_runner_;
+ base::WeakPtrFactory<UnsupportedProducerEndpoint> weak_ptr_factory_{
+ this}; // Keep last.
+};
+
+class UnsupportedConsumerEndpoint : public ConsumerEndpoint {
+ public:
+ UnsupportedConsumerEndpoint(Consumer* consumer, base::TaskRunner* task_runner)
+ : consumer_(consumer), task_runner_(task_runner) {
+ // The SDK will not to reconnect the consumer, so we just disconnect it
+ // immediately, which will cancel the tracing session.
+ auto weak_this = weak_ptr_factory_.GetWeakPtr();
+ task_runner_->PostTask([weak_this] {
+ if (weak_this)
+ weak_this->consumer_->OnDisconnect();
+ });
+ }
+ ~UnsupportedConsumerEndpoint() override = default;
+
+ void EnableTracing(const TraceConfig&,
+ base::ScopedFile = base::ScopedFile()) override {}
+ void ChangeTraceConfig(const TraceConfig&) override {}
+
+ void StartTracing() override {}
+ void DisableTracing() override {}
+
+ void Flush(uint32_t /*timeout_ms*/, FlushCallback callback) override {
+ callback(/*success=*/false);
+ }
+
+ void ReadBuffers() override {}
+ void FreeBuffers() override {}
+
+ void Detach(const std::string& /*key*/) override {}
+ void Attach(const std::string& /*key*/) override {}
+
+ void GetTraceStats() override {}
+ void ObserveEvents(uint32_t /*events_mask*/) override {}
+ void QueryServiceState(QueryServiceStateCallback) override {}
+ void QueryCapabilities(QueryCapabilitiesCallback) override {}
+
+ void SaveTraceForBugreport(SaveTraceForBugreportCallback) override {}
+
+ private:
+ Consumer* const consumer_;
+ base::TaskRunner* const task_runner_;
+ base::WeakPtrFactory<UnsupportedConsumerEndpoint> weak_ptr_factory_{
+ this}; // Keep last.
+};
+
+} // namespace
+
+// static
+TracingBackend* TracingBackendFake::GetInstance() {
+ static auto* instance = new TracingBackendFake();
+ return instance;
+}
+
+TracingBackendFake::TracingBackendFake() = default;
+
+std::unique_ptr<ProducerEndpoint> TracingBackendFake::ConnectProducer(
+ const ConnectProducerArgs& args) {
+ return std::unique_ptr<ProducerEndpoint>(
+ new UnsupportedProducerEndpoint(args.producer, args.task_runner));
+}
+
+std::unique_ptr<ConsumerEndpoint> TracingBackendFake::ConnectConsumer(
+ const ConnectConsumerArgs& args) {
+ return std::unique_ptr<ConsumerEndpoint>(
+ new UnsupportedConsumerEndpoint(args.consumer, args.task_runner));
+}
+
+} // namespace internal
+} // namespace perfetto
diff --git a/src/tracing/internal/tracing_muxer_impl.cc b/src/tracing/internal/tracing_muxer_impl.cc
index 54a355c..eed7477 100644
--- a/src/tracing/internal/tracing_muxer_impl.cc
+++ b/src/tracing/internal/tracing_muxer_impl.cc
@@ -38,6 +38,7 @@
#include "perfetto/tracing/data_source.h"
#include "perfetto/tracing/internal/data_source_internal.h"
#include "perfetto/tracing/internal/interceptor_trace_writer.h"
+#include "perfetto/tracing/internal/tracing_backend_fake.h"
#include "perfetto/tracing/trace_writer_base.h"
#include "perfetto/tracing/tracing.h"
#include "perfetto/tracing/tracing_backend.h"
@@ -572,6 +573,8 @@
auto session_id = session_id_;
muxer->task_runner_->PostTask([muxer, session_id, cb] {
auto* consumer = muxer->FindConsumer(session_id);
+ if (!consumer)
+ return;
consumer->start_complete_callback_ = cb;
});
}
@@ -583,8 +586,12 @@
auto session_id = session_id_;
muxer->task_runner_->PostTask([muxer, session_id, cb] {
auto* consumer = muxer->FindConsumer(session_id);
- if (!consumer)
+ if (!consumer) {
+ // Notify the client about concurrent disconnection of the session.
+ if (cb)
+ cb(TracingError{TracingError::kDisconnected, "Peer disconnected"});
return;
+ }
consumer->error_callback_ = cb;
});
}
@@ -596,6 +603,8 @@
auto session_id = session_id_;
muxer->task_runner_->PostTask([muxer, session_id, cb] {
auto* consumer = muxer->FindConsumer(session_id);
+ if (!consumer)
+ return;
consumer->stop_complete_callback_ = cb;
});
}
@@ -686,6 +695,12 @@
if (args.backends & ~(kSystemBackend | kInProcessBackend | kCustomBackend)) {
PERFETTO_FATAL("Unsupported tracing backend type");
}
+
+ // Fallback backend for consumer creation for an unsupported backend type.
+ // This backend simply fails any attempt to start a tracing session.
+ // NOTE: This backend instance has to be added last.
+ add_backend(internal::TracingBackendFake::GetInstance(),
+ BackendType::kUnspecifiedBackend);
}
// Can be called from any thread (but not concurrently).
@@ -1435,17 +1450,27 @@
// This is called via the public API Tracing::NewTrace().
// Can be called from any thread.
std::unique_ptr<TracingSession> TracingMuxerImpl::CreateTracingSession(
- BackendType backend_type) {
+ BackendType requested_backend_type) {
TracingSessionGlobalID session_id = ++next_tracing_session_id_;
// |backend_type| can only specify one backend, not an OR-ed mask.
- PERFETTO_CHECK((backend_type & (backend_type - 1)) == 0);
+ PERFETTO_CHECK((requested_backend_type & (requested_backend_type - 1)) == 0);
// Capturing |this| is fine because the TracingMuxer is a leaky singleton.
- task_runner_->PostTask([this, backend_type, session_id] {
+ task_runner_->PostTask([this, requested_backend_type, session_id] {
for (RegisteredBackend& backend : backends_) {
- if (backend_type && backend.type != backend_type)
+ if (requested_backend_type && backend.type &&
+ backend.type != requested_backend_type) {
continue;
+ }
+
+ // The last registered backend in |backends_| is the unsupported backend
+ // without a valid type.
+ if (!backend.type) {
+ PERFETTO_ELOG(
+ "No tracing backend ready for type=%d, consumer will disconnect",
+ requested_backend_type);
+ }
backend.consumers.emplace_back(
new ConsumerImpl(this, backend.type, backend.id, session_id));
@@ -1456,13 +1481,11 @@
consumer->Initialize(backend.backend->ConnectConsumer(conn_args));
return;
}
- PERFETTO_ELOG(
- "Cannot create tracing session, no tracing backend ready for type=%d",
- backend_type);
+ PERFETTO_DFATAL("Not reached");
});
return std::unique_ptr<TracingSession>(
- new TracingSessionImpl(this, session_id, backend_type));
+ new TracingSessionImpl(this, session_id, requested_backend_type));
}
void TracingMuxerImpl::InitializeInstance(const TracingInitArgs& args) {
diff --git a/src/tracing/test/api_integrationtest.cc b/src/tracing/test/api_integrationtest.cc
index acb86a0..69f4401 100644
--- a/src/tracing/test/api_integrationtest.cc
+++ b/src/tracing/test/api_integrationtest.cc
@@ -407,9 +407,15 @@
TestTracingSessionHandle* NewTrace(const perfetto::TraceConfig& cfg,
int fd = -1) {
+ return NewTrace(cfg, /*backend_type=*/GetParam(), fd);
+ }
+
+ TestTracingSessionHandle* NewTrace(const perfetto::TraceConfig& cfg,
+ perfetto::BackendType backend_type,
+ int fd = -1) {
sessions_.emplace_back();
TestTracingSessionHandle* handle = &sessions_.back();
- handle->session = perfetto::Tracing::NewTrace(/*BackendType=*/GetParam());
+ handle->session = perfetto::Tracing::NewTrace(backend_type);
handle->session->SetOnStopCallback([handle] { handle->on_stop.Notify(); });
handle->session->Setup(cfg, fd);
return handle;
@@ -2729,6 +2735,28 @@
tracing_session->get()->StopBlocking();
}
+TEST_P(PerfettoApiTest, UnsupportedBackend) {
+ // Create a new trace session with an invalid backend type specified.
+ // Specifically, the custom backend isn't initialized for these tests.
+ perfetto::TraceConfig cfg;
+ cfg.add_buffers()->set_size_kb(1024);
+ auto* tracing_session = NewTrace(cfg, perfetto::BackendType::kCustomBackend);
+
+ // Creating the consumer should cause an asynchronous disconnect error.
+ WaitableTestEvent got_error;
+ tracing_session->get()->SetOnErrorCallback([&](perfetto::TracingError error) {
+ EXPECT_EQ(perfetto::TracingError::kDisconnected, error.code);
+ EXPECT_FALSE(error.message.empty());
+ got_error.Notify();
+ });
+ got_error.Wait();
+
+ // Clear the callback for test tear down.
+ tracing_session->get()->SetOnErrorCallback(nullptr);
+ // Synchronize the consumer channel to ensure the callback has propagated.
+ tracing_session->get()->StopBlocking();
+}
+
TEST_P(PerfettoApiTest, GetTraceStats) {
perfetto::TraceConfig cfg;
cfg.set_duration_ms(500);