1 /*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "src/ipc/host_impl.h"
18
19 #include <memory>
20
21 #include "perfetto/ext/base/file_utils.h"
22 #include "perfetto/ext/base/scoped_file.h"
23 #include "perfetto/ext/base/temp_file.h"
24 #include "perfetto/ext/base/unix_socket.h"
25 #include "perfetto/ext/base/utils.h"
26 #include "perfetto/ext/ipc/service.h"
27 #include "perfetto/ext/ipc/service_descriptor.h"
28 #include "src/base/test/test_task_runner.h"
29 #include "src/ipc/buffered_frame_deserializer.h"
30 #include "src/ipc/test/test_socket.h"
31 #include "test/gtest_and_gmock.h"
32
33 #include "protos/perfetto/ipc/wire_protocol.gen.h"
34 #include "src/ipc/test/client_unittest_messages.gen.h"
35
36 namespace perfetto {
37 namespace ipc {
38 namespace {
39
40 using ::perfetto::ipc::gen::ReplyProto;
41 using ::perfetto::ipc::gen::RequestProto;
42 using ::testing::_;
43 using ::testing::Invoke;
44 using ::testing::InvokeWithoutArgs;
45 using ::testing::Return;
46
47 ipc::TestSocket kTestSocket{"host_impl_unittest"};
48
49 // RequestProto and ReplyProto are defined in client_unittest_messages.proto.
50
51 class FakeService : public Service {
52 public:
53 MOCK_METHOD2(OnFakeMethod1, void(const RequestProto&, DeferredBase*));
54
Invoker(Service * service,const ProtoMessage & req,DeferredBase deferred_reply)55 static void Invoker(Service* service,
56 const ProtoMessage& req,
57 DeferredBase deferred_reply) {
58 static_cast<FakeService*>(service)->OnFakeMethod1(
59 static_cast<const RequestProto&>(req), &deferred_reply);
60 }
61
RequestDecoder(const std::string & proto)62 static std::unique_ptr<ProtoMessage> RequestDecoder(
63 const std::string& proto) {
64 std::unique_ptr<ProtoMessage> reply(new RequestProto());
65 EXPECT_TRUE(reply->ParseFromString(proto));
66 return reply;
67 }
68
FakeService(const char * service_name)69 explicit FakeService(const char* service_name) {
70 descriptor_.service_name = service_name;
71 descriptor_.methods.push_back(
72 {"FakeMethod1", &RequestDecoder, nullptr, &Invoker});
73 }
74
GetDescriptor()75 const ServiceDescriptor& GetDescriptor() override { return descriptor_; }
76
TakeReceivedFD()77 base::ScopedFile TakeReceivedFD() { return ipc::Service::TakeReceivedFD(); }
78
79 base::ScopedFile received_fd_;
80 ServiceDescriptor descriptor_;
81 };
82
83 class FakeClient : public base::UnixSocket::EventListener {
84 public:
85 MOCK_METHOD0(OnConnect, void());
86 MOCK_METHOD0(OnDisconnect, void());
87 MOCK_METHOD1(OnServiceBound, void(const Frame::BindServiceReply&));
88 MOCK_METHOD1(OnInvokeMethodReply, void(const Frame::InvokeMethodReply&));
89 MOCK_METHOD1(OnFileDescriptorReceived, void(int));
90 MOCK_METHOD0(OnRequestError, void());
91
FakeClient(base::TaskRunner * task_runner)92 explicit FakeClient(base::TaskRunner* task_runner) {
93 sock_ = base::UnixSocket::Connect(kTestSocket.name(), this, task_runner,
94 kTestSocket.family(),
95 base::SockType::kStream);
96 }
97
98 ~FakeClient() override = default;
99
BindService(const std::string & service_name)100 void BindService(const std::string& service_name) {
101 Frame frame;
102 uint64_t request_id = requests_.empty() ? 1 : requests_.rbegin()->first + 1;
103 requests_.emplace(request_id, 0);
104 frame.set_request_id(request_id);
105 frame.mutable_msg_bind_service()->set_service_name(service_name);
106 SendFrame(frame);
107 }
108
InvokeMethod(ServiceID service_id,MethodID method_id,const ProtoMessage & args,bool drop_reply=false,int fd=-1)109 void InvokeMethod(ServiceID service_id,
110 MethodID method_id,
111 const ProtoMessage& args,
112 bool drop_reply = false,
113 int fd = -1) {
114 Frame frame;
115 uint64_t request_id = requests_.empty() ? 1 : requests_.rbegin()->first + 1;
116 requests_.emplace(request_id, 0);
117 frame.set_request_id(request_id);
118 frame.mutable_msg_invoke_method()->set_service_id(service_id);
119 frame.mutable_msg_invoke_method()->set_method_id(method_id);
120 frame.mutable_msg_invoke_method()->set_drop_reply(drop_reply);
121 frame.mutable_msg_invoke_method()->set_args_proto(args.SerializeAsString());
122 SendFrame(frame, fd);
123 }
124
125 // base::UnixSocket::EventListener implementation.
OnConnect(base::UnixSocket *,bool success)126 void OnConnect(base::UnixSocket*, bool success) override {
127 ASSERT_TRUE(success);
128 OnConnect();
129 }
130
OnDisconnect(base::UnixSocket *)131 void OnDisconnect(base::UnixSocket*) override { OnDisconnect(); }
132
OnDataAvailable(base::UnixSocket * sock)133 void OnDataAvailable(base::UnixSocket* sock) override {
134 ASSERT_EQ(sock_.get(), sock);
135 auto buf = frame_deserializer_.BeginReceive();
136 base::ScopedFile fd;
137 size_t rsize = sock->Receive(buf.data, buf.size, &fd);
138 ASSERT_TRUE(frame_deserializer_.EndReceive(rsize));
139 if (fd)
140 OnFileDescriptorReceived(*fd);
141 while (std::unique_ptr<Frame> frame = frame_deserializer_.PopNextFrame()) {
142 ASSERT_EQ(1u, requests_.count(frame->request_id()));
143 EXPECT_EQ(0, requests_[frame->request_id()]++);
144 if (frame->has_msg_bind_service_reply()) {
145 if (frame->msg_bind_service_reply().success())
146 last_bound_service_id_ = frame->msg_bind_service_reply().service_id();
147 return OnServiceBound(frame->msg_bind_service_reply());
148 }
149 if (frame->has_msg_invoke_method_reply())
150 return OnInvokeMethodReply(frame->msg_invoke_method_reply());
151 if (frame->has_msg_request_error())
152 return OnRequestError();
153 FAIL() << "Unexpected frame received from host";
154 }
155 }
156
SendFrame(const Frame & frame,int fd=-1)157 void SendFrame(const Frame& frame, int fd = -1) {
158 std::string buf = BufferedFrameDeserializer::Serialize(frame);
159 ASSERT_TRUE(sock_->Send(buf.data(), buf.size(), fd));
160 }
161
162 BufferedFrameDeserializer frame_deserializer_;
163 std::unique_ptr<base::UnixSocket> sock_;
164 std::map<uint64_t /* request_id */, int /* num_replies_received */> requests_;
165 ServiceID last_bound_service_id_;
166 };
167
168 class HostImplTest : public ::testing::Test {
169 public:
SetUp()170 void SetUp() override {
171 kTestSocket.Destroy();
172 task_runner_.reset(new base::TestTaskRunner());
173 Host* host =
174 Host::CreateInstance(kTestSocket.name(), task_runner_.get()).release();
175 ASSERT_NE(nullptr, host);
176 host_.reset(static_cast<HostImpl*>(host));
177 cli_.reset(new FakeClient(task_runner_.get()));
178 auto on_connect = task_runner_->CreateCheckpoint("on_connect");
179 EXPECT_CALL(*cli_, OnConnect()).WillOnce(Invoke(on_connect));
180 task_runner_->RunUntilCheckpoint("on_connect");
181 }
182
TearDown()183 void TearDown() override {
184 task_runner_->RunUntilIdle();
185 cli_.reset();
186 host_.reset();
187 task_runner_->RunUntilIdle();
188 task_runner_.reset();
189 kTestSocket.Destroy();
190 }
191
192 // ::testing::StrictMock<MockEventListener> proxy_events_;
193 std::unique_ptr<base::TestTaskRunner> task_runner_;
194 std::unique_ptr<HostImpl> host_;
195 std::unique_ptr<FakeClient> cli_;
196 };
197
TEST_F(HostImplTest,BindService)198 TEST_F(HostImplTest, BindService) {
199 // First bind the service when it doesn't exists yet and check that the
200 // BindService() request fails.
201 cli_->BindService("FakeService"); // FakeService does not exist yet.
202 auto on_bind_failure = task_runner_->CreateCheckpoint("on_bind_failure");
203 EXPECT_CALL(*cli_, OnServiceBound(_))
204 .WillOnce(Invoke([on_bind_failure](const Frame::BindServiceReply& reply) {
205 ASSERT_FALSE(reply.success());
206 on_bind_failure();
207 }));
208 task_runner_->RunUntilCheckpoint("on_bind_failure");
209
210 // Now expose the service and bind it.
211 ASSERT_TRUE(host_->ExposeService(
212 std::unique_ptr<Service>(new FakeService("FakeService"))));
213 auto on_bind_success = task_runner_->CreateCheckpoint("on_bind_success");
214 cli_->BindService("FakeService");
215 EXPECT_CALL(*cli_, OnServiceBound(_))
216 .WillOnce(Invoke([on_bind_success](const Frame::BindServiceReply& reply) {
217 ASSERT_TRUE(reply.success());
218 on_bind_success();
219 }));
220 task_runner_->RunUntilCheckpoint("on_bind_success");
221 }
222
TEST_F(HostImplTest,InvokeNonExistingMethod)223 TEST_F(HostImplTest, InvokeNonExistingMethod) {
224 FakeService* fake_service = new FakeService("FakeService");
225 ASSERT_TRUE(host_->ExposeService(std::unique_ptr<Service>(fake_service)));
226 auto on_bind = task_runner_->CreateCheckpoint("on_bind");
227 cli_->BindService("FakeService");
228 EXPECT_CALL(*cli_, OnServiceBound(_)).WillOnce(InvokeWithoutArgs(on_bind));
229 task_runner_->RunUntilCheckpoint("on_bind");
230
231 auto on_invoke_failure = task_runner_->CreateCheckpoint("on_invoke_failure");
232 cli_->InvokeMethod(cli_->last_bound_service_id_, 42, RequestProto());
233 EXPECT_CALL(*cli_, OnInvokeMethodReply(_))
234 .WillOnce(
235 Invoke([on_invoke_failure](const Frame::InvokeMethodReply& reply) {
236 ASSERT_FALSE(reply.success());
237 ASSERT_FALSE(reply.has_more());
238 on_invoke_failure();
239 }));
240 task_runner_->RunUntilCheckpoint("on_invoke_failure");
241 }
242
TEST_F(HostImplTest,InvokeMethod)243 TEST_F(HostImplTest, InvokeMethod) {
244 FakeService* fake_service = new FakeService("FakeService");
245 ASSERT_TRUE(host_->ExposeService(std::unique_ptr<Service>(fake_service)));
246 auto on_bind = task_runner_->CreateCheckpoint("on_bind");
247 cli_->BindService("FakeService");
248 EXPECT_CALL(*cli_, OnServiceBound(_)).WillOnce(InvokeWithoutArgs(on_bind));
249 task_runner_->RunUntilCheckpoint("on_bind");
250
251 RequestProto req_args;
252 req_args.set_data("foo");
253 cli_->InvokeMethod(cli_->last_bound_service_id_, 1, req_args);
254 auto on_reply_sent = task_runner_->CreateCheckpoint("on_reply_sent");
255 EXPECT_CALL(*fake_service, OnFakeMethod1(_, _))
256 .WillOnce(
257 Invoke([on_reply_sent](const RequestProto& req, DeferredBase* reply) {
258 ASSERT_EQ("foo", req.data());
259 std::unique_ptr<ReplyProto> reply_args(new ReplyProto());
260 reply_args->set_data("bar");
261 reply->Resolve(AsyncResult<ProtoMessage>(
262 std::unique_ptr<ProtoMessage>(reply_args.release())));
263 on_reply_sent();
264 }));
265 task_runner_->RunUntilCheckpoint("on_reply_sent");
266
267 auto on_reply_received = task_runner_->CreateCheckpoint("on_reply_received");
268 EXPECT_CALL(*cli_, OnInvokeMethodReply(_))
269 .WillOnce(
270 Invoke([on_reply_received](const Frame::InvokeMethodReply& reply) {
271 ASSERT_TRUE(reply.success());
272 ASSERT_FALSE(reply.has_more());
273 ReplyProto reply_args;
274 reply_args.ParseFromString(reply.reply_proto());
275 ASSERT_EQ("bar", reply_args.data());
276 on_reply_received();
277 }));
278 task_runner_->RunUntilCheckpoint("on_reply_received");
279 }
280
TEST_F(HostImplTest,InvokeMethodDropReply)281 TEST_F(HostImplTest, InvokeMethodDropReply) {
282 FakeService* fake_service = new FakeService("FakeService");
283 ASSERT_TRUE(host_->ExposeService(std::unique_ptr<Service>(fake_service)));
284 auto on_bind = task_runner_->CreateCheckpoint("on_bind");
285 cli_->BindService("FakeService");
286 EXPECT_CALL(*cli_, OnServiceBound(_)).WillOnce(InvokeWithoutArgs(on_bind));
287 task_runner_->RunUntilCheckpoint("on_bind");
288
289 // OnFakeMethod1 will:
290 // - Do nothing on the 1st call, when |drop_reply| == true.
291 // - Reply on the 2nd call, when |drop_reply| == false.
292 EXPECT_CALL(*fake_service, OnFakeMethod1(_, _))
293 .Times(2)
294 .WillRepeatedly(Invoke([](const RequestProto& req, DeferredBase* reply) {
295 if (req.data() == "drop_reply")
296 return;
297 std::unique_ptr<ReplyProto> reply_args(new ReplyProto());
298 reply_args->set_data("the_reply");
299 reply->Resolve(AsyncResult<ProtoMessage>(
300 std::unique_ptr<ProtoMessage>(reply_args.release())));
301 }));
302
303 auto on_reply_received = task_runner_->CreateCheckpoint("on_reply_received");
304 EXPECT_CALL(*cli_, OnInvokeMethodReply(_))
305 .WillOnce(
306 Invoke([on_reply_received](const Frame::InvokeMethodReply& reply) {
307 ASSERT_TRUE(reply.success());
308 ReplyProto reply_args;
309 reply_args.ParseFromString(reply.reply_proto());
310 ASSERT_EQ("the_reply", reply_args.data());
311 on_reply_received();
312 }));
313
314 // Invoke the method first with |drop_reply|=true, then |drop_reply|=false.
315 RequestProto rp;
316 rp.set_data("drop_reply");
317 cli_->InvokeMethod(cli_->last_bound_service_id_, 1, rp, true /*drop_reply*/);
318 rp.set_data("do_reply");
319 cli_->InvokeMethod(cli_->last_bound_service_id_, 1, rp, false /*drop_reply*/);
320
321 task_runner_->RunUntilCheckpoint("on_reply_received");
322 }
323
324 #if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
325 // File descriptor sending over IPC is not supported on Windows.
TEST_F(HostImplTest,SendFileDescriptor)326 TEST_F(HostImplTest, SendFileDescriptor) {
327 FakeService* fake_service = new FakeService("FakeService");
328 ASSERT_TRUE(host_->ExposeService(std::unique_ptr<Service>(fake_service)));
329 auto on_bind = task_runner_->CreateCheckpoint("on_bind");
330 cli_->BindService("FakeService");
331 EXPECT_CALL(*cli_, OnServiceBound(_)).WillOnce(InvokeWithoutArgs(on_bind));
332 task_runner_->RunUntilCheckpoint("on_bind");
333
334 static constexpr char kFileContent[] = "shared file";
335 RequestProto req_args;
336 cli_->InvokeMethod(cli_->last_bound_service_id_, 1, req_args);
337 auto on_reply_sent = task_runner_->CreateCheckpoint("on_reply_sent");
338 base::TempFile tx_file = base::TempFile::CreateUnlinked();
339 ASSERT_EQ(static_cast<size_t>(base::WriteAll(tx_file.fd(), kFileContent,
340 sizeof(kFileContent))),
341 sizeof(kFileContent));
342 EXPECT_CALL(*fake_service, OnFakeMethod1(_, _))
343 .WillOnce(Invoke(
344 [on_reply_sent, &tx_file](const RequestProto&, DeferredBase* reply) {
345 std::unique_ptr<ReplyProto> reply_args(new ReplyProto());
346 auto async_res = AsyncResult<ProtoMessage>(
347 std::unique_ptr<ProtoMessage>(reply_args.release()));
348 async_res.set_fd(tx_file.fd());
349 reply->Resolve(std::move(async_res));
350 on_reply_sent();
351 }));
352 task_runner_->RunUntilCheckpoint("on_reply_sent");
353 tx_file.ReleaseFD();
354
355 auto on_fd_received = task_runner_->CreateCheckpoint("on_fd_received");
356 EXPECT_CALL(*cli_, OnFileDescriptorReceived(_))
357 .WillOnce(Invoke([on_fd_received](int fd) {
358 char buf[sizeof(kFileContent)] = {};
359 ASSERT_EQ(0, lseek(fd, 0, SEEK_SET));
360 ASSERT_EQ(static_cast<int32_t>(sizeof(buf)),
361 PERFETTO_EINTR(read(fd, buf, sizeof(buf))));
362 ASSERT_STREQ(kFileContent, buf);
363 on_fd_received();
364 }));
365 EXPECT_CALL(*cli_, OnInvokeMethodReply(_));
366 task_runner_->RunUntilCheckpoint("on_fd_received");
367 }
368
TEST_F(HostImplTest,ReceiveFileDescriptor)369 TEST_F(HostImplTest, ReceiveFileDescriptor) {
370 auto received = task_runner_->CreateCheckpoint("received");
371 FakeService* fake_service = new FakeService("FakeService");
372 ASSERT_TRUE(host_->ExposeService(std::unique_ptr<Service>(fake_service)));
373 auto on_bind = task_runner_->CreateCheckpoint("on_bind");
374 cli_->BindService("FakeService");
375 EXPECT_CALL(*cli_, OnServiceBound(_)).WillOnce(InvokeWithoutArgs(on_bind));
376 task_runner_->RunUntilCheckpoint("on_bind");
377
378 static constexpr char kFileContent[] = "shared file";
379 RequestProto req_args;
380 base::TempFile tx_file = base::TempFile::CreateUnlinked();
381 ASSERT_EQ(static_cast<size_t>(base::WriteAll(tx_file.fd(), kFileContent,
382 sizeof(kFileContent))),
383 sizeof(kFileContent));
384 cli_->InvokeMethod(cli_->last_bound_service_id_, 1, req_args, false,
385 tx_file.fd());
386 EXPECT_CALL(*cli_, OnInvokeMethodReply(_));
387 base::ScopedFile rx_fd;
388 EXPECT_CALL(*fake_service, OnFakeMethod1(_, _))
389 .WillOnce(Invoke([received, &fake_service, &rx_fd](const RequestProto&,
390 DeferredBase*) {
391 rx_fd = fake_service->TakeReceivedFD();
392 received();
393 }));
394
395 task_runner_->RunUntilCheckpoint("received");
396
397 ASSERT_TRUE(rx_fd);
398 char buf[sizeof(kFileContent)] = {};
399 ASSERT_EQ(0, lseek(*rx_fd, 0, SEEK_SET));
400 ASSERT_EQ(static_cast<int32_t>(sizeof(buf)),
401 PERFETTO_EINTR(read(*rx_fd, buf, sizeof(buf))));
402 ASSERT_STREQ(kFileContent, buf);
403 }
404 #endif // !OS_WIN
405
406 // Invoke a method and immediately after disconnect the client.
TEST_F(HostImplTest,OnClientDisconnect)407 TEST_F(HostImplTest, OnClientDisconnect) {
408 FakeService* fake_service = new FakeService("FakeService");
409 ASSERT_TRUE(host_->ExposeService(std::unique_ptr<Service>(fake_service)));
410 auto on_bind = task_runner_->CreateCheckpoint("on_bind");
411 cli_->BindService("FakeService");
412 EXPECT_CALL(*cli_, OnServiceBound(_)).WillOnce(InvokeWithoutArgs(on_bind));
413 task_runner_->RunUntilCheckpoint("on_bind");
414
415 RequestProto req_args;
416 req_args.set_data("foo");
417 cli_->InvokeMethod(cli_->last_bound_service_id_, 1, req_args);
418 EXPECT_CALL(*cli_, OnInvokeMethodReply(_)).Times(0);
419 cli_.reset(); // Disconnect the client.
420 auto on_host_method = task_runner_->CreateCheckpoint("on_host_method");
421 EXPECT_CALL(*fake_service, OnFakeMethod1(_, _))
422 .WillOnce(
423 Invoke([on_host_method](const RequestProto& req, DeferredBase*) {
424 ASSERT_EQ("foo", req.data());
425 on_host_method();
426 }));
427 task_runner_->RunUntilCheckpoint("on_host_method");
428 }
429
430 // Like InvokeMethod, but instead of resolving the Deferred reply within the
431 // call stack, std::move()-s it outside an replies
TEST_F(HostImplTest,MoveReplyObjectAndReplyAsynchronously)432 TEST_F(HostImplTest, MoveReplyObjectAndReplyAsynchronously) {
433 FakeService* fake_service = new FakeService("FakeService");
434 ASSERT_TRUE(host_->ExposeService(std::unique_ptr<Service>(fake_service)));
435 auto on_bind = task_runner_->CreateCheckpoint("on_bind");
436 cli_->BindService("FakeService");
437 EXPECT_CALL(*cli_, OnServiceBound(_)).WillOnce(InvokeWithoutArgs(on_bind));
438 task_runner_->RunUntilCheckpoint("on_bind");
439
440 // Invokes the remote method and waits that the FakeService sees it. The reply
441 // is not resolved but just moved into |moved_reply|.
442 RequestProto req_args;
443 cli_->InvokeMethod(cli_->last_bound_service_id_, 1, req_args);
444 auto on_invoke = task_runner_->CreateCheckpoint("on_invoke");
445 DeferredBase moved_reply;
446 EXPECT_CALL(*fake_service, OnFakeMethod1(_, _))
447 .WillOnce(Invoke(
448 [on_invoke, &moved_reply](const RequestProto&, DeferredBase* reply) {
449 moved_reply = std::move(*reply);
450 on_invoke();
451 }));
452 task_runner_->RunUntilCheckpoint("on_invoke");
453
454 // Check that the FakeClient doesn't see any reply yet.
455 EXPECT_CALL(*cli_, OnInvokeMethodReply(_)).Times(0);
456 task_runner_->RunUntilIdle();
457 ASSERT_TRUE(::testing::Mock::VerifyAndClearExpectations(cli_.get()));
458
459 // Resolve the reply asynchronously in a deferred task.
460 task_runner_->PostTask([&moved_reply] {
461 std::unique_ptr<ReplyProto> reply_args(new ReplyProto());
462 reply_args->set_data("bar");
463 moved_reply.Resolve(AsyncResult<ProtoMessage>(
464 std::unique_ptr<ProtoMessage>(reply_args.release())));
465 });
466
467 auto on_reply_received = task_runner_->CreateCheckpoint("on_reply_received");
468 EXPECT_CALL(*cli_, OnInvokeMethodReply(_))
469 .WillOnce(
470 Invoke([on_reply_received](const Frame::InvokeMethodReply& reply) {
471 ASSERT_TRUE(reply.success());
472 ASSERT_FALSE(reply.has_more());
473 ReplyProto reply_args;
474 reply_args.ParseFromString(reply.reply_proto());
475 ASSERT_EQ("bar", reply_args.data());
476 on_reply_received();
477 }));
478 task_runner_->RunUntilCheckpoint("on_reply_received");
479 }
480
481 // TODO(primiano): add the tests below in next CLs.
482 // TEST(HostImplTest, ManyClients) {}
483 // TEST(HostImplTest, OverlappingRequstsOutOfOrder) {}
484 // TEST(HostImplTest, StreamingRequest) {}
485 // TEST(HostImplTest, ManyDropReplyRequestsDontLeakMemory) {}
486
487 } // namespace
488 } // namespace ipc
489 } // namespace perfetto
490