mirror of https://github.com/grpc/grpc.git
Posix Event Engine listener implementation (#31513)
* Util functions to help with posix engine listener implementation * sanity * update comments in posix_engine_listener_utils.h * review comments * iwyu * revert prev commit * iwyu * update build * update * regenerate projects * regenerate projects * minor fixes * update BUILD * sanity * update build * regenerate projects * fix unused parameter * sanity * update * sanity * regenerate_projects * remove unused variablepull/31678/head
parent
0d4000dd48
commit
c5a66bb08d
19 changed files with 565 additions and 10 deletions
@ -0,0 +1,235 @@ |
||||
// Copyright 2022 gRPC Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/lib/event_engine/posix_engine/posix_engine_listener.h" |
||||
|
||||
#include <string> |
||||
#include <utility> |
||||
|
||||
#include "absl/functional/any_invocable.h" |
||||
#include "absl/status/status.h" |
||||
#include "absl/strings/str_cat.h" |
||||
#include "absl/types/optional.h" |
||||
|
||||
#include <grpc/event_engine/event_engine.h> |
||||
#include <grpc/event_engine/memory_allocator.h> |
||||
#include <grpc/support/log.h> |
||||
|
||||
#include "src/core/lib/gprpp/status_helper.h" |
||||
#ifdef GRPC_POSIX_SOCKET_TCP |
||||
#include <errno.h> // IWYU pragma: keep |
||||
#include <sys/socket.h> // IWYU pragma: keep |
||||
#include <unistd.h> // IWYU pragma: keep |
||||
|
||||
#include "src/core/lib/event_engine/posix_engine/event_poller.h" |
||||
#include "src/core/lib/event_engine/posix_engine/posix_endpoint.h" |
||||
#include "src/core/lib/event_engine/posix_engine/tcp_socket_utils.h" |
||||
#include "src/core/lib/iomgr/socket_mutator.h" |
||||
#endif |
||||
|
||||
namespace grpc_event_engine { |
||||
namespace posix_engine { |
||||
|
||||
#ifdef GRPC_POSIX_SOCKET_TCP |
||||
PosixEngineListenerImpl::PosixEngineListenerImpl( |
||||
EventEngine::Listener::AcceptCallback on_accept, |
||||
absl::AnyInvocable<void(absl::Status)> on_shutdown, |
||||
const grpc_event_engine::experimental::EndpointConfig& config, |
||||
std::unique_ptr<grpc_event_engine::experimental::MemoryAllocatorFactory> |
||||
memory_allocator_factory, |
||||
PosixEventPoller* poller, std::shared_ptr<EventEngine> engine) |
||||
: poller_(poller), |
||||
options_(TcpOptionsFromEndpointConfig(config)), |
||||
engine_(std::move(engine)), |
||||
acceptors_(this), |
||||
on_accept_(std::move(on_accept)), |
||||
on_shutdown_(std::move(on_shutdown)), |
||||
memory_allocator_factory_(std::move(memory_allocator_factory)) {} |
||||
|
||||
absl::StatusOr<int> PosixEngineListenerImpl::Bind( |
||||
const EventEngine::ResolvedAddress& addr) { |
||||
EventEngine::ResolvedAddress res_addr = addr; |
||||
EventEngine::ResolvedAddress addr6_v4mapped; |
||||
int requested_port = SockaddrGetPort(res_addr); |
||||
absl::MutexLock lock(&this->mu_); |
||||
GPR_ASSERT(!this->started_); |
||||
GPR_ASSERT(addr.size() <= EventEngine::ResolvedAddress::MAX_SIZE_BYTES); |
||||
UnlinkIfUnixDomainSocket(addr); |
||||
|
||||
/// Check if this is a wildcard port, and if so, try to keep the port the same
|
||||
/// as some previously created listener socket.
|
||||
for (auto it = acceptors_.begin(); |
||||
requested_port == 0 && it != acceptors_.end(); it++) { |
||||
EventEngine::ResolvedAddress sockname_temp; |
||||
socklen_t len = static_cast<socklen_t>(sizeof(struct sockaddr_storage)); |
||||
if (0 == getsockname((*it)->Socket().sock.Fd(), |
||||
const_cast<sockaddr*>(sockname_temp.address()), |
||||
&len)) { |
||||
int used_port = SockaddrGetPort(sockname_temp); |
||||
if (used_port > 0) { |
||||
requested_port = used_port; |
||||
SockaddrSetPort(res_addr, requested_port); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
auto used_port = SockaddrIsWildcard(res_addr); |
||||
if (used_port.has_value()) { |
||||
requested_port = *used_port; |
||||
return ListenerContainerAddWildcardAddresses(acceptors_, options_, |
||||
requested_port); |
||||
} |
||||
if (SockaddrToV4Mapped(&res_addr, &addr6_v4mapped)) { |
||||
res_addr = addr6_v4mapped; |
||||
} |
||||
|
||||
auto result = CreateAndPrepareListenerSocket(options_, res_addr); |
||||
GRPC_RETURN_IF_ERROR(result.status()); |
||||
acceptors_.Append(*result); |
||||
return result->port; |
||||
} |
||||
|
||||
void PosixEngineListenerImpl::AsyncConnectionAcceptor::Start() { |
||||
Ref(); |
||||
handle_->NotifyOnRead(notify_on_accept_); |
||||
} |
||||
|
||||
void PosixEngineListenerImpl::AsyncConnectionAcceptor::NotifyOnAccept( |
||||
absl::Status status) { |
||||
if (!status.ok()) { |
||||
// Shutting down the acceptor. Unref the ref grabbed in
|
||||
// AsyncConnectionAcceptor::Start().
|
||||
Unref(); |
||||
return; |
||||
} |
||||
// loop until accept4 returns EAGAIN, and then re-arm notification.
|
||||
for (;;) { |
||||
EventEngine::ResolvedAddress addr; |
||||
memset(const_cast<sockaddr*>(addr.address()), 0, addr.size()); |
||||
// Note: If we ever decide to return this address to the user, remember to
|
||||
// strip off the ::ffff:0.0.0.0/96 prefix first.
|
||||
int fd = Accept4(handle_->WrappedFd(), addr, 1, 1); |
||||
if (fd < 0) { |
||||
switch (errno) { |
||||
case EINTR: |
||||
continue; |
||||
case EAGAIN: |
||||
case ECONNABORTED: |
||||
handle_->NotifyOnRead(notify_on_accept_); |
||||
return; |
||||
default: |
||||
gpr_log(GPR_ERROR, "Closing acceptor. Failed accept4: %s", |
||||
strerror(errno)); |
||||
// Shutting down the acceptor. Unref the ref grabbed in
|
||||
// AsyncConnectionAcceptor::Start().
|
||||
Unref(); |
||||
return; |
||||
} |
||||
} |
||||
|
||||
// For UNIX sockets, the accept call might not fill up the member
|
||||
// sun_path of sockaddr_un, so explicitly call getsockname to get it.
|
||||
if (addr.address()->sa_family == AF_UNIX) { |
||||
socklen_t len = EventEngine::ResolvedAddress::MAX_SIZE_BYTES; |
||||
if (getsockname(fd, const_cast<sockaddr*>(addr.address()), &len) < 0) { |
||||
gpr_log(GPR_ERROR, "Closing acceptor. Failed getsockname: %s", |
||||
strerror(errno)); |
||||
close(fd); |
||||
// Shutting down the acceptor. Unref the ref grabbed in
|
||||
// AsyncConnectionAcceptor::Start().
|
||||
Unref(); |
||||
return; |
||||
} |
||||
} |
||||
|
||||
PosixSocketWrapper sock(fd); |
||||
(void)sock.SetSocketNoSigpipeIfPossible(); |
||||
auto result = sock.ApplySocketMutatorInOptions( |
||||
GRPC_FD_SERVER_CONNECTION_USAGE, listener_->options_); |
||||
if (!result.ok()) { |
||||
gpr_log(GPR_ERROR, "Closing acceptor. Failed to apply socket mutator: %s", |
||||
result.ToString().c_str()); |
||||
// Shutting down the acceptor. Unref the ref grabbed in
|
||||
// AsyncConnectionAcceptor::Start().
|
||||
Unref(); |
||||
return; |
||||
} |
||||
|
||||
// Create an Endpoint here.
|
||||
std::string peer_name = *SockaddrToString(&addr, true); |
||||
auto endpoint = CreatePosixEndpoint( |
||||
/*handle=*/listener_->poller_->CreateHandle( |
||||
fd, peer_name, listener_->poller_->CanTrackErrors()), |
||||
/*on_shutdown=*/nullptr, /*engine=*/listener_->engine_, |
||||
/*allocator=*/ |
||||
listener_->memory_allocator_factory_->CreateMemoryAllocator( |
||||
absl::StrCat("endpoint-tcp-server-connection: ", peer_name)), |
||||
/*options=*/listener_->options_); |
||||
// Call on_accept_ and then resume accepting new connections by continuing
|
||||
// the parent for-loop.
|
||||
listener_->on_accept_( |
||||
std::move(endpoint), |
||||
listener_->memory_allocator_factory_->CreateMemoryAllocator( |
||||
absl::StrCat("on-accept-tcp-server-connection: ", peer_name))); |
||||
} |
||||
GPR_UNREACHABLE_CODE(return); |
||||
} |
||||
|
||||
void PosixEngineListenerImpl::AsyncConnectionAcceptor::Shutdown() { |
||||
// The ShutdownHandle whould trigger any waiting notify_on_accept_ to get
|
||||
// scheduled with the not-OK status.
|
||||
handle_->ShutdownHandle(absl::InternalError("Shutting down acceptor")); |
||||
Unref(); |
||||
} |
||||
|
||||
absl::Status PosixEngineListenerImpl::Start() { |
||||
absl::MutexLock lock(&this->mu_); |
||||
// Start each asynchronous acceptor.
|
||||
GPR_ASSERT(!this->started_); |
||||
this->started_ = true; |
||||
for (auto it = acceptors_.begin(); it != acceptors_.end(); it++) { |
||||
(*it)->Start(); |
||||
} |
||||
return absl::OkStatus(); |
||||
} |
||||
|
||||
void PosixEngineListenerImpl::TriggerShutdown() { |
||||
// This would get invoked from the destructor of the parent
|
||||
// PosixEngineListener object.
|
||||
absl::MutexLock lock(&this->mu_); |
||||
for (auto it = acceptors_.begin(); it != acceptors_.end(); it++) { |
||||
// Trigger shutdown of each asynchronous acceptor. This in-turn calls
|
||||
// ShutdownHandle on the associated poller event handle. It may also
|
||||
// immediately delete the asynchronous acceptor if the acceptor was never
|
||||
// started.
|
||||
(*it)->Shutdown(); |
||||
} |
||||
} |
||||
|
||||
PosixEngineListenerImpl::~PosixEngineListenerImpl() { |
||||
// This should get invoked only after all the AsyncConnectionAcceptor's have
|
||||
// been destroyed. This is because each AsyncConnectionAcceptor has a
|
||||
// shared_ptr ref to the parent PosixEngineListenerImpl.
|
||||
if (on_shutdown_ != nullptr) { |
||||
on_shutdown_(absl::InternalError("Shutting down listener")); |
||||
} |
||||
} |
||||
|
||||
#endif // GRPC_POSIX_SOCKET_TCP
|
||||
|
||||
} // namespace posix_engine
|
||||
} // namespace grpc_event_engine
|
@ -0,0 +1,223 @@ |
||||
// Copyright 2022 gRPC Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
#ifndef GRPC_CORE_LIB_EVENT_ENGINE_POSIX_ENGINE_POSIX_ENGINE_LISTENER_H |
||||
#define GRPC_CORE_LIB_EVENT_ENGINE_POSIX_ENGINE_POSIX_ENGINE_LISTENER_H |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include <string.h> |
||||
|
||||
#include <atomic> |
||||
#include <list> |
||||
#include <memory> |
||||
#include <string> |
||||
#include <utility> |
||||
|
||||
#include "absl/base/thread_annotations.h" |
||||
#include "absl/functional/any_invocable.h" |
||||
#include "absl/status/status.h" |
||||
#include "absl/status/statusor.h" |
||||
#include "absl/synchronization/mutex.h" |
||||
|
||||
#include <grpc/event_engine/endpoint_config.h> |
||||
#include <grpc/event_engine/event_engine.h> |
||||
#include <grpc/event_engine/memory_allocator.h> |
||||
|
||||
#include "src/core/lib/iomgr/port.h" |
||||
|
||||
#ifdef GRPC_POSIX_SOCKET_TCP |
||||
#include "src/core/lib/event_engine/posix_engine/event_poller.h" |
||||
#include "src/core/lib/event_engine/posix_engine/posix_engine_closure.h" |
||||
#include "src/core/lib/event_engine/posix_engine/posix_engine_listener_utils.h" |
||||
#include "src/core/lib/event_engine/posix_engine/tcp_socket_utils.h" |
||||
#endif |
||||
|
||||
namespace grpc_event_engine { |
||||
namespace posix_engine { |
||||
|
||||
#ifdef GRPC_POSIX_SOCKET_TCP |
||||
class PosixEngineListenerImpl |
||||
: public std::enable_shared_from_this<PosixEngineListenerImpl> { |
||||
public: |
||||
PosixEngineListenerImpl( |
||||
EventEngine::Listener::AcceptCallback on_accept, |
||||
absl::AnyInvocable<void(absl::Status)> on_shutdown, |
||||
const grpc_event_engine::experimental::EndpointConfig& config, |
||||
std::unique_ptr<grpc_event_engine::experimental::MemoryAllocatorFactory> |
||||
memory_allocator_factory, |
||||
PosixEventPoller* poller, std::shared_ptr<EventEngine> engine); |
||||
// Binds an address to the listener. This creates a ListenerSocket
|
||||
// and sets its fields appropriately.
|
||||
absl::StatusOr<int> Bind(const EventEngine::ResolvedAddress& addr); |
||||
// Signals event manager to listen for connections on all created sockets.
|
||||
absl::Status Start(); |
||||
// Trigger graceful shutdown of all asynchronous accept operations.
|
||||
void TriggerShutdown(); |
||||
|
||||
~PosixEngineListenerImpl(); |
||||
|
||||
private: |
||||
// This class represents accepting for one bind fd belonging to the listener.
|
||||
// Each AsyncConnectionAcceptor takes a ref to the parent
|
||||
// PosixEngineListenerImpl object. So the PosixEngineListenerImpl can be
|
||||
// deleted only after all AsyncConnectionAcceptor's get destroyed.
|
||||
class AsyncConnectionAcceptor { |
||||
public: |
||||
AsyncConnectionAcceptor(std::shared_ptr<EventEngine> engine, |
||||
std::shared_ptr<PosixEngineListenerImpl> listener, |
||||
ListenerSocketsContainer::ListenerSocket socket) |
||||
: engine_(std::move(engine)), |
||||
listener_(std::move(listener)), |
||||
socket_(socket), |
||||
handle_(listener_->poller_->CreateHandle( |
||||
socket_.sock.Fd(), *SockaddrToString(&socket_.addr, true), |
||||
listener_->poller_->CanTrackErrors())), |
||||
notify_on_accept_(PosixEngineClosure::ToPermanentClosure( |
||||
[this](absl::Status status) { NotifyOnAccept(status); })){}; |
||||
// Start listening for incoming connections on the socket.
|
||||
void Start(); |
||||
// Internal callback invoked when the socket has incoming connections to
|
||||
// process.
|
||||
void NotifyOnAccept(absl::Status status); |
||||
// Shutdown the poller handle associated with this socket.
|
||||
void Shutdown(); |
||||
void Ref() { ref_count_.fetch_add(1, std::memory_order_relaxed); } |
||||
void Unref() { |
||||
if (ref_count_.fetch_sub(1, std::memory_order_acq_rel) == 1) { |
||||
delete this; |
||||
} |
||||
} |
||||
ListenerSocketsContainer::ListenerSocket& Socket() { return socket_; } |
||||
~AsyncConnectionAcceptor() { |
||||
handle_->OrphanHandle(nullptr, nullptr, ""); |
||||
delete notify_on_accept_; |
||||
} |
||||
|
||||
private: |
||||
std::atomic<int> ref_count_{1}; |
||||
std::shared_ptr<EventEngine> engine_; |
||||
std::shared_ptr<PosixEngineListenerImpl> listener_; |
||||
ListenerSocketsContainer::ListenerSocket socket_; |
||||
EventHandle* handle_; |
||||
PosixEngineClosure* notify_on_accept_; |
||||
}; |
||||
class ListenerAsyncAcceptors : public ListenerSocketsContainer { |
||||
public: |
||||
explicit ListenerAsyncAcceptors(PosixEngineListenerImpl* listener) |
||||
: listener_(listener){}; |
||||
void Append(ListenerSocket socket) override { |
||||
acceptors_.push_back(new AsyncConnectionAcceptor( |
||||
listener_->engine_, listener_->shared_from_this(), socket)); |
||||
} |
||||
|
||||
absl::StatusOr<ListenerSocket> Find( |
||||
const grpc_event_engine::experimental::EventEngine::ResolvedAddress& |
||||
addr) override { |
||||
for (auto acceptor = acceptors_.begin(); acceptor != acceptors_.end(); |
||||
++acceptor) { |
||||
if ((*acceptor)->Socket().addr.size() == addr.size() && |
||||
memcmp((*acceptor)->Socket().addr.address(), addr.address(), |
||||
addr.size()) == 0) { |
||||
return (*acceptor)->Socket(); |
||||
} |
||||
} |
||||
return absl::NotFoundError("Socket not found!"); |
||||
} |
||||
|
||||
int Size() { return static_cast<int>(acceptors_.size()); } |
||||
|
||||
std::list<AsyncConnectionAcceptor*>::const_iterator begin() { |
||||
return acceptors_.begin(); |
||||
} |
||||
std::list<AsyncConnectionAcceptor*>::const_iterator end() { |
||||
return acceptors_.end(); |
||||
} |
||||
|
||||
private: |
||||
std::list<AsyncConnectionAcceptor*> acceptors_; |
||||
PosixEngineListenerImpl* listener_; |
||||
}; |
||||
friend class ListenerAsyncAcceptors; |
||||
friend class AsyncConnectionAcceptor; |
||||
// The mutex ensures thread safety when multiple threads try to call Bind
|
||||
// and Start in parallel.
|
||||
absl::Mutex mu_; |
||||
PosixEventPoller* poller_; |
||||
PosixTcpOptions options_; |
||||
std::shared_ptr<EventEngine> engine_; |
||||
// Linked list of sockets. One is created upon each successful bind
|
||||
// operation.
|
||||
ListenerAsyncAcceptors acceptors_ ABSL_GUARDED_BY(mu_); |
||||
// Callback to be invoked upon accepting a connection.
|
||||
EventEngine::Listener::AcceptCallback on_accept_; |
||||
// Callback to be invoked upon shutdown of listener.
|
||||
absl::AnyInvocable<void(absl::Status)> on_shutdown_; |
||||
// Set to true when the listener has started listening for new connections.
|
||||
// Any further bind operations would fail.
|
||||
bool started_ ABSL_GUARDED_BY(mu_) = false; |
||||
// Pointer to a slice allocator factory object which can generate
|
||||
// unique slice allocators for each new incoming connection.
|
||||
std::unique_ptr<grpc_event_engine::experimental::MemoryAllocatorFactory> |
||||
memory_allocator_factory_; |
||||
}; |
||||
|
||||
class PosixEngineListener |
||||
: public grpc_event_engine::experimental::EventEngine::Listener { |
||||
public: |
||||
PosixEngineListener( |
||||
grpc_event_engine::experimental::EventEngine::Listener::AcceptCallback |
||||
on_accept, |
||||
absl::AnyInvocable<void(absl::Status)> on_shutdown, |
||||
const grpc_event_engine::experimental::EndpointConfig& config, |
||||
std::unique_ptr<grpc_event_engine::experimental::MemoryAllocatorFactory> |
||||
memory_allocator_factory, |
||||
PosixEventPoller* poller, std::shared_ptr<EventEngine> engine) |
||||
: impl_(std::make_shared<PosixEngineListenerImpl>( |
||||
std::move(on_accept), std::move(on_shutdown), config, |
||||
std::move(memory_allocator_factory), poller, std::move(engine))) {} |
||||
~PosixEngineListener() override { impl_->TriggerShutdown(); }; |
||||
absl::StatusOr<int> Bind( |
||||
const grpc_event_engine::experimental::EventEngine::ResolvedAddress& addr) |
||||
override { |
||||
return impl_->Bind(addr); |
||||
} |
||||
absl::Status Start() override { return impl_->Start(); } |
||||
|
||||
private: |
||||
std::shared_ptr<PosixEngineListenerImpl> impl_; |
||||
}; |
||||
|
||||
#else // GRPC_POSIX_SOCKET_TCP
|
||||
|
||||
class PosixEngineListener |
||||
: public grpc_event_engine::experimental::EventEngine::Listener { |
||||
public: |
||||
PosixEngineListener() = default; |
||||
~PosixEngineListener() override = default; |
||||
absl::StatusOr<int> Bind(const grpc_event_engine::experimental::EventEngine:: |
||||
ResolvedAddress& /*addr*/) override { |
||||
GPR_ASSERT(false && |
||||
"EventEngine::Listener::Bind not supported on this platform"); |
||||
} |
||||
absl::Status Start() override { |
||||
GPR_ASSERT(false && |
||||
"EventEngine::Listener::Start not supported on this platform"); |
||||
} |
||||
}; |
||||
|
||||
#endif |
||||
|
||||
} // namespace posix_engine
|
||||
} // namespace grpc_event_engine
|
||||
#endif // GRPC_CORE_LIB_EVENT_ENGINE_POSIX_ENGINE_POSIX_ENGINE_LISTENER_H
|
Loading…
Reference in new issue