| // Copyright 2017 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "mojo/public/c/system/trap.h" |
| |
| #include <stdint.h> |
| |
| #include <array> |
| #include <map> |
| #include <memory> |
| #include <set> |
| |
| #include "base/compiler_specific.h" |
| #include "base/containers/span.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/rand_util.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "base/test/bind.h" |
| #include "base/threading/platform_thread.h" |
| #include "base/threading/simple_thread.h" |
| #include "base/time/time.h" |
| #include "mojo/core/embedder/embedder.h" |
| #include "mojo/core/test/mojo_test_base.h" |
| #include "mojo/public/c/system/data_pipe.h" |
| #include "mojo/public/c/system/types.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace mojo { |
| namespace core { |
| namespace { |
| |
| using TrapTest = test::MojoTestBase; |
| |
| class TriggerHelper { |
| public: |
| using ContextCallback = base::RepeatingCallback<void(const MojoTrapEvent&)>; |
| |
| TriggerHelper() = default; |
| |
| TriggerHelper(const TriggerHelper&) = delete; |
| TriggerHelper& operator=(const TriggerHelper&) = delete; |
| |
| ~TriggerHelper() = default; |
| |
| MojoResult CreateTrap(MojoHandle* handle) { |
| return MojoCreateTrap(&Notify, nullptr, handle); |
| } |
| |
| template <typename Handler> |
| uintptr_t CreateContext(Handler handler) { |
| return CreateContextWithCancel(handler, [] {}); |
| } |
| |
| template <typename Handler, typename CancelHandler> |
| uintptr_t CreateContextWithCancel(Handler handler, |
| CancelHandler cancel_handler) { |
| auto* context = |
| new NotificationContext(base::BindLambdaForTesting(handler)); |
| context->SetCancelCallback( |
| base::BindLambdaForTesting([cancel_handler, context] { |
| cancel_handler(); |
| delete context; |
| })); |
| return reinterpret_cast<uintptr_t>(context); |
| } |
| |
| private: |
| class NotificationContext { |
| public: |
| explicit NotificationContext(const ContextCallback& callback) |
| : callback_(callback) {} |
| |
| NotificationContext(const NotificationContext&) = delete; |
| NotificationContext& operator=(const NotificationContext&) = delete; |
| |
| ~NotificationContext() = default; |
| |
| void SetCancelCallback(base::OnceClosure cancel_callback) { |
| cancel_callback_ = std::move(cancel_callback); |
| } |
| |
| void Notify(const MojoTrapEvent& event) { |
| if (event.result == MOJO_RESULT_CANCELLED && cancel_callback_) |
| std::move(cancel_callback_).Run(); |
| else |
| callback_.Run(event); |
| } |
| |
| private: |
| const ContextCallback callback_; |
| base::OnceClosure cancel_callback_; |
| }; |
| |
| static void Notify(const MojoTrapEvent* event) { |
| reinterpret_cast<NotificationContext*>(event->trigger_context) |
| ->Notify(*event); |
| } |
| }; |
| |
| class ThreadedRunner : public base::SimpleThread { |
| public: |
| explicit ThreadedRunner(base::OnceClosure callback) |
| : SimpleThread("ThreadedRunner"), callback_(std::move(callback)) {} |
| |
| ThreadedRunner(const ThreadedRunner&) = delete; |
| ThreadedRunner& operator=(const ThreadedRunner&) = delete; |
| |
| ~ThreadedRunner() override = default; |
| |
| void Run() override { std::move(callback_).Run(); } |
| |
| private: |
| base::OnceClosure callback_; |
| }; |
| |
| void ExpectNoNotification(const MojoTrapEvent* event) { |
| NOTREACHED(); |
| } |
| |
| void ExpectOnlyCancel(const MojoTrapEvent* event) { |
| EXPECT_EQ(event->result, MOJO_RESULT_CANCELLED); |
| } |
| |
| TEST_F(TrapTest, InvalidArguments) { |
| EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, |
| MojoCreateTrap(&ExpectNoNotification, nullptr, nullptr)); |
| MojoHandle t; |
| EXPECT_EQ(MOJO_RESULT_OK, MojoCreateTrap(&ExpectNoNotification, nullptr, &t)); |
| |
| // Try to add triggers for handles which don't raise trappable signals. |
| EXPECT_EQ( |
| MOJO_RESULT_INVALID_ARGUMENT, |
| MojoAddTrigger(t, t, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, 0, nullptr)); |
| MojoHandle buffer_handle = CreateBuffer(42); |
| EXPECT_EQ( |
| MOJO_RESULT_INVALID_ARGUMENT, |
| MojoAddTrigger(t, buffer_handle, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, 0, nullptr)); |
| |
| // Try to remove a trigger on a non-trap handle. |
| EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, |
| MojoRemoveTrigger(buffer_handle, 0, nullptr)); |
| |
| // Try to arm an invalid or non-trap handle. |
| EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, |
| MojoArmTrap(MOJO_HANDLE_INVALID, nullptr, nullptr, nullptr)); |
| EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, |
| MojoArmTrap(buffer_handle, nullptr, nullptr, nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(buffer_handle)); |
| |
| // Try to arm with a non-null count but a null output buffer. |
| uint32_t num_blocking_events = 1; |
| EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, |
| MojoArmTrap(t, nullptr, &num_blocking_events, nullptr)); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t)); |
| } |
| |
| TEST_F(TrapTest, TrapMessagePipeReadable) { |
| MojoHandle a, b; |
| CreateMessagePipe(&a, &b); |
| |
| base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| TriggerHelper helper; |
| int num_expected_notifications = 1; |
| const uintptr_t readable_a_context = |
| helper.CreateContext([&](const MojoTrapEvent& event) { |
| EXPECT_GT(num_expected_notifications, 0); |
| num_expected_notifications -= 1; |
| |
| EXPECT_EQ(MOJO_RESULT_OK, event.result); |
| wait.Signal(); |
| }); |
| |
| MojoHandle t; |
| EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t)); |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, |
| readable_a_context, nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr)); |
| |
| const char kMessage1[] = "hey hey hey hey"; |
| const char kMessage2[] = "i said hey"; |
| const char kMessage3[] = "what's goin' on?"; |
| |
| // Writing to |b| multiple times should notify exactly once. |
| WriteMessage(b, kMessage1); |
| WriteMessage(b, kMessage2); |
| wait.Wait(); |
| |
| // This also shouldn't fire a notification; the trap is still disarmed. |
| WriteMessage(b, kMessage3); |
| |
| // Arming should fail with relevant information. |
| constexpr size_t kMaxBlockingEvents = 3; |
| uint32_t num_blocking_events = kMaxBlockingEvents; |
| MojoTrapEvent blocking_events[kMaxBlockingEvents] = { |
| {sizeof(blocking_events[0])}, |
| {sizeof(blocking_events[0])}, |
| {sizeof(blocking_events[0])}}; |
| EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, |
| MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0])); |
| EXPECT_EQ(1u, num_blocking_events); |
| EXPECT_EQ(readable_a_context, blocking_events[0].trigger_context); |
| EXPECT_EQ(MOJO_RESULT_OK, blocking_events[0].result); |
| |
| // Flush the three messages from above. |
| EXPECT_EQ(kMessage1, ReadMessage(a)); |
| EXPECT_EQ(kMessage2, ReadMessage(a)); |
| EXPECT_EQ(kMessage3, ReadMessage(a)); |
| |
| // Now we can rearm the trap. |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr)); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); |
| } |
| |
| TEST_F(TrapTest, CloseWatchedMessagePipeHandle) { |
| MojoHandle a, b; |
| CreateMessagePipe(&a, &b); |
| |
| base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| TriggerHelper helper; |
| const uintptr_t readable_a_context = helper.CreateContextWithCancel( |
| [](const MojoTrapEvent&) {}, [&] { wait.Signal(); }); |
| |
| MojoHandle t; |
| EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t)); |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, |
| readable_a_context, nullptr)); |
| |
| // Test that closing a watched handle fires an appropriate notification, even |
| // when the trap is unarmed. |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); |
| wait.Wait(); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t)); |
| } |
| |
| TEST_F(TrapTest, CloseWatchedMessagePipeHandlePeer) { |
| MojoHandle a, b; |
| CreateMessagePipe(&a, &b); |
| |
| base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| TriggerHelper helper; |
| const uintptr_t readable_a_context = |
| helper.CreateContext([&](const MojoTrapEvent& event) { |
| EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, event.result); |
| wait.Signal(); |
| }); |
| |
| MojoHandle t; |
| EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t)); |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, |
| readable_a_context, nullptr)); |
| |
| // Test that closing a watched handle's peer with an armed trap fires an |
| // appropriate notification. |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); |
| wait.Wait(); |
| |
| // And now arming should fail with correct information about |a|'s state. |
| constexpr size_t kMaxBlockingEvents = 3; |
| uint32_t num_blocking_events = kMaxBlockingEvents; |
| MojoTrapEvent blocking_events[kMaxBlockingEvents] = { |
| {sizeof(blocking_events[0])}, |
| {sizeof(blocking_events[0])}, |
| {sizeof(blocking_events[0])}}; |
| EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, |
| MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0])); |
| EXPECT_EQ(1u, num_blocking_events); |
| EXPECT_EQ(readable_a_context, blocking_events[0].trigger_context); |
| EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, blocking_events[0].result); |
| EXPECT_TRUE(blocking_events[0].signals_state.satisfied_signals & |
| MOJO_HANDLE_SIGNAL_PEER_CLOSED); |
| EXPECT_FALSE(blocking_events[0].signals_state.satisfiable_signals & |
| MOJO_HANDLE_SIGNAL_READABLE); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); |
| } |
| |
| TEST_F(TrapTest, TrapDataPipeConsumerReadable) { |
| constexpr size_t kTestPipeCapacity = 64; |
| MojoHandle producer, consumer; |
| CreateDataPipe(&producer, &consumer, kTestPipeCapacity); |
| |
| base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| TriggerHelper helper; |
| int num_expected_notifications = 1; |
| const uintptr_t readable_consumer_context = |
| helper.CreateContext([&](const MojoTrapEvent& event) { |
| EXPECT_GT(num_expected_notifications, 0); |
| num_expected_notifications -= 1; |
| |
| EXPECT_EQ(MOJO_RESULT_OK, event.result); |
| wait.Signal(); |
| }); |
| |
| MojoHandle t; |
| EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t)); |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoAddTrigger(t, consumer, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, |
| readable_consumer_context, nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr)); |
| |
| const char kMessage1[] = "hey hey hey hey"; |
| const char kMessage2[] = "i said hey"; |
| const char kMessage3[] = "what's goin' on?"; |
| |
| // Writing to |producer| multiple times should notify exactly once. |
| WriteData(producer, kMessage1); |
| WriteData(producer, kMessage2); |
| wait.Wait(); |
| |
| // This also shouldn't fire a notification; the trap is still disarmed. |
| WriteData(producer, kMessage3); |
| |
| // Arming should fail with relevant information. |
| constexpr size_t kMaxBlockingEvents = 3; |
| uint32_t num_blocking_events = kMaxBlockingEvents; |
| MojoTrapEvent blocking_events[kMaxBlockingEvents] = { |
| {sizeof(blocking_events[0])}, |
| {sizeof(blocking_events[0])}, |
| {sizeof(blocking_events[0])}}; |
| EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, |
| MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0])); |
| EXPECT_EQ(1u, num_blocking_events); |
| EXPECT_EQ(readable_consumer_context, blocking_events[0].trigger_context); |
| EXPECT_EQ(MOJO_RESULT_OK, blocking_events[0].result); |
| |
| // Flush the three messages from above. |
| EXPECT_EQ(kMessage1, ReadData(consumer, sizeof(kMessage1) - 1)); |
| EXPECT_EQ(kMessage2, ReadData(consumer, sizeof(kMessage2) - 1)); |
| EXPECT_EQ(kMessage3, ReadData(consumer, sizeof(kMessage3) - 1)); |
| |
| // Now we can rearm the trap. |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr)); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer)); |
| } |
| |
| TEST_F(TrapTest, TrapDataPipeConsumerNewDataReadable) { |
| constexpr size_t kTestPipeCapacity = 64; |
| MojoHandle producer, consumer; |
| CreateDataPipe(&producer, &consumer, kTestPipeCapacity); |
| |
| base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| TriggerHelper helper; |
| int num_new_data_notifications = 0; |
| const uintptr_t new_data_context = |
| helper.CreateContext([&](const MojoTrapEvent& event) { |
| num_new_data_notifications += 1; |
| |
| EXPECT_EQ(MOJO_RESULT_OK, event.result); |
| wait.Signal(); |
| }); |
| |
| MojoHandle t; |
| EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t)); |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoAddTrigger(t, consumer, MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, |
| new_data_context, nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr)); |
| |
| const char kMessage1[] = "hey hey hey hey"; |
| const char kMessage2[] = "i said hey"; |
| const char kMessage3[] = "what's goin' on?"; |
| |
| // Writing to |producer| multiple times should notify exactly once. |
| WriteData(producer, kMessage1); |
| WriteData(producer, kMessage2); |
| wait.Wait(); |
| |
| // This also shouldn't fire a notification; the trap is still disarmed. |
| WriteData(producer, kMessage3); |
| |
| // Arming should fail with relevant information. |
| constexpr size_t kMaxBlockingEvents = 3; |
| uint32_t num_blocking_events = kMaxBlockingEvents; |
| MojoTrapEvent blocking_events[kMaxBlockingEvents] = { |
| {sizeof(blocking_events[0])}, |
| {sizeof(blocking_events[0])}, |
| {sizeof(blocking_events[0])}}; |
| EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, |
| MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0])); |
| EXPECT_EQ(1u, num_blocking_events); |
| EXPECT_EQ(new_data_context, blocking_events[0].trigger_context); |
| EXPECT_EQ(MOJO_RESULT_OK, blocking_events[0].result); |
| |
| // Attempt to read more data than is available. Should fail but clear the |
| // NEW_DATA_READABLE signal. |
| char large_buffer[512]; |
| uint32_t large_read_size = 512; |
| MojoReadDataOptions options; |
| options.struct_size = sizeof(options); |
| options.flags = MOJO_READ_DATA_FLAG_ALL_OR_NONE; |
| EXPECT_EQ(MOJO_RESULT_OUT_OF_RANGE, |
| MojoReadData(consumer, &options, large_buffer, &large_read_size)); |
| |
| // Attempt to arm again. Should succeed. |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr)); |
| |
| // Write more data. Should notify. |
| wait.Reset(); |
| WriteData(producer, kMessage1); |
| wait.Wait(); |
| |
| // Reading some data should clear NEW_DATA_READABLE again so we can rearm. |
| EXPECT_EQ(kMessage1, ReadData(consumer, sizeof(kMessage1) - 1)); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr)); |
| |
| EXPECT_EQ(2, num_new_data_notifications); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer)); |
| } |
| |
| TEST_F(TrapTest, TrapDataPipeProducerWritable) { |
| constexpr size_t kTestPipeCapacity = 8; |
| MojoHandle producer, consumer; |
| CreateDataPipe(&producer, &consumer, kTestPipeCapacity); |
| |
| // Half the capacity of the data pipe. |
| const char kTestData[] = "aaaa"; |
| static_assert((sizeof(kTestData) - 1) * 2 == kTestPipeCapacity, |
| "Invalid test data for this test."); |
| |
| base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| TriggerHelper helper; |
| int num_expected_notifications = 1; |
| const uintptr_t writable_producer_context = |
| helper.CreateContext([&](const MojoTrapEvent& event) { |
| EXPECT_GT(num_expected_notifications, 0); |
| num_expected_notifications -= 1; |
| |
| EXPECT_EQ(MOJO_RESULT_OK, event.result); |
| wait.Signal(); |
| }); |
| |
| MojoHandle t; |
| EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t)); |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoAddTrigger(t, producer, MOJO_HANDLE_SIGNAL_WRITABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, |
| writable_producer_context, nullptr)); |
| |
| // The producer is already writable, so arming should fail with relevant |
| // information. |
| constexpr size_t kMaxBlockingEvents = 3; |
| uint32_t num_blocking_events = kMaxBlockingEvents; |
| MojoTrapEvent blocking_events[kMaxBlockingEvents] = { |
| {sizeof(blocking_events[0])}, |
| {sizeof(blocking_events[0])}, |
| {sizeof(blocking_events[0])}}; |
| EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, |
| MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0])); |
| EXPECT_EQ(1u, num_blocking_events); |
| EXPECT_EQ(writable_producer_context, blocking_events[0].trigger_context); |
| EXPECT_EQ(MOJO_RESULT_OK, blocking_events[0].result); |
| EXPECT_TRUE(blocking_events[0].signals_state.satisfied_signals & |
| MOJO_HANDLE_SIGNAL_WRITABLE); |
| |
| // Write some data, but don't fill the pipe yet. Arming should fail again. |
| WriteData(producer, kTestData); |
| EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, |
| MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0])); |
| EXPECT_EQ(1u, num_blocking_events); |
| EXPECT_EQ(writable_producer_context, blocking_events[0].trigger_context); |
| EXPECT_EQ(MOJO_RESULT_OK, blocking_events[0].result); |
| EXPECT_TRUE(blocking_events[0].signals_state.satisfied_signals & |
| MOJO_HANDLE_SIGNAL_WRITABLE); |
| |
| // Write more data, filling the pipe to capacity. Arming should succeed now. |
| WriteData(producer, kTestData); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr)); |
| |
| // Now read from the pipe, making the producer writable again. Should notify. |
| EXPECT_EQ(kTestData, ReadData(consumer, sizeof(kTestData) - 1)); |
| wait.Wait(); |
| |
| // Arming should fail again. |
| EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, |
| MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0])); |
| EXPECT_EQ(1u, num_blocking_events); |
| EXPECT_EQ(writable_producer_context, blocking_events[0].trigger_context); |
| EXPECT_EQ(MOJO_RESULT_OK, blocking_events[0].result); |
| EXPECT_TRUE(blocking_events[0].signals_state.satisfied_signals & |
| MOJO_HANDLE_SIGNAL_WRITABLE); |
| |
| // Fill the pipe once more and arm the trap. Should succeed. |
| WriteData(producer, kTestData); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr)); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer)); |
| } |
| |
| TEST_F(TrapTest, CloseWatchedDataPipeConsumerHandle) { |
| constexpr size_t kTestPipeCapacity = 8; |
| MojoHandle producer, consumer; |
| CreateDataPipe(&producer, &consumer, kTestPipeCapacity); |
| |
| base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| TriggerHelper helper; |
| const uintptr_t readable_consumer_context = helper.CreateContextWithCancel( |
| [](const MojoTrapEvent&) {}, [&] { wait.Signal(); }); |
| |
| MojoHandle t; |
| EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t)); |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoAddTrigger(t, consumer, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, |
| readable_consumer_context, nullptr)); |
| |
| // Closing the consumer should fire a cancellation notification. |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer)); |
| wait.Wait(); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t)); |
| } |
| |
| TEST_F(TrapTest, CloseWatchedDataPipeConsumerHandlePeer) { |
| constexpr size_t kTestPipeCapacity = 8; |
| MojoHandle producer, consumer; |
| CreateDataPipe(&producer, &consumer, kTestPipeCapacity); |
| |
| base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| TriggerHelper helper; |
| const uintptr_t readable_consumer_context = |
| helper.CreateContext([&](const MojoTrapEvent& event) { |
| EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, event.result); |
| wait.Signal(); |
| }); |
| |
| MojoHandle t; |
| EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t)); |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoAddTrigger(t, consumer, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, |
| readable_consumer_context, nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr)); |
| |
| // Closing the producer should fire a notification for an unsatisfiable |
| // condition. |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer)); |
| wait.Wait(); |
| |
| // Now attempt to rearm and expect appropriate error feedback. |
| constexpr size_t kMaxBlockingEvents = 3; |
| uint32_t num_blocking_events = kMaxBlockingEvents; |
| MojoTrapEvent blocking_events[kMaxBlockingEvents] = { |
| {sizeof(blocking_events[0])}, |
| {sizeof(blocking_events[0])}, |
| {sizeof(blocking_events[0])}}; |
| EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, |
| MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0])); |
| EXPECT_EQ(1u, num_blocking_events); |
| EXPECT_EQ(readable_consumer_context, blocking_events[0].trigger_context); |
| EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, blocking_events[0].result); |
| EXPECT_FALSE(blocking_events[0].signals_state.satisfiable_signals & |
| MOJO_HANDLE_SIGNAL_READABLE); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer)); |
| } |
| |
| TEST_F(TrapTest, CloseWatchedDataPipeProducerHandle) { |
| constexpr size_t kTestPipeCapacity = 8; |
| MojoHandle producer, consumer; |
| CreateDataPipe(&producer, &consumer, kTestPipeCapacity); |
| |
| base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| TriggerHelper helper; |
| const uintptr_t writable_producer_context = helper.CreateContextWithCancel( |
| [](const MojoTrapEvent&) {}, [&] { wait.Signal(); }); |
| |
| MojoHandle t; |
| EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t)); |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoAddTrigger(t, producer, MOJO_HANDLE_SIGNAL_WRITABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, |
| writable_producer_context, nullptr)); |
| |
| // Closing the consumer should fire a cancellation notification. |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer)); |
| wait.Wait(); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t)); |
| } |
| |
| TEST_F(TrapTest, CloseWatchedDataPipeProducerHandlePeer) { |
| constexpr size_t kTestPipeCapacity = 8; |
| MojoHandle producer, consumer; |
| CreateDataPipe(&producer, &consumer, kTestPipeCapacity); |
| |
| const char kTestMessageFullCapacity[] = "xxxxxxxx"; |
| static_assert(sizeof(kTestMessageFullCapacity) - 1 == kTestPipeCapacity, |
| "Invalid test message size for this test."); |
| |
| // Make the pipe unwritable initially. |
| WriteData(producer, kTestMessageFullCapacity); |
| |
| base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| TriggerHelper helper; |
| const uintptr_t writable_producer_context = |
| helper.CreateContext([&](const MojoTrapEvent& event) { |
| EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, event.result); |
| wait.Signal(); |
| }); |
| |
| MojoHandle t; |
| EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t)); |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoAddTrigger(t, producer, MOJO_HANDLE_SIGNAL_WRITABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, |
| writable_producer_context, nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr)); |
| |
| // Closing the consumer should fire a notification for an unsatisfiable |
| // condition, as the full data pipe can never be read from again and is |
| // therefore permanently full and unwritable. |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer)); |
| wait.Wait(); |
| |
| // Now attempt to rearm and expect appropriate error feedback. |
| constexpr size_t kMaxBlockingEvents = 3; |
| uint32_t num_blocking_events = kMaxBlockingEvents; |
| MojoTrapEvent blocking_events[kMaxBlockingEvents] = { |
| {sizeof(blocking_events[0])}, |
| {sizeof(blocking_events[0])}, |
| {sizeof(blocking_events[0])}}; |
| EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, |
| MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0])); |
| EXPECT_EQ(1u, num_blocking_events); |
| EXPECT_EQ(writable_producer_context, blocking_events[0].trigger_context); |
| EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, blocking_events[0].result); |
| EXPECT_FALSE(blocking_events[0].signals_state.satisfiable_signals & |
| MOJO_HANDLE_SIGNAL_WRITABLE); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer)); |
| } |
| |
| TEST_F(TrapTest, ArmWithNoTriggers) { |
| MojoHandle t; |
| EXPECT_EQ(MOJO_RESULT_OK, MojoCreateTrap(&ExpectNoNotification, nullptr, &t)); |
| EXPECT_EQ(MOJO_RESULT_NOT_FOUND, MojoArmTrap(t, nullptr, nullptr, nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t)); |
| } |
| |
| TEST_F(TrapTest, DuplicateTriggerContext) { |
| MojoHandle a, b; |
| CreateMessagePipe(&a, &b); |
| |
| MojoHandle t; |
| EXPECT_EQ(MOJO_RESULT_OK, MojoCreateTrap(&ExpectOnlyCancel, nullptr, &t)); |
| EXPECT_EQ( |
| MOJO_RESULT_OK, |
| MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, 0, nullptr)); |
| EXPECT_EQ( |
| MOJO_RESULT_ALREADY_EXISTS, |
| MojoAddTrigger(t, b, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, 0, nullptr)); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); |
| } |
| |
| TEST_F(TrapTest, RemoveUnknownTrigger) { |
| MojoHandle t; |
| EXPECT_EQ(MOJO_RESULT_OK, MojoCreateTrap(&ExpectNoNotification, nullptr, &t)); |
| EXPECT_EQ(MOJO_RESULT_NOT_FOUND, MojoRemoveTrigger(t, 1234, nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t)); |
| } |
| |
| TEST_F(TrapTest, ArmWithTriggerConditionAlreadySatisfied) { |
| MojoHandle a, b; |
| CreateMessagePipe(&a, &b); |
| |
| MojoHandle t; |
| EXPECT_EQ(MOJO_RESULT_OK, MojoCreateTrap(&ExpectOnlyCancel, nullptr, &t)); |
| EXPECT_EQ( |
| MOJO_RESULT_OK, |
| MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_WRITABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, 0, nullptr)); |
| |
| // |a| is always writable, so we can never arm this trap. |
| constexpr size_t kMaxBlockingEvents = 3; |
| uint32_t num_blocking_events = kMaxBlockingEvents; |
| MojoTrapEvent blocking_events[kMaxBlockingEvents] = { |
| {sizeof(blocking_events[0])}, |
| {sizeof(blocking_events[0])}, |
| {sizeof(blocking_events[0])}}; |
| EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, |
| MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0])); |
| EXPECT_EQ(1u, num_blocking_events); |
| EXPECT_EQ(0u, blocking_events[0].trigger_context); |
| EXPECT_EQ(MOJO_RESULT_OK, blocking_events[0].result); |
| EXPECT_TRUE(blocking_events[0].signals_state.satisfied_signals & |
| MOJO_HANDLE_SIGNAL_WRITABLE); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); |
| } |
| |
| TEST_F(TrapTest, ArmWithTriggerConditionAlreadyUnsatisfiable) { |
| MojoHandle a, b; |
| CreateMessagePipe(&a, &b); |
| |
| MojoHandle t; |
| EXPECT_EQ(MOJO_RESULT_OK, MojoCreateTrap(&ExpectOnlyCancel, nullptr, &t)); |
| EXPECT_EQ( |
| MOJO_RESULT_OK, |
| MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, 0, nullptr)); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); |
| |
| // |b| is closed and never wrote any messages, so |a| won't be readable again. |
| // MojoArmTrap() should fail, incidcating as much. |
| constexpr size_t kMaxBlockingEvents = 3; |
| uint32_t num_blocking_events = kMaxBlockingEvents; |
| MojoTrapEvent blocking_events[kMaxBlockingEvents] = { |
| {sizeof(blocking_events[0])}, |
| {sizeof(blocking_events[0])}, |
| {sizeof(blocking_events[0])}}; |
| EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, |
| MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0])); |
| EXPECT_EQ(1u, num_blocking_events); |
| EXPECT_EQ(0u, blocking_events[0].trigger_context); |
| EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, blocking_events[0].result); |
| EXPECT_TRUE(blocking_events[0].signals_state.satisfied_signals & |
| MOJO_HANDLE_SIGNAL_PEER_CLOSED); |
| EXPECT_FALSE(blocking_events[0].signals_state.satisfiable_signals & |
| MOJO_HANDLE_SIGNAL_READABLE); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); |
| } |
| |
| TEST_F(TrapTest, MultipleTriggers) { |
| MojoHandle a, b; |
| CreateMessagePipe(&a, &b); |
| |
| base::WaitableEvent a_wait(base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| base::WaitableEvent b_wait(base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| TriggerHelper helper; |
| int num_a_notifications = 0; |
| int num_b_notifications = 0; |
| uintptr_t readable_a_context = |
| helper.CreateContext([&](const MojoTrapEvent& event) { |
| num_a_notifications += 1; |
| EXPECT_EQ(MOJO_RESULT_OK, event.result); |
| a_wait.Signal(); |
| }); |
| uintptr_t readable_b_context = |
| helper.CreateContext([&](const MojoTrapEvent& event) { |
| num_b_notifications += 1; |
| EXPECT_EQ(MOJO_RESULT_OK, event.result); |
| b_wait.Signal(); |
| }); |
| |
| MojoHandle t; |
| EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t)); |
| |
| // Add two independent triggers to trap |a| or |b| readability. |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, |
| readable_a_context, nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoAddTrigger(t, b, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, |
| readable_b_context, nullptr)); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr)); |
| |
| const char kMessage1[] = "things are happening"; |
| const char kMessage2[] = "ok. ok. ok. ok."; |
| const char kMessage3[] = "plz wake up"; |
| |
| // Writing to |b| should signal |a|'s watch. |
| WriteMessage(b, kMessage1); |
| a_wait.Wait(); |
| a_wait.Reset(); |
| |
| // Subsequent messages on |b| should not trigger another notification. |
| WriteMessage(b, kMessage2); |
| WriteMessage(b, kMessage3); |
| |
| // Messages on |a| also shouldn't trigger |b|'s notification, since the |
| // trap should be disarmed by now. |
| WriteMessage(a, kMessage1); |
| WriteMessage(a, kMessage2); |
| WriteMessage(a, kMessage3); |
| |
| // Arming should fail. Since we only ask for at most one context's information |
| // that's all we should get back. Which one we get is unspecified. |
| constexpr size_t kMaxBlockingEvents = 3; |
| uint32_t num_blocking_events = 1; |
| MojoTrapEvent blocking_events[kMaxBlockingEvents] = { |
| {sizeof(blocking_events[0])}, |
| {sizeof(blocking_events[0])}, |
| {sizeof(blocking_events[0])}}; |
| EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, |
| MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0])); |
| EXPECT_EQ(1u, num_blocking_events); |
| EXPECT_TRUE(blocking_events[0].trigger_context == readable_a_context || |
| blocking_events[0].trigger_context == readable_b_context); |
| EXPECT_EQ(MOJO_RESULT_OK, blocking_events[0].result); |
| EXPECT_TRUE(blocking_events[0].signals_state.satisfied_signals & |
| MOJO_HANDLE_SIGNAL_WRITABLE); |
| |
| // Now try arming again, verifying that both contexts are returned. |
| num_blocking_events = kMaxBlockingEvents; |
| EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, |
| MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0])); |
| EXPECT_EQ(2u, num_blocking_events); |
| EXPECT_EQ(MOJO_RESULT_OK, blocking_events[0].result); |
| EXPECT_EQ(MOJO_RESULT_OK, blocking_events[1].result); |
| EXPECT_TRUE(blocking_events[0].signals_state.satisfied_signals & |
| MOJO_HANDLE_SIGNAL_WRITABLE); |
| EXPECT_TRUE(blocking_events[1].signals_state.satisfied_signals & |
| MOJO_HANDLE_SIGNAL_WRITABLE); |
| EXPECT_TRUE((blocking_events[0].trigger_context == readable_a_context && |
| blocking_events[1].trigger_context == readable_b_context) || |
| (blocking_events[0].trigger_context == readable_b_context && |
| blocking_events[1].trigger_context == readable_a_context)); |
| |
| // Flush out the test messages so we should be able to successfully rearm. |
| EXPECT_EQ(kMessage1, ReadMessage(a)); |
| EXPECT_EQ(kMessage2, ReadMessage(a)); |
| EXPECT_EQ(kMessage3, ReadMessage(a)); |
| EXPECT_EQ(kMessage1, ReadMessage(b)); |
| EXPECT_EQ(kMessage2, ReadMessage(b)); |
| EXPECT_EQ(kMessage3, ReadMessage(b)); |
| |
| // Add a trigger whose condition is always satisfied so we can't arm. Arming |
| // should fail with only this new watch's information. |
| uintptr_t writable_c_context = |
| helper.CreateContext([](const MojoTrapEvent&) { NOTREACHED(); }); |
| MojoHandle c, d; |
| CreateMessagePipe(&c, &d); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoAddTrigger(t, c, MOJO_HANDLE_SIGNAL_WRITABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, |
| writable_c_context, nullptr)); |
| num_blocking_events = kMaxBlockingEvents; |
| EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, |
| MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0])); |
| EXPECT_EQ(1u, num_blocking_events); |
| EXPECT_EQ(writable_c_context, blocking_events[0].trigger_context); |
| EXPECT_EQ(MOJO_RESULT_OK, blocking_events[0].result); |
| EXPECT_TRUE(blocking_events[0].signals_state.satisfied_signals & |
| MOJO_HANDLE_SIGNAL_WRITABLE); |
| |
| // Remove the new trigger and arming should succeed once again. |
| EXPECT_EQ(MOJO_RESULT_OK, MojoRemoveTrigger(t, writable_c_context, nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr)); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(c)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d)); |
| } |
| |
| TEST_F(TrapTest, ActivateOtherTriggerFromEventHandler) { |
| MojoHandle a, b; |
| CreateMessagePipe(&a, &b); |
| |
| static const char kTestMessageToA[] = "hello a"; |
| static const char kTestMessageToB[] = "hello b"; |
| |
| base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| |
| TriggerHelper helper; |
| MojoHandle t; |
| EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t)); |
| |
| uintptr_t readable_a_context = |
| helper.CreateContext([&](const MojoTrapEvent& event) { |
| EXPECT_EQ(MOJO_RESULT_OK, event.result); |
| EXPECT_EQ("hello a", ReadMessage(a)); |
| |
| // Re-arm the trap and signal |b|. |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr)); |
| WriteMessage(a, kTestMessageToB); |
| }); |
| |
| uintptr_t readable_b_context = |
| helper.CreateContext([&](const MojoTrapEvent& event) { |
| EXPECT_EQ(MOJO_RESULT_OK, event.result); |
| EXPECT_EQ(kTestMessageToB, ReadMessage(b)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr)); |
| wait.Signal(); |
| }); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, |
| readable_a_context, nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoAddTrigger(t, b, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, |
| readable_b_context, nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr)); |
| |
| // Send a message to |a|. The relevant trigger should be notified and the |
| // event handler should send a message to |b|, in turn notifying the other |
| // trigger. The second event handler will signal |wait|. |
| WriteMessage(b, kTestMessageToA); |
| wait.Wait(); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); |
| } |
| |
| TEST_F(TrapTest, ActivateSameTriggerFromEventHandler) { |
| MojoHandle a, b; |
| CreateMessagePipe(&a, &b); |
| |
| static const char kTestMessageToA[] = "hello a"; |
| |
| base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| |
| TriggerHelper helper; |
| MojoHandle t; |
| EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t)); |
| |
| int expected_notifications = 10; |
| uintptr_t readable_a_context = |
| helper.CreateContext([&](const MojoTrapEvent& event) { |
| EXPECT_EQ(MOJO_RESULT_OK, event.result); |
| EXPECT_EQ("hello a", ReadMessage(a)); |
| |
| EXPECT_GT(expected_notifications, 0); |
| expected_notifications -= 1; |
| if (expected_notifications == 0) { |
| wait.Signal(); |
| return; |
| } else { |
| // Re-arm the trap and signal |a| again. |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr)); |
| WriteMessage(b, kTestMessageToA); |
| } |
| }); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, |
| readable_a_context, nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr)); |
| |
| // Send a message to |a|. When the trigger above is activated, the event |
| // handler will rearm the trap and send another message to |a|. This will |
| // happen until |expected_notifications| reaches 0. |
| WriteMessage(b, kTestMessageToA); |
| wait.Wait(); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t)); |
| } |
| |
| TEST_F(TrapTest, ImplicitRemoveOtherTriggerWithinEventHandler) { |
| MojoHandle a, b; |
| CreateMessagePipe(&a, &b); |
| |
| MojoHandle c, d; |
| CreateMessagePipe(&c, &d); |
| |
| static const char kTestMessageToA[] = "hi a"; |
| static const char kTestMessageToC[] = "hi c"; |
| |
| base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| |
| TriggerHelper helper; |
| MojoHandle t; |
| EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t)); |
| |
| uintptr_t readable_a_context = helper.CreateContextWithCancel( |
| [](const MojoTrapEvent&) { NOTREACHED(); }, [&] { wait.Signal(); }); |
| |
| uintptr_t readable_c_context = |
| helper.CreateContext([&](const MojoTrapEvent& event) { |
| EXPECT_EQ(MOJO_RESULT_OK, event.result); |
| EXPECT_EQ(kTestMessageToC, ReadMessage(c)); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr)); |
| |
| // Must result in exactly ONE notification from the above trigger, for |
| // CANCELLED only. Because we cannot dispatch notifications until the |
| // stack unwinds, and because we must never dispatch non-cancellation |
| // notifications for a handle once it's been closed, we must be certain |
| // that cancellation due to closure preemptively invalidates any |
| // pending non-cancellation notifications queued on the current |
| // RequestContext, such as the one resulting from the WriteMessage here. |
| WriteMessage(b, kTestMessageToA); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); |
| |
| // Rearming should be fine since |a|'s trigger should already be |
| // implicitly removed (even though the notification will not have |
| // been invoked yet.) |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr)); |
| |
| // Nothing interesting should happen as a result of this. |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); |
| }); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, |
| readable_a_context, nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoAddTrigger(t, c, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, |
| readable_c_context, nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr)); |
| |
| WriteMessage(d, kTestMessageToC); |
| wait.Wait(); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(c)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d)); |
| } |
| |
| TEST_F(TrapTest, ExplicitRemoveOtherTriggerWithinEventHandler) { |
| MojoHandle a, b; |
| CreateMessagePipe(&a, &b); |
| |
| MojoHandle c, d; |
| CreateMessagePipe(&c, &d); |
| |
| static const char kTestMessageToA[] = "hi a"; |
| static const char kTestMessageToC[] = "hi c"; |
| |
| base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| |
| TriggerHelper helper; |
| MojoHandle t; |
| EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t)); |
| |
| uintptr_t readable_a_context = |
| helper.CreateContext([](const MojoTrapEvent&) { NOTREACHED(); }); |
| |
| uintptr_t readable_c_context = |
| helper.CreateContext([&](const MojoTrapEvent& event) { |
| EXPECT_EQ(MOJO_RESULT_OK, event.result); |
| EXPECT_EQ(kTestMessageToC, ReadMessage(c)); |
| |
| // Now rearm the trap. |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr)); |
| |
| // Should result in no notifications from the above trigger, because the |
| // trigger will have been removed by the time the event handler can |
| // execute. |
| WriteMessage(b, kTestMessageToA); |
| WriteMessage(b, kTestMessageToA); |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoRemoveTrigger(t, readable_a_context, nullptr)); |
| |
| // Rearming should be fine now. |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr)); |
| |
| // Nothing interesting should happen as a result of these. |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); |
| |
| wait.Signal(); |
| }); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, |
| readable_a_context, nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoAddTrigger(t, c, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, |
| readable_c_context, nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr)); |
| |
| WriteMessage(d, kTestMessageToC); |
| wait.Wait(); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(c)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d)); |
| } |
| |
| TEST_F(TrapTest, NestedCancellation) { |
| if (IsMojoIpczEnabled()) { |
| GTEST_SKIP() << "This test expects trap handlers to be reentrant in some " |
| << "edge cases, which is an unsafe artifact of pre-ipcz Mojo " |
| << "that is unsupported by MojoIpcz. This case should not be " |
| << "relevant to any current production usage."; |
| } |
| |
| MojoHandle a, b; |
| CreateMessagePipe(&a, &b); |
| |
| MojoHandle c, d; |
| CreateMessagePipe(&c, &d); |
| |
| static const char kTestMessageToA[] = "hey a"; |
| static const char kTestMessageToC[] = "hey c"; |
| static const char kTestMessageToD[] = "hey d"; |
| |
| // This is a tricky test. It establishes a trigger on |b| using one trap and |
| // triggers on |c| and |d| using another trap. |
| // |
| // A message is written to |d| to activate |c|'s trigger, and the resuling |
| // event handler invocation does the folllowing: |
| // 1. Writes to |a| to eventually activate |b|'s trigger. |
| // 2. Rearms |c|'s trap. |
| // 3. Writes to |d| to eventually activate |c|'s trigger again. |
| // |
| // Meanwhile, |b|'s event handler removes |c|'s trigger altogether before |
| // writing to |c| to activate |d|'s trigger. |
| // |
| // The net result should be that |c|'s trigger only gets activated once (from |
| // the first write to |d| above) and everyone else gets notified as expected. |
| |
| MojoHandle b_trap; |
| MojoHandle cd_trap; |
| TriggerHelper helper; |
| EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&b_trap)); |
| EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&cd_trap)); |
| |
| base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| uintptr_t readable_d_context = |
| helper.CreateContext([&](const MojoTrapEvent& event) { |
| EXPECT_EQ(MOJO_RESULT_OK, event.result); |
| EXPECT_EQ(kTestMessageToD, ReadMessage(d)); |
| wait.Signal(); |
| }); |
| |
| int num_expected_c_notifications = 1; |
| uintptr_t readable_c_context = |
| helper.CreateContext([&](const MojoTrapEvent& event) { |
| EXPECT_EQ(MOJO_RESULT_OK, event.result); |
| EXPECT_GT(num_expected_c_notifications--, 0); |
| |
| // Trigger an eventual |readable_b_context| notification. |
| WriteMessage(a, kTestMessageToA); |
| |
| EXPECT_EQ(kTestMessageToC, ReadMessage(c)); |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoArmTrap(cd_trap, nullptr, nullptr, nullptr)); |
| |
| // Trigger another eventual |readable_c_context| notification. |
| WriteMessage(d, kTestMessageToC); |
| }); |
| |
| uintptr_t readable_b_context = |
| helper.CreateContext([&](const MojoTrapEvent& event) { |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoRemoveTrigger(cd_trap, readable_c_context, nullptr)); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoArmTrap(cd_trap, nullptr, nullptr, nullptr)); |
| |
| WriteMessage(c, kTestMessageToD); |
| }); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoAddTrigger(b_trap, b, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, |
| readable_b_context, nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoAddTrigger(cd_trap, c, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, |
| readable_c_context, nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoAddTrigger(cd_trap, d, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, |
| readable_d_context, nullptr)); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(b_trap, nullptr, nullptr, nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(cd_trap, nullptr, nullptr, nullptr)); |
| |
| WriteMessage(d, kTestMessageToC); |
| wait.Wait(); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(cd_trap)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b_trap)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(c)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d)); |
| } |
| |
| TEST_F(TrapTest, RemoveSelfWithinEventHandler) { |
| MojoHandle a, b; |
| CreateMessagePipe(&a, &b); |
| |
| static const char kTestMessageToA[] = "hey a"; |
| |
| MojoHandle t; |
| TriggerHelper helper; |
| EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t)); |
| |
| base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| |
| static uintptr_t readable_a_context = |
| helper.CreateContext([&](const MojoTrapEvent& event) { |
| EXPECT_EQ(MOJO_RESULT_OK, event.result); |
| |
| // Subtle: Captures may be invalidated by MojoRemoveTrigger() below, |
| // since it may destroy this lambda. Copy the values we'll need for the |
| // remainder of this function. |
| MojoHandle a_handle = a; |
| MojoHandle trap = t; |
| uintptr_t context = readable_a_context; |
| auto& wait_event = wait; |
| |
| // There should be no problem removing this trigger from its own |
| // notification invocation. |
| EXPECT_EQ(MOJO_RESULT_OK, MojoRemoveTrigger(trap, context, nullptr)); |
| EXPECT_EQ(kTestMessageToA, ReadMessage(a_handle)); |
| |
| // Arming should fail because there are no longer any registered |
| // triggers on the trap. |
| EXPECT_EQ(MOJO_RESULT_NOT_FOUND, |
| MojoArmTrap(trap, nullptr, nullptr, nullptr)); |
| |
| // And closing |a| should be fine (and should not invoke this |
| // notification with MOJO_RESULT_CANCELLED) for the same reason. |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a_handle)); |
| |
| wait_event.Signal(); |
| }); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, |
| readable_a_context, nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr)); |
| |
| WriteMessage(b, kTestMessageToA); |
| wait.Wait(); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t)); |
| } |
| |
| TEST_F(TrapTest, CloseTrapWithinEventHandler) { |
| MojoHandle a, b; |
| CreateMessagePipe(&a, &b); |
| |
| static const char kTestMessageToA1[] = "hey a"; |
| static const char kTestMessageToA2[] = "hey a again"; |
| |
| MojoHandle t; |
| TriggerHelper helper; |
| EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t)); |
| |
| base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| |
| uintptr_t readable_a_context = |
| helper.CreateContext([&](const MojoTrapEvent& event) { |
| EXPECT_EQ(MOJO_RESULT_OK, event.result); |
| EXPECT_EQ(kTestMessageToA1, ReadMessage(a)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr)); |
| |
| // There should be no problem closing this trap from its own |
| // notification callback. Note that it may however delete this lambda |
| // and invalidate any captures, so we copy some captured values for use |
| // below. |
| MojoHandle a_handle = a; |
| MojoHandle b_handle = b; |
| base::WaitableEvent& wait_event = wait; |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t)); |
| |
| // And these should not trigger more notifications, because |t| has been |
| // closed already. |
| WriteMessage(b_handle, kTestMessageToA2); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b_handle)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a_handle)); |
| |
| wait_event.Signal(); |
| }); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, |
| readable_a_context, nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr)); |
| |
| WriteMessage(b, kTestMessageToA1); |
| wait.Wait(); |
| } |
| |
| TEST_F(TrapTest, CloseTrapAfterImplicitTriggerRemoval) { |
| MojoHandle a, b; |
| CreateMessagePipe(&a, &b); |
| |
| static const char kTestMessageToA[] = "hey a"; |
| |
| MojoHandle t; |
| TriggerHelper helper; |
| EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t)); |
| |
| base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| |
| uintptr_t readable_a_context = |
| helper.CreateContext([&](const MojoTrapEvent& event) { |
| EXPECT_EQ(MOJO_RESULT_OK, event.result); |
| EXPECT_EQ(kTestMessageToA, ReadMessage(a)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr)); |
| |
| // Closing `a` deletes its trigger which may in turn delete this lambda |
| // and invalidate any captures. Copy the values we need first. |
| base::WaitableEvent& wait_event = wait; |
| MojoHandle trap = t; |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(trap)); |
| wait_event.Signal(); |
| }); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, |
| readable_a_context, nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr)); |
| |
| WriteMessage(b, kTestMessageToA); |
| wait.Wait(); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); |
| } |
| |
| TEST_F(TrapTest, OtherThreadRemovesTriggerDuringEventHandler) { |
| MojoHandle a, b; |
| CreateMessagePipe(&a, &b); |
| |
| static const char kTestMessageToA[] = "hey a"; |
| |
| MojoHandle t; |
| TriggerHelper helper; |
| EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t)); |
| |
| base::WaitableEvent wait_for_notification( |
| base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| |
| base::WaitableEvent wait_for_cancellation( |
| base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| |
| static bool callback_done = false; |
| uintptr_t readable_a_context = helper.CreateContextWithCancel( |
| [&](const MojoTrapEvent& event) { |
| EXPECT_EQ(MOJO_RESULT_OK, event.result); |
| EXPECT_EQ(kTestMessageToA, ReadMessage(a)); |
| |
| wait_for_notification.Signal(); |
| |
| // Give the other thread sufficient time to race with the completion |
| // of this callback. There should be no race, since the cancellation |
| // notification must be mutually exclusive to this notification. |
| base::PlatformThread::Sleep(base::Seconds(1)); |
| |
| callback_done = true; |
| }, |
| [&] { |
| EXPECT_TRUE(callback_done); |
| wait_for_cancellation.Signal(); |
| }); |
| |
| ThreadedRunner runner(base::BindLambdaForTesting([&] { |
| wait_for_notification.Wait(); |
| |
| // Cancel the watch while the notification is still running. |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoRemoveTrigger(t, readable_a_context, nullptr)); |
| |
| wait_for_cancellation.Wait(); |
| |
| EXPECT_TRUE(callback_done); |
| })); |
| runner.Start(); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, |
| readable_a_context, nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr)); |
| |
| WriteMessage(b, kTestMessageToA); |
| runner.Join(); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); |
| } |
| |
| TEST_F(TrapTest, TriggersRemoveEachOtherWithinEventHandlers) { |
| if (IsMojoIpczEnabled()) { |
| GTEST_SKIP() << "This test deadlocks with MojoIpcz, because it expects " |
| << "trap handlers to be re-entrant in some edge cases. Not " |
| << "relevant to any current production usage."; |
| } |
| |
| MojoHandle a, b; |
| CreateMessagePipe(&a, &b); |
| |
| static const char kTestMessageToA[] = "hey a"; |
| static const char kTestMessageToB[] = "hey b"; |
| |
| base::WaitableEvent wait_for_a_to_notify( |
| base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| base::WaitableEvent wait_for_b_to_notify( |
| base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| base::WaitableEvent wait_for_a_to_cancel( |
| base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| base::WaitableEvent wait_for_b_to_cancel( |
| base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| |
| MojoHandle a_trap; |
| MojoHandle b_trap; |
| TriggerHelper helper; |
| EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&a_trap)); |
| EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&b_trap)); |
| |
| // We set up two traps, one triggered on |a| readability and one triggered on |
| // |b| readability. Each removes the other's trigger from within its own event |
| // handler. This should be safe, i.e., it should not deadlock in spite of the |
| // fact that we also guarantee mutually exclusive event handler invocation |
| // (including cancellations) on any given trap. |
| bool a_cancelled = false; |
| bool b_cancelled = false; |
| static uintptr_t readable_b_context; |
| uintptr_t readable_a_context = helper.CreateContextWithCancel( |
| [&](const MojoTrapEvent& event) { |
| EXPECT_EQ(MOJO_RESULT_OK, event.result); |
| EXPECT_EQ(kTestMessageToA, ReadMessage(a)); |
| |
| // Our removal from the other handler may delete this lambda and |
| // invalidate its captures. Copy the references we need first. |
| base::WaitableEvent& wait_for_b = wait_for_b_to_notify; |
| MojoHandle b_trap_handle = b_trap; |
| uintptr_t b_context = readable_b_context; |
| wait_for_a_to_notify.Signal(); |
| |
| wait_for_b.Wait(); |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoRemoveTrigger(b_trap_handle, b_context, nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b_trap_handle)); |
| }, |
| [&] { |
| a_cancelled = true; |
| wait_for_a_to_cancel.Signal(); |
| wait_for_b_to_cancel.Wait(); |
| }); |
| |
| readable_b_context = helper.CreateContextWithCancel( |
| [&](const MojoTrapEvent& event) { |
| EXPECT_EQ(MOJO_RESULT_OK, event.result); |
| EXPECT_EQ(kTestMessageToB, ReadMessage(b)); |
| |
| // Our removal from the other handler may delete this lambda and |
| // invalidate its captures. Copy the references we need first. |
| base::WaitableEvent& wait_for_a = wait_for_a_to_notify; |
| MojoHandle a_trap_handle = a_trap; |
| uintptr_t a_context = readable_a_context; |
| wait_for_b_to_notify.Signal(); |
| |
| wait_for_a.Wait(); |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoRemoveTrigger(a_trap_handle, a_context, nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a_trap_handle)); |
| }, |
| [&] { |
| b_cancelled = true; |
| wait_for_b_to_cancel.Signal(); |
| wait_for_a_to_cancel.Wait(); |
| }); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoAddTrigger(a_trap, a, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, |
| readable_a_context, nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(a_trap, nullptr, nullptr, nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoAddTrigger(b_trap, b, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, |
| readable_b_context, nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(b_trap, nullptr, nullptr, nullptr)); |
| |
| ThreadedRunner runner(base::BindOnce( |
| [](MojoHandle b) { WriteMessage(b, kTestMessageToA); }, b)); |
| runner.Start(); |
| |
| // To enforce that the two traps run concurrently, wait until the WriteMessage |
| // above has made a readable before firing the readable trap on b. |
| wait_for_a_to_notify.Wait(); |
| |
| WriteMessage(a, kTestMessageToB); |
| |
| wait_for_a_to_cancel.Wait(); |
| wait_for_b_to_cancel.Wait(); |
| runner.Join(); |
| |
| EXPECT_TRUE(a_cancelled); |
| EXPECT_TRUE(b_cancelled); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); |
| } |
| |
| TEST_F(TrapTest, AlwaysCancel) { |
| // Basic sanity check to ensure that all possible ways to remove a trigger |
| // result in a final MOJO_RESULT_CANCELLED notification. |
| |
| MojoHandle a, b; |
| CreateMessagePipe(&a, &b); |
| |
| MojoHandle t; |
| TriggerHelper helper; |
| EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t)); |
| |
| base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| auto ignore_event = [](const MojoTrapEvent&) {}; |
| auto signal_wait = [&] { wait.Signal(); }; |
| |
| // Cancel via |MojoRemoveTrigger()|. |
| uintptr_t context = helper.CreateContextWithCancel(ignore_event, signal_wait); |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, context, |
| nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoRemoveTrigger(t, context, nullptr)); |
| wait.Wait(); |
| wait.Reset(); |
| |
| // Cancel by closing the trigger's watched handle. |
| context = helper.CreateContextWithCancel(ignore_event, signal_wait); |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, context, |
| nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); |
| wait.Wait(); |
| wait.Reset(); |
| |
| // Cancel by closing the trap handle. |
| context = helper.CreateContextWithCancel(ignore_event, signal_wait); |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoAddTrigger(t, b, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, context, |
| nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t)); |
| wait.Wait(); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); |
| } |
| |
| TEST_F(TrapTest, ArmFailureCirculation) { |
| // Sanity check to ensure that all ready trigger events will eventually be |
| // returned over a finite number of calls to MojoArmTrap(). |
| |
| constexpr size_t kNumTestPipes = 100; |
| constexpr size_t kNumTestHandles = kNumTestPipes * 2; |
| std::array<MojoHandle, kNumTestHandles> handles; |
| |
| // Create a bunch of pipes and make sure they're all readable. |
| for (size_t i = 0; i < kNumTestPipes; ++i) { |
| CreateMessagePipe(&handles[i], &handles[i + kNumTestPipes]); |
| WriteMessage(handles[i], "hey"); |
| WriteMessage(handles[i + kNumTestPipes], "hay"); |
| WaitForSignals(handles[i], MOJO_HANDLE_SIGNAL_READABLE); |
| WaitForSignals(handles[i + kNumTestPipes], MOJO_HANDLE_SIGNAL_READABLE); |
| } |
| |
| // Create a trap and watch all of them for readability. |
| MojoHandle t; |
| EXPECT_EQ(MOJO_RESULT_OK, MojoCreateTrap(&ExpectOnlyCancel, nullptr, &t)); |
| for (size_t i = 0; i < kNumTestHandles; ++i) { |
| EXPECT_EQ( |
| MOJO_RESULT_OK, |
| MojoAddTrigger(t, handles[i], MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, i, nullptr)); |
| } |
| |
| // Keep trying to arm |t| until every trigger gets an entry in |
| // |ready_contexts|. If MojoArmTrap() is well-behaved, this should terminate |
| // eventually. |
| std::set<uintptr_t> ready_contexts; |
| while (ready_contexts.size() < kNumTestHandles) { |
| uint32_t num_blocking_events = 1; |
| MojoTrapEvent blocking_event = {sizeof(blocking_event)}; |
| EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, |
| MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_event)); |
| EXPECT_EQ(1u, num_blocking_events); |
| EXPECT_EQ(MOJO_RESULT_OK, blocking_event.result); |
| ready_contexts.insert(blocking_event.trigger_context); |
| } |
| |
| for (size_t i = 0; i < kNumTestHandles; ++i) |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(handles[i])); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t)); |
| } |
| |
| TEST_F(TrapTest, TriggerOnUnsatisfiedSignals) { |
| if (IsMojoIpczEnabled()) { |
| GTEST_SKIP() << "Monitoring for unsatisfied signals is not supported by " |
| << "MojoIpcz."; |
| } |
| |
| MojoHandle a, b; |
| CreateMessagePipe(&a, &b); |
| |
| base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| TriggerHelper helper; |
| const uintptr_t readable_a_context = |
| helper.CreateContext([&](const MojoTrapEvent& event) { |
| EXPECT_EQ(MOJO_RESULT_OK, event.result); |
| wait.Signal(); |
| }); |
| |
| MojoHandle t; |
| EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t)); |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, |
| readable_a_context, nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr)); |
| |
| const char kMessage[] = "this is not a message"; |
| |
| WriteMessage(b, kMessage); |
| wait.Wait(); |
| |
| // Now we know |a| is readable. Remove the trigger and add a new one to watch |
| // for a not-readable state. |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t)); |
| const uintptr_t not_readable_a_context = |
| helper.CreateContext([&](const MojoTrapEvent& event) { |
| EXPECT_EQ(MOJO_RESULT_OK, event.result); |
| wait.Signal(); |
| }); |
| EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t)); |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_UNSATISFIED, |
| not_readable_a_context, nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr)); |
| |
| // This should not block, because the event should be signaled by |
| // |not_readable_a_context| when we read the only available message off of |
| // |a|. |
| wait.Reset(); |
| EXPECT_EQ(kMessage, ReadMessage(a)); |
| wait.Wait(); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); |
| } |
| |
| TEST_F(TrapTest, TriggerDuringDestruction) { |
| // Regression test for races between trap event firing and trap destruction. |
| // See https://crbug.com/1385643. |
| MojoHandle a, b; |
| CreateMessagePipe(&a, &b); |
| |
| auto drain_a = [&](const MojoTrapEvent& event) { |
| MojoMessageHandle m; |
| while (MojoReadMessage(a, nullptr, &m) == MOJO_RESULT_OK) { |
| MojoDestroyMessage(m); |
| } |
| }; |
| |
| constexpr size_t kNumIterations = 1000; |
| for (size_t i = 0; i < kNumIterations; ++i) { |
| MojoHandle t; |
| TriggerHelper helper; |
| EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t)); |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, |
| helper.CreateContext(drain_a), nullptr)); |
| |
| drain_a(MojoTrapEvent{}); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr)); |
| |
| ThreadedRunner writer( |
| base::BindLambdaForTesting([&] { WriteMessage(b, "ping!"); })); |
| ThreadedRunner closer(base::BindLambdaForTesting([&] { MojoClose(t); })); |
| closer.Start(); |
| writer.Start(); |
| writer.Join(); |
| closer.Join(); |
| } |
| |
| MojoClose(a); |
| MojoClose(b); |
| } |
| |
| TEST_F(TrapTest, RaceDispatchAndBlockedCancel) { |
| // Regression test for https://crbug.com/1508753. This bug was caused by |
| // reordering of a MOJO_RESULT_CANCELLED event to before some other event for |
| // the same trap context, violating an API constraint that must be upheld for |
| // memory safety in application code. The scenario which could elicit the bug |
| // was as follows: |
| // |
| // 1. A single trap is watching two pipes, P and Q. |
| // 2. Thread A closes pipe P, triggering a CANCELLED event. |
| // 3. Thread A re-arms the trap from within the CANCELLED event handler. |
| // 4. Thread B changes Q's state to elicit a event for Q (not CANCELLED). |
| // 5. Thread B dispatch is blocked because thread A is still dispatching. |
| // 6. Before thread B gets a chance to be scheduled, thread A closes Q. |
| // 7. Thread A dispatches a CANCELLED event for Q. |
| // 8. Thread B is scheduled and proceeds to dispatch its Q event. [BAD] |
| |
| struct State; |
| |
| struct Pipe { |
| explicit Pipe(State* state) : state(state) { CreateMessagePipe(&a, &b); } |
| |
| uintptr_t context() const { return reinterpret_cast<uintptr_t>(this); } |
| |
| MojoHandle a; |
| MojoHandle b; |
| bool trigger_cancelled = false; |
| |
| // Back-reference to common state so it's reachable from the event handler. |
| const raw_ptr<State> state; |
| }; |
| |
| struct State { |
| Pipe pipe0{this}; |
| Pipe pipe1{this}; |
| MojoHandle trap; |
| base::WaitableEvent event; |
| }; |
| State state; |
| |
| // NOTE: + to turn the lambda into a function pointer. |
| const MojoTrapEventHandler event_handler = +[](const MojoTrapEvent* event) { |
| auto& pipe = *reinterpret_cast<Pipe*>(event->trigger_context); |
| auto& state = *pipe.state; |
| |
| // If the bug is present, this expectation can fail flakily. No event should |
| // fire for a pipe after its watch has been cancelled. |
| EXPECT_FALSE(pipe.trigger_cancelled); |
| |
| if (event->result == MOJO_RESULT_CANCELLED) { |
| pipe.trigger_cancelled = true; |
| |
| if (&pipe == &state.pipe0) { |
| // When pipe0's watch is cancelled (on the main thread by closure down |
| // below) we re-arm the trap immediately. This must succeed because |
| // `pipe1.a` is now the only handle being watched, and it's still in an |
| // uninteresting state. |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoArmTrap(state.trap, nullptr, nullptr, nullptr)); |
| |
| // Unblock the other thread so it can elicit a trap event on pipe1 now |
| // that the trap is re-armed. It will still block just before |
| // dispatching as long as we're still in this event handler on the main |
| // thread. |
| state.event.Signal(); |
| |
| // A nice long delay to make it very likely for the waiting |
| // ThreadedRunner to progress right up to its event dispatch. |
| base::PlatformThread::Sleep(base::Milliseconds(10)); |
| |
| // Trigger cancellation for pipe1 by closing its `a`. This will queue a |
| // CANCELLED event to fire on the same thread immediately after we |
| // return from this handler. |
| MojoClose(state.pipe1.a); |
| } |
| } |
| }; |
| |
| EXPECT_EQ(MOJO_RESULT_OK, |
| MojoCreateTrap(event_handler, nullptr, &state.trap)); |
| EXPECT_EQ( |
| MOJO_RESULT_OK, |
| MojoAddTrigger(state.trap, state.pipe0.a, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, |
| state.pipe0.context(), nullptr)); |
| EXPECT_EQ( |
| MOJO_RESULT_OK, |
| MojoAddTrigger(state.trap, state.pipe1.a, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, |
| state.pipe1.context(), nullptr)); |
| EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(state.trap, nullptr, nullptr, nullptr)); |
| |
| ThreadedRunner close_pipe1_b(base::BindLambdaForTesting([&] { |
| state.event.Wait(); |
| MojoClose(state.pipe1.b); |
| })); |
| close_pipe1_b.Start(); |
| |
| // Trigger cancellation of the watch on `pipe0.a`. See event_handler above. |
| MojoClose(state.pipe0.a); |
| |
| close_pipe1_b.Join(); |
| MojoClose(state.pipe0.b); |
| MojoClose(state.trap); |
| } |
| |
| base::RepeatingClosure g_do_random_thing_callback; |
| |
| void ReadAllMessages(const MojoTrapEvent* event) { |
| if (event->result == MOJO_RESULT_OK) { |
| MojoHandle handle = static_cast<MojoHandle>(event->trigger_context); |
| MojoMessageHandle message; |
| while (MojoReadMessage(handle, nullptr, &message) == MOJO_RESULT_OK) |
| MojoDestroyMessage(message); |
| } |
| |
| constexpr size_t kNumRandomThingsToDoOnNotify = 5; |
| for (size_t i = 0; i < kNumRandomThingsToDoOnNotify; ++i) |
| g_do_random_thing_callback.Run(); |
| } |
| |
| MojoHandle RandomHandle(base::span<MojoHandle> handles) { |
| return handles[base::RandInt(0, static_cast<int>(handles.size()) - 1)]; |
| } |
| |
| void DoRandomThing(base::span<MojoHandle> traps, |
| base::span<MojoHandle> watched_handles) { |
| switch (base::RandInt(0, 10)) { |
| case 0: |
| MojoClose(RandomHandle(traps)); |
| break; |
| case 1: |
| MojoClose(RandomHandle(watched_handles)); |
| break; |
| case 2: |
| case 3: |
| case 4: { |
| MojoMessageHandle message; |
| ASSERT_EQ(MOJO_RESULT_OK, MojoCreateMessage(nullptr, &message)); |
| ASSERT_EQ(MOJO_RESULT_OK, |
| MojoSetMessageContext(message, 1, nullptr, nullptr, nullptr)); |
| MojoWriteMessage(RandomHandle(watched_handles), message, nullptr); |
| break; |
| } |
| case 5: |
| case 6: { |
| MojoHandle t = RandomHandle(traps); |
| MojoHandle h = RandomHandle(watched_handles); |
| MojoAddTrigger(t, h, MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, |
| static_cast<uintptr_t>(h), nullptr); |
| break; |
| } |
| case 7: |
| case 8: { |
| uint32_t num_blocking_events = 1; |
| MojoTrapEvent blocking_event = {sizeof(blocking_event)}; |
| if (MojoArmTrap(RandomHandle(traps), nullptr, &num_blocking_events, |
| &blocking_event) == MOJO_RESULT_FAILED_PRECONDITION && |
| blocking_event.result == MOJO_RESULT_OK) { |
| ReadAllMessages(&blocking_event); |
| } |
| break; |
| } |
| case 9: |
| case 10: { |
| MojoHandle t = RandomHandle(traps); |
| MojoHandle h = RandomHandle(watched_handles); |
| MojoRemoveTrigger(t, static_cast<uintptr_t>(h), nullptr); |
| break; |
| } |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| TEST_F(TrapTest, ConcurrencyStressTest) { |
| // Regression test for https://crbug.com/740044. Exercises racy usage of the |
| // trap API to weed out potential crashes. |
| if (IsMojoIpczEnabled()) { |
| GTEST_SKIP() << "This test relies on implementation assumptions which are " |
| << "invalid when MojoIpcz is enabled; namely that it's safe " |
| << "to attempt operations on invalid handles."; |
| } |
| |
| constexpr size_t kNumTraps = 50; |
| constexpr size_t kNumWatchedHandles = 50; |
| static_assert(kNumWatchedHandles % 2 == 0, "Invalid number of test handles."); |
| |
| constexpr size_t kNumThreads = 10; |
| static constexpr size_t kNumOperationsPerThread = 400; |
| |
| std::array<MojoHandle, kNumTraps> traps; |
| std::array<MojoHandle, kNumWatchedHandles> watched_handles; |
| g_do_random_thing_callback = base::BindRepeating( |
| &DoRandomThing, base::span(traps), base::span(watched_handles)); |
| |
| for (size_t i = 0; i < kNumTraps; ++i) |
| MojoCreateTrap(&ReadAllMessages, nullptr, &traps[i]); |
| for (size_t i = 0; i < kNumWatchedHandles; i += 2) |
| CreateMessagePipe(&watched_handles[i], &watched_handles[i + 1]); |
| |
| std::array<std::unique_ptr<ThreadedRunner>, kNumThreads> threads; |
| for (size_t i = 0; i < kNumThreads; ++i) { |
| threads[i] = std::make_unique<ThreadedRunner>(base::BindOnce([] { |
| for (size_t i = 0; i < kNumOperationsPerThread; ++i) |
| g_do_random_thing_callback.Run(); |
| })); |
| threads[i]->Start(); |
| } |
| for (size_t i = 0; i < kNumThreads; ++i) |
| threads[i]->Join(); |
| for (size_t i = 0; i < kNumTraps; ++i) |
| MojoClose(traps[i]); |
| for (size_t i = 0; i < kNumWatchedHandles; ++i) |
| MojoClose(watched_handles[i]); |
| |
| g_do_random_thing_callback.Reset(); |
| } |
| |
| } // namespace |
| } // namespace core |
| } // namespace mojo |