diff --git a/wpiutil/src/main/native/cpp/EventLoopRunner.cpp b/wpiutil/src/main/native/cpp/EventLoopRunner.cpp new file mode 100644 index 0000000000..8c65d4a41b --- /dev/null +++ b/wpiutil/src/main/native/cpp/EventLoopRunner.cpp @@ -0,0 +1,89 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "wpi/EventLoopRunner.h" + +#include "wpi/SmallVector.h" +#include "wpi/condition_variable.h" +#include "wpi/mutex.h" +#include "wpi/uv/Async.h" +#include "wpi/uv/Loop.h" + +using namespace wpi; + +class EventLoopRunner::Thread : public SafeThread { + public: + Thread() : m_loop(uv::Loop::Create()) { + // set up async handles + if (!m_loop) return; + + // run function + auto doExec = uv::Async::Create(m_loop); + if (!doExec) return; + m_doExec = doExec; + doExec->wakeup.connect([ async = doExec.get(), this ]() { + uv::Loop& loop = async->GetLoopRef(); + { + std::lock_guard lock{m_mutex}; + for (auto&& func : m_exec) func(loop); + m_exec.clear(); + } + m_execDone.notify_all(); + }); + + // exit loop + auto doExit = uv::Async::Create(m_loop); + if (!doExit) return; + m_doExit = doExit; + doExit->wakeup.connect([async = doExit.get()]() { + // close all handles; this will (eventually) stop the loop + async->GetLoopRef().Walk([](uv::Handle& h) { h.Close(); }); + }); + } + + void Main() { + if (m_loop) m_loop->Run(); + } + + // the loop + std::shared_ptr m_loop; + + // run function + std::weak_ptr m_doExec; + wpi::SmallVector, 4> m_exec; + wpi::condition_variable m_execDone; + + // exit loop (thread cleanup) + std::weak_ptr m_doExit; +}; + +EventLoopRunner::EventLoopRunner() { m_owner.Start(new Thread); } + +EventLoopRunner::~EventLoopRunner() { + if (auto thr = m_owner.GetThread()) { + if (auto doExit = thr->m_doExit.lock()) doExit->Send(); + } +} + +void EventLoopRunner::ExecAsync(std::function func) { + if (auto thr = m_owner.GetThread()) { + if (auto doExec = thr->m_doExec.lock()) { + thr->m_exec.emplace_back(func); + doExec->Send(); + } + } +} + +void EventLoopRunner::ExecSync(std::function func) { + if (auto thr = m_owner.GetThread()) { + if (auto doExec = thr->m_doExec.lock()) { + thr->m_exec.emplace_back(func); + doExec->Send(); + thr->m_execDone.wait(thr.GetLock(), [&] { return thr->m_exec.empty(); }); + } + } +} diff --git a/wpiutil/src/main/native/cpp/raw_uv_ostream.cpp b/wpiutil/src/main/native/cpp/raw_uv_ostream.cpp new file mode 100644 index 0000000000..317de41be0 --- /dev/null +++ b/wpiutil/src/main/native/cpp/raw_uv_ostream.cpp @@ -0,0 +1,40 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "wpi/raw_uv_ostream.h" + +#include + +using namespace wpi; + +void raw_uv_ostream::write_impl(const char* data, size_t len) { + while (len > 0) { + // allocate additional buffers as required + if (m_left == 0) { + m_bufs.emplace_back(m_alloc()); + // we want bufs() to always be valid, so set len=0 and keep track of the + // amount of space remaining separately + m_left = m_bufs.back().len; + m_bufs.back().len = 0; + assert(m_left != 0); + } + + size_t amt = std::min(m_left, len); + auto& buf = m_bufs.back(); + std::memcpy(buf.base + buf.len, data, amt); + data += amt; + len -= amt; + buf.len += amt; + m_left -= amt; + } +} + +uint64_t raw_uv_ostream::current_pos() const { + uint64_t size = 0; + for (auto&& buf : m_bufs) size += buf.len; + return size; +} diff --git a/wpiutil/src/main/native/cpp/uv/Async.cpp b/wpiutil/src/main/native/cpp/uv/Async.cpp new file mode 100644 index 0000000000..e5014e0059 --- /dev/null +++ b/wpiutil/src/main/native/cpp/uv/Async.cpp @@ -0,0 +1,30 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "wpi/uv/Async.h" + +#include "wpi/uv/Loop.h" + +namespace wpi { +namespace uv { + +std::shared_ptr Async::Create(Loop& loop) { + auto h = std::make_shared(private_init{}); + int err = uv_async_init(loop.GetRaw(), h->GetRaw(), [](uv_async_t* handle) { + Async& h = *static_cast(handle->data); + h.wakeup(); + }); + if (err < 0) { + loop.ReportError(err); + return nullptr; + } + h->Keep(); + return h; +} + +} // namespace uv +} // namespace wpi diff --git a/wpiutil/src/main/native/cpp/uv/Check.cpp b/wpiutil/src/main/native/cpp/uv/Check.cpp new file mode 100644 index 0000000000..0f4cccf888 --- /dev/null +++ b/wpiutil/src/main/native/cpp/uv/Check.cpp @@ -0,0 +1,34 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "wpi/uv/Check.h" + +#include "wpi/uv/Loop.h" + +namespace wpi { +namespace uv { + +std::shared_ptr Check::Create(Loop& loop) { + auto h = std::make_shared(private_init{}); + int err = uv_check_init(loop.GetRaw(), h->GetRaw()); + if (err < 0) { + loop.ReportError(err); + return nullptr; + } + h->Keep(); + return h; +} + +void Check::Start() { + Invoke(&uv_check_start, GetRaw(), [](uv_check_t* handle) { + Check& h = *static_cast(handle->data); + h.check(); + }); +} + +} // namespace uv +} // namespace wpi diff --git a/wpiutil/src/main/native/cpp/uv/FsEvent.cpp b/wpiutil/src/main/native/cpp/uv/FsEvent.cpp new file mode 100644 index 0000000000..54ba31f3d8 --- /dev/null +++ b/wpiutil/src/main/native/cpp/uv/FsEvent.cpp @@ -0,0 +1,67 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "wpi/uv/FsEvent.h" + +#include + +#include "wpi/SmallString.h" +#include "wpi/uv/Loop.h" + +namespace wpi { +namespace uv { + +std::shared_ptr FsEvent::Create(Loop& loop) { + auto h = std::make_shared(private_init{}); + int err = uv_fs_event_init(loop.GetRaw(), h->GetRaw()); + if (err < 0) { + loop.ReportError(err); + return nullptr; + } + h->Keep(); + return h; +} + +void FsEvent::Start(const Twine& path, unsigned int flags) { + SmallString<128> pathBuf; + Invoke( + &uv_fs_event_start, GetRaw(), + [](uv_fs_event_t* handle, const char* filename, int events, int status) { + FsEvent& h = *static_cast(handle->data); + if (status < 0) + h.ReportError(status); + else + h.fsEvent(filename, events); + }, + path.toNullTerminatedStringRef(pathBuf).data(), flags); +} + +std::string FsEvent::GetPath() { + // Per the libuv docs, GetPath() always gives us a null-terminated string. + // common case should be small + char buf[128]; + size_t size = 128; + int r = uv_fs_event_getpath(GetRaw(), buf, &size); + if (r == 0) { + return buf; + } else if (r == UV_ENOBUFS) { + // need to allocate a big enough buffer + char* buf2 = static_cast(std::malloc(size)); + r = uv_fs_event_getpath(GetRaw(), buf2, &size); + if (r == 0) { + std::string out{buf2}; + std::free(buf2); + return out; + } + std::free(buf2); + } + ReportError(r); + return std::string{}; +} + +} // namespace uv +} // namespace wpi diff --git a/wpiutil/src/main/native/cpp/uv/GetAddrInfo.cpp b/wpiutil/src/main/native/cpp/uv/GetAddrInfo.cpp new file mode 100644 index 0000000000..21f6404560 --- /dev/null +++ b/wpiutil/src/main/native/cpp/uv/GetAddrInfo.cpp @@ -0,0 +1,55 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "wpi/uv/GetAddrInfo.h" + +#include "wpi/uv/Loop.h" +#include "wpi/uv/util.h" + +namespace wpi { +namespace uv { + +GetAddrInfoReq::GetAddrInfoReq() { + error = [this](Error err) { GetLoop().error(err); }; +} + +void GetAddrInfo(Loop& loop, const std::shared_ptr& req, + const Twine& node, const Twine& service, + const addrinfo* hints) { + SmallVector nodeStr; + SmallVector serviceStr; + int err = uv_getaddrinfo( + loop.GetRaw(), req->GetRaw(), + [](uv_getaddrinfo_t* req, int status, addrinfo* res) { + auto& h = *static_cast(req->data); + if (status < 0) + h.ReportError(status); + else + h.resolved(*res); + uv_freeaddrinfo(res); + h.Release(); // this is always a one-shot + }, + node.isNull() ? nullptr : node.toNullTerminatedStringRef(nodeStr).data(), + service.isNull() ? nullptr + : service.toNullTerminatedStringRef(serviceStr).data(), + hints); + if (err < 0) + loop.ReportError(err); + else + req->Keep(); +} + +void GetAddrInfo(Loop& loop, std::function callback, + const Twine& node, const Twine& service, + const addrinfo* hints) { + auto req = std::make_shared(); + req->resolved.connect(callback); + GetAddrInfo(loop, req, node, service, hints); +} + +} // namespace uv +} // namespace wpi diff --git a/wpiutil/src/main/native/cpp/uv/GetNameInfo.cpp b/wpiutil/src/main/native/cpp/uv/GetNameInfo.cpp new file mode 100644 index 0000000000..994aadc77c --- /dev/null +++ b/wpiutil/src/main/native/cpp/uv/GetNameInfo.cpp @@ -0,0 +1,90 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "wpi/uv/GetNameInfo.h" + +#include "wpi/uv/Loop.h" +#include "wpi/uv/util.h" + +namespace wpi { +namespace uv { + +GetNameInfoReq::GetNameInfoReq() { + error = [this](Error err) { GetLoop().error(err); }; +} + +void GetNameInfo(Loop& loop, const std::shared_ptr& req, + const sockaddr& addr, int flags) { + int err = uv_getnameinfo(loop.GetRaw(), req->GetRaw(), + [](uv_getnameinfo_t* req, int status, + const char* hostname, const char* service) { + auto& h = *static_cast(req->data); + if (status < 0) + h.ReportError(status); + else + h.resolved(hostname, service); + h.Release(); // this is always a one-shot + }, + &addr, flags); + if (err < 0) + loop.ReportError(err); + else + req->Keep(); +} + +void GetNameInfo(Loop& loop, + std::function callback, + const sockaddr& addr, int flags) { + auto req = std::make_shared(); + req->resolved.connect(callback); + GetNameInfo(loop, req, addr, flags); +} + +void GetNameInfo4(Loop& loop, const std::shared_ptr& req, + const Twine& ip, unsigned int port, int flags) { + sockaddr_in addr; + int err = NameToAddr(ip, port, &addr); + if (err < 0) + loop.ReportError(err); + else + GetNameInfo(loop, req, reinterpret_cast(addr), flags); +} + +void GetNameInfo4(Loop& loop, + std::function callback, + const Twine& ip, unsigned int port, int flags) { + sockaddr_in addr; + int err = NameToAddr(ip, port, &addr); + if (err < 0) + loop.ReportError(err); + else + GetNameInfo(loop, callback, reinterpret_cast(addr), flags); +} + +void GetNameInfo6(Loop& loop, const std::shared_ptr& req, + const Twine& ip, unsigned int port, int flags) { + sockaddr_in6 addr; + int err = NameToAddr(ip, port, &addr); + if (err < 0) + loop.ReportError(err); + else + GetNameInfo(loop, req, reinterpret_cast(addr), flags); +} + +void GetNameInfo6(Loop& loop, + std::function callback, + const Twine& ip, unsigned int port, int flags) { + sockaddr_in6 addr; + int err = NameToAddr(ip, port, &addr); + if (err < 0) + loop.ReportError(err); + else + GetNameInfo(loop, callback, reinterpret_cast(addr), flags); +} + +} // namespace uv +} // namespace wpi diff --git a/wpiutil/src/main/native/cpp/uv/Handle.cpp b/wpiutil/src/main/native/cpp/uv/Handle.cpp new file mode 100644 index 0000000000..552e6d9c89 --- /dev/null +++ b/wpiutil/src/main/native/cpp/uv/Handle.cpp @@ -0,0 +1,36 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "wpi/uv/Handle.h" + +using namespace wpi::uv; + +Handle::~Handle() noexcept { + if (!m_closed) { + uv_close(m_uv_handle, [](uv_handle_t* uv_handle) { delete uv_handle; }); + } else { + delete m_uv_handle; + } +} + +void Handle::Close() noexcept { + if (!IsClosing()) { + uv_close(m_uv_handle, [](uv_handle_t* handle) { + Handle& h = *static_cast(handle->data); + h.closed(); + h.Release(); // free ourselves + }); + m_closed = true; + } +} + +void Handle::AllocBuf(uv_handle_t* handle, size_t size, uv_buf_t* buf) { + auto& h = *static_cast(handle->data); + *buf = h.m_allocBuf(size); +} + +void Handle::DefaultFreeBuf(Buffer& buf) { buf.Deallocate(); } diff --git a/wpiutil/src/main/native/cpp/uv/Idle.cpp b/wpiutil/src/main/native/cpp/uv/Idle.cpp new file mode 100644 index 0000000000..9eae218156 --- /dev/null +++ b/wpiutil/src/main/native/cpp/uv/Idle.cpp @@ -0,0 +1,34 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "wpi/uv/Idle.h" + +#include "wpi/uv/Loop.h" + +namespace wpi { +namespace uv { + +std::shared_ptr Idle::Create(Loop& loop) { + auto h = std::make_shared(private_init{}); + int err = uv_idle_init(loop.GetRaw(), h->GetRaw()); + if (err < 0) { + loop.ReportError(err); + return nullptr; + } + h->Keep(); + return h; +} + +void Idle::Start() { + Invoke(&uv_idle_start, GetRaw(), [](uv_idle_t* handle) { + Idle& h = *static_cast(handle->data); + h.idle(); + }); +} + +} // namespace uv +} // namespace wpi diff --git a/wpiutil/src/main/native/cpp/uv/Loop.cpp b/wpiutil/src/main/native/cpp/uv/Loop.cpp new file mode 100644 index 0000000000..2602150ddc --- /dev/null +++ b/wpiutil/src/main/native/cpp/uv/Loop.cpp @@ -0,0 +1,64 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "wpi/uv/Loop.h" + +using namespace wpi::uv; + +Loop::Loop(const private_init&) noexcept { +#ifndef _WIN32 + // Ignore SIGPIPE (see https://github.com/joyent/libuv/issues/1254) + static bool once = []() { + signal(SIGPIPE, SIG_IGN); + return true; + }(); + (void)once; +#endif +} + +Loop::~Loop() noexcept { + if (m_loop) { + m_loop->data = nullptr; + Close(); + } +} + +std::shared_ptr Loop::Create() { + auto loop = std::make_shared(private_init{}); + if (uv_loop_init(&loop->m_loopStruct) < 0) return nullptr; + loop->m_loop = &loop->m_loopStruct; + loop->m_loop->data = loop.get(); + return loop; +} + +std::shared_ptr Loop::GetDefault() { + static std::shared_ptr loop = std::make_shared(private_init{}); + loop->m_loop = uv_default_loop(); + if (!loop->m_loop) return nullptr; + loop->m_loop->data = loop.get(); + return loop; +} + +void Loop::Close() { + int err = uv_loop_close(m_loop); + if (err < 0) ReportError(err); +} + +void Loop::Walk(std::function callback) { + uv_walk(m_loop, + [](uv_handle_t* handle, void* func) { + auto& h = *static_cast(handle->data); + auto& f = *static_cast*>(func); + f(h); + }, + &callback); +} + +void Loop::Fork() { + int err = uv_loop_fork(m_loop); + if (err < 0) ReportError(err); +} diff --git a/wpiutil/src/main/native/cpp/uv/NameToAddr.cpp b/wpiutil/src/main/native/cpp/uv/NameToAddr.cpp new file mode 100644 index 0000000000..b407c2b675 --- /dev/null +++ b/wpiutil/src/main/native/cpp/uv/NameToAddr.cpp @@ -0,0 +1,68 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "wpi/uv/util.h" // NOLINT(build/include_order) + +#include + +#include "wpi/SmallString.h" + +namespace wpi { +namespace uv { + +int NameToAddr(const Twine& ip, unsigned int port, sockaddr_in* addr) { + SmallString<128> tmp; + StringRef ipStr = ip.toNullTerminatedStringRef(tmp); + if (ipStr.empty()) { + std::memset(addr, 0, sizeof(sockaddr_in)); + addr->sin_family = PF_INET; + addr->sin_addr.s_addr = INADDR_ANY; + addr->sin_port = htons(port); + return 0; + } else { + return uv_ip4_addr(ipStr.data(), port, addr); + } +} + +int NameToAddr(const Twine& ip, unsigned int port, sockaddr_in6* addr) { + SmallString<128> tmp; + StringRef ipStr = ip.toNullTerminatedStringRef(tmp); + if (ipStr.empty()) { + std::memset(addr, 0, sizeof(sockaddr_in6)); + addr->sin6_family = PF_INET6; + addr->sin6_addr = in6addr_any; + addr->sin6_port = htons(port); + return 0; + } else { + return uv_ip6_addr(ipStr.data(), port, addr); + } +} + +int NameToAddr(const Twine& ip, in_addr* addr) { + SmallString<128> tmp; + StringRef ipStr = ip.toNullTerminatedStringRef(tmp); + if (ipStr.empty()) { + addr->s_addr = INADDR_ANY; + return 0; + } else { + return uv_inet_pton(AF_INET, ipStr.data(), addr); + } +} + +int NameToAddr(const Twine& ip, in6_addr* addr) { + SmallString<128> tmp; + StringRef ipStr = ip.toNullTerminatedStringRef(tmp); + if (ipStr.empty()) { + *addr = in6addr_any; + return 0; + } else { + return uv_inet_pton(AF_INET6, ipStr.data(), addr); + } +} + +} // namespace uv +} // namespace wpi diff --git a/wpiutil/src/main/native/cpp/uv/NetworkStream.cpp b/wpiutil/src/main/native/cpp/uv/NetworkStream.cpp new file mode 100644 index 0000000000..6e327a7c3e --- /dev/null +++ b/wpiutil/src/main/native/cpp/uv/NetworkStream.cpp @@ -0,0 +1,34 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "wpi/uv/NetworkStream.h" + +namespace wpi { +namespace uv { + +ConnectReq::ConnectReq() { + error = [this](Error err) { GetStream().error(err); }; +} + +void NetworkStream::Listen(int backlog) { + Invoke(&uv_listen, GetRawStream(), backlog, + [](uv_stream_t* handle, int status) { + auto& h = *static_cast(handle->data); + if (status < 0) + h.ReportError(status); + else + h.connection(); + }); +} + +void NetworkStream::Listen(std::function callback, int backlog) { + connection.connect(callback); + Listen(backlog); +} + +} // namespace uv +} // namespace wpi diff --git a/wpiutil/src/main/native/cpp/uv/Pipe.cpp b/wpiutil/src/main/native/cpp/uv/Pipe.cpp new file mode 100644 index 0000000000..8da724407d --- /dev/null +++ b/wpiutil/src/main/native/cpp/uv/Pipe.cpp @@ -0,0 +1,115 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "wpi/uv/Pipe.h" + +#include + +#include "wpi/SmallString.h" + +namespace wpi { +namespace uv { + +std::shared_ptr Pipe::Create(Loop& loop, bool ipc) { + auto h = std::make_shared(private_init{}); + int err = uv_pipe_init(loop.GetRaw(), h->GetRaw(), ipc ? 1 : 0); + if (err < 0) { + loop.ReportError(err); + return nullptr; + } + h->Keep(); + return h; +} + +std::shared_ptr Pipe::Accept() { + auto client = Create(GetLoopRef()); + if (!client) return nullptr; + if (!Accept(client)) { + client->Release(); + return nullptr; + } + return client; +} + +Pipe* Pipe::DoAccept() { return Accept().get(); } + +void Pipe::Bind(const Twine& name) { + SmallString<128> nameBuf; + Invoke(&uv_pipe_bind, GetRaw(), + name.toNullTerminatedStringRef(nameBuf).data()); +} + +void Pipe::Connect(const Twine& name, + const std::shared_ptr& req) { + SmallString<128> nameBuf; + uv_pipe_connect(req->GetRaw(), GetRaw(), + name.toNullTerminatedStringRef(nameBuf).data(), + [](uv_connect_t* req, int status) { + auto& h = *static_cast(req->data); + if (status < 0) + h.ReportError(status); + else + h.connected(); + h.Release(); // this is always a one-shot + }); + req->Keep(); +} + +void Pipe::Connect(const Twine& name, std::function callback) { + auto req = std::make_shared(); + req->connected.connect(callback); + Connect(name, req); +} + +std::string Pipe::GetSock() { + // Per libuv docs, the returned buffer is NOT null terminated. + // common case should be small + char buf[128]; + size_t size = 128; + int r = uv_pipe_getsockname(GetRaw(), buf, &size); + if (r == 0) { + return std::string{buf, size}; + } else if (r == UV_ENOBUFS) { + // need to allocate a big enough buffer + char* buf2 = static_cast(std::malloc(size)); + r = uv_pipe_getsockname(GetRaw(), buf2, &size); + if (r == 0) { + std::string out{buf2, size}; + std::free(buf2); + return out; + } + std::free(buf2); + } + ReportError(r); + return std::string{}; +} + +std::string Pipe::GetPeer() { + // Per libuv docs, the returned buffer is NOT null terminated. + // common case should be small + char buf[128]; + size_t size = 128; + int r = uv_pipe_getpeername(GetRaw(), buf, &size); + if (r == 0) { + return std::string{buf, size}; + } else if (r == UV_ENOBUFS) { + // need to allocate a big enough buffer + char* buf2 = static_cast(std::malloc(size)); + r = uv_pipe_getpeername(GetRaw(), buf2, &size); + if (r == 0) { + std::string out{buf2, size}; + std::free(buf2); + return out; + } + std::free(buf2); + } + ReportError(r); + return std::string{}; +} + +} // namespace uv +} // namespace wpi diff --git a/wpiutil/src/main/native/cpp/uv/Poll.cpp b/wpiutil/src/main/native/cpp/uv/Poll.cpp new file mode 100644 index 0000000000..f22c82817c --- /dev/null +++ b/wpiutil/src/main/native/cpp/uv/Poll.cpp @@ -0,0 +1,49 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "wpi/uv/Poll.h" + +#include "wpi/uv/Loop.h" + +namespace wpi { +namespace uv { + +std::shared_ptr Poll::Create(Loop& loop, int fd) { + auto h = std::make_shared(private_init{}); + int err = uv_poll_init(loop.GetRaw(), h->GetRaw(), fd); + if (err < 0) { + loop.ReportError(err); + return nullptr; + } + h->Keep(); + return h; +} + +std::shared_ptr Poll::CreateSocket(Loop& loop, uv_os_sock_t sock) { + auto h = std::make_shared(private_init{}); + int err = uv_poll_init_socket(loop.GetRaw(), h->GetRaw(), sock); + if (err < 0) { + loop.ReportError(err); + return nullptr; + } + h->Keep(); + return h; +} + +void Poll::Start(int events) { + Invoke(&uv_poll_start, GetRaw(), events, + [](uv_poll_t* handle, int status, int events) { + Poll& h = *static_cast(handle->data); + if (status < 0) + h.ReportError(status); + else + h.pollEvent(events); + }); +} + +} // namespace uv +} // namespace wpi diff --git a/wpiutil/src/main/native/cpp/uv/Prepare.cpp b/wpiutil/src/main/native/cpp/uv/Prepare.cpp new file mode 100644 index 0000000000..f27f477950 --- /dev/null +++ b/wpiutil/src/main/native/cpp/uv/Prepare.cpp @@ -0,0 +1,34 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "wpi/uv/Prepare.h" + +#include "wpi/uv/Loop.h" + +namespace wpi { +namespace uv { + +std::shared_ptr Prepare::Create(Loop& loop) { + auto h = std::make_shared(private_init{}); + int err = uv_prepare_init(loop.GetRaw(), h->GetRaw()); + if (err < 0) { + loop.ReportError(err); + return nullptr; + } + h->Keep(); + return h; +} + +void Prepare::Start() { + Invoke(&uv_prepare_start, GetRaw(), [](uv_prepare_t* handle) { + Prepare& h = *static_cast(handle->data); + h.prepare(); + }); +} + +} // namespace uv +} // namespace wpi diff --git a/wpiutil/src/main/native/cpp/uv/Process.cpp b/wpiutil/src/main/native/cpp/uv/Process.cpp new file mode 100644 index 0000000000..e87bcc3002 --- /dev/null +++ b/wpiutil/src/main/native/cpp/uv/Process.cpp @@ -0,0 +1,78 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "wpi/uv/Process.h" + +#include "wpi/SmallString.h" +#include "wpi/uv/Loop.h" +#include "wpi/uv/Pipe.h" + +namespace wpi { +namespace uv { + +ProcessOptions& ProcessOptions::StdioIgnore(size_t index) { + if (index >= m_stdio.size()) m_stdio.resize(index + 1); + m_stdio[index].flags = UV_IGNORE; + m_stdio[index].data.fd = 0; + return *this; +} + +ProcessOptions& ProcessOptions::StdioInherit(size_t index, int fd) { + if (index >= m_stdio.size()) m_stdio.resize(index + 1); + m_stdio[index].flags = UV_INHERIT_FD; + m_stdio[index].data.fd = fd; + return *this; +} + +ProcessOptions& ProcessOptions::StdioInherit(size_t index, Pipe& pipe) { + if (index >= m_stdio.size()) m_stdio.resize(index + 1); + m_stdio[index].flags = UV_INHERIT_STREAM; + m_stdio[index].data.stream = pipe.GetRawStream(); + return *this; +} + +ProcessOptions& ProcessOptions::StdioCreatePipe(size_t index, Pipe& pipe, + unsigned int flags) { + if (index >= m_stdio.size()) m_stdio.resize(index + 1); + m_stdio[index].flags = static_cast(UV_CREATE_PIPE | flags); + m_stdio[index].data.stream = pipe.GetRawStream(); + return *this; +} + +std::shared_ptr Process::Spawn(Loop& loop, const Twine& file, + char** args, + const ProcessOptions& options) { + // convert ProcessOptions to libuv structure + SmallString<128> fileBuf; + uv_process_options_t coptions; + coptions.exit_cb = [](uv_process_t* handle, int64_t status, int signal) { + auto& h = *static_cast(handle->data); + h.exited(status, signal); + }; + coptions.file = file.toNullTerminatedStringRef(fileBuf).data(); + coptions.args = args; + coptions.env = options.m_env; + coptions.cwd = options.m_cwd.empty() ? nullptr : options.m_cwd.c_str(); + coptions.flags = options.m_flags; + coptions.stdio_count = options.m_stdio.size(); + coptions.stdio = const_cast( + static_cast(options.m_stdio.data())); + coptions.uid = options.m_uid; + coptions.gid = options.m_gid; + + auto h = std::make_shared(private_init{}); + int err = uv_spawn(loop.GetRaw(), h->GetRaw(), &coptions); + if (err < 0) { + loop.ReportError(err); + return nullptr; + } + h->Keep(); + return h; +} + +} // namespace uv +} // namespace wpi diff --git a/wpiutil/src/main/native/cpp/uv/Signal.cpp b/wpiutil/src/main/native/cpp/uv/Signal.cpp new file mode 100644 index 0000000000..083b852110 --- /dev/null +++ b/wpiutil/src/main/native/cpp/uv/Signal.cpp @@ -0,0 +1,36 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "wpi/uv/Signal.h" + +#include "wpi/uv/Loop.h" + +namespace wpi { +namespace uv { + +std::shared_ptr Signal::Create(Loop& loop) { + auto h = std::make_shared(private_init{}); + int err = uv_signal_init(loop.GetRaw(), h->GetRaw()); + if (err < 0) { + loop.ReportError(err); + return nullptr; + } + h->Keep(); + return h; +} + +void Signal::Start(int signum) { + Invoke(&uv_signal_start, GetRaw(), + [](uv_signal_t* handle, int signum) { + Signal& h = *static_cast(handle->data); + h.signal(signum); + }, + signum); +} + +} // namespace uv +} // namespace wpi diff --git a/wpiutil/src/main/native/cpp/uv/Stream.cpp b/wpiutil/src/main/native/cpp/uv/Stream.cpp new file mode 100644 index 0000000000..b1fd2945d2 --- /dev/null +++ b/wpiutil/src/main/native/cpp/uv/Stream.cpp @@ -0,0 +1,106 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "wpi/uv/Stream.h" + +#include "wpi/SmallVector.h" + +using namespace wpi; +using namespace wpi::uv; + +namespace { +class CallbackWriteReq : public WriteReq { + public: + CallbackWriteReq(ArrayRef bufs, + std::function, Error)> callback) + : m_bufs{bufs.begin(), bufs.end()} { + finish.connect([=](Error err) { callback(m_bufs, err); }); + } + + private: + SmallVector m_bufs; +}; +} // namespace + +namespace wpi { +namespace uv { + +ShutdownReq::ShutdownReq() { + error = [this](Error err) { GetStream().error(err); }; +} + +WriteReq::WriteReq() { + error = [this](Error err) { GetStream().error(err); }; +} + +void Stream::Shutdown(const std::shared_ptr& req) { + if (Invoke(&uv_shutdown, req->GetRaw(), GetRawStream(), + [](uv_shutdown_t* req, int status) { + auto& h = *static_cast(req->data); + if (status < 0) + h.ReportError(status); + else + h.complete(); + h.Release(); // this is always a one-shot + })) + req->Keep(); +} + +void Stream::Shutdown(std::function callback) { + auto req = std::make_shared(); + if (callback) req->complete.connect(callback); + Shutdown(req); +} + +void Stream::StartRead() { + Invoke(&uv_read_start, GetRawStream(), &Handle::AllocBuf, + [](uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) { + auto& h = *static_cast(stream->data); + Buffer data = *buf; + + // nread=0 is simply ignored + if (nread == UV_EOF) + h.end(); + else if (nread > 0) + h.data(data, static_cast(nread)); + else if (nread < 0) + h.ReportError(nread); + + // free the buffer + h.FreeBuf(data); + }); +} + +void Stream::Write(ArrayRef bufs, + const std::shared_ptr& req) { + if (Invoke(&uv_write, req->GetRaw(), GetRawStream(), bufs.data(), bufs.size(), + [](uv_write_t* r, int status) { + auto& h = *static_cast(r->data); + if (status < 0) h.ReportError(status); + h.finish(Error(status)); + h.Release(); // this is always a one-shot + })) + req->Keep(); +} + +void Stream::Write( + ArrayRef bufs, + std::function, Error)> callback) { + Write(bufs, std::make_shared(bufs, callback)); +} + +int Stream::TryWrite(ArrayRef bufs) { + int val = uv_try_write(GetRawStream(), bufs.data(), bufs.size()); + if (val < 0) { + this->ReportError(val); + return 0; + } + return val; +} + +} // namespace uv +} // namespace wpi diff --git a/wpiutil/src/main/native/cpp/uv/Tcp.cpp b/wpiutil/src/main/native/cpp/uv/Tcp.cpp new file mode 100644 index 0000000000..e8a8127ec6 --- /dev/null +++ b/wpiutil/src/main/native/cpp/uv/Tcp.cpp @@ -0,0 +1,137 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "wpi/uv/Tcp.h" + +#include + +#include "wpi/uv/util.h" + +namespace wpi { +namespace uv { + +std::shared_ptr Tcp::Create(Loop& loop, unsigned int flags) { + auto h = std::make_shared(private_init{}); + int err = uv_tcp_init_ex(loop.GetRaw(), h->GetRaw(), flags); + if (err < 0) { + loop.ReportError(err); + return nullptr; + } + h->Keep(); + return h; +} + +std::shared_ptr Tcp::Accept() { + auto client = Create(GetLoopRef()); + if (!client) return nullptr; + if (!Accept(client)) { + client->Release(); + return nullptr; + } + return client; +} + +Tcp* Tcp::DoAccept() { return Accept().get(); } + +void Tcp::Bind(const Twine& ip, unsigned int port, unsigned int flags) { + sockaddr_in addr; + int err = NameToAddr(ip, port, &addr); + if (err < 0) + ReportError(err); + else + Bind(reinterpret_cast(addr), flags); +} + +void Tcp::Bind6(const Twine& ip, unsigned int port, unsigned int flags) { + sockaddr_in6 addr; + int err = NameToAddr(ip, port, &addr); + if (err < 0) + ReportError(err); + else + Bind(reinterpret_cast(addr), flags); +} + +sockaddr_storage Tcp::GetSock() { + sockaddr_storage name; + int len = sizeof(name); + if (!Invoke(&uv_tcp_getsockname, GetRaw(), reinterpret_cast(&name), + &len)) + std::memset(&name, 0, sizeof(name)); + return name; +} + +sockaddr_storage Tcp::GetPeer() { + sockaddr_storage name; + int len = sizeof(name); + if (!Invoke(&uv_tcp_getpeername, GetRaw(), reinterpret_cast(&name), + &len)) + std::memset(&name, 0, sizeof(name)); + return name; +} + +void Tcp::Connect(const sockaddr& addr, + const std::shared_ptr& req) { + if (Invoke(&uv_tcp_connect, req->GetRaw(), GetRaw(), &addr, + [](uv_connect_t* req, int status) { + auto& h = *static_cast(req->data); + if (status < 0) + h.ReportError(status); + else + h.connected(); + h.Release(); // this is always a one-shot + })) + req->Keep(); +} + +void Tcp::Connect(const sockaddr& addr, std::function callback) { + auto req = std::make_shared(); + req->connected.connect(callback); + Connect(addr, req); +} + +void Tcp::Connect(const Twine& ip, unsigned int port, + const std::shared_ptr& req) { + sockaddr_in addr; + int err = NameToAddr(ip, port, &addr); + if (err < 0) + ReportError(err); + else + Connect(reinterpret_cast(addr), req); +} + +void Tcp::Connect(const Twine& ip, unsigned int port, + std::function callback) { + sockaddr_in addr; + int err = NameToAddr(ip, port, &addr); + if (err < 0) + ReportError(err); + else + Connect(reinterpret_cast(addr), callback); +} + +void Tcp::Connect6(const Twine& ip, unsigned int port, + const std::shared_ptr& req) { + sockaddr_in6 addr; + int err = NameToAddr(ip, port, &addr); + if (err < 0) + ReportError(err); + else + Connect(reinterpret_cast(addr), req); +} + +void Tcp::Connect6(const Twine& ip, unsigned int port, + std::function callback) { + sockaddr_in6 addr; + int err = NameToAddr(ip, port, &addr); + if (err < 0) + ReportError(err); + else + Connect(reinterpret_cast(addr), callback); +} + +} // namespace uv +} // namespace wpi diff --git a/wpiutil/src/main/native/cpp/uv/Timer.cpp b/wpiutil/src/main/native/cpp/uv/Timer.cpp new file mode 100644 index 0000000000..4825ea3244 --- /dev/null +++ b/wpiutil/src/main/native/cpp/uv/Timer.cpp @@ -0,0 +1,46 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "wpi/uv/Timer.h" + +#include "wpi/uv/Loop.h" + +namespace wpi { +namespace uv { + +std::shared_ptr Timer::Create(Loop& loop) { + auto h = std::make_shared(private_init{}); + int err = uv_timer_init(loop.GetRaw(), h->GetRaw()); + if (err < 0) { + loop.ReportError(err); + return nullptr; + } + h->Keep(); + return h; +} + +void Timer::SingleShot(Loop& loop, Time timeout, std::function func) { + auto h = Create(loop); + if (!h) return; + h->timeout.connect([ theTimer = h.get(), func ]() { + func(); + theTimer->Close(); + }); + h->Start(timeout); +} + +void Timer::Start(Time timeout, Time repeat) { + Invoke(&uv_timer_start, GetRaw(), + [](uv_timer_t* handle) { + Timer& h = *static_cast(handle->data); + h.timeout(); + }, + timeout.count(), repeat.count()); +} + +} // namespace uv +} // namespace wpi diff --git a/wpiutil/src/main/native/cpp/uv/Tty.cpp b/wpiutil/src/main/native/cpp/uv/Tty.cpp new file mode 100644 index 0000000000..cdd6fd510a --- /dev/null +++ b/wpiutil/src/main/native/cpp/uv/Tty.cpp @@ -0,0 +1,27 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "wpi/uv/Tty.h" + +#include "wpi/uv/Loop.h" + +namespace wpi { +namespace uv { + +std::shared_ptr Tty::Create(Loop& loop, uv_file fd, bool readable) { + auto h = std::make_shared(private_init{}); + int err = uv_tty_init(loop.GetRaw(), h->GetRaw(), fd, readable ? 1 : 0); + if (err < 0) { + loop.ReportError(err); + return nullptr; + } + h->Keep(); + return h; +} + +} // namespace uv +} // namespace wpi diff --git a/wpiutil/src/main/native/cpp/uv/Udp.cpp b/wpiutil/src/main/native/cpp/uv/Udp.cpp new file mode 100644 index 0000000000..cc7208d1b4 --- /dev/null +++ b/wpiutil/src/main/native/cpp/uv/Udp.cpp @@ -0,0 +1,133 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "wpi/uv/Udp.h" + +#include + +#include "wpi/SmallString.h" +#include "wpi/SmallVector.h" +#include "wpi/uv/util.h" + +namespace { + +using namespace wpi; +using namespace wpi::uv; + +class CallbackUdpSendReq : public UdpSendReq { + public: + CallbackUdpSendReq( + ArrayRef bufs, + std::function, Error)> callback) + : m_bufs{bufs.begin(), bufs.end()} { + complete.connect([=](Error err) { callback(m_bufs, err); }); + } + + private: + SmallVector m_bufs; +}; + +} // namespace + +namespace wpi { +namespace uv { + +UdpSendReq::UdpSendReq() { + error = [this](Error err) { GetUdp().error(err); }; +} + +std::shared_ptr Udp::Create(Loop& loop, unsigned int flags) { + auto h = std::make_shared(private_init{}); + int err = uv_udp_init_ex(loop.GetRaw(), h->GetRaw(), flags); + if (err < 0) { + loop.ReportError(err); + return nullptr; + } + h->Keep(); + return h; +} + +void Udp::Bind(const Twine& ip, unsigned int port, unsigned int flags) { + sockaddr_in addr; + int err = NameToAddr(ip, port, &addr); + if (err < 0) + ReportError(err); + else + Bind(reinterpret_cast(addr), flags); +} + +void Udp::Bind6(const Twine& ip, unsigned int port, unsigned int flags) { + sockaddr_in6 addr; + int err = NameToAddr(ip, port, &addr); + if (err < 0) + ReportError(err); + else + Bind(reinterpret_cast(addr), flags); +} + +sockaddr_storage Udp::GetSock() { + sockaddr_storage name; + int len = sizeof(name); + if (!Invoke(&uv_udp_getsockname, GetRaw(), reinterpret_cast(&name), + &len)) + std::memset(&name, 0, sizeof(name)); + return name; +} + +void Udp::SetMembership(const Twine& multicastAddr, const Twine& interfaceAddr, + uv_membership membership) { + SmallString<128> multicastAddrBuf; + SmallString<128> interfaceAddrBuf; + Invoke(&uv_udp_set_membership, GetRaw(), + multicastAddr.toNullTerminatedStringRef(multicastAddrBuf).data(), + interfaceAddr.toNullTerminatedStringRef(interfaceAddrBuf).data(), + membership); +} + +void Udp::SetMulticastInterface(const Twine& interfaceAddr) { + SmallString<128> interfaceAddrBuf; + Invoke(&uv_udp_set_multicast_interface, GetRaw(), + interfaceAddr.toNullTerminatedStringRef(interfaceAddrBuf).data()); +} + +void Udp::Send(const sockaddr& addr, ArrayRef bufs, + const std::shared_ptr& req) { + if (Invoke(&uv_udp_send, req->GetRaw(), GetRaw(), bufs.data(), bufs.size(), + &addr, [](uv_udp_send_t* r, int status) { + auto& h = *static_cast(r->data); + if (status < 0) h.ReportError(status); + h.complete(Error(status)); + h.Release(); // this is always a one-shot + })) + req->Keep(); +} + +void Udp::Send(const sockaddr& addr, ArrayRef bufs, + std::function, Error)> callback) { + Send(addr, bufs, std::make_shared(bufs, callback)); +} + +void Udp::StartRecv() { + Invoke(&uv_udp_recv_start, GetRaw(), &AllocBuf, + [](uv_udp_t* handle, ssize_t nread, const uv_buf_t* buf, + const sockaddr* addr, unsigned flags) { + auto& h = *static_cast(handle->data); + Buffer data = *buf; + + // nread=0 is simply ignored + if (nread > 0) + h.received(data, static_cast(nread), *addr, flags); + else if (nread < 0) + h.ReportError(nread); + + // free the buffer + h.FreeBuf(data); + }); +} + +} // namespace uv +} // namespace wpi diff --git a/wpiutil/src/main/native/cpp/uv/Work.cpp b/wpiutil/src/main/native/cpp/uv/Work.cpp new file mode 100644 index 0000000000..495d94e258 --- /dev/null +++ b/wpiutil/src/main/native/cpp/uv/Work.cpp @@ -0,0 +1,48 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "wpi/uv/Work.h" + +#include "wpi/uv/Loop.h" + +namespace wpi { +namespace uv { + +WorkReq::WorkReq() { + error = [this](Error err) { GetLoop().error(err); }; +} + +void QueueWork(Loop& loop, const std::shared_ptr& req) { + int err = uv_queue_work(loop.GetRaw(), req->GetRaw(), + [](uv_work_t* req) { + auto& h = *static_cast(req->data); + h.work(); + }, + [](uv_work_t* req, int status) { + auto& h = *static_cast(req->data); + if (status < 0) + h.ReportError(status); + else + h.afterWork(); + h.Release(); // this is always a one-shot + }); + if (err < 0) + loop.ReportError(err); + else + req->Keep(); +} + +void QueueWork(Loop& loop, std::function work, + std::function afterWork) { + auto req = std::make_shared(); + req->work.connect(work); + req->afterWork.connect(afterWork); + QueueWork(loop, req); +} + +} // namespace uv +} // namespace wpi diff --git a/wpiutil/src/main/native/include/wpi/EventLoopRunner.h b/wpiutil/src/main/native/include/wpi/EventLoopRunner.h new file mode 100644 index 0000000000..9255b91cb7 --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/EventLoopRunner.h @@ -0,0 +1,50 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef WPIUTIL_WPI_EVENTLOOPRUNNER_H_ +#define WPIUTIL_WPI_EVENTLOOPRUNNER_H_ + +#include + +#include "wpi/SafeThread.h" +#include "wpi/uv/Loop.h" + +namespace wpi { + +/** + * Executes an event loop on a separate thread. + */ +class EventLoopRunner { + public: + EventLoopRunner(); + virtual ~EventLoopRunner(); + + /** + * Run a function asynchronously (once) on the loop. + * This is safe to call from any thread, but is NOT safe to call from the + * provided function (it will deadlock). + * @param func function to execute on the loop + */ + void ExecAsync(std::function func); + + /** + * Run a function synchronously (once) on the loop. + * This is safe to call from any thread, but is NOT safe to call from the + * provided function (it will deadlock). + * This does not return until the function finishes executing. + * @param func function to execute on the loop + */ + void ExecSync(std::function func); + + private: + class Thread; + wpi::SafeThreadOwner m_owner; +}; + +} // namespace wpi + +#endif // WPIUTIL_WPI_EVENTLOOPRUNNER_H_ diff --git a/wpiutil/src/main/native/include/wpi/raw_uv_ostream.h b/wpiutil/src/main/native/include/wpi/raw_uv_ostream.h new file mode 100644 index 0000000000..e35b4a8450 --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/raw_uv_ostream.h @@ -0,0 +1,70 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef WPIUTIL_WPI_RAW_UV_OSTREAM_H_ +#define WPIUTIL_WPI_RAW_UV_OSTREAM_H_ + +#include "wpi/ArrayRef.h" +#include "wpi/SmallVector.h" +#include "wpi/raw_ostream.h" +#include "wpi/uv/Buffer.h" + +namespace wpi { + +/** + * raw_ostream style output to a SmallVector of uv::Buffer buffers. Fixed-size + * buffers are allocated and appended as necessary to fit the data being output. + * The SmallVector need not be empty at start. + */ +class raw_uv_ostream : public raw_ostream { + public: + /** + * Construct a new raw_uv_ostream. + * @param bufs Buffers vector. NOT cleared on construction. + * @param allocSize Size to allocate for each buffer; allocation will be + * performed using Buffer::Allocate(). + */ + raw_uv_ostream(SmallVectorImpl& bufs, size_t allocSize) + : m_bufs(bufs), + m_alloc([=]() { return uv::Buffer::Allocate(allocSize); }) { + SetUnbuffered(); + } + + /** + * Construct a new raw_uv_ostream. + * @param bufs Buffers vector. NOT cleared on construction. + * @param alloc Allocator. + */ + raw_uv_ostream(SmallVectorImpl& bufs, + std::function alloc) + : m_bufs(bufs), m_alloc(alloc) { + SetUnbuffered(); + } + + ~raw_uv_ostream() override = default; + + /** + * Returns an ArrayRef to the buffers. + */ + ArrayRef bufs() { return m_bufs; } + + void flush() = delete; + + private: + void write_impl(const char* data, size_t len) override; + uint64_t current_pos() const override; + + SmallVectorImpl& m_bufs; + std::function m_alloc; + + // How much allocated space is left in the current buffer. + size_t m_left = 0; +}; + +} // namespace wpi + +#endif // WPIUTIL_WPI_RAW_UV_OSTREAM_H_ diff --git a/wpiutil/src/main/native/include/wpi/uv/Async.h b/wpiutil/src/main/native/include/wpi/uv/Async.h new file mode 100644 index 0000000000..ec3c32f149 --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/uv/Async.h @@ -0,0 +1,68 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef WPIUTIL_WPI_UV_ASYNC_H_ +#define WPIUTIL_WPI_UV_ASYNC_H_ + +#include + +#include + +#include "wpi/Signal.h" +#include "wpi/uv/Handle.h" + +namespace wpi { +namespace uv { + +class Loop; + +/** + * Async handle. + * Async handles allow the user to "wakeup" the event loop and have a signal + * generated from another thread. + */ +class Async final : public HandleImpl { + struct private_init {}; + + public: + explicit Async(const private_init&) {} + ~Async() noexcept override = default; + + /** + * Create an async handle. + * + * @param loop Loop object where this handle runs. + */ + static std::shared_ptr Create(Loop& loop); + + /** + * Create an async handle. + * + * @param loop Loop object where this handle runs. + */ + static std::shared_ptr Create(const std::shared_ptr& loop) { + return Create(*loop); + } + + /** + * Wakeup the event loop and emit the event. + * + * It’s safe to call this function from any thread. + * An async event will be emitted on the loop thread. + */ + void Send() { Invoke(&uv_async_send, GetRaw()); } + + /** + * Signal generated (on event loop thread) when the async event occurs. + */ + sig::Signal<> wakeup; +}; + +} // namespace uv +} // namespace wpi + +#endif // WPIUTIL_WPI_UV_ASYNC_H_ diff --git a/wpiutil/src/main/native/include/wpi/uv/Buffer.h b/wpiutil/src/main/native/include/wpi/uv/Buffer.h new file mode 100644 index 0000000000..9f69f03c13 --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/uv/Buffer.h @@ -0,0 +1,92 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef WPIUTIL_WPI_UV_BUFFER_H_ +#define WPIUTIL_WPI_UV_BUFFER_H_ + +#include + +#include +#include +#include + +#include "wpi/ArrayRef.h" +#include "wpi/StringRef.h" + +namespace wpi { +namespace uv { + +/** + * Data buffer. Convenience wrapper around uv_buf_t. + */ +class Buffer : public uv_buf_t { + public: + Buffer() { + base = nullptr; + len = 0; + } + /*implicit*/ Buffer(const uv_buf_t& oth) { // NOLINT(runtime/explicit) + base = oth.base; + len = oth.len; + } + /*implicit*/ Buffer(StringRef str) // NOLINT(runtime/explicit) + : Buffer{str.data(), str.size()} {} + /*implicit*/ Buffer(ArrayRef arr) // NOLINT(runtime/explicit) + : Buffer{reinterpret_cast(arr.data()), arr.size()} {} + Buffer(char* base_, size_t len_) { + base = base_; + len = len_; + } + Buffer(const char* base_, size_t len_) { + base = const_cast(base_); + len = len_; + } + + ArrayRef data() const { return ArrayRef{base, len}; } + MutableArrayRef data() { return MutableArrayRef{base, len}; } + + operator ArrayRef() const { return data(); } + operator MutableArrayRef() { return data(); } + + static Buffer Allocate(size_t size) { return Buffer{new char[size], size}; } + + static Buffer Dup(StringRef in) { + Buffer buf = Allocate(in.size()); + std::memcpy(buf.base, in.data(), in.size()); + return buf; + } + + static Buffer Dup(ArrayRef in) { + Buffer buf = Allocate(in.size()); + std::memcpy(buf.base, in.begin(), in.size()); + return buf; + } + + void Deallocate() { + delete[] base; + base = nullptr; + len = 0; + } + + Buffer Move() { + Buffer buf = *this; + base = nullptr; + len = 0; + return buf; + } + + friend void swap(Buffer& a, Buffer& b) { + using std::swap; + swap(a.base, b.base); + swap(a.len, b.len); + } +}; + +} // namespace uv +} // namespace wpi + +#endif // WPIUTIL_WPI_UV_BUFFER_H_ diff --git a/wpiutil/src/main/native/include/wpi/uv/Check.h b/wpiutil/src/main/native/include/wpi/uv/Check.h new file mode 100644 index 0000000000..e64a9f067b --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/uv/Check.h @@ -0,0 +1,70 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef WPIUTIL_WPI_UV_CHECK_H_ +#define WPIUTIL_WPI_UV_CHECK_H_ + +#include + +#include + +#include "wpi/Signal.h" +#include "wpi/uv/Handle.h" + +namespace wpi { +namespace uv { + +class Loop; + +/** + * Check handle. + * Check handles will generate a signal once per loop iteration, right + * after polling for I/O. + */ +class Check final : public HandleImpl { + struct private_init {}; + + public: + explicit Check(const private_init&) {} + ~Check() noexcept override = default; + + /** + * Create a check handle. + * + * @param loop Loop object where this handle runs. + */ + static std::shared_ptr Create(Loop& loop); + + /** + * Create a check handle. + * + * @param loop Loop object where this handle runs. + */ + static std::shared_ptr Create(const std::shared_ptr& loop) { + return Create(*loop); + } + + /** + * Start the handle. + */ + void Start(); + + /** + * Stop the handle. The signal will no longer be generated. + */ + void Stop() { Invoke(&uv_check_stop, GetRaw()); } + + /** + * Signal generated once per loop iteration after polling for I/O. + */ + sig::Signal<> check; +}; + +} // namespace uv +} // namespace wpi + +#endif // WPIUTIL_WPI_UV_CHECK_H_ diff --git a/wpiutil/src/main/native/include/wpi/uv/Error.h b/wpiutil/src/main/native/include/wpi/uv/Error.h new file mode 100644 index 0000000000..07a0ab81da --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/uv/Error.h @@ -0,0 +1,51 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef WPIUTIL_WPI_UV_ERROR_H_ +#define WPIUTIL_WPI_UV_ERROR_H_ + +#include + +namespace wpi { +namespace uv { + +/** + * Error code. + */ +class Error { + public: + Error() : m_err(UV_UNKNOWN) {} + explicit Error(int err) : m_err(err) {} + + /** + * Boolean conversion. Returns true if error, false if ok. + */ + explicit operator bool() const { return m_err < 0; } + + /** + * Returns the error code. + */ + int code() const { return m_err; } + + /** + * Returns the error message. + */ + const char* str() const { return uv_strerror(m_err); } + + /** + * Returns the error name. + */ + const char* name() const { return uv_err_name(m_err); } + + private: + int m_err; +}; + +} // namespace uv +} // namespace wpi + +#endif // WPIUTIL_WPI_UV_ERROR_H_ diff --git a/wpiutil/src/main/native/include/wpi/uv/FsEvent.h b/wpiutil/src/main/native/include/wpi/uv/FsEvent.h new file mode 100644 index 0000000000..cf9184894d --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/uv/FsEvent.h @@ -0,0 +1,84 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef WPIUTIL_WPI_UV_FSEVENT_H_ +#define WPIUTIL_WPI_UV_FSEVENT_H_ + +#include + +#include +#include + +#include "wpi/Signal.h" +#include "wpi/Twine.h" +#include "wpi/uv/Handle.h" + +namespace wpi { +namespace uv { + +class Loop; + +/** + * Filesystem Event handle. + */ +class FsEvent final : public HandleImpl { + struct private_init {}; + + public: + explicit FsEvent(const private_init&) {} + ~FsEvent() noexcept override = default; + + /** + * Create a filesystem event handle. + * + * @param loop Loop object where this handle runs. + */ + static std::shared_ptr Create(Loop& loop); + + /** + * Create a filesystem event handle. + * + * @param loop Loop object where this handle runs. + */ + static std::shared_ptr Create(const std::shared_ptr& loop) { + return Create(*loop); + } + + /** + * Start watching the specified path for changes. + * + * @param path Path to watch for changes + * @param events Bitmask of event flags. Only UV_FS_EVENT_RECURSIVE is + * supported (and only on OSX and Windows). + */ + void Start(const Twine& path, unsigned int flags = 0); + + /** + * Stop watching for changes. + */ + void Stop() { Invoke(&uv_fs_event_stop, GetRaw()); } + + /** + * Get the path being monitored. Signals error and returns empty string if + * an error occurs. + * @return Monitored path. + */ + std::string GetPath(); + + /** + * Signal generated when a filesystem change occurs. The first parameter + * is the filename (if a directory was passed to Start(), the filename is + * relative to that directory). The second parameter is an ORed mask of + * UV_RENAME and UV_CHANGE. + */ + sig::Signal fsEvent; +}; + +} // namespace uv +} // namespace wpi + +#endif // WPIUTIL_WPI_UV_FSEVENT_H_ diff --git a/wpiutil/src/main/native/include/wpi/uv/GetAddrInfo.h b/wpiutil/src/main/native/include/wpi/uv/GetAddrInfo.h new file mode 100644 index 0000000000..deb4a24e3a --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/uv/GetAddrInfo.h @@ -0,0 +1,125 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef WPIUTIL_WPI_UV_GETADDRINFO_H_ +#define WPIUTIL_WPI_UV_GETADDRINFO_H_ + +#include + +#include +#include + +#include "wpi/Signal.h" +#include "wpi/Twine.h" +#include "wpi/uv/Request.h" + +namespace wpi { +namespace uv { + +class Loop; + +/** + * GetAddrInfo request. + * For use with `GetAddrInfo()` function family. + */ +class GetAddrInfoReq : public RequestImpl { + public: + GetAddrInfoReq(); + + Loop& GetLoop() const { return *static_cast(GetRaw()->loop->data); } + + /** + * Resolved lookup signal. + * Parameter is resolved address info. + */ + sig::Signal resolved; +}; + +/** + * Asynchronous getaddrinfo(3). HandleResolvedAddress() is called on the + * request when the resolution completes. HandleError() is called on the + * request if any errors occur. + * + * Either node or service may be null (`Twine::createNull()`) but not both. + * + * @param loop Event loop + * @param req request + * @param node Either a numerical network address or a network hostname. + * @param service Either a service name or a port number as a string. + * @param hints Optional `addrinfo` data structure with additional address + * type constraints. + */ +void GetAddrInfo(Loop& loop, const std::shared_ptr& req, + const Twine& node, const Twine& service = Twine::createNull(), + const addrinfo* hints = nullptr); + +/** + * Asynchronous getaddrinfo(3). HandleResolvedAddress() is called on the + * request when the resolution completes. HandleError() is called on the + * request if any errors occur. + * + * Either node or service may be null (`Twine::createNull()`) but not both. + * + * @param loop Event loop + * @param req request + * @param node Either a numerical network address or a network hostname. + * @param service Either a service name or a port number as a string. + * @param hints Optional `addrinfo` data structure with additional address + * type constraints. + */ +inline void GetAddrInfo(const std::shared_ptr& loop, + const std::shared_ptr& req, + const Twine& node, + const Twine& service = Twine::createNull(), + const addrinfo* hints = nullptr) { + GetAddrInfo(*loop, req, node, service, hints); +} + +/** + * Asynchronous getaddrinfo(3). The callback is called when the resolution + * completes, and errors are forwarded to the loop. This is a convenience + * wrapper. + * + * Either node or service may be null (`Twine::createNull()`) but not both. + * + * @param loop Event loop + * @param callback Callback function to call when resolution completes + * @param node Either a numerical network address or a network hostname. + * @param service Either a service name or a port number as a string. + * @param hints Optional `addrinfo` data structure with additional address + * type constraints. + */ +void GetAddrInfo(Loop& loop, std::function callback, + const Twine& node, const Twine& service = Twine::createNull(), + const addrinfo* hints = nullptr); + +/** + * Asynchronous getaddrinfo(3). The callback is called when the resolution + * completes, and errors are forwarded to the loop. This is a convenience + * wrapper. + * + * Either node or service may be null (`Twine::createNull()`) but not both. + * + * @param loop Event loop + * @param callback Callback function to call when resolution completes + * @param node Either a numerical network address or a network hostname. + * @param service Either a service name or a port number as a string. + * @param hints Optional `addrinfo` data structure with additional address + * type constraints. + */ +inline void GetAddrInfo(const std::shared_ptr& loop, + std::function callback, + const Twine& node, + const Twine& service = Twine::createNull(), + const addrinfo* hints = nullptr) { + GetAddrInfo(*loop, callback, node, service, hints); +} + +} // namespace uv +} // namespace wpi + +#endif // WPIUTIL_WPI_UV_GETADDRINFO_H_ diff --git a/wpiutil/src/main/native/include/wpi/uv/GetNameInfo.h b/wpiutil/src/main/native/include/wpi/uv/GetNameInfo.h new file mode 100644 index 0000000000..d6f4d623bc --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/uv/GetNameInfo.h @@ -0,0 +1,228 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef WPIUTIL_WPI_UV_GETNAMEINFO_H_ +#define WPIUTIL_WPI_UV_GETNAMEINFO_H_ + +#include + +#include +#include + +#include "wpi/Signal.h" +#include "wpi/Twine.h" +#include "wpi/uv/Request.h" + +namespace wpi { +namespace uv { + +class Loop; + +/** + * GetNameInfo request. + * For use with `GetNameInfo()` function family. + */ +class GetNameInfoReq : public RequestImpl { + public: + GetNameInfoReq(); + + Loop& GetLoop() const { return *static_cast(GetRaw()->loop->data); } + + /** + * Resolved lookup signal. + * Parameters are hostname and service. + */ + sig::Signal resolved; +}; + +/** + * Asynchronous getnameinfo(3). HandleResolvedName() is called on the + * request when the resolution completes. HandleError() is called on the + * request if any errors occur. + * + * @param loop Event loop + * @param req request + * @param addr Initialized `sockaddr_in` or `sockaddr_in6` data structure. + * @param flags Optional flags to modify the behavior of `getnameinfo`. + */ +void GetNameInfo(Loop& loop, const std::shared_ptr& req, + const sockaddr& addr, int flags = 0); + +/** + * Asynchronous getnameinfo(3). HandleResolvedName() is called on the + * request when the resolution completes. HandleError() is called on the + * request if any errors occur. + * + * @param loop Event loop + * @param req request + * @param addr Initialized `sockaddr_in` or `sockaddr_in6` data structure. + * @param flags Optional flags to modify the behavior of `getnameinfo`. + */ +inline void GetNameInfo(const std::shared_ptr& loop, + const std::shared_ptr& req, + const sockaddr& addr, int flags = 0) { + GetNameInfo(*loop, req, addr, flags); +} + +/** + * Asynchronous getnameinfo(3). The callback is called when the resolution + * completes, and errors are forwarded to the loop. + * + * @param loop Event loop + * @param callback Callback function to call when resolution completes + * @param addr Initialized `sockaddr_in` or `sockaddr_in6` data structure. + * @param flags Optional flags to modify the behavior of `getnameinfo`. + */ +void GetNameInfo(Loop& loop, + std::function callback, + const sockaddr& addr, int flags = 0); + +/** + * Asynchronous getnameinfo(3). The callback is called when the resolution + * completes, and errors are forwarded to the loop. + * + * @param loop Event loop + * @param callback Callback function to call when resolution completes + * @param addr Initialized `sockaddr_in` or `sockaddr_in6` data structure. + * @param flags Optional flags to modify the behavior of `getnameinfo`. + * @return Connection object for the callback + */ +inline void GetNameInfo(const std::shared_ptr& loop, + std::function callback, + const sockaddr& addr, int flags = 0) { + GetNameInfo(*loop, callback, addr, flags); +} + +/** + * Asynchronous IPv4 getnameinfo(3). HandleResolvedName() is called on the + * request when the resolution completes. HandleError() is called on the + * request if any errors occur. + * + * @param loop Event loop + * @param req request + * @param ip A valid IPv4 address + * @param port A valid port number + * @param flags Optional flags to modify the behavior of `getnameinfo`. + */ +void GetNameInfo4(Loop& loop, const std::shared_ptr& req, + const Twine& ip, unsigned int port, int flags = 0); + +/** + * Asynchronous IPv4 getnameinfo(3). HandleResolvedName() is called on the + * request when the resolution completes. HandleError() is called on the + * request if any errors occur. + * + * @param loop Event loop + * @param req request + * @param ip A valid IPv4 address + * @param port A valid port number + * @param flags Optional flags to modify the behavior of `getnameinfo`. + */ +inline void GetNameInfo4(const std::shared_ptr& loop, + const std::shared_ptr& req, + const Twine& ip, unsigned int port, int flags = 0) { + return GetNameInfo4(*loop, req, ip, port, flags); +} + +/** + * Asynchronous IPv4 getnameinfo(3). The callback is called when the resolution + * completes, and errors are forwarded to the loop. + * + * @param loop Event loop + * @param callback Callback function to call when resolution completes + * @param ip A valid IPv4 address + * @param port A valid port number + * @param flags Optional flags to modify the behavior of `getnameinfo`. + */ +void GetNameInfo4(Loop& loop, + std::function callback, + const Twine& ip, unsigned int port, int flags = 0); + +/** + * Asynchronous IPv4 getnameinfo(3). The callback is called when the resolution + * completes, and errors are forwarded to the loop. This is a convenience + * wrapper. + * + * @param loop Event loop + * @param ip A valid IPv4 address + * @param port A valid port number + * @param callback Callback function to call when resolution completes + * @param flags Optional flags to modify the behavior of `getnameinfo`. + */ +inline void GetNameInfo4(const std::shared_ptr& loop, + std::function callback, + const Twine& ip, unsigned int port, int flags = 0) { + return GetNameInfo4(*loop, callback, ip, port, flags); +} + +/** + * Asynchronous IPv6 getnameinfo(3). HandleResolvedName() is called on the + * request when the resolution completes. HandleError() is called on the + * request if any errors occur. + * + * @param loop Event loop + * @param req request + * @param ip A valid IPv6 address + * @param port A valid port number + * @param flags Optional flags to modify the behavior of `getnameinfo`. + */ +void GetNameInfo6(Loop& loop, const std::shared_ptr& req, + const Twine& ip, unsigned int port, int flags = 0); + +/** + * Asynchronous IPv6 getnameinfo(3). HandleResolvedName() is called on the + * request when the resolution completes. HandleError() is called on the + * request if any errors occur. + * + * @param loop Event loop + * @param req request + * @param ip A valid IPv6 address + * @param port A valid port number + * @param flags Optional flags to modify the behavior of `getnameinfo`. + */ +inline void GetNameInfo6(const std::shared_ptr& loop, + const std::shared_ptr& req, + const Twine& ip, unsigned int port, int flags = 0) { + GetNameInfo6(*loop, req, ip, port, flags); +} + +/** + * Asynchronous IPv6 getnameinfo(3). The callback is called when the resolution + * completes, and errors are forwarded to the loop. This is a convenience + * wrapper. + * + * @param loop Event loop + * @param callback Callback function to call when resolution completes + * @param ip A valid IPv6 address + * @param port A valid port number + * @param flags Optional flags to modify the behavior of `getnameinfo`. + */ +void GetNameInfo6(Loop& loop, + std::function callback, + const Twine& ip, unsigned int port, int flags = 0); + +/** + * Asynchronous IPv6 getnameinfo(3). The callback is called when the resolution + * completes, and errors are forwarded to the loop. This is a convenience + * wrapper. + * + * @param loop Event loop + * @param callback Callback function to call when resolution completes + * @param ip A valid IPv6 address + * @param port A valid port number + * @param flags Optional flags to modify the behavior of `getnameinfo`. + */ +inline void GetNameInfo6(const std::shared_ptr& loop, + std::function callback, + const Twine& ip, unsigned int port, int flags = 0) { + return GetNameInfo6(*loop, callback, ip, port, flags); +} + +} // namespace uv +} // namespace wpi + +#endif // WPIUTIL_WPI_UV_GETNAMEINFO_H_ diff --git a/wpiutil/src/main/native/include/wpi/uv/Handle.h b/wpiutil/src/main/native/include/wpi/uv/Handle.h new file mode 100644 index 0000000000..4ee0d9df38 --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/uv/Handle.h @@ -0,0 +1,272 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef WPIUTIL_WPI_UV_HANDLE_H_ +#define WPIUTIL_WPI_UV_HANDLE_H_ + +#include + +#include +#include +#include + +#include "wpi/Signal.h" +#include "wpi/StringRef.h" +#include "wpi/uv/Buffer.h" +#include "wpi/uv/Error.h" +#include "wpi/uv/Loop.h" + +namespace wpi { +namespace uv { + +/** + * Handle. + * Handles are not moveable or copyable and cannot be directly constructed. + * This class provides shared_ptr ownership and shared_from_this. + * Use the specific handle type Create() functions to create handles. + */ +class Handle : public std::enable_shared_from_this { + public: + using Type = uv_handle_type; + + Handle(const Handle&) = delete; + Handle(Handle&&) = delete; + Handle& operator=(const Handle&) = delete; + Handle& operator=(Handle&&) = delete; + virtual ~Handle() noexcept; + + /** + * Get the type of the handle. + * + * A base handle offers no functionality to promote it to the actual handle + * type. By means of this function, the type of the underlying handle as + * specified by Type is made available. + * + * @return The actual type of the handle. + */ + Type GetType() const noexcept { return m_uv_handle->type; } + + /** + * Get the name of the type of the handle. E.g. "pipe" for pipe handles. + */ + StringRef GetTypeName() const noexcept { + return uv_handle_type_name(m_uv_handle->type); + } + + /** + * Get the loop where this handle runs. + * + * @return The loop. + */ + std::shared_ptr GetLoop() const noexcept { + return GetLoopRef().shared_from_this(); + } + + /** + * Get the loop where this handle runs. + * + * @return The loop. + */ + Loop& GetLoopRef() const noexcept { + return *static_cast(m_uv_handle->loop->data); + } + + /** + * Check if the handle is active. + * + * What _active_ means depends on the type of handle: + * + * * An AsyncHandle handle is always active and cannot be deactivated, + * except by closing it with uv_close(). + * * A PipeHandle, TcpHandle, UDPHandle, etc. handle - basically any handle + * that deals with I/O - is active when it is doing something that involves + * I/O, like reading, writing, connecting, accepting new connections, etc. + * * A CheckHandle, IdleHandle, TimerHandle, etc. handle is active when it + * has been started with a call to `Start()`. + * + * Rule of thumb: if a handle of type `FooHandle` has a `Start()` member + * method, then it’s active from the moment that method is called. Likewise, + * `Stop()` deactivates the handle again. + * + * @return True if the handle is active, false otherwise. + */ + bool IsActive() const noexcept { return uv_is_active(m_uv_handle) != 0; } + + /** + * Check if a handle is closing or closed. + * + * This function should only be used between the initialization of the + * handle and the arrival of the close callback. + * + * @return True if the handle is closing or closed, false otherwise. + */ + bool IsClosing() const noexcept { return uv_is_closing(m_uv_handle) != 0; } + + /** + * Request handle to be closed. + * + * This **must** be called on each handle before memory is released. + * In-progress requests are cancelled and this can result in error() being + * emitted. + * + * The handle will emit closed() when finished. + */ + void Close() noexcept; + + /** + * Reference the given handle. + * + * References are idempotent, that is, if a handle is already referenced + * calling this function again will have no effect. + */ + void Reference() noexcept { uv_ref(m_uv_handle); } + + /** + * Unreference the given handle. + * + * References are idempotent, that is, if a handle is not referenced calling + * this function again will have no effect. + */ + void Unreference() noexcept { uv_unref(m_uv_handle); } + + /** + * Check if the given handle is referenced. + * @return True if the handle is referenced, false otherwise. + */ + bool HasReference() const noexcept { return uv_has_ref(m_uv_handle) != 0; } + + /** + * Return the size of the underlying handle type. + * @return The size of the underlying handle type. + */ + size_t RawSize() const noexcept { return uv_handle_size(m_uv_handle->type); } + + /** + * Get the underlying handle data structure. + * + * @return The underlying handle data structure. + */ + uv_handle_t* GetRawHandle() const noexcept { return m_uv_handle; } + + /** + * Set the functions used for allocating and releasing buffers. The size + * passed to the allocator function is a "suggested" size--it's just an + * indication, not related in any way to the pending data to be read. The + * user is free to allocate the amount of memory they decide. For example, + * applications with custom allocation schemes may decide to use a different + * size which matches the memory chunks they already have for other purposes. + * + * @warning Be very careful changing the allocator after the loop has started + * running; there are no interlocks between this and buffers currently in + * flight. + * + * @param alloc Allocation function + * @param dealloc Deallocation function + */ + void SetBufferAllocator(std::function alloc, + std::function dealloc) { + m_allocBuf = alloc; + m_freeBuf = dealloc; + } + + /** + * Free a buffer. Uses the function provided to SetBufFree() or + * Buffer::Deallocate by default. + * + * @param buf The buffer + */ + void FreeBuf(Buffer& buf) const noexcept { m_freeBuf(buf); } + + /** + * Gets user-defined data. + * @return User-defined data if any, nullptr otherwise. + */ + template + std::shared_ptr GetData() const { + return std::static_pointer_cast(m_data); + } + + /** + * Sets user-defined data. + * @param data User-defined arbitrary data. + */ + void SetData(std::shared_ptr data) { m_data = std::move(data); } + + /** + * Error signal + */ + sig::Signal error; + + /** + * Closed signal + */ + sig::Signal<> closed; + + /** + * Report an error. + * @param err Error code + */ + void ReportError(int err) { error(Error(err)); } + + protected: + explicit Handle(uv_handle_t* uv_handle) : m_uv_handle{uv_handle} { + m_uv_handle->data = this; + } + + void Keep() noexcept { m_self = shared_from_this(); } + void Release() noexcept { m_self.reset(); } + + static void AllocBuf(uv_handle_t* handle, size_t size, uv_buf_t* buf); + static void DefaultFreeBuf(Buffer& buf); + + template + bool Invoke(F&& f, Args&&... args) { + auto err = std::forward(f)(std::forward(args)...); + if (err < 0) ReportError(err); + return err == 0; + } + + private: + std::shared_ptr m_self; + uv_handle_t* m_uv_handle; + bool m_closed = false; + std::function m_allocBuf{&Buffer::Allocate}; + std::function m_freeBuf{&DefaultFreeBuf}; + std::shared_ptr m_data; +}; + +/** + * Handle. + */ +template +class HandleImpl : public Handle { + public: + std::shared_ptr shared_from_this() { + return std::static_pointer_cast(Handle::shared_from_this()); + } + + std::shared_ptr shared_from_this() const { + return std::static_pointer_cast(Handle::shared_from_this()); + } + + /** + * Get the underlying handle data structure. + * + * @return The underlying handle data structure. + */ + U* GetRaw() const noexcept { + return reinterpret_cast(this->GetRawHandle()); + } + + protected: + HandleImpl() : Handle{reinterpret_cast(new U)} {} +}; + +} // namespace uv +} // namespace wpi + +#endif // WPIUTIL_WPI_UV_HANDLE_H_ diff --git a/wpiutil/src/main/native/include/wpi/uv/Idle.h b/wpiutil/src/main/native/include/wpi/uv/Idle.h new file mode 100644 index 0000000000..e8278b2b8c --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/uv/Idle.h @@ -0,0 +1,79 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef WPIUTIL_WPI_UV_IDLE_H_ +#define WPIUTIL_WPI_UV_IDLE_H_ + +#include + +#include + +#include "wpi/Signal.h" +#include "wpi/uv/Handle.h" + +namespace wpi { +namespace uv { + +class Loop; + +/** + * Idle handle. + * + * Idle handles will generate a signal once per loop iteration, right + * before the Prepare handles. + * + * The notable difference with Prepare handles is that when there are active + * idle handles, the loop will perform a zero timeout poll instead of blocking + * for I/O. + * + * @warning Despite the name, idle handles will signal every loop iteration, + * not when the loop is actually "idle". This also means they can easly become + * CPU hogs. + */ +class Idle final : public HandleImpl { + struct private_init {}; + + public: + explicit Idle(const private_init&) {} + ~Idle() noexcept override = default; + + /** + * Create an idle handle. + * + * @param loop Loop object where this handle runs. + */ + static std::shared_ptr Create(Loop& loop); + + /** + * Create an idle handle. + * + * @param loop Loop object where this handle runs. + */ + static std::shared_ptr Create(const std::shared_ptr& loop) { + return Create(*loop); + } + + /** + * Start the handle. + */ + void Start(); + + /** + * Stop the handle. The signal will no longer be generated. + */ + void Stop() { Invoke(&uv_idle_stop, GetRaw()); } + + /** + * Signal generated once per loop iteration prior to Prepare signals. + */ + sig::Signal<> idle; +}; + +} // namespace uv +} // namespace wpi + +#endif // WPIUTIL_WPI_UV_IDLE_H_ diff --git a/wpiutil/src/main/native/include/wpi/uv/Loop.h b/wpiutil/src/main/native/include/wpi/uv/Loop.h new file mode 100644 index 0000000000..2bcf77c026 --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/uv/Loop.h @@ -0,0 +1,230 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef WPIUTIL_WPI_UV_LOOP_H_ +#define WPIUTIL_WPI_UV_LOOP_H_ + +#include + +#include +#include +#include +#include + +#include "wpi/Signal.h" +#include "wpi/uv/Error.h" + +namespace wpi { +namespace uv { + +class Handle; + +/** + * Event loop. + * + * The event loop is the central part of uv functionality. It takes care of + * polling for I/O and scheduling signals to be generated based on different + * sources of events. + * + * The event loop is not moveable, copyable, or directly constructible. Use + * Create() to create an event loop, or GetDefault() to get the default loop + * if you know your program will only have a single one. + */ +class Loop final : public std::enable_shared_from_this { + struct private_init {}; + + public: + using Time = std::chrono::duration; + + enum Mode { + kDefault = UV_RUN_DEFAULT, + kOnce = UV_RUN_ONCE, + kNoWait = UV_RUN_NOWAIT + }; + + explicit Loop(const private_init&) noexcept; + + Loop(const Loop&) = delete; + Loop& operator=(const Loop&) = delete; + Loop(Loop&& oth) = delete; + Loop& operator=(Loop&& oth) = delete; + + ~Loop() noexcept; + + /** + * Create a new event loop. The created loop is not the default event loop. + * + * @return The newly created loop. May return nullptr if a failure occurs. + */ + static std::shared_ptr Create(); + + /** + * Create the default event loop. Only use this event loop if a single loop + * is needed for the entire application. + * + * @return The newly created loop. May return nullptr if a failure occurs. + */ + static std::shared_ptr GetDefault(); + + /** + * Release all internal loop resources. + * + * Call this function only when the loop has finished executing and all open + * handles and requests have been closed, or the loop will emit an error. + * + * error() will be emitted in case of errors. + */ + void Close(); + + /** + * Run the event loop. + * + * Available modes are: + * + * * `Loop::kDefault`: Run the event loop until there are no + * active and referenced handles or requests. + * * `Loop::kOnce`: Run a single event loop iteration. Note that this + * function blocksif there are no pending callbacks. + * * `Loop::kNoWait`: Run a single event loop iteration, but don't block + * if there are no pending callbacks. + * + * @return True when done, false in all other cases. + */ + bool Run(Mode mode = kDefault) { + return uv_run(m_loop, static_cast(static_cast(mode))) == + 0; + } + + /** + * Check if there are active resources. + * + * @return True if there are active resources in the loop. + */ + bool IsAlive() const noexcept { return uv_loop_alive(m_loop) != 0; } + + /** + * Stop the event loop. + * + * This will cause Run() to end as soon as possible. + * This will happen not sooner than the next loop iteration. + * If this function was called before blocking for I/O, the loop won’t block + * for I/O on this iteration. + */ + void Stop() noexcept { uv_stop(m_loop); } + + /** + * Get backend file descriptor. + * + * Only kqueue, epoll and event ports are supported. + * This can be used in conjunction with `run(Loop::kNoWait)` to poll + * in one thread and run the event loop’s callbacks in another. + * + * @return The backend file descriptor. + */ + int GetDescriptor() const noexcept { return uv_backend_fd(m_loop); } + + /** + * Get the poll timeout. + * + * @return A `std::pair` composed of a boolean value that is true in case of + * valid timeout, false otherwise, and the timeout + * (`std::chrono::duration`). + */ + std::pair GetTimeout() const noexcept { + auto to = uv_backend_timeout(m_loop); + return std::make_pair(to == -1, Time{to}); + } + + /** + * Return the current timestamp in milliseconds. + * + * The timestamp is cached at the start of the event loop tick. + * The timestamp increases monotonically from some arbitrary point in + * time. + * Don’t make assumptions about the starting point, you will only get + * disappointed. + * + * @return The current timestamp in milliseconds (actual type is + * `std::chrono::duration`). + */ + Time Now() const noexcept { return Time{uv_now(m_loop)}; } + + /** + * Update the event loop’s concept of _now_. + * + * The current time is cached at the start of the event loop tick in order + * to reduce the number of time-related system calls. + * You won’t normally need to call this function unless you have callbacks + * that block the event loop for longer periods of time, where _longer_ is + * somewhat subjective but probably on the order of a millisecond or more. + */ + void UpdateTime() noexcept { uv_update_time(m_loop); } + + /** + * Walk the list of handles. + * + * The callback will be executed once for each handle that is still active. + * + * @param callback A function to be invoked once for each active handle. + */ + void Walk(std::function callback); + + /** + * Reinitialize any kernel state necessary in the child process after + * a fork(2) system call. + * + * Previously started watchers will continue to be started in the child + * process. + * + * It is necessary to explicitly call this function on every event loop + * created in the parent process that you plan to continue to use in the + * child, including the default loop (even if you don’t continue to use it + * in the parent). This function must be called before calling any API + * function using the loop in the child. Failure to do so will result in + * undefined behaviour, possibly including duplicate events delivered to + * both parent and child or aborting the child process. + * + * When possible, it is preferred to create a new loop in the child process + * instead of reusing a loop created in the parent. New loops created in the + * child process after the fork should not use this function. + * + * Note that this function is not implemented on Windows. + * Note also that this function is experimental in `libuv`. It may contain + * bugs, and is subject to change or removal. API and ABI stability is not + * guaranteed. + * + * error() will be emitted in case of errors. + */ + void Fork(); + + /** + * Get the underlying event loop data structure. + * + * @return The underlying event loop data structure. + */ + uv_loop_t* GetRaw() const noexcept { return m_loop; } + + /** + * Error signal + */ + sig::Signal error; + + /** + * Reports error. + * @param err Error code + */ + void ReportError(int err) { error(Error(err)); } + + private: + uv_loop_t* m_loop; + uv_loop_t m_loopStruct; +}; + +} // namespace uv +} // namespace wpi + +#endif // WPIUTIL_WPI_UV_LOOP_H_ diff --git a/wpiutil/src/main/native/include/wpi/uv/NetworkStream.h b/wpiutil/src/main/native/include/wpi/uv/NetworkStream.h new file mode 100644 index 0000000000..05c1c92f41 --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/uv/NetworkStream.h @@ -0,0 +1,156 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef WPIUTIL_WPI_UV_NETWORKSTREAM_H_ +#define WPIUTIL_WPI_UV_NETWORKSTREAM_H_ + +#include + +#include +#include + +#include "wpi/Signal.h" +#include "wpi/uv/Stream.h" + +namespace wpi { +namespace uv { + +class NetworkStream; + +/** + * Connection request. + */ +class ConnectReq : public RequestImpl { + public: + ConnectReq(); + + NetworkStream& GetStream() const { + return *static_cast(GetRaw()->handle->data); + } + + /** + * Connection completed signal. + */ + sig::Signal<> connected; +}; + +/** + * Network stream handle. + * This is an abstract type; there are two network stream implementations (Tcp + * and Pipe). + */ +class NetworkStream : public Stream { + public: + static constexpr int kDefaultBacklog = 128; + + std::shared_ptr shared_from_this() { + return std::static_pointer_cast(Handle::shared_from_this()); + } + + std::shared_ptr shared_from_this() const { + return std::static_pointer_cast( + Handle::shared_from_this()); + } + + /** + * Start listening for incoming connections. When a new incoming connection + * is received the connection signal is generated. + * @param backlog the number of connections the kernel might queue, same as + * listen(2). + */ + void Listen(int backlog = kDefaultBacklog); + + /** + * Start listening for incoming connections. This is a convenience wrapper + * around `Listen(int)` that also connects a callback to the connection + * signal. When a new incoming connection is received the connection signal + * is generated (and the callback is called). + * @param callback the callback to call when a connection is received. + * `Accept()` should be called from this callback. + * @param backlog the number of connections the kernel might queue, same as + * listen(2). + */ + void Listen(std::function callback, int backlog = kDefaultBacklog); + + /** + * Accept incoming connection. + * + * This call is used in conjunction with `Listen()` to accept incoming + * connections. Call this function after receiving a ListenEvent event to + * accept the connection. + * An error signal will be emitted in case of errors. + * + * When the connection signal is emitted it is guaranteed that this + * function will complete successfully the first time. If you attempt to use + * it more than once, it may fail. + * It is suggested to only call this function once per connection signal. + * + * @return The stream handle for the accepted connection, or nullptr on error. + */ + std::shared_ptr Accept() { + return DoAccept()->shared_from_this(); + } + + /** + * Accept incoming connection. + * + * This call is used in conjunction with `Listen()` to accept incoming + * connections. Call this function after receiving a connection signal to + * accept the connection. + * An error signal will be emitted in case of errors. + * + * When the connection signal is emitted it is guaranteed that this + * function will complete successfully the first time. If you attempt to use + * it more than once, it may fail. + * It is suggested to only call this function once per connection signal. + * + * @param client Client stream object. + * @return False on error. + */ + bool Accept(const std::shared_ptr& client) { + return Invoke(&uv_accept, GetRawStream(), client->GetRawStream()); + } + + /** + * Signal generated when an incoming connection is received. + */ + sig::Signal<> connection; + + protected: + explicit NetworkStream(uv_stream_t* uv_stream) : Stream{uv_stream} {} + + virtual NetworkStream* DoAccept() = 0; +}; + +template +class NetworkStreamImpl : public NetworkStream { + public: + std::shared_ptr shared_from_this() { + return std::static_pointer_cast(Handle::shared_from_this()); + } + + std::shared_ptr shared_from_this() const { + return std::static_pointer_cast(Handle::shared_from_this()); + } + + /** + * Get the underlying handle data structure. + * + * @return The underlying handle data structure. + */ + U* GetRaw() const noexcept { + return reinterpret_cast(this->GetRawHandle()); + } + + protected: + NetworkStreamImpl() : NetworkStream{reinterpret_cast(new U)} {} +}; + +} // namespace uv +} // namespace wpi + +#endif // WPIUTIL_WPI_UV_NETWORKSTREAM_H_ diff --git a/wpiutil/src/main/native/include/wpi/uv/Pipe.h b/wpiutil/src/main/native/include/wpi/uv/Pipe.h new file mode 100644 index 0000000000..b4d8af2dbc --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/uv/Pipe.h @@ -0,0 +1,194 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef WPIUTIL_WPI_UV_PIPE_H_ +#define WPIUTIL_WPI_UV_PIPE_H_ + +#include + +#include +#include +#include + +#include "wpi/Twine.h" +#include "wpi/uv/NetworkStream.h" + +namespace wpi { +namespace uv { + +class Loop; +class PipeConnectReq; + +/** + * Pipe handle. + * Pipe handles provide an abstraction over local domain sockets on Unix and + * named pipes on Windows. + */ +class Pipe final : public NetworkStreamImpl { + struct private_init {}; + + public: + explicit Pipe(const private_init&) {} + ~Pipe() noexcept override = default; + + /** + * Create a pipe handle. + * + * @param loop Loop object where this handle runs. + * @param ipc Indicates if this pipe will be used for handle passing between + * processes. + */ + static std::shared_ptr Create(Loop& loop, bool ipc = false); + + /** + * Create a pipe handle. + * + * @param loop Loop object where this handle runs. + * @param ipc Indicates if this pipe will be used for handle passing between + * processes. + */ + static std::shared_ptr Create(const std::shared_ptr& loop, + bool ipc = false) { + return Create(*loop, ipc); + } + + /** + * Accept incoming connection. + * + * This call is used in conjunction with `Listen()` to accept incoming + * connections. Call this function after receiving a ListenEvent event to + * accept the connection. + * An error signal will be emitted in case of errors. + * + * When the connection signal is emitted it is guaranteed that this + * function will complete successfully the first time. If you attempt to use + * it more than once, it may fail. + * It is suggested to only call this function once per connection signal. + * + * @return The stream handle for the accepted connection, or nullptr on error. + */ + std::shared_ptr Accept(); + + /** + * Accept incoming connection. + * + * This call is used in conjunction with `Listen()` to accept incoming + * connections. Call this function after receiving a connection signal to + * accept the connection. + * An error signal will be emitted in case of errors. + * + * When the connection signal is emitted it is guaranteed that this + * function will complete successfully the first time. If you attempt to use + * it more than once, it may fail. + * It is suggested to only call this function once per connection signal. + * + * @param client Client stream object. + * @return False on error. + */ + bool Accept(const std::shared_ptr& client) { + return NetworkStream::Accept(client); + } + + /** + * Open an existing file descriptor or HANDLE as a pipe. + * + * @note The passed file descriptor or HANDLE is not checked for its type, but + * it's required that it represents a valid pipe. + * + * @param file A valid file handle (either a file descriptor or a HANDLE). + */ + void Open(uv_file file) { Invoke(&uv_pipe_open, GetRaw(), file); } + + /** + * Bind the pipe to a file path (Unix) or a name (Windows). + * + * @note Paths on Unix get truncated to `sizeof(sockaddr_un.sun_path)` bytes, + * typically between 92 and 108 bytes. + * + * @param name File path (Unix) or name (Windows). + */ + void Bind(const Twine& name); + + /** + * Connect to the Unix domain socket or the named pipe. + * + * @note Paths on Unix get truncated to `sizeof(sockaddr_un.sun_path)` bytes, + * typically between 92 and 108 bytes. + * + * HandleConnected() is called on the request when the connection has been + * established. + * HandleError() is called on the request in case of errors during the + * connection. + * + * @param name File path (Unix) or name (Windows). + * @param req connection request + */ + void Connect(const Twine& name, const std::shared_ptr& req); + + /** + * Connect to the Unix domain socket or the named pipe. + * + * @note Paths on Unix get truncated to `sizeof(sockaddr_un.sun_path)` bytes, + * typically between 92 and 108 bytes. + * + * The callback is called when the connection has been established. Errors + * are reported to the stream error handler. + * + * @param name File path (Unix) or name (Windows). + * @param callback Callback function to call when connection established + */ + void Connect(const Twine& name, std::function callback); + + /** + * Get the name of the Unix domain socket or the named pipe. + * @return The name (will be empty if an error occurred). + */ + std::string GetSock(); + + /** + * Get the name of the Unix domain socket or the named pipe to which the + * handle is connected. + * @return The name (will be empty if an error occurred). + */ + std::string GetPeer(); + + /** + * Set the number of pending pipe instance handles when the pipe server is + * waiting for connections. + * @note This setting applies to Windows only. + * @param count Number of pending handles. + */ + void SetPendingInstances(int count) { + uv_pipe_pending_instances(GetRaw(), count); + } + + /** + * Alters pipe permissions, allowing it to be accessed from processes run + * by different users. Makes the pipe writable or readable by all users. + * Mode can be UV_WRITABLE, UV_READABLE, or both. This function is blocking. + * @param flags chmod flags + */ + void Chmod(int flags) { Invoke(&uv_pipe_chmod, GetRaw(), flags); } + + private: + Pipe* DoAccept() override; +}; + +/** + * Pipe connection request. + */ +class PipeConnectReq : public ConnectReq { + public: + Pipe& GetStream() const { + return *static_cast(&ConnectReq::GetStream()); + } +}; + +} // namespace uv +} // namespace wpi + +#endif // WPIUTIL_WPI_UV_PIPE_H_ diff --git a/wpiutil/src/main/native/include/wpi/uv/Poll.h b/wpiutil/src/main/native/include/wpi/uv/Poll.h new file mode 100644 index 0000000000..b8721accaa --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/uv/Poll.h @@ -0,0 +1,93 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef WPIUTIL_WPI_UV_POLL_H_ +#define WPIUTIL_WPI_UV_POLL_H_ + +#include + +#include + +#include "wpi/Signal.h" +#include "wpi/uv/Handle.h" + +namespace wpi { +namespace uv { + +class Loop; + +/** + * Poll handle. + */ +class Poll final : public HandleImpl { + struct private_init {}; + + public: + explicit Poll(const private_init&) {} + ~Poll() noexcept override = default; + + /** + * Create a poll handle using a file descriptor. + * + * @param loop Loop object where this handle runs. + * @param fd File descriptor. + */ + static std::shared_ptr Create(Loop& loop, int fd); + + /** + * Create a poll handle using a file descriptor. + * + * @param loop Loop object where this handle runs. + * @param fd File descriptor. + */ + static std::shared_ptr Create(const std::shared_ptr& loop, + int fd) { + return Create(*loop, fd); + } + + /** + * Create a poll handle using a socket descriptor. + * + * @param loop Loop object where this handle runs. + * @param sock Socket descriptor. + */ + static std::shared_ptr CreateSocket(Loop& loop, uv_os_sock_t sock); + + /** + * Create a poll handle using a socket descriptor. + * + * @param loop Loop object where this handle runs. + * @param sock Socket descriptor. + */ + static std::shared_ptr CreateSocket(const std::shared_ptr& loop, + uv_os_sock_t sock) { + return CreateSocket(*loop, sock); + } + + /** + * Start polling the file descriptor. + * + * @param events Bitmask of events (UV_READABLE, UV_WRITEABLE, UV_PRIORITIZED, + * and UV_DISCONNECT). + */ + void Start(int events); + + /** + * Stop polling the file descriptor. + */ + void Stop() { Invoke(&uv_poll_stop, GetRaw()); } + + /** + * Signal generated when a poll event occurs. + */ + sig::Signal pollEvent; +}; + +} // namespace uv +} // namespace wpi + +#endif // WPIUTIL_WPI_UV_POLL_H_ diff --git a/wpiutil/src/main/native/include/wpi/uv/Prepare.h b/wpiutil/src/main/native/include/wpi/uv/Prepare.h new file mode 100644 index 0000000000..600922f9d8 --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/uv/Prepare.h @@ -0,0 +1,70 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef WPIUTIL_WPI_UV_PREPARE_H_ +#define WPIUTIL_WPI_UV_PREPARE_H_ + +#include + +#include + +#include "wpi/Signal.h" +#include "wpi/uv/Handle.h" + +namespace wpi { +namespace uv { + +class Loop; + +/** + * Prepare handle. + * Prepare handles will generate a signal once per loop iteration, right + * before polling for I/O. + */ +class Prepare final : public HandleImpl { + struct private_init {}; + + public: + explicit Prepare(const private_init&) {} + ~Prepare() noexcept override = default; + + /** + * Create a prepare handle. + * + * @param loop Loop object where this handle runs. + */ + static std::shared_ptr Create(Loop& loop); + + /** + * Create a prepare handle. + * + * @param loop Loop object where this handle runs. + */ + static std::shared_ptr Create(const std::shared_ptr& loop) { + return Create(*loop); + } + + /** + * Start the handle. + */ + void Start(); + + /** + * Stop the handle. The signal will no longer be generated. + */ + void Stop() { Invoke(&uv_prepare_stop, GetRaw()); } + + /** + * Signal generated once per loop iteration prior to polling for I/O. + */ + sig::Signal<> prepare; +}; + +} // namespace uv +} // namespace wpi + +#endif // WPIUTIL_WPI_UV_PREPARE_H_ diff --git a/wpiutil/src/main/native/include/wpi/uv/Process.h b/wpiutil/src/main/native/include/wpi/uv/Process.h new file mode 100644 index 0000000000..82b6d68954 --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/uv/Process.h @@ -0,0 +1,226 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef WPIUTIL_WPI_UV_PROCESS_H_ +#define WPIUTIL_WPI_UV_PROCESS_H_ + +#include + +#include +#include + +#include "wpi/Signal.h" +#include "wpi/SmallVector.h" +#include "wpi/Twine.h" +#include "wpi/uv/Handle.h" + +namespace wpi { +namespace uv { + +class Loop; +class Pipe; + +/** + * Process options. + */ +class ProcessOptions final { + friend class Process; + + public: + /** + * Set environment variables for the subprocess. If not set or set to + * nullptr, the parent's environment is used. + * @param env environment variables + */ + ProcessOptions& SetEnv(char** env) { + m_env = env; + return *this; + } + + /** + * Set the current working directory for the subprocess. + * @param cwd current working directory + */ + ProcessOptions& SetCwd(const Twine& cwd) { + m_cwd = cwd.str(); + return *this; + } + + /** + * Set the child process' user id. + * @param uid user id + */ + ProcessOptions& SetUid(uv_uid_t uid) noexcept { + m_uid = uid; + m_flags |= UV_PROCESS_SETUID; + return *this; + } + + /** + * Set the child process' group id. + * @param gid group id + */ + ProcessOptions& SetGid(uv_gid_t gid) noexcept { + m_gid = gid; + m_flags |= UV_PROCESS_SETGID; + return *this; + } + + /** + * Set flags. + * @param flags Bitmask values from uv_process_flags. + */ + ProcessOptions& SetFlags(unsigned int flags) noexcept { + m_flags |= flags; + return *this; + } + + /** + * Clear flags. + * @param flags Bitmask values from uv_process_flags. + */ + ProcessOptions& ClearFlags(unsigned int flags) noexcept { + m_flags &= ~flags; + return *this; + } + + /** + * Explicitly ignore a stdio. + * @param index stdio index + */ + ProcessOptions& StdioIgnore(size_t index); + + /** + * Inherit a file descriptor from the parent process. + * @param index stdio index + * @param fd parent file descriptor + */ + ProcessOptions& StdioInherit(size_t index, int fd); + + /** + * Inherit a pipe from the parent process. + * @param index stdio index + * @param pipe pipe + */ + ProcessOptions& StdioInherit(size_t index, Pipe& pipe); + + /** + * Create a pipe between the child and the parent. + * @param index stdio index + * @param pipe pipe + * @param flags Some combination of UV_READABLE_PIPE, UV_WRITABLE_PIPE, and + * UV_OVERLAPPED_PIPE (Windows only, ignored on Unix). + */ + ProcessOptions& StdioCreatePipe(size_t index, Pipe& pipe, unsigned int flags); + + private: + char** m_env = nullptr; + std::string m_cwd; + unsigned int m_flags = 0; + struct StdioContainer : public uv_stdio_container_t { + StdioContainer() { + flags = UV_IGNORE; + data.fd = 0; + } + }; + SmallVector m_stdio; + uv_uid_t m_uid = 0; + uv_gid_t m_gid = 0; +}; + +/** + * Process handle. + * Process handles will spawn a new process and allow the user to control it + * and establish communication channels with it using streams. + */ +class Process final : public HandleImpl { + struct private_init {}; + + public: + explicit Process(const private_init&) {} + ~Process() noexcept override = default; + + /** + * Disables inheritance for file descriptors / handles that this process + * inherited from its parent. The effect is that child processes spawned + * by this process don't accidentally inherit these handles. + * + * It is recommended to call this function as early in your program as + * possible, before the inherited file descriptors can be closed or + * duplicated. + */ + static void DisableStdioInheritance() { uv_disable_stdio_inheritance(); } + + /** + * Starts a process. If the process is not successfully spawned, an error + * is generated on the loop and this function returns nullptr. + * + * Possible reasons for failing to spawn would include (but not be limited to) + * the file to execute not existing, not having permissions to use the setuid + * or setgid specified, or not having enough memory to allocate for the new + * process. + * + * @param loop Loop object where this handle runs. + * @param file Path pointing to the program to be executed + * @param args Command line arguments + * @param options Process options + */ + static std::shared_ptr Spawn( + Loop& loop, const Twine& file, char** args, + const ProcessOptions& options = ProcessOptions{}); + + /** + * Starts a process. If the process is not successfully spawned, an error + * is generated on the loop and this function returns nullptr. + * + * Possible reasons for failing to spawn would include (but not be limited to) + * the file to execute not existing, not having permissions to use the setuid + * or setgid specified, or not having enough memory to allocate for the new + * process. + * + * @param loop Loop object where this handle runs. + * @param file Path pointing to the program to be executed + * @param args Command line arguments + * @param options Process options + */ + static std::shared_ptr Spawn( + const std::shared_ptr& loop, const Twine& file, char** args, + const ProcessOptions& options = ProcessOptions{}) { + return Spawn(*loop, file, args, options); + } + + /** + * Sends the specified signal to the process. + * @param signum signal number + */ + void Kill(int signum) { Invoke(&uv_process_kill, GetRaw(), signum); } + + /** + * Sends the specified signal to the given PID. + * @param pid process ID + * @param signum signal number + * @return 0 on success, otherwise error code. + */ + static int Kill(int pid, int signum) noexcept { return uv_kill(pid, signum); } + + /** + * Get the process ID. + * @return Process ID. + */ + uv_pid_t GetPid() const noexcept { return GetRaw()->pid; } + + /** + * Signal generated when the process exits. The parameters are the exit + * status and the signal that caused the process to terminate, if any. + */ + sig::Signal exited; +}; + +} // namespace uv +} // namespace wpi + +#endif // WPIUTIL_WPI_UV_PROCESS_H_ diff --git a/wpiutil/src/main/native/include/wpi/uv/Request.h b/wpiutil/src/main/native/include/wpi/uv/Request.h new file mode 100644 index 0000000000..c33ca832c7 --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/uv/Request.h @@ -0,0 +1,171 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef WPIUTIL_WPI_UV_REQUEST_H_ +#define WPIUTIL_WPI_UV_REQUEST_H_ + +#include + +#include +#include + +#include "wpi/uv/Error.h" + +namespace wpi { +namespace uv { + +/** + * Request. Requests are not moveable or copyable. + * This class provides shared_ptr ownership and shared_from_this. + */ +class Request : public std::enable_shared_from_this { + public: + using Type = uv_req_type; + + Request(const Request&) = delete; + Request(Request&&) = delete; + Request& operator=(const Request&) = delete; + Request& operator=(Request&&) = delete; + virtual ~Request() noexcept = default; + + /** + * Get the type of the request. + * + * A base request offers no functionality to promote it to the actual request + * type. By means of this function, the type of the underlying request as + * specified by Type is made available. + * + * @return The actual type of the request. + */ + Type GetType() const noexcept { return m_uv_req->type; } + + /** + * Get the name of the type of the request. E.g. "connect" for connect. + */ + const char* GetTypeName() const noexcept { + return uv_req_type_name(m_uv_req->type); + } + + /** + * Cancel a pending request. + * + * This method fails if the request is executing or has finished + * executing. + * It can emit an error signal in case of errors. + * + * @return True in case of success, false otherwise. + */ + bool Cancel() { return uv_cancel(m_uv_req) == 0; } + + /** + * Return the size of the underlying request type. + * @return The size of the underlying request type. + */ + size_t RawSize() const noexcept { return uv_req_size(m_uv_req->type); } + + /** + * Get the underlying request data structure. + * + * @return The underlying request data structure. + */ + uv_req_t* GetRawReq() noexcept { return m_uv_req; } + + /** + * Get the underlying request data structure. + * + * @return The underlying request data structure. + */ + const uv_req_t* GetRawReq() const noexcept { return m_uv_req; } + + /** + * Keep this request in memory even if no outside shared_ptr references + * remain. To release call Release(). + * + * Derived classes can override this method for different memory management + * approaches (e.g. pooled storage of requests). + */ + virtual void Keep() noexcept { m_self = shared_from_this(); } + + /** + * No longer force holding this request in memory. Does not immediately + * destroy the object unless no outside shared_ptr references remain. + * + * Derived classes can override this method for different memory management + * approaches (e.g. pooled storage of requests). + */ + virtual void Release() noexcept { m_self.reset(); } + + /** + * Error callback. By default, this is set up to report errors to the handle + * that created this request. + * @param err error code + */ + std::function error; + + /** + * Report an error. + * @param err Error code + */ + void ReportError(int err) { error(Error(err)); } + + protected: + /** + * Constructor. + */ + explicit Request(uv_req_t* uv_req) : m_uv_req{uv_req} { + m_uv_req->data = this; + } + + private: + std::shared_ptr m_self; + uv_req_t* m_uv_req; +}; + +/** + * Request. Requests are not moveable or copyable. + * @tparam T CRTP derived class + * @tparam U underlying libuv request type + */ +template +class RequestImpl : public Request { + public: + std::shared_ptr shared_from_this() { + return std::static_pointer_cast(this->shared_from_this()); + } + + std::shared_ptr shared_from_this() const { + return std::static_pointer_cast(this->shared_from_this()); + } + + /** + * Get the underlying request data structure. + * + * @return The underlying request data structure. + */ + U* GetRaw() noexcept { return &m_uv_req; } + + /** + * Get the underlying request data structure. + * + * @return The underlying request data structure. + */ + const U* GetRaw() const noexcept { return &m_uv_req; } + + protected: + /** + * Constructor. + */ + RequestImpl() : Request{reinterpret_cast(&m_uv_req)} {} + + private: + U m_uv_req; +}; + +} // namespace uv +} // namespace wpi + +#endif // WPIUTIL_WPI_UV_REQUEST_H_ diff --git a/wpiutil/src/main/native/include/wpi/uv/Signal.h b/wpiutil/src/main/native/include/wpi/uv/Signal.h new file mode 100644 index 0000000000..5fe34c6f18 --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/uv/Signal.h @@ -0,0 +1,84 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef WPIUTIL_WPI_UV_SIGNAL_H_ +#define WPIUTIL_WPI_UV_SIGNAL_H_ + +#include + +#include + +#include "wpi/Signal.h" +#include "wpi/uv/Handle.h" + +namespace wpi { +namespace uv { + +class Loop; + +/** + * Signal handle. + */ +class Signal final : public HandleImpl { + struct private_init {}; + + public: + explicit Signal(const private_init&) {} + ~Signal() noexcept override = default; + + /** + * Create a signal handle. + * + * @param loop Loop object where this handle runs. + */ + static std::shared_ptr Create(Loop& loop); + + /** + * Create a signal handle. + * + * @param loop Loop object where this handle runs. + */ + static std::shared_ptr Create(const std::shared_ptr& loop) { + return Create(*loop); + } + + /** + * Start watching for the given signal. + * + * @param signum Signal to watch for. + */ + void Start(int signum); + + /** + * Start watching for the given signal. Same as Start() but the signal + * handler is reset the moment the signal is received. + * + * @param signum Signal to watch for. + */ + void StartOneshot(int signum); + + /** + * Stop watching for the signal. + */ + void Stop() { Invoke(&uv_signal_stop, GetRaw()); } + + /** + * Get the signal being monitored. + * @return Signal number. + */ + int GetSignal() const { return GetRaw()->signum; } + + /** + * Signal generated when a signal occurs. + */ + sig::Signal signal; +}; + +} // namespace uv +} // namespace wpi + +#endif // WPIUTIL_WPI_UV_SIGNAL_H_ diff --git a/wpiutil/src/main/native/include/wpi/uv/Stream.h b/wpiutil/src/main/native/include/wpi/uv/Stream.h new file mode 100644 index 0000000000..cd518919bb --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/uv/Stream.h @@ -0,0 +1,249 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef WPIUTIL_WPI_UV_STREAM_H_ +#define WPIUTIL_WPI_UV_STREAM_H_ + +#include + +#include +#include + +#include "wpi/ArrayRef.h" +#include "wpi/Signal.h" +#include "wpi/uv/Buffer.h" +#include "wpi/uv/Handle.h" +#include "wpi/uv/Request.h" + +namespace wpi { +namespace uv { + +class Stream; + +/** + * Shutdown request. + */ +class ShutdownReq : public RequestImpl { + public: + ShutdownReq(); + + Stream& GetStream() const { + return *static_cast(GetRaw()->handle->data); + } + + /** + * Shutdown completed signal. + */ + sig::Signal<> complete; +}; + +/** + * Write request. + */ +class WriteReq : public RequestImpl { + public: + WriteReq(); + + Stream& GetStream() const { + return *static_cast(GetRaw()->handle->data); + } + + /** + * Write completed signal. This is called even if an error occurred. + * @param err error value + */ + sig::Signal finish; +}; + +/** + * Stream handle. + * Stream handles provide an abstraction of a duplex communication channel. + * This is an abstract type; there are three stream implementations (Tcp, + * Pipe, and Tty). + */ +class Stream : public Handle { + public: + std::shared_ptr shared_from_this() { + return std::static_pointer_cast(Handle::shared_from_this()); + } + + std::shared_ptr shared_from_this() const { + return std::static_pointer_cast(Handle::shared_from_this()); + } + + /** + * Shutdown the outgoing (write) side of a duplex stream. It waits for pending + * write requests to complete. HandleShutdownComplete() is called on the + * request after shutdown is complete. + * + * @param req shutdown request + */ + void Shutdown(const std::shared_ptr& req); + + /** + * Shutdown the outgoing (write) side of a duplex stream. It waits for pending + * write requests to complete. The callback is called after shutdown is + * complete. Errors will be reported to the stream error handler. + * + * @param callback Callback function to call when shutdown completes + * @return Connection object for the callback + */ + void Shutdown(std::function callback = nullptr); + + /** + * Start reading data from an incoming stream. + * + * A data signal will be emitted several times until there is no more + * data to read or `StopRead()` is called. + * An end signal will be emitted when there is no more data to read. + */ + void StartRead(); + + /** + * Stop reading data from the stream. + * + * This function is idempotent and may be safely called on a stopped stream. + */ + void StopRead() { Invoke(&uv_read_stop, GetRawStream()); } + + /** + * Write data to the stream. + * + * Data are written in order. The lifetime of the data pointers passed in + * the `bufs` parameter must exceed the lifetime of the write request. + * An easy way to ensure this is to have the write request keep track of + * the data and use either its Complete() function or destructor to free the + * data. + * + * HandleWriteComplete() will be called on the request object when the data + * has been written. HandleWriteComplete() is called even if an error occurs. + * HandleError() will be called on the request object in case of errors. + * + * @param bufs The buffers to be written to the stream. + * @param req write request + */ + void Write(ArrayRef bufs, const std::shared_ptr& req); + + /** + * Write data to the stream. + * + * Data are written in order. The lifetime of the data pointers passed in + * the `bufs` parameter must exceed the lifetime of the write request. + * The callback can be used to free data after the request completes. + * + * The callback will be called when the data has been written. Errors will + * be reported to the stream error handler. + * + * @param bufs The buffers to be written to the stream. + * @param callback Callback function to call when the write completes + */ + void Write(ArrayRef bufs, + std::function, Error)> callback); + + /** + * Queue a write request if it can be completed immediately. + * + * Same as `Write()`, but won’t queue a write request if it can’t be + * completed immediately. + * An error signal will be emitted in case of errors. + * + * @param bufs The buffers to be written to the stream. + * @return Number of bytes written. + */ + int TryWrite(ArrayRef bufs); + + /** + * Check if the stream is readable. + * @return True if the stream is readable, false otherwise. + */ + bool IsReadable() const noexcept { + return uv_is_readable(GetRawStream()) == 1; + } + + /** + * @brief Checks if the stream is writable. + * @return True if the stream is writable, false otherwise. + */ + bool IsWritable() const noexcept { + return uv_is_writable(GetRawStream()) == 1; + } + + /** + * Enable or disable blocking mode for a stream. + * + * When blocking mode is enabled all writes complete synchronously. The + * interface remains unchanged otherwise, e.g. completion or failure of the + * operation will still be reported through events which are emitted + * asynchronously. + * + * @param enable True to enable blocking mode, false otherwise. + * @return True in case of success, false otherwise. + */ + bool SetBlocking(bool enable) noexcept { + return uv_stream_set_blocking(GetRawStream(), enable) == 0; + } + + /** + * Gets the amount of queued bytes waiting to be sent. + * @return Amount of queued bytes waiting to be sent. + */ + size_t GetWriteQueueSize() const noexcept { + return GetRawStream()->write_queue_size; + } + + /** + * Get the underlying stream data structure. + * + * @return The underlying stream data structure. + */ + uv_stream_t* GetRawStream() const noexcept { + return reinterpret_cast(GetRawHandle()); + } + + /** + * Signal generated when data was read on a stream. + */ + sig::Signal data; + + /** + * Signal generated when no more read data is available. + */ + sig::Signal<> end; + + protected: + explicit Stream(uv_stream_t* uv_stream) + : Handle{reinterpret_cast(uv_stream)} {} +}; + +template +class StreamImpl : public Stream { + public: + std::shared_ptr shared_from_this() { + return std::static_pointer_cast(Handle::shared_from_this()); + } + + std::shared_ptr shared_from_this() const { + return std::static_pointer_cast(Handle::shared_from_this()); + } + + /** + * Get the underlying handle data structure. + * + * @return The underlying handle data structure. + */ + U* GetRaw() const noexcept { + return reinterpret_cast(this->GetRawHandle()); + } + + protected: + StreamImpl() : Stream{reinterpret_cast(new U)} {} +}; + +} // namespace uv +} // namespace wpi + +#endif // WPIUTIL_WPI_UV_STREAM_H_ diff --git a/wpiutil/src/main/native/include/wpi/uv/Tcp.h b/wpiutil/src/main/native/include/wpi/uv/Tcp.h new file mode 100644 index 0000000000..38b9624088 --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/uv/Tcp.h @@ -0,0 +1,322 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef WPIUTIL_WPI_UV_TCP_H_ +#define WPIUTIL_WPI_UV_TCP_H_ + +#include + +#include +#include +#include + +#include "wpi/Twine.h" +#include "wpi/uv/NetworkStream.h" + +namespace wpi { +namespace uv { + +class Loop; +class TcpConnectReq; + +/** + * TCP handle. + * TCP handles are used to represent both TCP streams and servers. + */ +class Tcp final : public NetworkStreamImpl { + struct private_init {}; + + public: + using Time = std::chrono::duration; + + explicit Tcp(const private_init&) {} + ~Tcp() noexcept override = default; + + /** + * Create a TCP handle. + * + * @param loop Loop object where this handle runs. + * @param flags Flags + */ + static std::shared_ptr Create(Loop& loop, + unsigned int flags = AF_UNSPEC); + + /** + * Create a TCP handle. + * + * @param loop Loop object where this handle runs. + * @param flags Flags + */ + static std::shared_ptr Create(const std::shared_ptr& loop, + unsigned int flags = AF_UNSPEC) { + return Create(*loop, flags); + } + + /** + * Accept incoming connection. + * + * This call is used in conjunction with `Listen()` to accept incoming + * connections. Call this function after receiving a ListenEvent event to + * accept the connection. + * An error signal will be emitted in case of errors. + * + * When the connection signal is emitted it is guaranteed that this + * function will complete successfully the first time. If you attempt to use + * it more than once, it may fail. + * It is suggested to only call this function once per connection signal. + * + * @return The stream handle for the accepted connection, or nullptr on error. + */ + std::shared_ptr Accept(); + + /** + * Accept incoming connection. + * + * This call is used in conjunction with `Listen()` to accept incoming + * connections. Call this function after receiving a connection signal to + * accept the connection. + * An error signal will be emitted in case of errors. + * + * When the connection signal is emitted it is guaranteed that this + * function will complete successfully the first time. If you attempt to use + * it more than once, it may fail. + * It is suggested to only call this function once per connection signal. + * + * @param client Client stream object. + * @return False on error. + */ + bool Accept(const std::shared_ptr& client) { + return NetworkStream::Accept(client); + } + + /** + * Open an existing file descriptor or SOCKET as a TCP handle. + * + * @note The passed file descriptor or SOCKET is not checked for its type, but + * it's required that it represents a valid stream socket. + * + * @param sock A valid socket handle (either a file descriptor or a SOCKET). + */ + void Open(uv_os_sock_t sock) { Invoke(&uv_tcp_open, GetRaw(), sock); } + + /** + * Enable/Disable Nagle's algorithm. + * @param enable True to enable it, false otherwise. + * @return True in case of success, false otherwise. + */ + bool SetNoDelay(bool enable) { return uv_tcp_nodelay(GetRaw(), enable) == 0; } + + /** + * Enable/Disable TCP keep-alive. + * @param enable True to enable it, false otherwise. + * @param time Initial delay in seconds (use + * `std::chrono::duration`). + * @return True in case of success, false otherwise. + */ + bool SetKeepAlive(bool enable, Time time = Time{0}) { + return uv_tcp_keepalive(GetRaw(), enable, time.count()) == 0; + } + + /** + * Enable/Disable simultaneous asynchronous accept requests. + * + * Enable/Disable simultaneous asynchronous accept requests that are + * queued by the operating system when listening for new TCP + * connections. + * This setting is used to tune a TCP server for the desired performance. + * Having simultaneous accepts can significantly improve the rate of + * accepting connections (which is why it is enabled by default) but may + * lead to uneven load distribution in multi-process setups. + * + * @param enable True to enable it, false otherwise. + * @return True in case of success, false otherwise. + */ + bool SetSimultaneousAccepts(bool enable) { + return uv_tcp_simultaneous_accepts(GetRaw(), enable) == 0; + } + + /** + * Bind the handle to an IPv4 or IPv6 address and port. + * + * A successful call to this function does not guarantee that the call to + * `Listen()` or `Connect()` will work properly. + * An error signal can be emitted because of either this function or the + * ones mentioned above. + * + * @param addr Initialized `sockaddr_in` or `sockaddr_in6` data structure. + * @param flags Optional additional flags. + */ + void Bind(const sockaddr& addr, unsigned int flags = 0) { + Invoke(&uv_tcp_bind, GetRaw(), &addr, flags); + } + + /** + * Bind the handle to an IPv4 address and port. + * + * A successful call to this function does not guarantee that the call to + * `Listen()` or `Connect()` will work properly. + * An error signal can be emitted because of either this function or the + * ones mentioned above. + * + * Available flags are: + * + * @param ip The address to which to bind. + * @param port The port to which to bind. + * @param flags Optional additional flags. + */ + void Bind(const Twine& ip, unsigned int port, unsigned int flags = 0); + + /** + * Bind the handle to an IPv6 address and port. + * + * A successful call to this function does not guarantee that the call to + * `Listen()` or `Connect()` will work properly. + * An error signal can be emitted because of either this function or the + * ones mentioned above. + * + * Available flags are: + * + * @param ip The address to which to bind. + * @param port The port to which to bind. + * @param flags Optional additional flags. + */ + void Bind6(const Twine& ip, unsigned int port, unsigned int flags = 0); + + /** + * Get the current address to which the handle is bound. + * @return The address (will be zeroed if an error occurred). + */ + sockaddr_storage GetSock(); + + /** + * Get the address of the peer connected to the handle. + * @return The address (will be zeroed if an error occurred). + */ + sockaddr_storage GetPeer(); + + /** + * Establish an IPv4 or IPv6 TCP connection. + * + * On Windows if the addr is initialized to point to an unspecified address + * (`0.0.0.0` or `::`) it will be changed to point to localhost. This is + * done to match the behavior of Linux systems. + * + * HandleConnected() is called on the request when the connection has been + * established. + * HandleError() is called on the request in case of errors during the + * connection. + * + * @param addr Initialized `sockaddr_in` or `sockaddr_in6` data structure. + * @param req connection request + */ + void Connect(const sockaddr& addr, const std::shared_ptr& req); + + /** + * Establish an IPv4 or IPv6 TCP connection. + * + * On Windows if the addr is initialized to point to an unspecified address + * (`0.0.0.0` or `::`) it will be changed to point to localhost. This is + * done to match the behavior of Linux systems. + * + * The callback is called when the connection has been established. Errors + * are reported to the stream error handler. + * + * @param addr Initialized `sockaddr_in` or `sockaddr_in6` data structure. + * @param callback Callback function to call when connection established + */ + void Connect(const sockaddr& addr, std::function callback); + + /** + * Establish an IPv4 TCP connection. + * + * On Windows if the addr is initialized to point to an unspecified address + * (`0.0.0.0` or `::`) it will be changed to point to localhost. This is + * done to match the behavior of Linux systems. + * + * HandleConnected() is called on the request when the connection has been + * established. + * HandleError() is called on the request in case of errors during the + * connection. + * + * @param ip The address to which to connect to. + * @param port The port to which to connect to. + * @param req connection request + */ + void Connect(const Twine& ip, unsigned int port, + const std::shared_ptr& req); + + /** + * Establish an IPv4 TCP connection. + * + * On Windows if the addr is initialized to point to an unspecified address + * (`0.0.0.0` or `::`) it will be changed to point to localhost. This is + * done to match the behavior of Linux systems. + * + * The callback is called when the connection has been established. Errors + * are reported to the stream error handler. + * + * @param ip The address to which to connect to. + * @param port The port to which to connect to. + * @param callback Callback function to call when connection established + */ + void Connect(const Twine& ip, unsigned int port, + std::function callback); + + /** + * Establish an IPv6 TCP connection. + * + * On Windows if the addr is initialized to point to an unspecified address + * (`0.0.0.0` or `::`) it will be changed to point to localhost. This is + * done to match the behavior of Linux systems. + * + * HandleConnected() is called on the request when the connection has been + * established. + * HandleError() is called on the request in case of errors during the + * connection. + * + * @param ip The address to which to connect to. + * @param port The port to which to connect to. + * @param req connection request + */ + void Connect6(const Twine& ip, unsigned int port, + const std::shared_ptr& req); + + /** + * Establish an IPv6 TCP connection. + * + * On Windows if the addr is initialized to point to an unspecified address + * (`0.0.0.0` or `::`) it will be changed to point to localhost. This is + * done to match the behavior of Linux systems. + * + * The callback is called when the connection has been established. Errors + * are reported to the stream error handler. + * + * @param ip The address to which to connect to. + * @param port The port to which to connect to. + * @param callback Callback function to call when connection established + */ + void Connect6(const Twine& ip, unsigned int port, + std::function callback); + + private: + Tcp* DoAccept() override; +}; + +/** + * TCP connection request. + */ +class TcpConnectReq : public ConnectReq { + public: + Tcp& GetStream() const { + return *static_cast(&ConnectReq::GetStream()); + } +}; + +} // namespace uv +} // namespace wpi + +#endif // WPIUTIL_WPI_UV_TCP_H_ diff --git a/wpiutil/src/main/native/include/wpi/uv/Timer.h b/wpiutil/src/main/native/include/wpi/uv/Timer.h new file mode 100644 index 0000000000..bdafee3263 --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/uv/Timer.h @@ -0,0 +1,139 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef WPIUTIL_WPI_UV_TIMER_H_ +#define WPIUTIL_WPI_UV_TIMER_H_ + +#include + +#include +#include +#include + +#include "wpi/Signal.h" +#include "wpi/uv/Handle.h" + +namespace wpi { +namespace uv { + +class Loop; + +/** + * Timer handle. + * Timer handles are used to schedule signals to be called in the future. + */ +class Timer final : public HandleImpl { + struct private_init {}; + + public: + using Time = std::chrono::duration; + + explicit Timer(const private_init&) {} + ~Timer() noexcept override = default; + + /** + * Create a timer handle. + * + * @param loop Loop object where this handle runs. + */ + static std::shared_ptr Create(Loop& loop); + + /** + * Create a timer handle. + * + * @param loop Loop object where this handle runs. + */ + static std::shared_ptr Create(const std::shared_ptr& loop) { + return Create(*loop); + } + + /** + * Create a timer that calls a functor after a given time interval. + * + * @param loop Loop object where the timer should run. + * @param timeout Time interval + * @param func Functor + */ + static void SingleShot(Loop& loop, Time timeout, std::function func); + + /** + * Create a timer that calls a functor after a given time interval. + * + * @param loop Loop object where the timer should run. + * @param timeout Time interval + * @param func Functor + */ + static void SingleShot(const std::shared_ptr& loop, Time timeout, + std::function func) { + return SingleShot(*loop, timeout, func); + } + + /** + * Start the timer. + * + * If timeout is zero, an event is emitted on the next event loop + * iteration. If repeat is non-zero, an event is emitted first + * after timeout milliseconds and then repeatedly after repeat milliseconds. + * + * @param timeout Milliseconds before to emit an event (use + * `std::chrono::duration`). + * @param repeat Milliseconds between successive events (use + * `std::chrono::duration`). + */ + void Start(Time timeout, Time repeat = Time{0}); + + /** + * Stop the timer. + */ + void Stop() { Invoke(&uv_timer_stop, GetRaw()); } + + /** + * Stop the timer and restart it if it was repeating. + * + * Stop the timer, and if it is repeating restart it using the repeat value + * as the timeout. + * If the timer has never been started before it emits sigError. + */ + void Again() { Invoke(&uv_timer_again, GetRaw()); } + + /** + * Set the repeat interval value. + * + * The timer will be scheduled to run on the given interval and will follow + * normal timer semantics in the case of a time-slice overrun. + * For example, if a 50ms repeating timer first runs for 17ms, it will be + * scheduled to run again 33ms later. If other tasks consume more than the + * 33ms following the first timer event, then another event will be emitted + * as soon as possible. + * + * If the repeat value is set from a listener bound to an event, it does + * not immediately take effect. If the timer was non-repeating before, it + * will have been stopped. If it was repeating, then the old repeat value + * will have been used to schedule the next timeout. + * + * @param repeat Repeat interval in milliseconds (use + * `std::chrono::duration`). + */ + void SetRepeat(Time repeat) { uv_timer_set_repeat(GetRaw(), repeat.count()); } + + /** + * Get the timer repeat value. + * @return Timer repeat value in milliseconds (as a + * `std::chrono::duration`). + */ + Time GetRepeat() const { return Time{uv_timer_get_repeat(GetRaw())}; } + + /** + * Signal generated when the timeout event occurs. + */ + sig::Signal<> timeout; +}; + +} // namespace uv +} // namespace wpi + +#endif // WPIUTIL_WPI_UV_TIMER_H_ diff --git a/wpiutil/src/main/native/include/wpi/uv/Tty.h b/wpiutil/src/main/native/include/wpi/uv/Tty.h new file mode 100644 index 0000000000..a15d8eb7dc --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/uv/Tty.h @@ -0,0 +1,88 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef WPIUTIL_WPI_UV_TTY_H_ +#define WPIUTIL_WPI_UV_TTY_H_ + +#include + +#include +#include + +#include "wpi/uv/Stream.h" + +namespace wpi { +namespace uv { + +class Loop; +class Tty; + +/** + * TTY handle. + * TTY handles represent a stream for the console. + */ +class Tty final : public StreamImpl { + struct private_init {}; + + public: + explicit Tty(const private_init&) {} + ~Tty() noexcept override = default; + + /** + * Create a TTY handle. + * + * @param loop Loop object where this handle runs. + * @param fd File descriptor, usually 0=stdin, 1=stdout, 2=stderr + * @param readable Specifies if you plan on calling StartRead(). stdin is + * readable, stdout is not. + */ + static std::shared_ptr Create(Loop& loop, uv_file fd, bool readable); + + /** + * Create a TTY handle. + * + * @param loop Loop object where this handle runs. + * @param fd File descriptor, usually 0=stdin, 1=stdout, 2=stderr + * @param readable Specifies if you plan on calling StartRead(). stdin is + * readable, stdout is not. + */ + static std::shared_ptr Create(const std::shared_ptr& loop, + uv_file fd, bool readable) { + return Create(*loop, fd, readable); + } + + /** + * Set the TTY using the specified terminal mode. + * + * @param mode terminal mode + */ + void SetMode(uv_tty_mode_t mode) { + int err = uv_tty_set_mode(GetRaw(), mode); + if (err < 0) ReportError(err); + } + + /** + * Reset TTY settings to default values for the next process to take over. + * Typically called when the program exits. + */ + void ResetMode() { Invoke(&uv_tty_reset_mode); } + + /** + * Gets the current window size. + * @return Window size (pair of width and height). + */ + std::pair GetWindowSize() { + int width = 0, height = 0; + Invoke(&uv_tty_get_winsize, GetRaw(), &width, &height); + return std::make_pair(width, height); + } +}; + +} // namespace uv +} // namespace wpi + +#endif // WPIUTIL_WPI_UV_TTY_H_ diff --git a/wpiutil/src/main/native/include/wpi/uv/Udp.h b/wpiutil/src/main/native/include/wpi/uv/Udp.h new file mode 100644 index 0000000000..9c9ade72b8 --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/uv/Udp.h @@ -0,0 +1,266 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef WPIUTIL_WPI_UV_UDP_H_ +#define WPIUTIL_WPI_UV_UDP_H_ + +#include + +#include +#include + +#include "wpi/ArrayRef.h" +#include "wpi/Signal.h" +#include "wpi/Twine.h" +#include "wpi/uv/Handle.h" +#include "wpi/uv/Request.h" + +namespace wpi { +namespace uv { + +class Loop; +class Udp; + +/** + * UDP send request. + */ +class UdpSendReq : public RequestImpl { + public: + UdpSendReq(); + + Udp& GetUdp() const { return *static_cast(GetRaw()->handle->data); } + + /** + * Send completed signal. This is called even if an error occurred. + * @param err error value + */ + sig::Signal complete; +}; + +/** + * UDP handle. + * UDP handles encapsulate UDP communication for both clients and servers. + */ +class Udp final : public HandleImpl { + struct private_init {}; + + public: + explicit Udp(const private_init&) {} + ~Udp() noexcept override = default; + + /** + * Create a UDP handle. + * + * @param loop Loop object where this handle runs. + * @param flags Flags + */ + static std::shared_ptr Create(Loop& loop, + unsigned int flags = AF_UNSPEC); + + /** + * Create a UDP handle. + * + * @param loop Loop object where this handle runs. + * @param flags Flags + */ + static std::shared_ptr Create(const std::shared_ptr& loop, + unsigned int flags = AF_UNSPEC) { + return Create(*loop, flags); + } + + /** + * Open an existing file descriptor or SOCKET as a UDP handle. + * + * @param sock A valid socket handle (either a file descriptor or a SOCKET). + */ + void Open(uv_os_sock_t sock) { Invoke(&uv_udp_open, GetRaw(), sock); } + + /** + * Bind the handle to an IPv4 or IPv6 address and port. + * + * @param addr Initialized `sockaddr_in` or `sockaddr_in6` data structure. + * @param flags Optional additional flags. + */ + void Bind(const sockaddr& addr, unsigned int flags = 0) { + Invoke(&uv_udp_bind, GetRaw(), &addr, flags); + } + + /** + * Bind the handle to an IPv4 address and port. + * + * @param ip The address to which to bind. + * @param port The port to which to bind. + * @param flags Optional additional flags. + */ + void Bind(const Twine& ip, unsigned int port, unsigned int flags = 0); + + /** + * Bind the handle to an IPv6 address and port. + * + * @param ip The address to which to bind. + * @param port The port to which to bind. + * @param flags Optional additional flags. + */ + void Bind6(const Twine& ip, unsigned int port, unsigned int flags = 0); + + /** + * Get the current address to which the handle is bound. + * @return The address (will be zeroed if an error occurred). + */ + sockaddr_storage GetSock(); + + /** + * Set membership for a multicast address. + * + * @param multicastAddr Multicast address to set membership for + * @param interfaceAddr Interface address + * @param membership Should be UV_JOIN_GROUP or UV_LEAVE_GROUP + */ + void SetMembership(const Twine& multicastAddr, const Twine& interfaceAddr, + uv_membership membership); + + /** + * Set IP multicast loop flag. Makes multicast packets loop back to local + * sockets. + * + * @param enabled True for enabled, false for disabled + */ + void SetMulticastLoop(bool enabled) { + Invoke(&uv_udp_set_multicast_loop, GetRaw(), enabled ? 1 : 0); + } + + /** + * Set the multicast TTL. + * + * @param ttl Time to live (1-255) + */ + void SetMulticastTtl(int ttl) { + Invoke(&uv_udp_set_multicast_ttl, GetRaw(), ttl); + } + + /** + * Set the multicast interface to send or receive data on. + * + * @param interfaceAddr Interface address + */ + void SetMulticastInterface(const Twine& interfaceAddr); + + /** + * Set broadcast on or off. + * + * @param enabled True for enabled, false for disabled + */ + void SetBroadcast(bool enabled) { + Invoke(&uv_udp_set_broadcast, GetRaw(), enabled ? 1 : 0); + } + + /** + * Set the time to live (TTL). + * + * @param ttl Time to live (1-255) + */ + void SetTtl(int ttl) { Invoke(&uv_udp_set_ttl, GetRaw(), ttl); } + + /** + * Send data over the UDP socket. If the socket has not previously been bound + * with Bind() it will be bound to 0.0.0.0 (the "all interfaces" IPv4 address) + * and a random port number. + * + * Data are written in order. The lifetime of the data pointers passed in + * the `bufs` parameter must exceed the lifetime of the send request. + * The callback can be used to free data after the request completes. + * + * HandleSendComplete() will be called on the request object when the data + * has been written. HandleSendComplete() is called even if an error occurs. + * HandleError() will be called on the request object in case of errors. + * + * @param addr sockaddr_in or sockaddr_in6 with the address and port of the + * remote peer. + * @param bufs The buffers to be written to the stream. + * @param req write request + */ + void Send(const sockaddr& addr, ArrayRef bufs, + const std::shared_ptr& req); + + /** + * Send data over the UDP socket. If the socket has not previously been bound + * with Bind() it will be bound to 0.0.0.0 (the "all interfaces" IPv4 address) + * and a random port number. + * + * Data are written in order. The lifetime of the data pointers passed in + * the `bufs` parameter must exceed the lifetime of the send request. + * The callback can be used to free data after the request completes. + * + * The callback will be called when the data has been sent. Errors will + * be reported via the error signal. + * + * @param addr sockaddr_in or sockaddr_in6 with the address and port of the + * remote peer. + * @param bufs The buffers to be sent. + * @param callback Callback function to call when the data has been sent. + */ + void Send(const sockaddr& addr, ArrayRef bufs, + std::function, Error)> callback); + + /** + * Same as Send(), but won't queue a send request if it can't be completed + * immediately. + * + * @param addr sockaddr_in or sockaddr_in6 with the address and port of the + * remote peer. + * @param bufs The buffers to be send. + * @return Number of bytes sent. + */ + int TrySend(const sockaddr& addr, ArrayRef bufs) { + int val = uv_udp_try_send(GetRaw(), bufs.data(), bufs.size(), &addr); + if (val < 0) { + this->ReportError(val); + return 0; + } + return val; + } + + /** + * Prepare for receiving data. If the socket has not previously been bound + * with Bind() it is bound to 0.0.0.0 (the "all interfaces" IPv4 address) and + * a random port number. + * + * A received signal will be emitted for each received data packet until + * `StopRecv()` is called. + */ + void StartRecv(); + + /** + * Stop listening for incoming datagrams. + */ + void StopRecv() { Invoke(&uv_udp_recv_stop, GetRaw()); } + + /** + * Gets the amount of queued bytes waiting to be sent. + * @return Amount of queued bytes waiting to be sent. + */ + size_t GetSendQueueSize() const noexcept { return GetRaw()->send_queue_size; } + + /** + * Gets the amount of queued packets waiting to be sent. + * @return Amount of queued packets waiting to be sent. + */ + size_t GetSendQueueCount() const noexcept { + return GetRaw()->send_queue_count; + } + + /** + * Signal generated for each incoming datagram. Parameters are the buffer, + * the number of bytes received, the address of the sender, and flags. + */ + sig::Signal received; +}; + +} // namespace uv +} // namespace wpi + +#endif // WPIUTIL_WPI_UV_UDP_H_ diff --git a/wpiutil/src/main/native/include/wpi/uv/Work.h b/wpiutil/src/main/native/include/wpi/uv/Work.h new file mode 100644 index 0000000000..bf061fb61c --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/uv/Work.h @@ -0,0 +1,97 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef WPIUTIL_WPI_UV_WORK_H_ +#define WPIUTIL_WPI_UV_WORK_H_ + +#include + +#include +#include + +#include "wpi/Signal.h" +#include "wpi/uv/Request.h" + +namespace wpi { +namespace uv { + +class Loop; + +/** + * Work request. + * For use with `QueueWork()` function family. + */ +class WorkReq : public RequestImpl { + public: + WorkReq(); + + Loop& GetLoop() const { return *static_cast(GetRaw()->loop->data); } + + /** + * Function(s) that will be run on the thread pool. + */ + sig::Signal<> work; + + /** + * Function(s) that will be run on the loop thread after the work on the + * thread pool has been completed by the work callback. + */ + sig::Signal<> afterWork; +}; + +/** + * Initializes a work request which will run on the thread pool. + * + * @param loop Event loop + * @param req request + */ +void QueueWork(Loop& loop, const std::shared_ptr& req); + +/** + * Initializes a work request which will run on the thread pool. + * + * @param loop Event loop + * @param req request + */ +inline void QueueWork(const std::shared_ptr& loop, + const std::shared_ptr& req) { + QueueWork(*loop, req); +} + +/** + * Initializes a work request which will run on the thread pool. The work + * callback will be run on a thread from the thread pool, and the afterWork + * callback will be called on the loop thread after the work completes. Any + * errors are forwarded to the loop. + * + * @param loop Event loop + * @param work Work callback (called from separate thread) + * @param afterWork After work callback (called on loop thread) + */ +void QueueWork(Loop& loop, std::function work, + std::function afterWork); + +/** + * Initializes a work request which will run on the thread pool. The work + * callback will be run on a thread from the thread pool, and the afterWork + * callback will be called on the loop thread after the work completes. Any + * errors are forwarded to the loop. + * + * @param loop Event loop + * @param work Work callback (called from separate thread) + * @param afterWork After work callback (called on loop thread) + */ +inline void QueueWork(const std::shared_ptr& loop, + std::function work, + std::function afterWork) { + QueueWork(*loop, work, afterWork); +} + +} // namespace uv +} // namespace wpi + +#endif // WPIUTIL_WPI_UV_WORK_H_ diff --git a/wpiutil/src/main/native/include/wpi/uv/util.h b/wpiutil/src/main/native/include/wpi/uv/util.h new file mode 100644 index 0000000000..3795aa879f --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/uv/util.h @@ -0,0 +1,131 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef WPIUTIL_WPI_UV_UTIL_H_ +#define WPIUTIL_WPI_UV_UTIL_H_ + +#include + +#include + +#include "wpi/Twine.h" + +namespace wpi { +namespace uv { + +/** + * Convert a binary structure containing an IPv4 address to a string. + * @param addr Binary structure + * @param ip Output string (any type that has `assign(char*, char*)`) + * @param port Output port number + * @return Error (same as `uv_ip4_name()`). + */ +template +int AddrToName(const sockaddr_in& addr, T* ip, unsigned int* port) { + char name[128]; + int err = uv_ip4_name(&addr, name, 128); + if (err == 0) { + ip->assign(name, name + std::strlen(name)); + *port = ntohs(addr.sin_port); + } else { + ip->assign(name, name); + } + return err; +} + +/** + * Convert a binary structure containing an IPv6 address to a string. + * @param addr Binary structure + * @param ip Output string (any type that has `assign(char*, char*)`) + * @param port Output port number + * @return Error (same as `uv_ip6_name()`). + */ +template +int AddrToName(const sockaddr_in6& addr, T* ip, unsigned int* port) { + char name[128]; + int err = uv_ip6_name(&addr, name, 128); + if (err == 0) { + ip->assign(name, name + std::strlen(name)); + *port = ntohs(addr.sin6_port); + } else { + ip->assign(name, name); + } + return err; +} + +/** + * Convert a binary IPv4 address to a string. + * @param addr Binary address + * @param ip Output string (any type that has `assign(char*, char*)`) + * @return Error (same as `uv_inet_ntop()`). + */ +template +int AddrToName(const in_addr& addr, T* ip) { + char name[128]; + int err = uv_inet_ntop(AF_INET, &addr, name, 128); + if (err == 0) + ip->assign(name, name + std::strlen(name)); + else + ip->assign(name, name); + return err; +} + +/** + * Convert a binary IPv6 address to a string. + * @param addr Binary address + * @param ip Output string (any type that has `assign(char*, char*)`) + * @return Error (same as `uv_inet_ntop()`). + */ +template +int AddrToName(const in6_addr& addr, T* ip) { + char name[128]; + int err = uv_inet_ntop(AF_INET6, &addr, name, 128); + if (err == 0) + ip->assign(name, name + std::strlen(name)); + else + ip->assign(name, name); + return err; +} + +/** + * Convert a string containing an IPv4 address to a binary structure. + * @param ip IPv4 address string + * @param port Port number + * @param addr Output binary structure + * @return Error (same as `uv_ip4_addr()`). + */ +int NameToAddr(const Twine& ip, unsigned int port, sockaddr_in* addr); + +/** + * Convert a string containing an IPv6 address to a binary structure. + * @param ip IPv6 address string + * @param port Port number + * @param addr Output binary structure + * @return Error (same as `uv_ip6_addr()`). + */ +int NameToAddr(const Twine& ip, unsigned int port, sockaddr_in6* addr); + +/** + * Convert a string containing an IPv4 address to binary format. + * @param ip IPv4 address string + * @param addr Output binary + * @return Error (same as `uv_inet_pton()`). + */ +int NameToAddr(const Twine& ip, in_addr* addr); + +/** + * Convert a string containing an IPv6 address to binary format. + * @param ip IPv6 address string + * @param addr Output binary + * @return Error (same as `uv_inet_pton()`). + */ +int NameToAddr(const Twine& ip, in6_addr* addr); + +} // namespace uv +} // namespace wpi + +#endif // WPIUTIL_WPI_UV_UTIL_H_ diff --git a/wpiutil/src/test/native/cpp/raw_uv_stream_test.cpp b/wpiutil/src/test/native/cpp/raw_uv_stream_test.cpp new file mode 100644 index 0000000000..d1a0f6c000 --- /dev/null +++ b/wpiutil/src/test/native/cpp/raw_uv_stream_test.cpp @@ -0,0 +1,58 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "wpi/raw_uv_ostream.h" // NOLINT(build/include_order) + +#include "gtest/gtest.h" + +namespace wpi { + +TEST(RawUvStreamTest, BasicWrite) { + SmallVector bufs; + raw_uv_ostream os(bufs, 1024); + os << "12"; + os << "34"; + ASSERT_EQ(bufs.size(), 1u); + ASSERT_EQ(bufs[0].len, 4u); + ASSERT_EQ(bufs[0].base[0], '1'); + ASSERT_EQ(bufs[0].base[1], '2'); + ASSERT_EQ(bufs[0].base[2], '3'); + ASSERT_EQ(bufs[0].base[3], '4'); +} + +TEST(RawUvStreamTest, BoundaryWrite) { + SmallVector bufs; + raw_uv_ostream os(bufs, 4); + ASSERT_EQ(bufs.size(), 0u); + os << "12"; + ASSERT_EQ(bufs.size(), 1u); + os << "34"; + ASSERT_EQ(bufs.size(), 1u); + os << "56"; + ASSERT_EQ(bufs.size(), 2u); +} + +TEST(RawUvStreamTest, LargeWrite) { + SmallVector bufs; + raw_uv_ostream os(bufs, 4); + os << "123456"; + ASSERT_EQ(bufs.size(), 2u); + ASSERT_EQ(bufs[1].len, 2u); + ASSERT_EQ(bufs[1].base[0], '5'); +} + +TEST(RawUvStreamTest, PrevDataWrite) { + SmallVector bufs; + bufs.emplace_back(uv::Buffer::Allocate(1024)); + raw_uv_ostream os(bufs, 1024); + os << "1234"; + ASSERT_EQ(bufs.size(), 2u); + ASSERT_EQ(bufs[0].len, 1024u); + ASSERT_EQ(bufs[1].len, 4u); +} + +} // namespace wpi diff --git a/wpiutil/src/test/native/cpp/uv/UvAsyncTest.cpp b/wpiutil/src/test/native/cpp/uv/UvAsyncTest.cpp new file mode 100644 index 0000000000..156d395a8e --- /dev/null +++ b/wpiutil/src/test/native/cpp/uv/UvAsyncTest.cpp @@ -0,0 +1,105 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "wpi/uv/Async.h" // NOLINT(build/include_order) + +#include "gtest/gtest.h" // NOLINT(build/include_order) + +#include +#include + +#include "wpi/mutex.h" +#include "wpi/uv/Loop.h" +#include "wpi/uv/Prepare.h" + +namespace wpi { +namespace uv { + +TEST(UvAsync, Test) { + std::atomic_int async_cb_called{0}; + int prepare_cb_called = 0; + int close_cb_called = 0; + + wpi::mutex mutex; + mutex.lock(); + + std::thread theThread; + + auto loop = Loop::Create(); + auto async = Async::Create(loop); + auto prepare = Prepare::Create(loop); + + loop->error.connect([](Error) { FAIL(); }); + + prepare->error.connect([](Error) { FAIL(); }); + prepare->closed.connect([&] { close_cb_called++; }); + prepare->prepare.connect([&] { + if (prepare_cb_called++) return; + theThread = std::thread([&] { + for (;;) { + mutex.lock(); + int n = async_cb_called; + mutex.unlock(); + + if (n == 3) { + break; + } + + async->Send(); + + std::this_thread::yield(); + } + }); + mutex.unlock(); + }); + prepare->Start(); + + async->error.connect([](Error) { FAIL(); }); + async->closed.connect([&] { close_cb_called++; }); + async->wakeup.connect([&] { + mutex.lock(); + int n = ++async_cb_called; + mutex.unlock(); + + if (n == 3) { + async->Close(); + prepare->Close(); + } + }); + + loop->Run(); + + ASSERT_GT(prepare_cb_called, 0); + ASSERT_EQ(async_cb_called, 3); + ASSERT_EQ(close_cb_called, 2); + + if (theThread.joinable()) theThread.join(); +} + +} // namespace uv +} // namespace wpi diff --git a/wpiutil/src/test/native/cpp/uv/UvGetAddrInfoTest.cpp b/wpiutil/src/test/native/cpp/uv/UvGetAddrInfoTest.cpp new file mode 100644 index 0000000000..aa5648fbd8 --- /dev/null +++ b/wpiutil/src/test/native/cpp/uv/UvGetAddrInfoTest.cpp @@ -0,0 +1,108 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "wpi/uv/GetAddrInfo.h" // NOLINT(build/include_order) + +#include "gtest/gtest.h" // NOLINT(build/include_order) + +#include "wpi/uv/Loop.h" + +#define CONCURRENT_COUNT 10 + +namespace wpi { +namespace uv { + +TEST(UvGetAddrInfo, BothNull) { + int fail_cb_called = 0; + + auto loop = Loop::Create(); + loop->error.connect([&](Error err) { + ASSERT_EQ(err.code(), UV_EINVAL); + fail_cb_called++; + }); + + GetAddrInfo(loop, [](const addrinfo&) { FAIL(); }, Twine::createNull()); + loop->Run(); + ASSERT_EQ(fail_cb_called, 1); +} + +TEST(UvGetAddrInfo, FailedLookup) { + int fail_cb_called = 0; + + auto loop = Loop::Create(); + loop->error.connect([&](Error err) { + ASSERT_EQ(fail_cb_called, 0); + ASSERT_LT(err.code(), 0); + fail_cb_called++; + }); + + // Use a FQDN by ending in a period + GetAddrInfo(loop, [](const addrinfo&) { FAIL(); }, "xyzzy.xyzzy.xyzzy."); + loop->Run(); + ASSERT_EQ(fail_cb_called, 1); +} + +TEST(UvGetAddrInfo, Basic) { + int getaddrinfo_cbs = 0; + + auto loop = Loop::Create(); + loop->error.connect([](Error) { FAIL(); }); + + GetAddrInfo(loop, [&](const addrinfo&) { getaddrinfo_cbs++; }, "localhost"); + + loop->Run(); + + ASSERT_EQ(getaddrinfo_cbs, 1); +} + +TEST(UvGetAddrInfo, Concurrent) { + int getaddrinfo_cbs = 0; + int callback_counts[CONCURRENT_COUNT]; + + auto loop = Loop::Create(); + loop->error.connect([](Error) { FAIL(); }); + + for (int i = 0; i < CONCURRENT_COUNT; i++) { + callback_counts[i] = 0; + GetAddrInfo(loop, + [i, &callback_counts, &getaddrinfo_cbs](const addrinfo&) { + callback_counts[i]++; + getaddrinfo_cbs++; + }, + "localhost"); + } + + loop->Run(); + + for (int i = 0; i < CONCURRENT_COUNT; i++) { + ASSERT_EQ(callback_counts[i], 1); + } +} + +} // namespace uv +} // namespace wpi diff --git a/wpiutil/src/test/native/cpp/uv/UvGetNameInfoTest.cpp b/wpiutil/src/test/native/cpp/uv/UvGetNameInfoTest.cpp new file mode 100644 index 0000000000..16de32985a --- /dev/null +++ b/wpiutil/src/test/native/cpp/uv/UvGetNameInfoTest.cpp @@ -0,0 +1,77 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "wpi/uv/GetNameInfo.h" // NOLINT(build/include_order) + +#include "gtest/gtest.h" // NOLINT(build/include_order) + +#include "wpi/uv/Loop.h" + +namespace wpi { +namespace uv { + +TEST(UvGetNameInfo, BasicIp4) { + int getnameinfo_cbs = 0; + + auto loop = Loop::Create(); + loop->error.connect([](Error) { FAIL(); }); + + GetNameInfo4(loop, + [&](const char* hostname, const char* service) { + ASSERT_NE(hostname, nullptr); + ASSERT_NE(service, nullptr); + getnameinfo_cbs++; + }, + "127.0.0.1", 80); + + loop->Run(); + + ASSERT_EQ(getnameinfo_cbs, 1); +} + +TEST(UvGetNameInfo, BasicIp6) { + int getnameinfo_cbs = 0; + + auto loop = Loop::Create(); + loop->error.connect([](Error) { FAIL(); }); + + GetNameInfo6(loop, + [&](const char* hostname, const char* service) { + ASSERT_NE(hostname, nullptr); + ASSERT_NE(service, nullptr); + getnameinfo_cbs++; + }, + "::1", 80); + + loop->Run(); + + ASSERT_EQ(getnameinfo_cbs, 1); +} + +} // namespace uv +} // namespace wpi diff --git a/wpiutil/src/test/native/cpp/uv/UvLoopWalkTest.cpp b/wpiutil/src/test/native/cpp/uv/UvLoopWalkTest.cpp new file mode 100644 index 0000000000..78abf69fa7 --- /dev/null +++ b/wpiutil/src/test/native/cpp/uv/UvLoopWalkTest.cpp @@ -0,0 +1,70 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "wpi/uv/Loop.h" // NOLINT(build/include_order) + +#include "gtest/gtest.h" // NOLINT(build/include_order) + +#include "wpi/uv/Timer.h" + +namespace wpi { +namespace uv { + +TEST(UvLoop, Walk) { + int seen_timer_handle = 0; + + auto loop = Loop::Create(); + auto timer = Timer::Create(loop); + + loop->error.connect([](Error) { FAIL(); }); + + timer->error.connect([](Error) { FAIL(); }); + + timer->timeout.connect([&, theTimer = timer.get() ] { + theTimer->GetLoopRef().Walk([&](Handle& it) { + if (&it == timer.get()) seen_timer_handle++; + }); + theTimer->Close(); + }); + timer->Start(Timer::Time{1}); + + // Start event loop, expect to see the timer handle + ASSERT_EQ(seen_timer_handle, 0); + loop->Run(); + ASSERT_EQ(seen_timer_handle, 1); + + // Loop is finished, should not see our timer handle + seen_timer_handle = 0; + loop->Walk([&](Handle& it) { + if (&it == timer.get()) seen_timer_handle++; + }); + ASSERT_EQ(seen_timer_handle, 0); +} + +} // namespace uv +} // namespace wpi diff --git a/wpiutil/src/test/native/cpp/uv/UvTimerTest.cpp b/wpiutil/src/test/native/cpp/uv/UvTimerTest.cpp new file mode 100644 index 0000000000..706e1eb61c --- /dev/null +++ b/wpiutil/src/test/native/cpp/uv/UvTimerTest.cpp @@ -0,0 +1,74 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "wpi/uv/Timer.h" // NOLINT(build/include_order) + +#include "gtest/gtest.h" + +namespace wpi { +namespace uv { + +TEST(UvTimer, StartAndStop) { + auto loop = Loop::Create(); + auto handleNoRepeat = Timer::Create(loop); + auto handleRepeat = Timer::Create(loop); + + bool checkTimerNoRepeatEvent = false; + bool checkTimerRepeatEvent = false; + + handleNoRepeat->error.connect([](Error) { FAIL(); }); + handleRepeat->error.connect([](Error) { FAIL(); }); + + handleNoRepeat->timeout.connect( + [&checkTimerNoRepeatEvent, handle = handleNoRepeat.get() ] { + ASSERT_FALSE(checkTimerNoRepeatEvent); + checkTimerNoRepeatEvent = true; + handle->Stop(); + handle->Close(); + ASSERT_TRUE(handle->IsClosing()); + }); + + handleRepeat->timeout.connect( + [&checkTimerRepeatEvent, handle = handleRepeat.get() ] { + if (checkTimerRepeatEvent) { + handle->Stop(); + handle->Close(); + ASSERT_TRUE(handle->IsClosing()); + } else { + checkTimerRepeatEvent = true; + ASSERT_FALSE(handle->IsClosing()); + } + }); + + handleNoRepeat->Start(Timer::Time{0}, Timer::Time{0}); + handleRepeat->Start(Timer::Time{0}, Timer::Time{1}); + + ASSERT_TRUE(handleNoRepeat->IsActive()); + ASSERT_FALSE(handleNoRepeat->IsClosing()); + + ASSERT_TRUE(handleRepeat->IsActive()); + ASSERT_FALSE(handleRepeat->IsClosing()); + + loop->Run(); + + ASSERT_TRUE(checkTimerNoRepeatEvent); + ASSERT_TRUE(checkTimerRepeatEvent); +} + +TEST(UvTimer, Repeat) { + auto loop = Loop::Create(); + auto handle = Timer::Create(loop); + + handle->SetRepeat(Timer::Time{42}); + ASSERT_EQ(handle->GetRepeat(), Timer::Time{42}); + handle->Close(); + + loop->Run(); // forces close callback to run +} + +} // namespace uv +} // namespace wpi