blob: 93a7dcbdecee4965720b99ab7afa2cefe1d4b935 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fidl/fuchsia.testing.deadline/cpp/wire.h>
#include <fidl/fuchsia.testing/cpp/wire.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/port.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/port.h>
#include <zircon/utc.h>
#include <thread>
#include <src/lib/fake-clock/named-timer/named_timer.h>
namespace fake_clock = fuchsia_testing;
namespace {
fidl::UnownedClientEnd<fake_clock::FakeClock> GetService() {
static std::once_flag svc_connect_once;
static fidl::ClientEnd<fake_clock::FakeClock> fake_clock;
// Writing log messages almost anywhere here will crash the program. So,
// we must do without logging. Also, any errors here will result in bizarre
// low level stack traces, since the C runtime library calls into this code.
std::call_once(svc_connect_once, []() {
if (!fake_clock.is_valid()) {
zx::result result = component::Connect<fake_clock::FakeClock>();
if (result.is_error()) {
FX_PLOGS(ERROR, result.status_value())
<< "Failed to connect to fuchsia.testing.FakeClock service";
}
fake_clock = std::move(result.value());
}
});
return fake_clock.borrow();
}
zx::eventpair MakeEvent(zx_time_t deadline) {
zx::eventpair l, r;
if (zx_status_t status = zx::eventpair::create(0, &l, &r); status != ZX_OK) {
ZX_PANIC("%s", zx_status_get_string(status));
}
const fidl::Status result = fidl::WireCall(GetService())->RegisterEvent(std::move(r), deadline);
ZX_ASSERT_MSG(result.ok(), "%s", result.FormatDescription().c_str());
return l;
}
} // namespace
__EXPORT zx_status_t zx_futex_wait(const zx_futex_t* value_ptr, zx_futex_t current_value,
zx_handle_t new_futex_owner, zx_time_t deadline) {
ZX_ASSERT_MSG(deadline == ZX_TIME_INFINITE,
"zx_futex_wait with deadline is currently supported by FakeClock library");
return _zx_futex_wait(value_ptr, current_value, new_futex_owner, deadline);
}
__EXPORT zx_status_t zx_channel_call(zx_handle_t handle, uint32_t options, zx_time_t deadline,
const zx_channel_call_args_t* args, uint32_t* actual_bytes,
uint32_t* actual_handles) {
// TODO(brunodalbo) There may be a way to get channel_call working if we create a temporary
// channel and an auxiliary thread, but looks like most channel_call call sites don't define
// deadlines.
ZX_ASSERT_MSG(deadline == ZX_TIME_INFINITE,
"zx_channel_call with deadline is not yet supported by FakeClock library");
return _zx_channel_call(handle, options, deadline, args, actual_bytes, actual_handles);
}
__EXPORT zx_time_t zx_clock_get_monotonic() {
const fidl::WireResult result = fidl::WireCall(GetService())->Get();
ZX_ASSERT_MSG(result.ok(), "%s", result.FormatDescription().c_str());
return result.value().time;
}
__EXPORT zx_time_t zx_deadline_after(zx_duration_t duration) {
return zx_time_add_duration(zx_clock_get_monotonic(), duration);
}
__EXPORT zx_status_t zx_nanosleep(zx_time_t deadline) {
zx::eventpair e = MakeEvent(deadline);
if (zx_status_t status =
_zx_object_wait_one(e.get(), ZX_EVENTPAIR_SIGNALED, ZX_TIME_INFINITE, nullptr) != ZX_OK) {
ZX_PANIC("%s", zx_status_get_string(status));
}
return ZX_OK;
}
// wait_one is implemented by making it a wait_many on an infinite deadline with two items: one is
// the original handle+signals, the other is the eventpair created from the fake-clock service.
__EXPORT zx_status_t zx_object_wait_one(zx_handle_t handle, zx_signals_t signals,
zx_time_t deadline, zx_signals_t* observed) {
if (deadline == ZX_TIME_INFINITE || deadline == 0) {
return _zx_object_wait_one(handle, signals, deadline, observed);
}
zx::eventpair e = MakeEvent(deadline);
zx_wait_item_t items[] = {
{
.handle = e.get(),
.waitfor = ZX_EVENTPAIR_SIGNALED,
},
{
.handle = handle,
.waitfor = signals,
},
};
zx_status_t status = _zx_object_wait_many(items, 2, ZX_TIME_INFINITE);
if (observed) {
*observed = items[1].pending;
}
if (status != ZX_OK) {
return status;
}
if ((items[0].pending & ZX_EVENTPAIR_SIGNALED) != 0) {
return ZX_ERR_TIMED_OUT;
}
return ZX_OK;
}
// wait_many is implemented by adding an extra eventpair handle extracted from fake-clock to the
// wait list, and changing the deadline to infinite. If the number of items on the wait is already
// ZX_WAIT_MANY_MAX_ITEMS (meaning we can't add an extra item), we create a port instead and
// register all the wait items to it.
__EXPORT zx_status_t zx_object_wait_many(zx_wait_item_t* items, size_t num_items,
zx_time_t deadline) {
if (deadline == ZX_TIME_INFINITE || deadline == 0 || num_items > ZX_WAIT_MANY_MAX_ITEMS) {
return _zx_object_wait_many(items, num_items, deadline);
}
if (num_items == ZX_WAIT_MANY_MAX_ITEMS) {
// can't add a new item, we need to build a port and wait on it.
zx::port port;
if (zx_status_t status = zx::port::create(0, &port); status != ZX_OK) {
ZX_PANIC("%s", zx_status_get_string(status));
}
for (size_t i = 0; i < num_items; i++) {
if (zx_status_t status =
zx::unowned_handle(items[i].handle)->wait_async(port, i, items[i].waitfor, 0);
status != ZX_OK) {
return status;
}
}
zx::eventpair event = MakeEvent(deadline);
if (zx_status_t status = event.wait_async(port, num_items, ZX_EVENTPAIR_SIGNALED, 0);
status != ZX_OK) {
ZX_PANIC("%s", zx_status_get_string(status));
}
auto update_item = [&items, num_items](const zx_port_packet& packet) {
if (packet.key == num_items) {
if (packet.signal.observed & ZX_EVENTPAIR_SIGNALED) {
return true;
}
} else {
items[packet.key].pending = packet.signal.observed;
}
return false;
};
zx_port_packet_t packet;
if (zx_status_t status = port.wait(zx::time::infinite(), &packet); status != ZX_OK) {
return status;
}
// update_item will return true if the first packet out of the port is a timeout.
if (update_item(packet)) {
return ZX_ERR_TIMED_OUT;
}
// many things may have happened at once, how we just keep polling the port with a zero deadline
// and updating the items
while (port.wait(zx::time(0), &packet) == ZX_OK) {
if (update_item(packet)) {
break;
}
}
return ZX_OK;
}
// we can just add an extra item, but we'll need to copy all the wait items
zx_wait_item_t tmp[ZX_WAIT_MANY_MAX_ITEMS];
std::copy_n(items, num_items, tmp);
zx::eventpair event = MakeEvent(deadline);
tmp[num_items].pending = 0;
tmp[num_items].waitfor = ZX_EVENTPAIR_SIGNALED;
tmp[num_items].handle = event.get();
zx_status_t status = _zx_object_wait_many(tmp, num_items + 1, ZX_TIME_INFINITE);
// copy everything back:
std::copy_n(tmp, num_items, items);
if (status != ZX_OK) {
return status;
}
if ((tmp[num_items].pending & ZX_EVENTPAIR_SIGNALED) != 0) {
return ZX_ERR_TIMED_OUT;
}
return ZX_OK;
}
// port_wait adds an extra fake-clock eventpair handle to the port and changes the deadline to
// ZX_TIME_INFINITE.
__EXPORT zx_status_t zx_port_wait(zx_handle_t handle, zx_time_t deadline,
zx_port_packet_t* packet) {
if (deadline == ZX_TIME_INFINITE) {
return _zx_port_wait(handle, deadline, packet);
}
zx::eventpair event = MakeEvent(deadline);
uint64_t key = 0xFACEFACE00000000 | event.get();
if (zx_status_t status = zx_object_wait_async(event.get(), handle, key, ZX_EVENTPAIR_SIGNALED, 0);
status != ZX_OK) {
ZX_PANIC("%s", zx_status_get_string(status));
}
zx_port_packet_t tmp;
zx_status_t status = _zx_port_wait(handle, ZX_TIME_INFINITE, &tmp);
// always cancel the wait in case it wasn't a timeout
zx::unowned_port(handle)->cancel(event, key);
if (status != ZX_OK) {
return status;
}
if (tmp.type == ZX_PKT_TYPE_SIGNAL_ONE && tmp.key == key &&
tmp.signal.observed == ZX_EVENTPAIR_SIGNALED) {
return ZX_ERR_TIMED_OUT;
}
*packet = tmp;
return ZX_OK;
}
// timer_create changes the type of returned handle from an actual timer to one side of an eventpair
// created by fake-clock. It relies on the fact that ZX_EVENTPAIR_SIGNALED is the same bit as
// ZX_TIMER_SIGNALED, meaning unless clients are inspecting the handle type, they shouldn't be able
// to tell the difference.
__EXPORT zx_status_t zx_timer_create(uint32_t options, zx_clock_t clock_id, zx_handle_t* out) {
// We're replacing a timer with an event, and shamelessly using the fact that
// ZX_EVENTPAIR_SIGNALED and ZX_TIMER_SIGNAL collide, this assertion protects that assumption more
// strongly.
static_assert(ZX_EVENTPAIR_SIGNALED == ZX_TIMER_SIGNALED);
if (clock_id != ZX_CLOCK_MONOTONIC) {
// NOTE: _zx_timer_create will just fail according to the docs.
return _zx_timer_create(options, clock_id, out);
}
// Create an event with infinite deadline and return that instead of a timer handle
*out = MakeEvent(ZX_TIME_INFINITE).release();
return ZX_OK;
}
__EXPORT zx_status_t zx_timer_set(zx_handle_t handle, zx_time_t deadline, zx_duration_t slack) {
zx::eventpair e;
if (zx_status_t status = zx::unowned_eventpair(handle)->duplicate(ZX_RIGHT_SAME_RIGHTS, &e);
status != ZX_OK) {
return status;
}
// reschedule the event with the fake clock service:
const fidl::WireResult result =
fidl::WireCall(GetService())->RescheduleEvent(std::move(e), deadline);
ZX_ASSERT_MSG(result.ok(), "%s", result.FormatDescription().c_str());
return ZX_OK;
}
__EXPORT zx_status_t zx_timer_cancel(zx_handle_t handle) {
zx::eventpair e;
if (zx_status_t status = zx::unowned_eventpair(handle)->duplicate(ZX_RIGHT_SAME_RIGHTS, &e);
status != ZX_OK) {
return status;
}
const fidl::WireResult result = fidl::WireCall(GetService())->CancelEvent(std::move(e));
ZX_ASSERT_MSG(result.ok(), "%s", result.FormatDescription().c_str());
return ZX_OK;
}
__EXPORT bool create_named_deadline(char* component, size_t component_len, char* code,
size_t code_len, zx_time_t duration, zx_time_t* out) {
const fidl::WireResult result =
fidl::WireCall(GetService())
->CreateNamedDeadline(
{
.component_id = fidl::StringView::FromExternal(component, component_len),
.code = fidl::StringView::FromExternal(code, code_len),
},
duration);
ZX_ASSERT_MSG(result.ok(), "%s", result.FormatDescription().c_str());
*out = result.value().deadline;
return true;
}
__EXPORT zx_handle_t zx_utc_reference_get() {
#ifdef FAKE_CLOCK_ALLOW_UTC
return _zx_utc_reference_get();
#else
FX_LOGS(FATAL) << "UTC clock may interact in unexpected ways with the fake-clock library. "
<< "See //src/lib/fake-clock/README.md for ways to fix this.";
return ZX_HANDLE_INVALID;
#endif // FAKE_CLOCK_ALLOW_UTC
}