b43ad44f4c
Implement an epoll-backed fdevent_context, that reduces overhead when we're polling many file descriptors. FdeventTest.smoke goes from ~5.2s to ~3.3s when run on the host (after this patch's modification to change it from chaining 10 file descriptors together to 512). Test: adb_test on host Test: adbd_test on blueline Test: test_adb.py Test: test_device.py Change-Id: Iacf0093aa7bebea31e447c2cb012af72d8c3297e
260 lines
6.9 KiB
C++
260 lines
6.9 KiB
C++
/*
|
|
* Copyright 2006, Brian Swetland <swetland@frotz.net>
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#define TRACE_TAG FDEVENT
|
|
|
|
#include "sysdeps.h"
|
|
|
|
#include <inttypes.h>
|
|
|
|
#include <android-base/logging.h>
|
|
#include <android-base/stringprintf.h>
|
|
#include <android-base/threads.h>
|
|
|
|
#include "adb_utils.h"
|
|
#include "fdevent.h"
|
|
#include "fdevent_epoll.h"
|
|
#include "fdevent_poll.h"
|
|
|
|
using namespace std::chrono_literals;
|
|
using std::chrono::duration_cast;
|
|
|
|
void invoke_fde(struct fdevent* fde, unsigned events) {
|
|
if (auto f = std::get_if<fd_func>(&fde->func)) {
|
|
(*f)(fde->fd.get(), events, fde->arg);
|
|
} else if (auto f = std::get_if<fd_func2>(&fde->func)) {
|
|
(*f)(fde, events, fde->arg);
|
|
} else {
|
|
__builtin_unreachable();
|
|
}
|
|
}
|
|
|
|
std::string dump_fde(const fdevent* fde) {
|
|
std::string state;
|
|
if (fde->state & FDE_READ) {
|
|
state += "R";
|
|
}
|
|
if (fde->state & FDE_WRITE) {
|
|
state += "W";
|
|
}
|
|
if (fde->state & FDE_ERROR) {
|
|
state += "E";
|
|
}
|
|
return android::base::StringPrintf("(fdevent %" PRIu64 ": fd %d %s)", fde->id, fde->fd.get(),
|
|
state.c_str());
|
|
}
|
|
|
|
fdevent* fdevent_context::Create(unique_fd fd, std::variant<fd_func, fd_func2> func, void* arg) {
|
|
CheckMainThread();
|
|
CHECK_GE(fd.get(), 0);
|
|
|
|
int fd_num = fd.get();
|
|
|
|
fdevent* fde = new fdevent();
|
|
fde->id = fdevent_id_++;
|
|
fde->state = 0;
|
|
fde->fd = std::move(fd);
|
|
fde->func = func;
|
|
fde->arg = arg;
|
|
if (!set_file_block_mode(fde->fd, false)) {
|
|
// Here is not proper to handle the error. If it fails here, some error is
|
|
// likely to be detected by poll(), then we can let the callback function
|
|
// to handle it.
|
|
LOG(ERROR) << "failed to set non-blocking mode for fd " << fde->fd.get();
|
|
}
|
|
|
|
auto [it, inserted] = this->installed_fdevents_.emplace(fd_num, fde);
|
|
CHECK(inserted);
|
|
UNUSED(it);
|
|
|
|
this->Register(fde);
|
|
return fde;
|
|
}
|
|
|
|
unique_fd fdevent_context::Destroy(fdevent* fde) {
|
|
CheckMainThread();
|
|
if (!fde) {
|
|
return {};
|
|
}
|
|
|
|
this->Unregister(fde);
|
|
|
|
auto erased = this->installed_fdevents_.erase(fde->fd.get());
|
|
CHECK_EQ(1UL, erased);
|
|
|
|
unique_fd result = std::move(fde->fd);
|
|
delete fde;
|
|
return result;
|
|
}
|
|
|
|
void fdevent_context::Add(fdevent* fde, unsigned events) {
|
|
CHECK(!(events & FDE_TIMEOUT));
|
|
Set(fde, fde->state | events);
|
|
}
|
|
|
|
void fdevent_context::Del(fdevent* fde, unsigned events) {
|
|
CHECK(!(events & FDE_TIMEOUT));
|
|
Set(fde, fde->state & ~events);
|
|
}
|
|
|
|
void fdevent_context::SetTimeout(fdevent* fde, std::optional<std::chrono::milliseconds> timeout) {
|
|
CheckMainThread();
|
|
fde->timeout = timeout;
|
|
fde->last_active = std::chrono::steady_clock::now();
|
|
}
|
|
|
|
std::optional<std::chrono::milliseconds> fdevent_context::CalculatePollDuration() {
|
|
std::optional<std::chrono::milliseconds> result = std::nullopt;
|
|
auto now = std::chrono::steady_clock::now();
|
|
CheckMainThread();
|
|
|
|
for (const auto& [fd, fde] : this->installed_fdevents_) {
|
|
UNUSED(fd);
|
|
auto timeout_opt = fde->timeout;
|
|
if (timeout_opt) {
|
|
auto deadline = fde->last_active + *timeout_opt;
|
|
auto time_left = duration_cast<std::chrono::milliseconds>(deadline - now);
|
|
if (time_left < 0ms) {
|
|
time_left = 0ms;
|
|
}
|
|
|
|
if (!result) {
|
|
result = time_left;
|
|
} else {
|
|
result = std::min(*result, time_left);
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void fdevent_context::HandleEvents(const std::vector<fdevent_event>& events) {
|
|
for (const auto& event : events) {
|
|
invoke_fde(event.fde, event.events);
|
|
}
|
|
FlushRunQueue();
|
|
}
|
|
|
|
void fdevent_context::FlushRunQueue() {
|
|
// We need to be careful around reentrancy here, since a function we call can queue up another
|
|
// function.
|
|
while (true) {
|
|
std::function<void()> fn;
|
|
{
|
|
std::lock_guard<std::mutex> lock(this->run_queue_mutex_);
|
|
if (this->run_queue_.empty()) {
|
|
break;
|
|
}
|
|
fn = std::move(this->run_queue_.front());
|
|
this->run_queue_.pop_front();
|
|
}
|
|
fn();
|
|
}
|
|
}
|
|
|
|
void fdevent_context::CheckMainThread() {
|
|
if (main_thread_id_) {
|
|
CHECK_EQ(*main_thread_id_, android::base::GetThreadId());
|
|
}
|
|
}
|
|
|
|
void fdevent_context::Run(std::function<void()> fn) {
|
|
{
|
|
std::lock_guard<std::mutex> lock(run_queue_mutex_);
|
|
run_queue_.push_back(std::move(fn));
|
|
}
|
|
|
|
Interrupt();
|
|
}
|
|
|
|
void fdevent_context::TerminateLoop() {
|
|
terminate_loop_ = true;
|
|
Interrupt();
|
|
}
|
|
|
|
static std::unique_ptr<fdevent_context> fdevent_create_context() {
|
|
#if defined(__linux__)
|
|
return std::make_unique<fdevent_context_epoll>();
|
|
#else
|
|
return std::make_unique<fdevent_context_poll>();
|
|
#endif
|
|
}
|
|
|
|
static auto& g_ambient_fdevent_context =
|
|
*new std::unique_ptr<fdevent_context>(fdevent_create_context());
|
|
|
|
static fdevent_context* fdevent_get_ambient() {
|
|
return g_ambient_fdevent_context.get();
|
|
}
|
|
|
|
fdevent* fdevent_create(int fd, fd_func func, void* arg) {
|
|
unique_fd ufd(fd);
|
|
return fdevent_get_ambient()->Create(std::move(ufd), func, arg);
|
|
}
|
|
|
|
fdevent* fdevent_create(int fd, fd_func2 func, void* arg) {
|
|
unique_fd ufd(fd);
|
|
return fdevent_get_ambient()->Create(std::move(ufd), func, arg);
|
|
}
|
|
|
|
unique_fd fdevent_release(fdevent* fde) {
|
|
return fdevent_get_ambient()->Destroy(fde);
|
|
}
|
|
|
|
void fdevent_destroy(fdevent* fde) {
|
|
fdevent_get_ambient()->Destroy(fde);
|
|
}
|
|
|
|
void fdevent_set(fdevent* fde, unsigned events) {
|
|
fdevent_get_ambient()->Set(fde, events);
|
|
}
|
|
|
|
void fdevent_add(fdevent* fde, unsigned events) {
|
|
fdevent_get_ambient()->Add(fde, events);
|
|
}
|
|
|
|
void fdevent_del(fdevent* fde, unsigned events) {
|
|
fdevent_get_ambient()->Del(fde, events);
|
|
}
|
|
|
|
void fdevent_set_timeout(fdevent* fde, std::optional<std::chrono::milliseconds> timeout) {
|
|
fdevent_get_ambient()->SetTimeout(fde, timeout);
|
|
}
|
|
|
|
void fdevent_run_on_main_thread(std::function<void()> fn) {
|
|
fdevent_get_ambient()->Run(std::move(fn));
|
|
}
|
|
|
|
void fdevent_loop() {
|
|
fdevent_get_ambient()->Loop();
|
|
}
|
|
|
|
void check_main_thread() {
|
|
fdevent_get_ambient()->CheckMainThread();
|
|
}
|
|
|
|
void fdevent_terminate_loop() {
|
|
fdevent_get_ambient()->TerminateLoop();
|
|
}
|
|
|
|
size_t fdevent_installed_count() {
|
|
return fdevent_get_ambient()->InstalledCount();
|
|
}
|
|
|
|
void fdevent_reset() {
|
|
g_ambient_fdevent_context = fdevent_create_context();
|
|
}
|