1764 lines
65 KiB
C++
1764 lines
65 KiB
C++
// Copyright 2017 The Chromium 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 <stdint.h>
|
|
|
|
#include <map>
|
|
#include <memory>
|
|
#include <set>
|
|
|
|
#include "base/bind.h"
|
|
#include "base/callback.h"
|
|
#include "base/macros.h"
|
|
#include "base/memory/ptr_util.h"
|
|
#include "base/rand_util.h"
|
|
#include "base/synchronization/waitable_event.h"
|
|
#include "base/test/bind_test_util.h"
|
|
#include "base/threading/platform_thread.h"
|
|
#include "base/threading/simple_thread.h"
|
|
#include "base/time/time.h"
|
|
#include "mojo/core/test/mojo_test_base.h"
|
|
#include "mojo/public/c/system/data_pipe.h"
|
|
#include "mojo/public/c/system/trap.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() {}
|
|
~TriggerHelper() {}
|
|
|
|
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::BindOnce(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() {}
|
|
|
|
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_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(NotificationContext);
|
|
};
|
|
|
|
static void Notify(const MojoTrapEvent* event) {
|
|
reinterpret_cast<NotificationContext*>(event->trigger_context)
|
|
->Notify(*event);
|
|
}
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(TriggerHelper);
|
|
};
|
|
|
|
class ThreadedRunner : public base::SimpleThread {
|
|
public:
|
|
explicit ThreadedRunner(base::OnceClosure callback)
|
|
: SimpleThread("ThreadedRunner"), callback_(std::move(callback)) {}
|
|
~ThreadedRunner() override {}
|
|
|
|
void Run() override { std::move(callback_).Run(); }
|
|
|
|
private:
|
|
base::OnceClosure callback_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(ThreadedRunner);
|
|
};
|
|
|
|
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));
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
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) {
|
|
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);
|
|
|
|
// There should be no problem removing this trigger from its own
|
|
// notification invocation.
|
|
EXPECT_EQ(MOJO_RESULT_OK,
|
|
MojoRemoveTrigger(t, readable_a_context, nullptr));
|
|
EXPECT_EQ(kTestMessageToA, ReadMessage(a));
|
|
|
|
// Arming should fail because there are no longer any registered
|
|
// triggers on the trap.
|
|
EXPECT_EQ(MOJO_RESULT_NOT_FOUND,
|
|
MojoArmTrap(t, 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));
|
|
|
|
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, 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.
|
|
EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
|
|
|
|
// And these should not trigger more notifications, because |t| has been
|
|
// closed already.
|
|
WriteMessage(b, kTestMessageToA2);
|
|
EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
|
|
EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
|
|
|
|
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, 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));
|
|
|
|
// This will cue up a notification for |MOJO_RESULT_CANCELLED|...
|
|
EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
|
|
|
|
// ...but it should never fire because we close the trap here.
|
|
EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
|
|
|
|
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, 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::TimeDelta::FromSeconds(1));
|
|
|
|
callback_done = true;
|
|
},
|
|
[&] {
|
|
EXPECT_TRUE(callback_done);
|
|
wait_for_cancellation.Signal();
|
|
});
|
|
|
|
ThreadedRunner runner(base::BindOnce(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(b));
|
|
EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
|
|
}
|
|
|
|
TEST_F(TrapTest, TriggersRemoveEachOtherWithinEventHandlers) {
|
|
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));
|
|
wait_for_a_to_notify.Signal();
|
|
wait_for_b_to_notify.Wait();
|
|
EXPECT_EQ(MOJO_RESULT_OK,
|
|
MojoRemoveTrigger(b_trap, readable_b_context, nullptr));
|
|
EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b_trap));
|
|
},
|
|
[&] {
|
|
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));
|
|
wait_for_b_to_notify.Signal();
|
|
wait_for_a_to_notify.Wait();
|
|
EXPECT_EQ(MOJO_RESULT_OK,
|
|
MojoRemoveTrigger(a_trap, readable_a_context, nullptr));
|
|
EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a_trap));
|
|
},
|
|
[&] {
|
|
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();
|
|
|
|
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;
|
|
MojoHandle handles[kNumTestHandles];
|
|
|
|
// 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) {
|
|
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));
|
|
}
|
|
|
|
base::Closure 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(MojoHandle* handles, size_t size) {
|
|
return handles[base::RandInt(0, static_cast<int>(size) - 1)];
|
|
}
|
|
|
|
void DoRandomThing(MojoHandle* traps,
|
|
size_t num_traps,
|
|
MojoHandle* watched_handles,
|
|
size_t num_watched_handles) {
|
|
switch (base::RandInt(0, 10)) {
|
|
case 0:
|
|
MojoClose(RandomHandle(traps, num_traps));
|
|
break;
|
|
case 1:
|
|
MojoClose(RandomHandle(watched_handles, num_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, num_watched_handles),
|
|
message, nullptr);
|
|
break;
|
|
}
|
|
case 5:
|
|
case 6: {
|
|
MojoHandle t = RandomHandle(traps, num_traps);
|
|
MojoHandle h = RandomHandle(watched_handles, num_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, num_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, num_traps);
|
|
MojoHandle h = RandomHandle(watched_handles, num_watched_handles);
|
|
MojoRemoveTrigger(t, static_cast<uintptr_t>(h), nullptr);
|
|
break;
|
|
}
|
|
default:
|
|
NOTREACHED();
|
|
break;
|
|
}
|
|
}
|
|
|
|
TEST_F(TrapTest, ConcurrencyStressTest) {
|
|
// Regression test for https://crbug.com/740044. Exercises racy usage of the
|
|
// trap API to weed out potential crashes.
|
|
|
|
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;
|
|
|
|
MojoHandle traps[kNumTraps];
|
|
MojoHandle watched_handles[kNumWatchedHandles];
|
|
g_do_random_thing_callback = base::BindRepeating(
|
|
&DoRandomThing, traps, kNumTraps, watched_handles, kNumWatchedHandles);
|
|
|
|
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::unique_ptr<ThreadedRunner> threads[kNumThreads];
|
|
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]);
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace core
|
|
} // namespace mojo
|