mirror of https://github.com/grpc/grpc.git
Revert "Revert "Creating a posix oracle event engine and a suite of event engine client tests"" (#30060)
* Revert "Revert "Creating a posix oracle event engine and a suite of event engine client tests (#29714)" (#30042)"
This reverts commit 1630efd8ab
.
* fix typos
pull/30088/head
parent
870fe8624f
commit
c03388853c
22 changed files with 1561 additions and 25 deletions
@ -0,0 +1,69 @@ |
||||
// Copyright 2021 The 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_PROMISE_H |
||||
#define GRPC_CORE_LIB_EVENT_ENGINE_PROMISE_H |
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include <grpc/support/log.h> |
||||
|
||||
#include "src/core/lib/gprpp/sync.h" |
||||
|
||||
namespace grpc_event_engine { |
||||
namespace experimental { |
||||
|
||||
/// A minimal promise implementation.
|
||||
///
|
||||
/// This is light-duty, syntactical sugar around cv wait & signal, which is
|
||||
/// useful in some cases. A more robust implementation is being worked on
|
||||
/// separately.
|
||||
template <typename T> |
||||
class Promise { |
||||
public: |
||||
// The getter will wait until the setter has been called, and will return the
|
||||
// value passed during Set.
|
||||
T& Get() { |
||||
grpc_core::MutexLock lock(&mu_); |
||||
if (!set_) { |
||||
cv_.Wait(&mu_); |
||||
} |
||||
return val_; |
||||
} |
||||
// This setter can only be called exactly once without a Reset.
|
||||
// Will automatically unblock getters.
|
||||
void Set(T&& val) { |
||||
grpc_core::MutexLock lock(&mu_); |
||||
GPR_ASSERT(!set_); |
||||
val_ = std::move(val); |
||||
set_ = true; |
||||
cv_.SignalAll(); |
||||
} |
||||
|
||||
// Can only be called after a set operation.
|
||||
void Reset() { |
||||
grpc_core::MutexLock lock(&mu_); |
||||
GPR_ASSERT(set_); |
||||
set_ = false; |
||||
} |
||||
|
||||
private: |
||||
grpc_core::Mutex mu_; |
||||
grpc_core::CondVar cv_; |
||||
T val_; |
||||
bool set_ = false; |
||||
}; |
||||
|
||||
} // namespace experimental
|
||||
} // namespace grpc_event_engine
|
||||
|
||||
#endif // GRPC_CORE_LIB_EVENT_ENGINE_PROMISE_H
|
@ -0,0 +1,205 @@ |
||||
// 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 "test/core/event_engine/test_suite/event_engine_test_utils.h" |
||||
|
||||
#include <cstring> |
||||
#include <memory> |
||||
#include <string> |
||||
#include <utility> |
||||
|
||||
#include "absl/status/status.h" |
||||
#include "absl/status/statusor.h" |
||||
#include "absl/strings/str_cat.h" |
||||
#include "absl/synchronization/mutex.h" |
||||
#include "absl/time/time.h" |
||||
|
||||
#include <grpc/event_engine/endpoint_config.h> |
||||
#include <grpc/event_engine/event_engine.h> |
||||
#include <grpc/event_engine/memory_allocator.h> |
||||
#include <grpc/event_engine/slice_buffer.h> |
||||
#include <grpc/slice_buffer.h> |
||||
#include <grpc/support/alloc.h> |
||||
#include <grpc/support/log.h> |
||||
|
||||
#include "src/core/lib/address_utils/parse_address.h" |
||||
#include "src/core/lib/event_engine/channel_args_endpoint_config.h" |
||||
#include "src/core/lib/resource_quota/memory_quota.h" |
||||
#include "src/core/lib/uri/uri_parser.h" |
||||
|
||||
using Endpoint = ::grpc_event_engine::experimental::EventEngine::Endpoint; |
||||
using Listener = ::grpc_event_engine::experimental::EventEngine::Listener; |
||||
|
||||
namespace grpc_event_engine { |
||||
namespace experimental { |
||||
|
||||
EventEngine::ResolvedAddress URIToResolvedAddress(std::string address_str) { |
||||
grpc_resolved_address addr; |
||||
absl::StatusOr<grpc_core::URI> uri = grpc_core::URI::Parse(address_str); |
||||
if (!uri.ok()) { |
||||
gpr_log(GPR_ERROR, "Failed to parse. Error: %s", |
||||
uri.status().ToString().c_str()); |
||||
GPR_ASSERT(uri.ok()); |
||||
} |
||||
GPR_ASSERT(grpc_parse_uri(*uri, &addr)); |
||||
return EventEngine::ResolvedAddress( |
||||
reinterpret_cast<const sockaddr*>(addr.addr), addr.len); |
||||
} |
||||
|
||||
void AppendStringToSliceBuffer(SliceBuffer* buf, std::string data) { |
||||
buf->Append(Slice::FromCopiedString(data)); |
||||
} |
||||
|
||||
std::string ExtractSliceBufferIntoString(SliceBuffer* buf) { |
||||
if (!buf->Length()) { |
||||
return std::string(); |
||||
} |
||||
std::string tmp(buf->Length(), '\0'); |
||||
char* bytes = const_cast<char*>(tmp.c_str()); |
||||
grpc_slice_buffer_move_first_into_buffer(buf->RawSliceBuffer(), buf->Length(), |
||||
bytes); |
||||
return tmp; |
||||
} |
||||
|
||||
absl::Status SendValidatePayload(std::string data, Endpoint* send_endpoint, |
||||
Endpoint* receive_endpoint) { |
||||
GPR_ASSERT(receive_endpoint != nullptr && send_endpoint != nullptr); |
||||
int num_bytes_written = data.size(); |
||||
Promise<bool> read_promise; |
||||
Promise<bool> write_promise; |
||||
SliceBuffer read_slice_buf; |
||||
SliceBuffer write_slice_buf; |
||||
|
||||
AppendStringToSliceBuffer(&write_slice_buf, data); |
||||
EventEngine::Endpoint::ReadArgs args = {num_bytes_written}; |
||||
std::function<void(absl::Status)> read_cb; |
||||
read_cb = [receive_endpoint, &read_slice_buf, &read_cb, &read_promise, |
||||
&args](absl::Status status) { |
||||
GPR_ASSERT(status.ok()); |
||||
if (read_slice_buf.Length() == static_cast<size_t>(args.read_hint_bytes)) { |
||||
read_promise.Set(true); |
||||
return; |
||||
} |
||||
args.read_hint_bytes -= read_slice_buf.Length(); |
||||
receive_endpoint->Read(std::move(read_cb), &read_slice_buf, &args); |
||||
}; |
||||
// Start asynchronous reading at the receive_endpoint.
|
||||
receive_endpoint->Read(std::move(read_cb), &read_slice_buf, &args); |
||||
// Start asynchronous writing at the send_endpoint.
|
||||
send_endpoint->Write( |
||||
[&write_promise](absl::Status status) { |
||||
GPR_ASSERT(status.ok()); |
||||
write_promise.Set(true); |
||||
}, |
||||
&write_slice_buf, nullptr); |
||||
// Wait for async write to complete.
|
||||
GPR_ASSERT(write_promise.Get() == true); |
||||
// Wait for async read to complete.
|
||||
GPR_ASSERT(read_promise.Get() == true); |
||||
// Check if data written == data read
|
||||
if (data != ExtractSliceBufferIntoString(&read_slice_buf)) { |
||||
return absl::CancelledError("Data read != Data written"); |
||||
} |
||||
return absl::OkStatus(); |
||||
} |
||||
|
||||
absl::Status ConnectionManager::BindAndStartListener( |
||||
std::vector<std::string> addrs, bool listener_type_oracle) { |
||||
grpc_core::MutexLock lock(&mu_); |
||||
if (addrs.empty()) { |
||||
return absl::InvalidArgumentError( |
||||
"Atleast one bind address must be specified"); |
||||
} |
||||
for (auto& addr : addrs) { |
||||
if (listeners_.find(addr) != listeners_.end()) { |
||||
// There is already a listener at this address. Return error.
|
||||
return absl::AlreadyExistsError( |
||||
absl::StrCat("Listener already existis for address: ", addr)); |
||||
} |
||||
} |
||||
Listener::AcceptCallback accept_cb = |
||||
[this](std::unique_ptr<Endpoint> ep, |
||||
MemoryAllocator /*memory_allocator*/) { |
||||
last_in_progress_connection_.SetServerEndpoint(std::move(ep)); |
||||
}; |
||||
|
||||
EventEngine* event_engine = listener_type_oracle ? oracle_event_engine_.get() |
||||
: test_event_engine_.get(); |
||||
|
||||
auto status = event_engine->CreateListener( |
||||
std::move(accept_cb), |
||||
[](absl::Status status) { GPR_ASSERT(status.ok()); }, |
||||
ChannelArgsEndpointConfig(nullptr), |
||||
std::make_unique<grpc_core::MemoryQuota>("foo")); |
||||
if (!status.ok()) { |
||||
return status.status(); |
||||
} |
||||
|
||||
std::shared_ptr<Listener> listener((*status).release()); |
||||
for (auto& addr : addrs) { |
||||
auto bind_status = listener->Bind(URIToResolvedAddress(addr)); |
||||
if (!bind_status.ok()) { |
||||
gpr_log(GPR_ERROR, "Binding listener failed: %s", |
||||
bind_status.status().ToString().c_str()); |
||||
return bind_status.status(); |
||||
} |
||||
} |
||||
GPR_ASSERT(listener->Start().ok()); |
||||
// Insert same listener pointer for all bind addresses after the listener
|
||||
// has started successfully.
|
||||
for (auto& addr : addrs) { |
||||
listeners_.insert(std::make_pair(addr, listener)); |
||||
} |
||||
return absl::OkStatus(); |
||||
} |
||||
|
||||
absl::StatusOr<std::tuple<std::unique_ptr<Endpoint>, std::unique_ptr<Endpoint>>> |
||||
ConnectionManager::CreateConnection(std::string target_addr, |
||||
EventEngine::Duration timeout, |
||||
bool client_type_oracle) { |
||||
// Only allow one CreateConnection call to proceed at a time.
|
||||
grpc_core::MutexLock lock(&mu_); |
||||
std::string conn_name = |
||||
absl::StrCat("connection-", std::to_string(num_processed_connections_++)); |
||||
EventEngine* event_engine = client_type_oracle ? oracle_event_engine_.get() |
||||
: test_event_engine_.get(); |
||||
event_engine->Connect( |
||||
[this](absl::StatusOr<std::unique_ptr<Endpoint>> status) { |
||||
if (!status.ok()) { |
||||
gpr_log(GPR_ERROR, "Connect failed: %s", |
||||
status.status().ToString().c_str()); |
||||
last_in_progress_connection_.SetClientEndpoint(nullptr); |
||||
} else { |
||||
last_in_progress_connection_.SetClientEndpoint(std::move(*status)); |
||||
} |
||||
}, |
||||
URIToResolvedAddress(target_addr), ChannelArgsEndpointConfig(nullptr), |
||||
memory_quota_->CreateMemoryAllocator(conn_name), timeout); |
||||
|
||||
auto client_endpoint = last_in_progress_connection_.GetClientEndpoint(); |
||||
if (client_endpoint != nullptr && |
||||
listeners_.find(target_addr) != listeners_.end()) { |
||||
// There is a listener for the specified address. Wait until it
|
||||
// creates a ServerEndpoint after accepting the connection.
|
||||
auto server_endpoint = last_in_progress_connection_.GetServerEndpoint(); |
||||
GPR_ASSERT(server_endpoint != nullptr); |
||||
// Set last_in_progress_connection_ to nullptr
|
||||
return std::make_tuple(std::move(client_endpoint), |
||||
std::move(server_endpoint)); |
||||
} |
||||
return absl::CancelledError("Failed to create connection."); |
||||
} |
||||
|
||||
} // namespace experimental
|
||||
} // namespace grpc_event_engine
|
@ -0,0 +1,126 @@ |
||||
// 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_TEST_CORE_EVENT_ENGINE_TEST_SUITE_EVENT_ENGINE_TEST_UTILS_H_ |
||||
#define GRPC_TEST_CORE_EVENT_ENGINE_TEST_SUITE_EVENT_ENGINE_TEST_UTILS_H_ |
||||
|
||||
#include <functional> |
||||
#include <map> |
||||
#include <memory> |
||||
#include <string> |
||||
#include <utility> |
||||
|
||||
#include "absl/status/status.h" |
||||
#include "absl/status/statusor.h" |
||||
|
||||
#include <grpc/event_engine/event_engine.h> |
||||
#include <grpc/event_engine/memory_allocator.h> |
||||
|
||||
#include "src/core/lib/event_engine/promise.h" |
||||
#include "src/core/lib/resource_quota/memory_quota.h" |
||||
|
||||
using EventEngineFactory = std::function< |
||||
std::unique_ptr<grpc_event_engine::experimental::EventEngine>()>; |
||||
|
||||
namespace grpc_event_engine { |
||||
namespace experimental { |
||||
|
||||
void AppendStringToSliceBuffer(SliceBuffer* buf, std::string data); |
||||
|
||||
std::string ExtractSliceBufferIntoString(SliceBuffer* buf); |
||||
|
||||
EventEngine::ResolvedAddress URIToResolvedAddress(std::string address_str); |
||||
|
||||
// A helper method to exchange data between two endpoints. It is assumed that
|
||||
// both endpoints are connected. The data (specified as a string) is written by
|
||||
// the sender_endpoint and read by the receiver_endpoint. It returns OK
|
||||
// status only if data written == data read. It also blocks the calling thread
|
||||
// until said Write and Read operations are complete.
|
||||
absl::Status SendValidatePayload(std::string data, |
||||
EventEngine::Endpoint* send_endpoint, |
||||
EventEngine::Endpoint* receive_endpoint); |
||||
|
||||
// A helper class to create clients/listeners and connections between them.
|
||||
// The clients and listeners can be created by the oracle event engine
|
||||
// or the event engine under test. The class provides handles into the
|
||||
// connections that are created. Inidividual tests can test expected behavior by
|
||||
// exchanging arbitrary data over these connections.
|
||||
class ConnectionManager { |
||||
public: |
||||
ConnectionManager(std::unique_ptr<EventEngine> test_event_engine, |
||||
std::unique_ptr<EventEngine> oracle_event_engine) |
||||
: memory_quota_(std::make_unique<grpc_core::MemoryQuota>("foo")), |
||||
test_event_engine_(std::move(test_event_engine)), |
||||
oracle_event_engine_(std::move(oracle_event_engine)) {} |
||||
~ConnectionManager() = default; |
||||
|
||||
// It creates and starts a listener bound to all the specified list of
|
||||
// addresses. If successful, return OK status. The type of the listener is
|
||||
// determined by the 2nd argument.
|
||||
absl::Status BindAndStartListener(std::vector<std::string> addrs, |
||||
bool listener_type_oracle = true); |
||||
|
||||
// If connection is successful, returns a tuple containing:
|
||||
// 1. a pointer to the client side endpoint of the connection.
|
||||
// 2. a pointer to the server side endpoint of the connection.
|
||||
// If un-successful it returns a non-OK status containing the error
|
||||
// encountered.
|
||||
absl::StatusOr<std::tuple<std::unique_ptr<EventEngine::Endpoint>, |
||||
std::unique_ptr<EventEngine::Endpoint>>> |
||||
CreateConnection(std::string target_addr, EventEngine::Duration timeout, |
||||
bool client_type_oracle); |
||||
|
||||
private: |
||||
class Connection { |
||||
public: |
||||
Connection() = default; |
||||
~Connection() = default; |
||||
|
||||
void SetClientEndpoint( |
||||
std::unique_ptr<EventEngine::Endpoint>&& client_endpoint) { |
||||
client_endpoint_promise_.Set(std::move(client_endpoint)); |
||||
} |
||||
void SetServerEndpoint( |
||||
std::unique_ptr<EventEngine::Endpoint>&& server_endpoint) { |
||||
server_endpoint_promise_.Set(std::move(server_endpoint)); |
||||
} |
||||
std::unique_ptr<EventEngine::Endpoint> GetClientEndpoint() { |
||||
auto client_endpoint = std::move(client_endpoint_promise_.Get()); |
||||
client_endpoint_promise_.Reset(); |
||||
return client_endpoint; |
||||
} |
||||
std::unique_ptr<EventEngine::Endpoint> GetServerEndpoint() { |
||||
auto server_endpoint = std::move(server_endpoint_promise_.Get()); |
||||
server_endpoint_promise_.Reset(); |
||||
return server_endpoint; |
||||
} |
||||
|
||||
private: |
||||
Promise<std::unique_ptr<EventEngine::Endpoint>> client_endpoint_promise_; |
||||
Promise<std::unique_ptr<EventEngine::Endpoint>> server_endpoint_promise_; |
||||
}; |
||||
|
||||
grpc_core::Mutex mu_; |
||||
std::unique_ptr<grpc_core::MemoryQuota> memory_quota_; |
||||
int num_processed_connections_ = 0; |
||||
Connection last_in_progress_connection_; |
||||
std::map<std::string, std::shared_ptr<EventEngine::Listener>> listeners_; |
||||
std::unique_ptr<EventEngine> test_event_engine_; |
||||
std::unique_ptr<EventEngine> oracle_event_engine_; |
||||
}; |
||||
|
||||
} // namespace experimental
|
||||
} // namespace grpc_event_engine
|
||||
|
||||
#endif // GRPC_TEST_CORE_EVENT_ENGINE_TEST_SUITE_EVENT_ENGINE_TEST_UTILS_H_
|
@ -0,0 +1,473 @@ |
||||
// 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 "test/core/event_engine/test_suite/oracle_event_engine_posix.h" |
||||
|
||||
#include <poll.h> |
||||
#include <sys/poll.h> |
||||
#include <sys/socket.h> |
||||
|
||||
#include <algorithm> |
||||
#include <cerrno> |
||||
#include <cstring> |
||||
#include <memory> |
||||
|
||||
#include "absl/status/status.h" |
||||
#include "absl/strings/str_cat.h" |
||||
#include "absl/synchronization/mutex.h" |
||||
#include "absl/time/clock.h" |
||||
#include "absl/time/time.h" |
||||
|
||||
#include <grpc/event_engine/event_engine.h> |
||||
#include <grpc/support/alloc.h> |
||||
#include <grpc/support/log.h> |
||||
|
||||
#include "src/core/lib/address_utils/sockaddr_utils.h" |
||||
#include "src/core/lib/iomgr/resolved_address.h" |
||||
|
||||
namespace grpc_event_engine { |
||||
namespace experimental { |
||||
|
||||
namespace { |
||||
|
||||
const char* kStopMessage = "STOP"; |
||||
|
||||
grpc_resolved_address CreateGRPCResolvedAddress( |
||||
const EventEngine::ResolvedAddress& ra) { |
||||
grpc_resolved_address grpc_addr; |
||||
memcpy(grpc_addr.addr, ra.address(), ra.size()); |
||||
grpc_addr.len = ra.size(); |
||||
return grpc_addr; |
||||
} |
||||
|
||||
// Blocks until poll(2) indicates that one of the fds has pending I/O
|
||||
// the deadline is reached whichever comes first. Returns an OK
|
||||
// status a valid I/O event is available for at least one of the fds, a Status
|
||||
// with canonical code DEADLINE_EXCEEDED if the deadline expired and a non-OK
|
||||
// Status if any other error occurred.
|
||||
absl::Status PollFds(struct pollfd* pfds, int nfds, absl::Duration timeout) { |
||||
int rv; |
||||
while (true) { |
||||
if (timeout != absl::InfiniteDuration()) { |
||||
rv = poll(pfds, nfds, |
||||
static_cast<int>(absl::ToInt64Milliseconds(timeout))); |
||||
} else { |
||||
rv = poll(pfds, nfds, /* timeout = */ -1); |
||||
} |
||||
const int saved_errno = errno; |
||||
errno = saved_errno; |
||||
if (rv >= 0 || errno != EINTR) { |
||||
break; |
||||
} |
||||
} |
||||
if (rv < 0) { |
||||
return absl::UnknownError(std::strerror(errno)); |
||||
} |
||||
if (rv == 0) { |
||||
return absl::CancelledError("Deadline exceeded"); |
||||
} |
||||
return absl::OkStatus(); |
||||
} |
||||
|
||||
absl::Status BlockUntilReadable(int fd) { |
||||
struct pollfd pfd; |
||||
pfd.fd = fd; |
||||
pfd.events = POLLIN; |
||||
pfd.revents = 0; |
||||
return PollFds(&pfd, 1, absl::InfiniteDuration()); |
||||
} |
||||
|
||||
absl::Status BlockUntilWritableWithTimeout(int fd, absl::Duration timeout) { |
||||
struct pollfd pfd; |
||||
pfd.fd = fd; |
||||
pfd.events = POLLOUT; |
||||
pfd.revents = 0; |
||||
return PollFds(&pfd, 1, timeout); |
||||
} |
||||
|
||||
absl::Status BlockUntilWritable(int fd) { |
||||
return BlockUntilWritableWithTimeout(fd, absl::InfiniteDuration()); |
||||
} |
||||
|
||||
// Tries to read upto num_expected_bytes from the socket. It returns early if
|
||||
// specified data is not yet available.
|
||||
std::string TryReadBytes(int sockfd, int& saved_errno, int num_expected_bytes) { |
||||
int ret = 0; |
||||
static constexpr int kDefaultNumExpectedBytes = 1024; |
||||
if (num_expected_bytes <= 0) { |
||||
num_expected_bytes = kDefaultNumExpectedBytes; |
||||
} |
||||
std::string read_data = std::string(num_expected_bytes, '\0'); |
||||
char* buffer = const_cast<char*>(read_data.c_str()); |
||||
int pending_bytes = num_expected_bytes; |
||||
do { |
||||
errno = 0; |
||||
ret = read(sockfd, buffer + num_expected_bytes - pending_bytes, |
||||
pending_bytes); |
||||
if (ret > 0) { |
||||
pending_bytes -= ret; |
||||
} |
||||
} while (pending_bytes > 0 && ((ret > 0) || (ret < 0 && errno == EINTR))); |
||||
saved_errno = errno; |
||||
return read_data.substr(0, num_expected_bytes - pending_bytes); |
||||
} |
||||
|
||||
// Blocks calling thread until the specified number of bytes have been
|
||||
// read from the provided socket or it encounters an unrecoverable error. It
|
||||
// puts the read bytes into a string and returns the string. If it encounters an
|
||||
// error, it returns an empty string and updates saved_errno with the
|
||||
// appropriate errno.
|
||||
std::string ReadBytes(int sockfd, int& saved_errno, int num_expected_bytes) { |
||||
std::string read_data; |
||||
do { |
||||
saved_errno = 0; |
||||
read_data += TryReadBytes(sockfd, saved_errno, |
||||
num_expected_bytes - read_data.length()); |
||||
if (saved_errno == EAGAIN && |
||||
read_data.length() < static_cast<size_t>(num_expected_bytes)) { |
||||
GPR_ASSERT(BlockUntilReadable(sockfd).ok()); |
||||
} else if (saved_errno != 0 && num_expected_bytes > 0) { |
||||
read_data.clear(); |
||||
break; |
||||
} |
||||
} while (read_data.length() < static_cast<size_t>(num_expected_bytes)); |
||||
return read_data; |
||||
} |
||||
|
||||
// Tries to write the specified bytes over the socket. It returns the number of
|
||||
// bytes actually written.
|
||||
int TryWriteBytes(int sockfd, int& saved_errno, std::string write_bytes) { |
||||
int ret = 0; |
||||
int pending_bytes = write_bytes.length(); |
||||
do { |
||||
errno = 0; |
||||
ret = write(sockfd, |
||||
write_bytes.c_str() + write_bytes.length() - pending_bytes, |
||||
pending_bytes); |
||||
if (ret > 0) { |
||||
pending_bytes -= ret; |
||||
} |
||||
} while (pending_bytes > 0 && ((ret > 0) || (ret < 0 && errno == EINTR))); |
||||
saved_errno = errno; |
||||
return write_bytes.length() - pending_bytes; |
||||
} |
||||
|
||||
// Blocks calling thread until the specified number of bytes have been
|
||||
// written over the provided socket or it encounters an unrecoverable error. The
|
||||
// bytes to write are specified as a string. If it encounters an error, it
|
||||
// returns an empty string and updates saved_errno with the appropriate errno
|
||||
// and returns a value less than zero.
|
||||
int WriteBytes(int sockfd, int& saved_errno, std::string write_bytes) { |
||||
int ret = 0; |
||||
int original_write_length = write_bytes.length(); |
||||
do { |
||||
saved_errno = 0; |
||||
ret = TryWriteBytes(sockfd, saved_errno, write_bytes); |
||||
if (saved_errno == EAGAIN && ret < static_cast<int>(write_bytes.length())) { |
||||
GPR_ASSERT(ret >= 0); |
||||
GPR_ASSERT(BlockUntilWritable(sockfd).ok()); |
||||
} else if (saved_errno != 0) { |
||||
GPR_ASSERT(ret < 0); |
||||
return ret; |
||||
} |
||||
write_bytes = write_bytes.substr(ret, std::string::npos); |
||||
} while (write_bytes.length() > 0); |
||||
return original_write_length; |
||||
} |
||||
} // namespace
|
||||
|
||||
PosixOracleEndpoint::PosixOracleEndpoint(int socket_fd) |
||||
: socket_fd_(socket_fd) { |
||||
read_ops_ = grpc_core::Thread( |
||||
"read_ops_thread", |
||||
[](void* arg) { |
||||
static_cast<PosixOracleEndpoint*>(arg)->ProcessReadOperations(); |
||||
}, |
||||
this); |
||||
write_ops_ = grpc_core::Thread( |
||||
"write_ops_thread", |
||||
[](void* arg) { |
||||
static_cast<PosixOracleEndpoint*>(arg)->ProcessWriteOperations(); |
||||
}, |
||||
this); |
||||
read_ops_.Start(); |
||||
write_ops_.Start(); |
||||
} |
||||
|
||||
void PosixOracleEndpoint::Shutdown() { |
||||
absl::MutexLock lock(&mu_); |
||||
if (absl::exchange(is_shutdown_, true)) { |
||||
return; |
||||
} |
||||
read_ops_channel_.Set(ReadOperation()); |
||||
write_ops_channel_.Set(WriteOperation()); |
||||
read_ops_.Join(); |
||||
write_ops_.Join(); |
||||
} |
||||
|
||||
std::unique_ptr<PosixOracleEndpoint> PosixOracleEndpoint::Create( |
||||
int socket_fd) { |
||||
return std::make_unique<PosixOracleEndpoint>(socket_fd); |
||||
} |
||||
|
||||
PosixOracleEndpoint::~PosixOracleEndpoint() { |
||||
Shutdown(); |
||||
close(socket_fd_); |
||||
} |
||||
|
||||
void PosixOracleEndpoint::Read(std::function<void(absl::Status)> on_read, |
||||
SliceBuffer* buffer, const ReadArgs* args) { |
||||
GPR_ASSERT(buffer != nullptr); |
||||
int read_hint_bytes = |
||||
args != nullptr ? std::max(1, static_cast<int>(args->read_hint_bytes)) |
||||
: 0; |
||||
read_ops_channel_.Set( |
||||
ReadOperation(read_hint_bytes, buffer, std::move(on_read))); |
||||
} |
||||
|
||||
void PosixOracleEndpoint::Write(std::function<void(absl::Status)> on_writable, |
||||
SliceBuffer* data, const WriteArgs* /*args*/) { |
||||
GPR_ASSERT(data != nullptr); |
||||
write_ops_channel_.Set(WriteOperation(data, std::move(on_writable))); |
||||
} |
||||
|
||||
void PosixOracleEndpoint::ProcessReadOperations() { |
||||
gpr_log(GPR_INFO, "Starting thread to process read ops ..."); |
||||
while (true) { |
||||
ReadOperation read_op; |
||||
read_op = read_ops_channel_.Get(); |
||||
read_ops_channel_.Reset(); |
||||
if (!read_op.IsValid()) { |
||||
read_op(std::string(), absl::CancelledError("Closed")); |
||||
break; |
||||
} |
||||
int saved_errno; |
||||
std::string read_data = |
||||
ReadBytes(socket_fd_, saved_errno, read_op.GetNumBytesToRead()); |
||||
read_op(read_data, read_data.empty() ? absl::CancelledError(absl::StrCat( |
||||
"Read failed with error = ", |
||||
std::strerror(saved_errno))) |
||||
: absl::OkStatus()); |
||||
} |
||||
gpr_log(GPR_INFO, "Shutting down read ops thread ..."); |
||||
} |
||||
|
||||
void PosixOracleEndpoint::ProcessWriteOperations() { |
||||
gpr_log(GPR_INFO, "Starting thread to process write ops ..."); |
||||
while (true) { |
||||
WriteOperation write_op; |
||||
write_op = write_ops_channel_.Get(); |
||||
write_ops_channel_.Reset(); |
||||
if (!write_op.IsValid()) { |
||||
write_op(absl::CancelledError("Closed")); |
||||
break; |
||||
} |
||||
int saved_errno; |
||||
int ret = WriteBytes(socket_fd_, saved_errno, write_op.GetBytesToWrite()); |
||||
write_op( |
||||
ret < 0 ? absl::CancelledError(absl::StrCat( |
||||
"Write failed with error = ", std::strerror(saved_errno))) |
||||
: absl::OkStatus()); |
||||
} |
||||
gpr_log(GPR_INFO, "Shutting down write ops thread ..."); |
||||
} |
||||
|
||||
PosixOracleListener::PosixOracleListener( |
||||
EventEngine::Listener::AcceptCallback on_accept, |
||||
std::function<void(absl::Status)> on_shutdown, |
||||
std::unique_ptr<MemoryAllocatorFactory> memory_allocator_factory) |
||||
: on_accept_(std::move(on_accept)), |
||||
on_shutdown_(std::move(on_shutdown)), |
||||
memory_allocator_factory_(std::move(memory_allocator_factory)) { |
||||
if (pipe(pipefd_) == -1) { |
||||
gpr_log(GPR_ERROR, "Error creating pipe: %s", std::strerror(errno)); |
||||
abort(); |
||||
} |
||||
} |
||||
|
||||
absl::Status PosixOracleListener::Start() { |
||||
absl::MutexLock lock(&mu_); |
||||
GPR_ASSERT(!listener_fds_.empty()); |
||||
if (absl::exchange(is_started_, true)) { |
||||
return absl::InternalError("Cannot start listener more than once ..."); |
||||
} |
||||
serve_ = grpc_core::Thread( |
||||
"accept_thread", |
||||
[](void* arg) { |
||||
static_cast<PosixOracleListener*>(arg)->HandleIncomingConnections(); |
||||
}, |
||||
this); |
||||
serve_.Start(); |
||||
return absl::OkStatus(); |
||||
} |
||||
|
||||
PosixOracleListener::~PosixOracleListener() { |
||||
absl::MutexLock lock(&mu_); |
||||
if (!is_started_) { |
||||
serve_.Join(); |
||||
return; |
||||
} |
||||
for (int i = 0; i < static_cast<int>(listener_fds_.size()); i++) { |
||||
shutdown(listener_fds_[i], SHUT_RDWR); |
||||
} |
||||
// Send a STOP message over the pipe.
|
||||
write(pipefd_[1], kStopMessage, strlen(kStopMessage)); |
||||
serve_.Join(); |
||||
on_shutdown_(absl::OkStatus()); |
||||
} |
||||
|
||||
void PosixOracleListener::HandleIncomingConnections() { |
||||
gpr_log(GPR_INFO, "Starting accept thread ..."); |
||||
GPR_ASSERT(!listener_fds_.empty()); |
||||
int nfds = listener_fds_.size(); |
||||
// Add one extra file descriptor to poll the pipe fd.
|
||||
++nfds; |
||||
struct pollfd* pfds = |
||||
static_cast<struct pollfd*>(gpr_malloc(sizeof(struct pollfd) * nfds)); |
||||
memset(pfds, 0, sizeof(struct pollfd) * nfds); |
||||
while (true) { |
||||
for (int i = 0; i < nfds; i++) { |
||||
pfds[i].fd = i == nfds - 1 ? pipefd_[0] : listener_fds_[i]; |
||||
pfds[i].events = POLLIN; |
||||
pfds[i].revents = 0; |
||||
} |
||||
if (!PollFds(pfds, nfds, absl::InfiniteDuration()).ok()) { |
||||
break; |
||||
} |
||||
int saved_errno = 0; |
||||
if ((pfds[nfds - 1].revents & POLLIN) && |
||||
ReadBytes(pipefd_[0], saved_errno, strlen(kStopMessage)) == |
||||
std::string(kStopMessage)) { |
||||
break; |
||||
} |
||||
for (int i = 0; i < nfds - 1; i++) { |
||||
if (!(pfds[i].revents & POLLIN)) { |
||||
continue; |
||||
} |
||||
// pfds[i].fd has a readable event.
|
||||
int client_sock_fd = accept(pfds[i].fd, nullptr, nullptr); |
||||
if (client_sock_fd < 0) { |
||||
gpr_log(GPR_ERROR, |
||||
"Error accepting new connection: %s. Ignoring connection " |
||||
"attempt ...", |
||||
std::strerror(errno)); |
||||
continue; |
||||
} |
||||
on_accept_(PosixOracleEndpoint::Create(client_sock_fd), |
||||
memory_allocator_factory_->CreateMemoryAllocator("test")); |
||||
} |
||||
} |
||||
gpr_log(GPR_INFO, "Shutting down accept thread ..."); |
||||
gpr_free(pfds); |
||||
} |
||||
|
||||
absl::StatusOr<int> PosixOracleListener::Bind( |
||||
const EventEngine::ResolvedAddress& addr) { |
||||
absl::MutexLock lock(&mu_); |
||||
int new_socket; |
||||
int opt = -1; |
||||
grpc_resolved_address address = CreateGRPCResolvedAddress(addr); |
||||
const char* scheme = grpc_sockaddr_get_uri_scheme(&address); |
||||
if (scheme == nullptr || strcmp(scheme, "ipv6") != 0) { |
||||
return absl::UnimplementedError( |
||||
"Unsupported bind address type. Only IPV6 addresses are supported " |
||||
"currently by the PosixOracleListener ..."); |
||||
} |
||||
|
||||
// Creating a new socket file descriptor.
|
||||
if ((new_socket = socket(AF_INET6, SOCK_STREAM, 0)) <= 0) { |
||||
return absl::UnknownError( |
||||
absl::StrCat("Error creating socket: ", std::strerror(errno))); |
||||
} |
||||
// MacOS biulds fail if SO_REUSEADDR and SO_REUSEPORT are set in the same
|
||||
// setsockopt syscall. So they are set separately one after the other.
|
||||
if (setsockopt(new_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) { |
||||
return absl::UnknownError( |
||||
absl::StrCat("Error setsockopt(SO_REUSEADDR): ", std::strerror(errno))); |
||||
} |
||||
if (setsockopt(new_socket, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt))) { |
||||
return absl::UnknownError( |
||||
absl::StrCat("Error setsockopt(SO_REUSEPORT): ", std::strerror(errno))); |
||||
} |
||||
|
||||
// Forcefully bind the new socket.
|
||||
if (bind(new_socket, reinterpret_cast<const struct sockaddr*>(addr.address()), |
||||
address.len) < 0) { |
||||
return absl::UnknownError( |
||||
absl::StrCat("Error bind: ", std::strerror(errno))); |
||||
} |
||||
// Set the new socket to listen for one active connection at a time.
|
||||
if (listen(new_socket, 1) < 0) { |
||||
return absl::UnknownError( |
||||
absl::StrCat("Error listen: ", std::strerror(errno))); |
||||
} |
||||
listener_fds_.push_back(new_socket); |
||||
return 0; |
||||
} |
||||
|
||||
// PosixOracleEventEngine implements blocking connect. It blocks the calling
|
||||
// thread until either connect succeeds or fails with timeout.
|
||||
EventEngine::ConnectionHandle PosixOracleEventEngine::Connect( |
||||
OnConnectCallback on_connect, const ResolvedAddress& addr, |
||||
const EndpointConfig& /*args*/, MemoryAllocator /*memory_allocator*/, |
||||
EventEngine::Duration timeout) { |
||||
int client_sock_fd; |
||||
absl::Time deadline = absl::Now() + absl::FromChrono(timeout); |
||||
grpc_resolved_address address = CreateGRPCResolvedAddress(addr); |
||||
const char* scheme = grpc_sockaddr_get_uri_scheme(&address); |
||||
if (scheme == nullptr || strcmp(scheme, "ipv6") != 0) { |
||||
on_connect( |
||||
absl::CancelledError("Unsupported bind address type. Only ipv6 " |
||||
"addresses are currently supported.")); |
||||
return {}; |
||||
} |
||||
if ((client_sock_fd = socket(AF_INET6, SOCK_STREAM, 0)) < 0) { |
||||
on_connect(absl::CancelledError(absl::StrCat( |
||||
"Connect failed: socket creation error: ", std::strerror(errno)))); |
||||
return {}; |
||||
} |
||||
int err; |
||||
int num_retries = 0; |
||||
static constexpr int kMaxRetries = 5; |
||||
do { |
||||
err = connect(client_sock_fd, const_cast<struct sockaddr*>(addr.address()), |
||||
address.len); |
||||
if (err < 0 && (errno == EINPROGRESS || errno == EWOULDBLOCK)) { |
||||
auto status = BlockUntilWritableWithTimeout( |
||||
client_sock_fd, |
||||
std::max(deadline - absl::Now(), absl::ZeroDuration())); |
||||
if (!status.ok()) { |
||||
on_connect(status); |
||||
return {}; |
||||
} |
||||
} else if (err < 0) { |
||||
if (errno != ECONNREFUSED || ++num_retries > kMaxRetries) { |
||||
on_connect(absl::CancelledError("Connect failed.")); |
||||
return {}; |
||||
} |
||||
// If ECONNREFUSED && num_retries < kMaxRetries, wait a while and try
|
||||
// again.
|
||||
absl::SleepFor(absl::Milliseconds(100)); |
||||
} |
||||
} while (err < 0 && absl::Now() < deadline); |
||||
if (err < 0 && absl::Now() >= deadline) { |
||||
on_connect(absl::CancelledError("Deadline exceeded")); |
||||
} else { |
||||
on_connect(PosixOracleEndpoint::Create(client_sock_fd)); |
||||
} |
||||
return {}; |
||||
} |
||||
|
||||
} // namespace experimental
|
||||
} // namespace grpc_event_engine
|
@ -0,0 +1,194 @@ |
||||
// 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_TEST_CORE_EVENT_ENGINE_TEST_SUITE_ORACLE_EVENT_ENGINE_POSIX_H_ |
||||
#define GRPC_TEST_CORE_EVENT_ENGINE_TEST_SUITE_ORACLE_EVENT_ENGINE_POSIX_H_ |
||||
|
||||
#include <functional> |
||||
#include <memory> |
||||
#include <string> |
||||
#include <unordered_map> |
||||
#include <utility> |
||||
|
||||
#include "absl/status/status.h" |
||||
#include "absl/status/statusor.h" |
||||
#include "absl/time/time.h" |
||||
|
||||
#include <grpc/event_engine/event_engine.h> |
||||
#include <grpc/event_engine/slice_buffer.h> |
||||
#include <grpc/support/log.h> |
||||
|
||||
#include "src/core/lib/event_engine/promise.h" |
||||
#include "src/core/lib/gprpp/thd.h" |
||||
#include "src/core/lib/resource_quota/memory_quota.h" |
||||
#include "test/core/event_engine/test_suite/event_engine_test_utils.h" |
||||
|
||||
namespace grpc_event_engine { |
||||
namespace experimental { |
||||
|
||||
class PosixOracleEndpoint : public EventEngine::Endpoint { |
||||
public: |
||||
explicit PosixOracleEndpoint(int socket_fd); |
||||
static std::unique_ptr<PosixOracleEndpoint> Create(int socket_fd); |
||||
~PosixOracleEndpoint() override; |
||||
void Read(std::function<void(absl::Status)> on_read, SliceBuffer* buffer, |
||||
const ReadArgs* args) override; |
||||
void Write(std::function<void(absl::Status)> on_writable, SliceBuffer* data, |
||||
const WriteArgs* args) override; |
||||
void Shutdown(); |
||||
EventEngine::ResolvedAddress& GetPeerAddress() const override { |
||||
GPR_ASSERT(false && "unimplemented"); |
||||
} |
||||
EventEngine::ResolvedAddress& GetLocalAddress() const override { |
||||
GPR_ASSERT(false && "unimplemented"); |
||||
} |
||||
|
||||
private: |
||||
// An internal helper class definition of Read operations to be performed
|
||||
// by the TCPServerEndpoint.
|
||||
class ReadOperation { |
||||
public: |
||||
ReadOperation() |
||||
: num_bytes_to_read_(-1), buffer_(nullptr), on_complete_(nullptr) {} |
||||
ReadOperation(int num_bytes_to_read, SliceBuffer* buffer, |
||||
std::function<void(absl::Status)>&& on_complete) |
||||
: num_bytes_to_read_(num_bytes_to_read), |
||||
buffer_(buffer), |
||||
on_complete_(std::move(on_complete)) {} |
||||
bool IsValid() { return num_bytes_to_read_ >= 0 && buffer_ != nullptr; } |
||||
int GetNumBytesToRead() const { return num_bytes_to_read_; } |
||||
void operator()(std::string read_data, absl::Status status) { |
||||
if (on_complete_ != nullptr) { |
||||
AppendStringToSliceBuffer(absl::exchange(buffer_, nullptr), read_data); |
||||
absl::exchange(on_complete_, nullptr)(status); |
||||
} |
||||
} |
||||
|
||||
private: |
||||
int num_bytes_to_read_; |
||||
SliceBuffer* buffer_; |
||||
std::function<void(absl::Status)> on_complete_; |
||||
}; |
||||
|
||||
// An internal helper class definition of Write operations to be performed
|
||||
// by the TCPServerEndpoint.
|
||||
class WriteOperation { |
||||
public: |
||||
WriteOperation() : bytes_to_write_(std::string()), on_complete_(nullptr) {} |
||||
WriteOperation(SliceBuffer* buffer, |
||||
std::function<void(absl::Status)>&& on_complete) |
||||
: bytes_to_write_(ExtractSliceBufferIntoString(buffer)), |
||||
on_complete_(std::move(on_complete)) {} |
||||
bool IsValid() { return bytes_to_write_.length() > 0; } |
||||
std::string GetBytesToWrite() const { return bytes_to_write_; } |
||||
void operator()(absl::Status status) { |
||||
if (on_complete_ != nullptr) { |
||||
absl::exchange(on_complete_, nullptr)(status); |
||||
} |
||||
} |
||||
|
||||
private: |
||||
std::string bytes_to_write_; |
||||
std::function<void(absl::Status)> on_complete_; |
||||
}; |
||||
|
||||
void ProcessReadOperations(); |
||||
void ProcessWriteOperations(); |
||||
|
||||
mutable absl::Mutex mu_; |
||||
bool is_shutdown_ = false; |
||||
int socket_fd_; |
||||
Promise<ReadOperation> read_ops_channel_; |
||||
Promise<WriteOperation> write_ops_channel_; |
||||
grpc_core::Thread read_ops_ ABSL_GUARDED_BY(mu_); |
||||
grpc_core::Thread write_ops_ ABSL_GUARDED_BY(mu_); |
||||
}; |
||||
|
||||
class PosixOracleListener : public EventEngine::Listener { |
||||
public: |
||||
PosixOracleListener( |
||||
EventEngine::Listener::AcceptCallback on_accept, |
||||
std::function<void(absl::Status)> on_shutdown, |
||||
std::unique_ptr<MemoryAllocatorFactory> memory_allocator_factory); |
||||
~PosixOracleListener() override; |
||||
absl::StatusOr<int> Bind(const EventEngine::ResolvedAddress& addr) override; |
||||
absl::Status Start() override; |
||||
|
||||
private: |
||||
void HandleIncomingConnections(); |
||||
|
||||
mutable absl::Mutex mu_; |
||||
EventEngine::Listener::AcceptCallback on_accept_; |
||||
std::function<void(absl::Status)> on_shutdown_; |
||||
std::unique_ptr<MemoryAllocatorFactory> memory_allocator_factory_; |
||||
grpc_core::Thread serve_; |
||||
int pipefd_[2]; |
||||
bool is_started_ = false; |
||||
std::vector<int> listener_fds_; |
||||
}; |
||||
|
||||
// A posix based oracle event engine.
|
||||
class PosixOracleEventEngine final : public EventEngine { |
||||
public: |
||||
PosixOracleEventEngine() = default; |
||||
~PosixOracleEventEngine() override = default; |
||||
|
||||
absl::StatusOr<std::unique_ptr<Listener>> CreateListener( |
||||
Listener::AcceptCallback on_accept, |
||||
std::function<void(absl::Status)> on_shutdown, |
||||
const EndpointConfig& /*config*/, |
||||
std::unique_ptr<MemoryAllocatorFactory> memory_allocator_factory) |
||||
override { |
||||
return std::make_unique<PosixOracleListener>( |
||||
std::move(on_accept), std::move(on_shutdown), |
||||
std::move(memory_allocator_factory)); |
||||
} |
||||
|
||||
ConnectionHandle Connect(OnConnectCallback on_connect, |
||||
const ResolvedAddress& addr, |
||||
const EndpointConfig& args, |
||||
MemoryAllocator memory_allocator, |
||||
EventEngine::Duration timeout) override; |
||||
|
||||
bool CancelConnect(ConnectionHandle /*handle*/) override { |
||||
GPR_ASSERT(false && "unimplemented"); |
||||
} |
||||
bool IsWorkerThread() override { return false; }; |
||||
std::unique_ptr<DNSResolver> GetDNSResolver( |
||||
const DNSResolver::ResolverOptions& /*options*/) override { |
||||
GPR_ASSERT(false && "unimplemented"); |
||||
} |
||||
void Run(Closure* /*closure*/) override { |
||||
GPR_ASSERT(false && "unimplemented"); |
||||
} |
||||
void Run(std::function<void()> /*closure*/) override { |
||||
GPR_ASSERT(false && "unimplemented"); |
||||
} |
||||
TaskHandle RunAfter(EventEngine::Duration /*duration*/, |
||||
Closure* /*closure*/) override { |
||||
GPR_ASSERT(false && "unimplemented"); |
||||
} |
||||
TaskHandle RunAfter(EventEngine::Duration /*duration*/, |
||||
std::function<void()> /*closure*/) override { |
||||
GPR_ASSERT(false && "unimplemented"); |
||||
} |
||||
bool Cancel(TaskHandle /*handle*/) override { |
||||
GPR_ASSERT(false && "unimplemented"); |
||||
} |
||||
}; |
||||
|
||||
} // namespace experimental
|
||||
} // namespace grpc_event_engine
|
||||
|
||||
#endif // GRPC_TEST_CORE_EVENT_ENGINE_TEST_SUITE_ORACLE_EVENT_ENGINE_POSIX_H_
|
@ -0,0 +1,29 @@ |
||||
// 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 "test/core/event_engine/test_suite/oracle_event_engine_posix.h" |
||||
|
||||
#include "test/core/event_engine/test_suite/event_engine_test.h" |
||||
#include "test/core/util/test_config.h" |
||||
|
||||
int main(int argc, char** argv) { |
||||
testing::InitGoogleTest(&argc, argv); |
||||
grpc::testing::TestEnvironment env(&argc, argv); |
||||
auto ee_factory = []() { |
||||
return absl::make_unique< |
||||
grpc_event_engine::experimental::PosixOracleEventEngine>(); |
||||
}; |
||||
SetEventEngineFactories(/*ee_factory=*/ee_factory, |
||||
/*oracle_ee_factory=*/ee_factory); |
||||
return RUN_ALL_TESTS(); |
||||
} |
Loading…
Reference in new issue