mirror of https://github.com/grpc/grpc.git
gRPC EventEngine Interface (#25795)
parent
a483e6ca35
commit
4d4ee609c1
30 changed files with 694 additions and 50 deletions
@ -0,0 +1,38 @@ |
||||
# gRPC EventEngine |
||||
|
||||
An EventEngine handles all cross-platform I/O, task execution, and DNS |
||||
resolution for gRPC. A default, cross-platform implementation is provided with |
||||
gRPC, but part of the intent here is to provide an interface for external |
||||
integrators to bring their own functionality. This allows for integration with |
||||
external event loops, siloing I/O and task execution between channels or |
||||
servers, and other custom integrations that were previously unsupported. |
||||
|
||||
*WARNING*: This is experimental code and is subject to change. |
||||
|
||||
## High level expectations of an EventEngine implementation |
||||
|
||||
### Provide their own I/O threads |
||||
EventEngines are expected to internally create whatever threads are required to |
||||
perform I/O and execute callbacks. For example, an EventEngine implementation |
||||
may want to spawn separate thread pools for polling and callback execution. |
||||
|
||||
### Provisioning data buffers via Slice allocation |
||||
At a high level, gRPC provides a `ResourceQuota` system that allows gRPC to |
||||
reclaim memory and degrade gracefully when memory reaches application-defined |
||||
thresholds. To enable this feature, the memory allocation of read/write buffers |
||||
within an EventEngine must be acquired in the form of Slices from |
||||
SliceAllocators. This is covered more fully in the gRFC and code. |
||||
|
||||
### Documentating expectations around callback execution |
||||
Some callbacks may be expensive to run. EventEngines should decide on and |
||||
document whether callback execution might block polling operations. This way, |
||||
application developers can plan accordingly (e.g., run their expensive callbacks |
||||
on a separate thread if necessary). |
||||
|
||||
### Handling concurrent usage |
||||
Assume that gRPC may use an EventEngine concurrently across multiple threads. |
||||
|
||||
## TODO: documentation |
||||
|
||||
* Example usage |
||||
* Link to gRFC |
@ -0,0 +1,28 @@ |
||||
// 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_EVENT_ENGINE_CHANNEL_ARGS_H |
||||
#define GRPC_EVENT_ENGINE_CHANNEL_ARGS_H |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
namespace grpc_event_engine { |
||||
namespace experimental { |
||||
|
||||
// TODO(hork): define
|
||||
class ChannelArgs; |
||||
|
||||
} // namespace experimental
|
||||
} // namespace grpc_event_engine
|
||||
|
||||
#endif // GRPC_EVENT_ENGINE_CHANNEL_ARGS_H
|
@ -0,0 +1,281 @@ |
||||
// 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_EVENT_ENGINE_EVENT_ENGINE_H |
||||
#define GRPC_EVENT_ENGINE_EVENT_ENGINE_H |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include <functional> |
||||
#include <vector> |
||||
|
||||
#include "absl/status/status.h" |
||||
#include "absl/status/statusor.h" |
||||
#include "absl/time/time.h" |
||||
|
||||
#include "grpc/event_engine/channel_args.h" |
||||
#include "grpc/event_engine/slice_allocator.h" |
||||
|
||||
// TODO(hork): explicitly define lifetimes and ownership of all objects.
|
||||
// TODO(hork): Define the Endpoint::Write metrics collection system
|
||||
|
||||
namespace grpc_event_engine { |
||||
namespace experimental { |
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// The EventEngine encapsulates all platform-specific behaviors related to low
|
||||
/// level network I/O, timers, asynchronous execution, and DNS resolution.
|
||||
///
|
||||
/// This interface allows developers to provide their own event management and
|
||||
/// network stacks. Motivating uses cases for supporting custom EventEngines
|
||||
/// include the ability to hook into external event loops, and using different
|
||||
/// EventEngine instances for each channel to better insulate network I/O and
|
||||
/// callback processing from other channels.
|
||||
///
|
||||
/// A default cross-platform EventEngine instance is provided by gRPC.
|
||||
///
|
||||
/// LIFESPAN AND OWNERSHIP
|
||||
///
|
||||
/// gRPC takes shared ownership of EventEngines via std::shared_ptrs to ensure
|
||||
/// that the engines remain available until they are no longer needed. Depending
|
||||
/// on the use case, engines may live until gRPC is shut down.
|
||||
///
|
||||
/// EXAMPLE USAGE (Not yet implemented)
|
||||
///
|
||||
/// Custom EventEngines can be specified per channel, and allow configuration
|
||||
/// for both clients and servers. To set a custom EventEngine for a client
|
||||
/// channel, you can do something like the following:
|
||||
///
|
||||
/// ChannelArguments args;
|
||||
/// std::shared_ptr<EventEngine> engine = std::make_shared<MyEngine>(...);
|
||||
/// args.SetEventEngine(engine);
|
||||
/// MyAppClient client(grpc::CreateCustomChannel(
|
||||
/// "localhost:50051", grpc::InsecureChannelCredentials(), args));
|
||||
///
|
||||
/// A gRPC server can use a custom EventEngine by calling the
|
||||
/// ServerBuilder::SetEventEngine method:
|
||||
///
|
||||
/// ServerBuilder builder;
|
||||
/// std::shared_ptr<EventEngine> engine = std::make_shared<MyEngine>(...);
|
||||
/// builder.SetEventEngine(engine);
|
||||
/// std::unique_ptr<Server> server(builder.BuildAndStart());
|
||||
/// server->Wait();
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
class EventEngine { |
||||
public: |
||||
/// A basic callable function. The first argument to all callbacks is an
|
||||
/// absl::Status indicating the status of the operation associated with this
|
||||
/// callback. Each EventEngine method that takes a callback parameter, defines
|
||||
/// the expected sets and meanings of statuses for that use case.
|
||||
using Callback = std::function<void(absl::Status)>; |
||||
struct TaskHandle { |
||||
intptr_t key; |
||||
}; |
||||
/// A thin wrapper around a platform-specific sockaddr type. A sockaddr struct
|
||||
/// exists on all platforms that gRPC supports.
|
||||
///
|
||||
/// Platforms are expected to provide definitions for:
|
||||
/// * sockaddr
|
||||
/// * sockaddr_in
|
||||
/// * sockaddr_in6
|
||||
class ResolvedAddress { |
||||
public: |
||||
static constexpr socklen_t MAX_SIZE_BYTES = 128; |
||||
|
||||
ResolvedAddress(const sockaddr* address, socklen_t size); |
||||
const struct sockaddr* address() const; |
||||
socklen_t size() const; |
||||
|
||||
private: |
||||
char address_[MAX_SIZE_BYTES]; |
||||
socklen_t size_; |
||||
}; |
||||
|
||||
/// An Endpoint represents one end of a connection between a gRPC client and
|
||||
/// server. Endpoints are created when connections are established, and
|
||||
/// Endpoint operations are gRPC's primary means of communication.
|
||||
///
|
||||
/// Endpoints must use the provided SliceAllocator for all data buffer memory
|
||||
/// allocations. gRPC allows applications to set memory constraints per
|
||||
/// Channel or Server, and the implementation depends on all dynamic memory
|
||||
/// allocation being handled by the quota system.
|
||||
class Endpoint { |
||||
public: |
||||
virtual ~Endpoint() = 0; |
||||
|
||||
// TODO(hork): define status codes for the callback
|
||||
/// Read data from the Endpoint.
|
||||
///
|
||||
/// When data is available on the connection, that data is moved into the
|
||||
/// \a buffer, and the \a on_read callback is called. The caller must ensure
|
||||
/// that the callback has access to the buffer when executed later.
|
||||
/// Ownership of the buffer is not transferred. Valid slices *may* be placed
|
||||
/// into the buffer even if the callback is invoked with Status != OK.
|
||||
virtual void Read(Callback on_read, SliceBuffer* buffer, |
||||
absl::Time deadline) = 0; |
||||
// TODO(hork): define status codes for the callback
|
||||
/// Write data out on the connection.
|
||||
///
|
||||
/// \a on_writable is called when the connection is ready for more data. The
|
||||
/// Slices within the \a data buffer may be mutated at will by the Endpoint
|
||||
/// until \a on_writable is called. The \a data SliceBuffer will remain
|
||||
/// valid after calling \a Write, but its state is otherwise undefined.
|
||||
virtual void Write(Callback on_writable, SliceBuffer* data, |
||||
absl::Time deadline) = 0; |
||||
// TODO(hork): define status codes for the callback
|
||||
// TODO(hork): define cleanup operations, lifetimes, responsibilities.
|
||||
virtual void Close(Callback on_close) = 0; |
||||
/// These methods return an address in the format described in DNSResolver.
|
||||
/// The returned values are owned by the Endpoint and are expected to remain
|
||||
/// valid for the life of the Endpoint.
|
||||
virtual const ResolvedAddress* GetPeerAddress() const = 0; |
||||
virtual const ResolvedAddress* GetLocalAddress() const = 0; |
||||
}; |
||||
|
||||
/// Called when a new connection is established. This callback takes ownership
|
||||
/// of the Endpoint and is responsible for its destruction.
|
||||
using OnConnectCallback = std::function<void(absl::Status, Endpoint*)>; |
||||
|
||||
/// An EventEngine Listener listens for incoming connection requests from gRPC
|
||||
/// clients and initiates request processing once connections are established.
|
||||
class Listener { |
||||
public: |
||||
/// A callback handle, used to cancel a callback. Called when the listener
|
||||
/// has accepted a new client connection. This callback takes ownership of
|
||||
/// the Endpoint and is responsible its destruction.
|
||||
using AcceptCallback = std::function<void(absl::Status, Endpoint*)>; |
||||
|
||||
virtual ~Listener() = 0; |
||||
|
||||
// TODO(hork): define return status codes
|
||||
// TODO(hork): requires output port argument, return value, or callback
|
||||
/// Bind an address/port to this Listener. It is expected that multiple
|
||||
/// addresses/ports can be bound to this Listener before Listener::Start has
|
||||
/// been called.
|
||||
virtual absl::Status Bind(const ResolvedAddress& addr) = 0; |
||||
virtual absl::Status Start() = 0; |
||||
virtual absl::Status Shutdown() = 0; |
||||
}; |
||||
|
||||
// TODO(hork): define status codes for the callback
|
||||
// TODO(hork): define return status codes
|
||||
// TODO(hork): document status arg meanings for on_accept and on_shutdown
|
||||
/// Factory method to create a network listener.
|
||||
virtual absl::StatusOr<Listener> CreateListener( |
||||
Listener::AcceptCallback on_accept, Callback on_shutdown, |
||||
const ChannelArgs& args, |
||||
SliceAllocatorFactory slice_allocator_factory) = 0; |
||||
// TODO(hork): define status codes for the callback
|
||||
// TODO(hork): define return status codes
|
||||
/// Creates a network connection to a remote network listener.
|
||||
virtual absl::Status Connect(OnConnectCallback on_connect, |
||||
const ResolvedAddress& addr, |
||||
const ChannelArgs& args, |
||||
SliceAllocator slice_allocator, |
||||
absl::Time deadline) = 0; |
||||
|
||||
/// The DNSResolver that provides asynchronous resolution.
|
||||
class DNSResolver { |
||||
public: |
||||
/// A task handle for DNS Resolution requests.
|
||||
struct LookupTaskHandle { |
||||
intptr_t key; |
||||
}; |
||||
/// A DNS SRV record type.
|
||||
struct SRVRecord { |
||||
std::string host; |
||||
int port = 0; |
||||
int priority = 0; |
||||
int weight = 0; |
||||
}; |
||||
/// Called with the collection of sockaddrs that were resolved from a given
|
||||
/// target address.
|
||||
using LookupHostnameCallback = |
||||
std::function<void(absl::Status, std::vector<ResolvedAddress>)>; |
||||
/// Called with a collection of SRV records.
|
||||
using LookupSRVCallback = |
||||
std::function<void(absl::Status, std::vector<SRVRecord>)>; |
||||
/// Called with the result of a TXT record lookup
|
||||
using LookupTXTCallback = std::function<void(absl::Status, std::string)>; |
||||
|
||||
virtual ~DNSResolver() = 0; |
||||
|
||||
// TODO(hork): define status codes for the callback
|
||||
/// Asynchronously resolve an address. \a default_port may be a non-numeric
|
||||
/// named service port, and will only be used if \a address does not already
|
||||
/// contain a port component.
|
||||
virtual LookupTaskHandle LookupHostname(LookupHostnameCallback on_resolve, |
||||
absl::string_view address, |
||||
absl::string_view default_port, |
||||
absl::Time deadline) = 0; |
||||
// TODO(hork): define status codes for the callback
|
||||
virtual LookupTaskHandle LookupSRV(LookupSRVCallback on_resolve, |
||||
absl::string_view name, |
||||
absl::Time deadline) = 0; |
||||
// TODO(hork): define status codes for the callback
|
||||
virtual LookupTaskHandle LookupTXT(LookupTXTCallback on_resolve, |
||||
absl::string_view name, |
||||
absl::Time deadline) = 0; |
||||
/// Cancel an asynchronous lookup operation.
|
||||
virtual void TryCancelLookup(LookupTaskHandle handle) = 0; |
||||
}; |
||||
|
||||
virtual ~EventEngine() = 0; |
||||
|
||||
// TODO(hork): define return status codes
|
||||
/// Retrieves an instance of a DNSResolver.
|
||||
virtual absl::StatusOr<DNSResolver> GetDNSResolver() = 0; |
||||
|
||||
/// Intended for future expansion of Task run functionality.
|
||||
struct RunOptions {}; |
||||
// TODO(hork): define status codes for the callback
|
||||
// TODO(hork): consider recommendation to make TaskHandle an output arg
|
||||
/// Run a callback as soon as possible.
|
||||
virtual TaskHandle Run(Callback fn, RunOptions opts) = 0; |
||||
// TODO(hork): define status codes for the callback
|
||||
/// Synonymous with scheduling an alarm to run at time \a when.
|
||||
virtual TaskHandle RunAt(absl::Time when, Callback fn, RunOptions opts) = 0; |
||||
/// Immediately tries to cancel a callback.
|
||||
/// Note that this is a "best effort" cancellation. No guarantee is made that
|
||||
/// the callback will be cancelled, the call could be in any stage.
|
||||
///
|
||||
/// There are three scenarios in which we may cancel a scheduled function:
|
||||
/// 1. We cancel the execution before it has run.
|
||||
/// 2. The callback has already run.
|
||||
/// 3. We can't cancel it because it is "in flight".
|
||||
///
|
||||
/// In all cases, the cancellation is still considered successful, the
|
||||
/// callback will be run exactly once from either cancellation or from its
|
||||
/// activation.
|
||||
virtual void TryCancel(TaskHandle handle) = 0; |
||||
// TODO(hork): define return status codes
|
||||
// TODO(hork): Carefully evaluate shutdown requirements, determine if we need
|
||||
// a callback parameter to be added to this method.
|
||||
/// Immediately run all callbacks with status indicating the shutdown. Every
|
||||
/// EventEngine is expected to shut down exactly once. No new callbacks/tasks
|
||||
/// should be scheduled after shutdown has begun. Any registered callbacks
|
||||
/// must be executed.
|
||||
virtual absl::Status Shutdown() = 0; |
||||
}; |
||||
|
||||
// Lazily instantiate and return a default global EventEngine instance if no
|
||||
// custom instance is provided. If a custom EventEngine is provided for every
|
||||
// channel/server via ChannelArgs, this method should never be called, and the
|
||||
// default instance will never be instantiated.
|
||||
std::shared_ptr<EventEngine> GetDefaultEventEngine(); |
||||
|
||||
} // namespace experimental
|
||||
} // namespace grpc_event_engine
|
||||
|
||||
#endif // GRPC_EVENT_ENGINE_EVENT_ENGINE_H
|
@ -0,0 +1,81 @@ |
||||
// 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_EVENT_ENGINE_SLICE_ALLOCATOR_H |
||||
#define GRPC_EVENT_ENGINE_SLICE_ALLOCATOR_H |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include <functional> |
||||
|
||||
#include "absl/status/status.h" |
||||
|
||||
// forward-declaring an internal struct, not used publicly.
|
||||
struct grpc_resource_quota; |
||||
struct grpc_resource_user; |
||||
|
||||
namespace grpc_event_engine { |
||||
namespace experimental { |
||||
|
||||
// TODO(nnoble): forward declared here, needs definition.
|
||||
class SliceBuffer; |
||||
|
||||
class SliceAllocator { |
||||
public: |
||||
// gRPC-internal constructor
|
||||
explicit SliceAllocator(grpc_resource_user* user); |
||||
// Not copyable
|
||||
SliceAllocator(SliceAllocator& other) = delete; |
||||
SliceAllocator& operator=(const SliceAllocator& other) = delete; |
||||
// Moveable
|
||||
SliceAllocator(SliceAllocator&& other) = default; |
||||
SliceAllocator& operator=(SliceAllocator&& other) = default; |
||||
~SliceAllocator(); |
||||
|
||||
using AllocateCallback = |
||||
std::function<void(absl::Status, SliceBuffer* buffer)>; |
||||
// TODO(hork): explain what happens under resource exhaustion.
|
||||
/// Requests \a size bytes from gRPC, and populates \a dest with the allocated
|
||||
/// slices. Ownership of the \a SliceBuffer is not transferred.
|
||||
absl::Status Allocate(size_t size, SliceBuffer* dest, |
||||
SliceAllocator::AllocateCallback cb); |
||||
|
||||
private: |
||||
grpc_resource_user* resource_user_; |
||||
}; |
||||
|
||||
class SliceAllocatorFactory { |
||||
public: |
||||
// gRPC-internal constructor
|
||||
explicit SliceAllocatorFactory(grpc_resource_quota* quota); |
||||
// Not copyable
|
||||
SliceAllocatorFactory(SliceAllocatorFactory& other) = delete; |
||||
SliceAllocatorFactory& operator=(const SliceAllocatorFactory& other) = delete; |
||||
// Moveable
|
||||
SliceAllocatorFactory(SliceAllocatorFactory&& other) = default; |
||||
SliceAllocatorFactory& operator=(SliceAllocatorFactory&& other) = default; |
||||
~SliceAllocatorFactory(); |
||||
|
||||
/// On Endpoint creation, call \a CreateSliceAllocator with the name of the
|
||||
/// endpoint peer (a URI string, most likely). Note: \a peer_name must outlive
|
||||
/// the Endpoint.
|
||||
SliceAllocator CreateSliceAllocator(absl::string_view peer_name); |
||||
|
||||
private: |
||||
grpc_resource_quota* resource_quota_; |
||||
}; |
||||
|
||||
} // namespace experimental
|
||||
} // namespace grpc_event_engine
|
||||
|
||||
#endif // GRPC_EVENT_ENGINE_SLICE_ALLOCATOR_H
|
@ -0,0 +1,59 @@ |
||||
// 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.
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "grpc/event_engine/slice_allocator.h" |
||||
|
||||
#include <functional> |
||||
|
||||
#include "absl/status/status.h" |
||||
|
||||
#include "src/core/lib/iomgr/resource_quota.h" |
||||
|
||||
namespace grpc_event_engine { |
||||
namespace experimental { |
||||
|
||||
SliceAllocator::SliceAllocator(grpc_resource_user* user) |
||||
: resource_user_(user) { |
||||
grpc_resource_user_ref(resource_user_); |
||||
}; |
||||
|
||||
SliceAllocator::~SliceAllocator() { grpc_resource_user_unref(resource_user_); }; |
||||
|
||||
absl::Status SliceAllocator::Allocate(size_t size, SliceBuffer* dest, |
||||
SliceAllocator::AllocateCallback cb) { |
||||
// TODO(hork): implement
|
||||
(void)size; |
||||
(void)dest; |
||||
(void)cb; |
||||
return absl::OkStatus(); |
||||
}; |
||||
|
||||
SliceAllocatorFactory::SliceAllocatorFactory(grpc_resource_quota* quota) |
||||
: resource_quota_(quota) { |
||||
grpc_resource_quota_ref_internal(resource_quota_); |
||||
}; |
||||
|
||||
SliceAllocatorFactory::~SliceAllocatorFactory() { |
||||
grpc_resource_quota_unref_internal(resource_quota_); |
||||
} |
||||
|
||||
SliceAllocator SliceAllocatorFactory::CreateSliceAllocator( |
||||
absl::string_view peer_name) { |
||||
return SliceAllocator( |
||||
grpc_resource_user_create(resource_quota_, peer_name.data())); |
||||
} |
||||
|
||||
} // namespace experimental
|
||||
} // namespace grpc_event_engine
|
@ -0,0 +1,37 @@ |
||||
// 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.
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include <string.h> |
||||
|
||||
#include "grpc/event_engine/event_engine.h" |
||||
#include "grpc/support/log.h" |
||||
|
||||
namespace grpc_event_engine { |
||||
namespace experimental { |
||||
|
||||
EventEngine::ResolvedAddress::ResolvedAddress(const sockaddr* address, |
||||
socklen_t size) { |
||||
GPR_ASSERT(size <= sizeof(address_)); |
||||
memcpy(&address_, address, size); |
||||
} |
||||
|
||||
const struct sockaddr* EventEngine::ResolvedAddress::address() const { |
||||
return reinterpret_cast<const struct sockaddr*>(address_); |
||||
} |
||||
|
||||
socklen_t EventEngine::ResolvedAddress::size() const { return size_; } |
||||
|
||||
} // namespace experimental
|
||||
} // namespace grpc_event_engine
|
Loading…
Reference in new issue