| // Copyright 2018 The Fuchsia Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include <lib/fit/defer.h> | |
| #include <time.h> | |
| #include <unistd.h> | |
| #include <gtest/gtest.h> | |
| #include "src/developer/debug/shared/platform_message_loop.h" | |
| #if defined(__Fuchsia__) | |
| #include <lib/zx/socket.h> | |
| #include "src/developer/debug/shared/channel_watcher.h" | |
| #include "src/developer/debug/shared/socket_watcher.h" | |
| #endif | |
| namespace debug { | |
| namespace { | |
| // This helper class sets a boolean when it's destructed. Tests can use it to ensure that the | |
| // lifetime of a lambda is correct. | |
| class SetOnDestruct { | |
| public: | |
| // The flag must outlive this class. | |
| explicit SetOnDestruct(bool* flag) : flag_(flag) {} | |
| ~SetOnDestruct() { *flag_ = true; } | |
| private: | |
| bool* flag_; | |
| }; | |
| } // namespace | |
| // This test either passes or hangs forever because the post didn't work. | |
| // We could add a timer timeout, but if regular task posting doesn't work it's | |
| // not clear why timer tasks would. | |
| TEST(MessageLoop, PostQuit) { | |
| PlatformMessageLoop loop; | |
| std::string error_message; | |
| ASSERT_TRUE(loop.Init(&error_message)) << error_message; | |
| loop.PostTask(FROM_HERE, [loop_ptr = &loop]() { loop_ptr->QuitNow(); }); | |
| loop.Run(); | |
| loop.Cleanup(); | |
| } | |
| // Like the above but expresses the task as a fpromise::promise. | |
| TEST(MessageLoop, PostPendingTaskQuit) { | |
| PlatformMessageLoop loop; | |
| std::string error_message; | |
| ASSERT_TRUE(loop.Init(&error_message)) << error_message; | |
| loop.PostTask(FROM_HERE, fpromise::make_promise([&loop]() { loop.QuitNow(); })); | |
| loop.Run(); | |
| loop.Cleanup(); | |
| } | |
| TEST(MessageLoop, TimerQuit) { | |
| const uint64_t kNano = 1000000000; | |
| PlatformMessageLoop loop; | |
| std::string error_message; | |
| ASSERT_TRUE(loop.Init(&error_message)) << error_message; | |
| struct timespec start; | |
| ASSERT_FALSE(clock_gettime(CLOCK_MONOTONIC, &start)); | |
| loop.PostTimer(FROM_HERE, 50, [loop_ptr = &loop]() { loop_ptr->QuitNow(); }); | |
| loop.Run(); | |
| struct timespec end; | |
| ASSERT_FALSE(clock_gettime(CLOCK_MONOTONIC, &end)); | |
| ASSERT_GE(end.tv_sec, start.tv_sec); | |
| uint64_t nsec = (end.tv_sec - start.tv_sec) * kNano; | |
| nsec += end.tv_nsec; | |
| nsec -= start.tv_nsec; | |
| EXPECT_GE(nsec, 50u); | |
| // If we test an upper bound for nsec this test could potentially be flaky. | |
| // We don't actually make any guarantees about the upper bound anyway. | |
| loop.Cleanup(); | |
| } | |
| TEST(MessageLoop, TimerQuitFromLoop) { | |
| const uint64_t kNano = 1000000000; | |
| PlatformMessageLoop loop; | |
| std::string error_message; | |
| ASSERT_TRUE(loop.Init(&error_message)) << error_message; | |
| struct timespec start; | |
| ASSERT_FALSE(clock_gettime(CLOCK_MONOTONIC, &start)); | |
| loop.PostTask(FROM_HERE, | |
| [&loop]() { loop.PostTimer(FROM_HERE, 50, [&loop]() { loop.QuitNow(); }); }); | |
| loop.Run(); | |
| struct timespec end; | |
| ASSERT_FALSE(clock_gettime(CLOCK_MONOTONIC, &end)); | |
| ASSERT_GE(end.tv_sec, start.tv_sec); | |
| uint64_t nsec = (end.tv_sec - start.tv_sec) * kNano; | |
| nsec += end.tv_nsec; | |
| nsec -= start.tv_nsec; | |
| EXPECT_GE(nsec, 50u); | |
| // If we test an upper bound for nsec this test could potentially be flaky. | |
| // We don't actually make any guarantees about the upper bound anyway. | |
| loop.Cleanup(); | |
| } | |
| // Tests a promise that suspends itself and then continues. | |
| TEST(MessageLoop, SuspendPromise) { | |
| PlatformMessageLoop loop; | |
| std::string error_message; | |
| ASSERT_TRUE(loop.Init(&error_message)) << error_message; | |
| bool lambda_destructed = false; | |
| fpromise::suspended_task suspended; | |
| int run_count = 0; | |
| bool should_complete = false; | |
| loop.PostTask(FROM_HERE, fpromise::make_promise( | |
| [&should_complete, &run_count, &suspended, | |
| destructed = std::make_shared<SetOnDestruct>(&lambda_destructed)]( | |
| fpromise::context& context) -> fpromise::result<> { | |
| run_count++; | |
| if (should_complete) | |
| return fpromise::ok(); | |
| suspended = context.suspend_task(); // So we can signal later. | |
| return fpromise::pending(); | |
| })); | |
| // Should not have run yet. | |
| EXPECT_EQ(0, run_count); | |
| // Pulse the message loop. The task should have run once (responded with "suspend") and set the | |
| // suspended_task. | |
| loop.PostTask(FROM_HERE, [&loop]() { loop.QuitNow(); }); | |
| loop.Run(); | |
| EXPECT_EQ(1, run_count); | |
| EXPECT_TRUE(suspended); | |
| // Run the loop again without doing anything. Nothing should have happened because the task was | |
| // not unsuspended. | |
| loop.PostTask(FROM_HERE, [&loop]() { loop.QuitNow(); }); | |
| loop.Run(); | |
| EXPECT_EQ(1, run_count); | |
| // Mark the task as runnable again. It should run once but still report pending. Note that we | |
| // run the task synchronously from the resume_task() message as explained in | |
| // MessageLoop::resolve_ticket(). | |
| suspended.resume_task(); | |
| EXPECT_EQ(2, run_count); | |
| EXPECT_TRUE(suspended); | |
| EXPECT_FALSE(lambda_destructed); // Lambda should not be deleted. | |
| // Tell the task to complete and signal again. It should be done. | |
| should_complete = true; | |
| suspended.resume_task(); | |
| loop.PostTask(FROM_HERE, [&loop]() { loop.QuitNow(); }); | |
| loop.Run(); | |
| EXPECT_EQ(3, run_count); | |
| EXPECT_FALSE(suspended); | |
| EXPECT_TRUE(lambda_destructed); // Lambda should be deleted. | |
| loop.Cleanup(); | |
| } | |
| // Duplicates the suspended_task controlling the suspended promise. | |
| TEST(MessageLoop, DuplicateSuspendedPromise) { | |
| PlatformMessageLoop loop; | |
| std::string error_message; | |
| ASSERT_TRUE(loop.Init(&error_message)) << error_message; | |
| bool lambda_destructed = false; | |
| fpromise::suspended_task suspended; | |
| int run_count = 0; | |
| bool should_complete = false; | |
| loop.PostTask(FROM_HERE, fpromise::make_promise( | |
| [&should_complete, &run_count, &suspended, | |
| destructed = std::make_shared<SetOnDestruct>(&lambda_destructed)]( | |
| fpromise::context& context) -> fpromise::result<> { | |
| run_count++; | |
| if (should_complete) | |
| return fpromise::ok(); | |
| suspended = context.suspend_task(); // So we can signal later. | |
| return fpromise::pending(); | |
| })); | |
| // Should not have run yet. | |
| EXPECT_EQ(0, run_count); | |
| // Pulse the message loop. The task should have run once (responded with "suspend") and set the | |
| // suspended_task. | |
| loop.PostTask(FROM_HERE, [&loop]() { loop.QuitNow(); }); | |
| loop.Run(); | |
| EXPECT_EQ(1, run_count); | |
| EXPECT_TRUE(suspended); | |
| // Duplicate the suspended task handle. | |
| fpromise::suspended_task suspended2 = suspended; | |
| should_complete = true; | |
| suspended.resume_task(); // Should run synchronously. | |
| EXPECT_EQ(2, run_count); | |
| // Resuming the other one does nothing. This suspend was already marked resolved from the other | |
| // token. | |
| should_complete = true; | |
| suspended2.resume_task(); | |
| loop.PostTask(FROM_HERE, [&loop]() { loop.QuitNow(); }); | |
| loop.Run(); | |
| EXPECT_EQ(2, run_count); // Same as before. | |
| EXPECT_TRUE(lambda_destructed); | |
| loop.Cleanup(); | |
| } | |
| // Tests a promise that suspends itself and then becomes abandoned (deleted before it's runnable). | |
| TEST(MessageLoop, AbandonPromise) { | |
| PlatformMessageLoop loop; | |
| std::string error_message; | |
| ASSERT_TRUE(loop.Init(&error_message)) << error_message; | |
| bool lambda_destructed = false; | |
| fpromise::suspended_task suspended; | |
| int run_count = 0; | |
| loop.PostTask(FROM_HERE, fpromise::make_promise( | |
| [&run_count, &suspended, | |
| destructed = std::make_shared<SetOnDestruct>(&lambda_destructed)]( | |
| fpromise::context& context) -> fpromise::result<> { | |
| run_count++; | |
| suspended = context.suspend_task(); // So we can signal later. | |
| return fpromise::pending(); | |
| })); | |
| // Should not have run yet. | |
| EXPECT_EQ(0, run_count); | |
| // Pulse the message loop. The task should have run once (responded with "suspend") and set the | |
| // suspended_task. | |
| loop.PostTask(FROM_HERE, [&loop]() { loop.QuitNow(); }); | |
| loop.Run(); | |
| EXPECT_EQ(1, run_count); | |
| EXPECT_TRUE(suspended); | |
| // Free the suspended task. This should free the lambda and not run it. | |
| suspended = fpromise::suspended_task(); | |
| EXPECT_EQ(1, run_count); | |
| EXPECT_TRUE(lambda_destructed); | |
| loop.Cleanup(); | |
| } | |
| // Runs a promise right away without posting to the message loop. | |
| TEST(MessageLoop, RunPromiseSync) { | |
| PlatformMessageLoop loop; | |
| std::string error_message; | |
| ASSERT_TRUE(loop.Init(&error_message)) << error_message; | |
| bool lambda_destructed = false; | |
| fpromise::suspended_task suspended; | |
| int run_count = 0; | |
| bool should_complete = false; | |
| loop.RunTask(FROM_HERE, fpromise::make_promise( | |
| [&run_count, &suspended, &should_complete, | |
| destructed = std::make_shared<SetOnDestruct>(&lambda_destructed)]( | |
| fpromise::context& context) -> fpromise::result<> { | |
| run_count++; | |
| if (should_complete) | |
| return fpromise::ok(); | |
| suspended = context.suspend_task(); // So we can signal later. | |
| return fpromise::pending(); | |
| })); | |
| // Should have run but not completed. | |
| EXPECT_EQ(1, run_count); | |
| EXPECT_FALSE(lambda_destructed); | |
| EXPECT_TRUE(suspended); | |
| // Pulse the message loop. | |
| loop.PostTask(FROM_HERE, [&loop]() { loop.QuitNow(); }); | |
| loop.Run(); | |
| EXPECT_EQ(1, run_count); // Same as before. | |
| // Unsuspend, the task should complete. | |
| should_complete = true; | |
| suspended.resume_task(); | |
| loop.PostTask(FROM_HERE, [&loop]() { loop.QuitNow(); }); | |
| loop.Run(); | |
| EXPECT_EQ(2, run_count); | |
| EXPECT_FALSE(suspended); | |
| EXPECT_TRUE(lambda_destructed); | |
| loop.Cleanup(); | |
| } | |
| // Runs a promise without posting from inside another promise. | |
| TEST(MessageLoop, RunNestedPromiseSync) { | |
| PlatformMessageLoop loop; | |
| std::string error_message; | |
| ASSERT_TRUE(loop.Init(&error_message)) << error_message; | |
| fpromise::suspended_task inner_suspended; | |
| int inner_run_count = 0; | |
| bool inner_should_complete = false; | |
| fpromise::suspended_task outer_suspended; | |
| int outer_run_count = 0; | |
| bool outer_should_complete = false; | |
| loop.PostTask( | |
| FROM_HERE, fpromise::make_promise([&](fpromise::context& context) -> fpromise::result<> { | |
| outer_run_count++; | |
| if (outer_should_complete) | |
| return fpromise::ok(); | |
| int old_inner_run_count = inner_run_count; | |
| loop.RunTask(FROM_HERE, | |
| fpromise::make_promise([&](fpromise::context& context) -> fpromise::result<> { | |
| inner_run_count++; | |
| if (inner_should_complete) | |
| return fpromise::ok(); | |
| inner_suspended = context.suspend_task(); | |
| return fpromise::pending(); | |
| })); | |
| EXPECT_EQ(inner_run_count, old_inner_run_count + 1); // Should have run once. | |
| outer_suspended = context.suspend_task(); // So we can signal later. | |
| return fpromise::pending(); | |
| })); | |
| // Nothing should have happened yet. | |
| EXPECT_EQ(0, inner_run_count); | |
| EXPECT_EQ(0, outer_run_count); | |
| // Pulse the message loop. Both outer and inner loops should run once and suspend. | |
| loop.PostTask(FROM_HERE, [&loop]() { loop.QuitNow(); }); | |
| loop.Run(); | |
| EXPECT_EQ(1, inner_run_count); | |
| EXPECT_EQ(1, outer_run_count); | |
| // Let the inner one complete. | |
| inner_should_complete = true; | |
| inner_suspended.resume_task(); | |
| loop.PostTask(FROM_HERE, [&loop]() { loop.QuitNow(); }); | |
| loop.Run(); | |
| EXPECT_EQ(2, inner_run_count); // One more run. | |
| EXPECT_EQ(1, outer_run_count); // Same as before. | |
| // Run the outer one again but it will still return async. This will queue up another inner loop | |
| // but this time the inner loop should exit right away and not set the inner suspended. | |
| inner_suspended = fpromise::suspended_task(); | |
| outer_suspended.resume_task(); | |
| loop.PostTask(FROM_HERE, [&loop]() { loop.QuitNow(); }); | |
| loop.Run(); | |
| EXPECT_EQ(3, inner_run_count); // One more run. | |
| EXPECT_EQ(2, outer_run_count); // One more run. | |
| EXPECT_FALSE(inner_suspended); // Not suspended, it completed synchronously now. | |
| // Complete the outer one. | |
| outer_should_complete = true; | |
| outer_suspended.resume_task(); | |
| loop.PostTask(FROM_HERE, [&loop]() { loop.QuitNow(); }); | |
| loop.Run(); | |
| EXPECT_EQ(3, inner_run_count); // Same as before | |
| EXPECT_EQ(3, outer_run_count); // One more run. | |
| loop.Cleanup(); | |
| } | |
| TEST(MessageLoop, WatchPipeFD) { | |
| // Make a pipe to talk about. | |
| fbl::unique_fd pipe_out; | |
| fbl::unique_fd pipe_in; | |
| ASSERT_TRUE(CreateLocalNonBlockingPipe(&pipe_out, &pipe_in)); | |
| PlatformMessageLoop loop; | |
| std::string error_message; | |
| ASSERT_TRUE(loop.Init(&error_message)) << error_message; | |
| // Scope everything to before MessageLoop::Cleanup(). | |
| { | |
| bool got_read = false; | |
| bool got_write = true; | |
| bool got_err = true; | |
| // Going to write to pipefd[1] -> read from pipefd[0]. | |
| MessageLoop::WatchHandle watch_handle = | |
| loop.WatchFD(MessageLoop::WatchMode::kRead, pipe_out.get(), | |
| [&](int fd, bool read, bool write, bool err) { | |
| got_read = read; | |
| got_write = write; | |
| got_err = err; | |
| loop.QuitNow(); | |
| }); | |
| ASSERT_TRUE(watch_handle.watching()); | |
| // Enqueue a task that should cause pipefd[0] to become readable. | |
| loop.PostTask(FROM_HERE, [write_fd = pipe_in.get()]() { write(write_fd, "Hello", 5); }); | |
| // This will quit on success because the OnFDReady callback called QuitNow, | |
| // or hang forever on failure. | |
| // TODO(brettw) add a timeout when timers are supported in the message loop. | |
| loop.Run(); | |
| EXPECT_TRUE(got_read); | |
| EXPECT_FALSE(got_write); | |
| EXPECT_FALSE(got_err); | |
| } | |
| loop.Cleanup(); | |
| } | |
| TEST(MessageLoop, RunUntilNoTasks) { | |
| PlatformMessageLoop loop; | |
| static constexpr int kCallCount = 5; | |
| struct Calls { | |
| int called[kCallCount]; | |
| }; | |
| Calls calls; | |
| for (int i = 0; i < kCallCount; i++) { | |
| calls.called[i] = -1; | |
| } | |
| std::string error_message; | |
| ASSERT_TRUE(loop.Init(&error_message)) << error_message; | |
| { | |
| loop.PostTask(FROM_HERE, [&calls]() mutable { calls.called[0] = 0; }); | |
| loop.PostTask(FROM_HERE, [&calls]() mutable { calls.called[1] = 1; }); | |
| // Nested calles should work. | |
| loop.PostTask(FROM_HERE, [&calls, &loop]() mutable { | |
| loop.PostTask(FROM_HERE, [&calls, &loop]() { | |
| loop.PostTask(FROM_HERE, [&calls]() { calls.called[4] = 4; }); | |
| calls.called[3] = 3; | |
| }); | |
| calls.called[2] = 2; | |
| }); | |
| loop.RunUntilNoTasks(); | |
| // All should've been called in the expected order. | |
| EXPECT_EQ(calls.called[0], 0); | |
| EXPECT_EQ(calls.called[1], 1); | |
| EXPECT_EQ(calls.called[2], 2); | |
| EXPECT_EQ(calls.called[3], 3); | |
| EXPECT_EQ(calls.called[4], 4); | |
| } | |
| loop.Cleanup(); | |
| } | |
| TEST(MessageLoop, RunUntilNoTasks_EmptyQueue) { | |
| PlatformMessageLoop loop; | |
| static constexpr int kCallCount = 5; | |
| struct Calls { | |
| int called[kCallCount]; | |
| }; | |
| Calls calls; | |
| for (int i = 0; i < kCallCount; i++) { | |
| calls.called[i] = -1; | |
| } | |
| std::string error_message; | |
| ASSERT_TRUE(loop.Init(&error_message)) << error_message; | |
| { | |
| loop.RunUntilNoTasks(); | |
| } | |
| loop.Cleanup(); | |
| } | |
| // Cleanup should destruct tasks correctly. | |
| TEST(MessageLoop, CorrectCleanup) { | |
| PlatformMessageLoop loop; | |
| std::string error_message; | |
| ASSERT_TRUE(loop.Init(&error_message)) << error_message; | |
| // Post a task that quits the loop immediately. | |
| loop.PostTask(FROM_HERE, [&loop]() { loop.QuitNow(); }); | |
| // Post a timer in the future. | |
| bool timer_destructed = false; | |
| auto on_timer_destruct = fit::defer([&timer_destructed]() { timer_destructed = true; }); | |
| loop.PostTimer(FROM_HERE, 100, [on_timer_destruct = std::move(on_timer_destruct)]() {}); | |
| // Watch a pipe forever. | |
| fbl::unique_fd pipe_out; | |
| fbl::unique_fd pipe_in; | |
| ASSERT_TRUE(CreateLocalNonBlockingPipe(&pipe_out, &pipe_in)); | |
| MessageLoop::WatchHandle watch_handle; | |
| auto on_destruct = fit::defer([&watch_handle]() { watch_handle = {}; }); | |
| watch_handle = loop.WatchFD( | |
| MessageLoop::WatchMode::kRead, pipe_out.get(), | |
| [on_destruct = std::move(on_destruct)](int fd, bool read, bool write, bool err) {}); | |
| loop.Run(); | |
| EXPECT_FALSE(timer_destructed); | |
| EXPECT_TRUE(watch_handle.watching()); | |
| loop.Cleanup(); | |
| EXPECT_TRUE(timer_destructed); | |
| EXPECT_FALSE(watch_handle.watching()); | |
| } | |
| #if defined(__Fuchsia__) | |
| namespace { | |
| class ReadableWatcher : public ChannelWatcher, public SocketWatcher { | |
| public: | |
| explicit ReadableWatcher(MessageLoop* loop) : loop_(loop) {} | |
| void OnSocketReadable(zx_handle_t socket_handle) override { loop_->QuitNow(); } | |
| void OnChannelReadable(zx_handle_t channel_handle) override { | |
| char buf[6] = {'\0'}; | |
| uint32_t actual_bytes = 0; | |
| // We could get two Readable notifications, once when the sender writes to the channel, and then | |
| // again when the channel is closed. The first will have the "Hello" message, which will be | |
| // consumed. The second can come when the channel is closed, but there may or may not be any | |
| // data left in the channel. | |
| zx_channel_read(channel_handle, 0, &buf, nullptr, 5, 0, &actual_bytes, nullptr); | |
| // We haven't received any data before this, so we should have gotten something from the | |
| // channel here. The next time this function is called should not have anything else from the | |
| // sender. | |
| if (actual_bytes > 0) { | |
| ASSERT_EQ(actual_bytes, 5u); | |
| ASSERT_STREQ(buf, "Hello"); | |
| have_gotten_channel_data_ = true; | |
| } | |
| } | |
| void OnPeerClosed(zx_handle_t) override { | |
| // We should have always drained the channel data before acknowledging the PEER_CLOSED event. | |
| ASSERT_TRUE(have_gotten_channel_data_); | |
| loop_->QuitNow(); | |
| } | |
| private: | |
| bool have_gotten_channel_data_; | |
| MessageLoop* loop_; | |
| }; | |
| } // namespace | |
| TEST(MessageLoop, ZirconSocket) { | |
| zx::socket sender, receiver; | |
| ASSERT_EQ(ZX_OK, zx::socket::create(ZX_SOCKET_STREAM, &sender, &receiver)); | |
| PlatformMessageLoop loop; | |
| std::string error_message; | |
| ASSERT_TRUE(loop.Init(&error_message)) << error_message; | |
| // Scope everything to before MessageLoop::Cleanup(). | |
| { | |
| ReadableWatcher watcher(&loop); | |
| MessageLoop::WatchHandle watch_handle; | |
| ASSERT_EQ(ZX_OK, loop.WatchSocket(MessageLoop::WatchMode::kRead, receiver.get(), &watcher, | |
| &watch_handle)); | |
| ASSERT_TRUE(watch_handle.watching()); | |
| // Enqueue a task that should cause receiver to become readable. | |
| loop.PostTask(FROM_HERE, [&sender]() { sender.write(0, "Hello", 5, nullptr); }); | |
| // This will quit on success because the OnSocketReadable callback called | |
| // QuitNow, or hang forever on failure. | |
| // TODO(brettw) add a timeout when timers are supported in the message loop. | |
| loop.Run(); | |
| } | |
| loop.Cleanup(); | |
| } | |
| TEST(MessageLoop, ZirconChannel) { | |
| zx::channel sender, receiver; | |
| ASSERT_EQ(ZX_OK, zx::channel::create(0, &sender, &receiver)); | |
| PlatformMessageLoop loop; | |
| std::string error_message; | |
| ASSERT_TRUE(loop.Init(&error_message)) << error_message; | |
| // Scope everything to before MessageLoop::Cleanup(). | |
| { | |
| ReadableWatcher watcher(&loop); | |
| MessageLoop::WatchHandle watch_handle; | |
| ASSERT_EQ(ZX_OK, loop.WatchChannel(receiver.get(), &watcher, &watch_handle)); | |
| ASSERT_TRUE(watch_handle.watching()); | |
| // Send a message across the channel. | |
| loop.PostTask(FROM_HERE, [&sender]() { sender.write(0, "Hello", 5, nullptr, 0); }); | |
| // Close the channel to signal we're done. | |
| loop.PostTask(FROM_HERE, [&sender]() { sender.reset(); }); | |
| // This will quit on success because the OnChannelReadable callback called | |
| // QuitNow, or hang forever on failure. | |
| loop.Run(); | |
| } | |
| loop.Cleanup(); | |
| } | |
| TEST(MessageLoop, DrainChannelBeforeClose) { | |
| zx::channel sender, receiver; | |
| ASSERT_EQ(ZX_OK, zx::channel::create(0, &sender, &receiver)); | |
| PlatformMessageLoop loop; | |
| std::string error_message; | |
| ASSERT_TRUE(loop.Init(&error_message)) << error_message; | |
| // Scope everything to before MessageLoop::Cleanup(). | |
| { | |
| ReadableWatcher watcher(&loop); | |
| MessageLoop::WatchHandle watch_handle; | |
| ASSERT_EQ(ZX_OK, loop.WatchChannel(receiver.get(), &watcher, &watch_handle)); | |
| ASSERT_TRUE(watch_handle.watching()); | |
| // Synchronously send a message across the channel, then immediately hang up. The reader should | |
| // always drain the channel before acknowledging the peer closed event. | |
| sender.write(0, "Hello", 5, nullptr, 0); | |
| sender.reset(); | |
| // This will quit on success because the OnChannelReadable callback called | |
| // QuitNow, or hang forever on failure. | |
| loop.Run(); | |
| } | |
| loop.Cleanup(); | |
| } | |
| #endif | |
| } // namespace debug |