mirror of https://github.com/grpc/grpc.git
Merge pull request #23581 from markdroth/server_c++
Convert grpc_server to idiomatic C++.pull/23728/head
commit
7f7a6916f6
34 changed files with 1519 additions and 1427 deletions
File diff suppressed because it is too large
Load Diff
@ -1,121 +1,397 @@ |
|||||||
/*
|
//
|
||||||
* |
// Copyright 2015 gRPC authors.
|
||||||
* Copyright 2015 gRPC authors. |
//
|
||||||
* |
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
// you may not use this file except in compliance with the License.
|
||||||
* you may not use this file except in compliance with the License. |
// You may obtain a copy of the License at
|
||||||
* You may obtain a copy of the License at |
//
|
||||||
* |
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
//
|
||||||
* |
// Unless required by applicable law or agreed to in writing, software
|
||||||
* Unless required by applicable law or agreed to in writing, software |
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, |
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
// See the License for the specific language governing permissions and
|
||||||
* See the License for the specific language governing permissions and |
// limitations under the License.
|
||||||
* limitations under the License. |
//
|
||||||
* |
|
||||||
*/ |
|
||||||
|
|
||||||
#ifndef GRPC_CORE_LIB_SURFACE_SERVER_H |
#ifndef GRPC_CORE_LIB_SURFACE_SERVER_H |
||||||
#define GRPC_CORE_LIB_SURFACE_SERVER_H |
#define GRPC_CORE_LIB_SURFACE_SERVER_H |
||||||
|
|
||||||
#include <grpc/support/port_platform.h> |
#include <grpc/support/port_platform.h> |
||||||
|
|
||||||
|
#include <list> |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
#include "absl/types/optional.h" |
||||||
|
|
||||||
#include <grpc/grpc.h> |
#include <grpc/grpc.h> |
||||||
|
|
||||||
|
#include "src/core/lib/channel/channel_args.h" |
||||||
#include "src/core/lib/channel/channel_stack.h" |
#include "src/core/lib/channel/channel_stack.h" |
||||||
#include "src/core/lib/channel/channelz.h" |
#include "src/core/lib/channel/channelz.h" |
||||||
#include "src/core/lib/debug/trace.h" |
#include "src/core/lib/debug/trace.h" |
||||||
|
#include "src/core/lib/gprpp/atomic.h" |
||||||
|
#include "src/core/lib/surface/completion_queue.h" |
||||||
#include "src/core/lib/transport/transport.h" |
#include "src/core/lib/transport/transport.h" |
||||||
|
|
||||||
extern const grpc_channel_filter grpc_server_top_filter; |
|
||||||
|
|
||||||
/** Lightweight tracing of server channel state */ |
|
||||||
extern grpc_core::TraceFlag grpc_server_channel_trace; |
|
||||||
|
|
||||||
namespace grpc_core { |
namespace grpc_core { |
||||||
|
|
||||||
/// Interface for listeners.
|
extern TraceFlag grpc_server_channel_trace; |
||||||
/// Implementations must override the Orphan() method, which should stop
|
|
||||||
/// listening and initiate destruction of the listener.
|
class Server : public InternallyRefCounted<Server> { |
||||||
class ServerListenerInterface : public Orphanable { |
|
||||||
public: |
public: |
||||||
virtual ~ServerListenerInterface() = default; |
// Filter vtable.
|
||||||
|
static const grpc_channel_filter kServerTopFilter; |
||||||
|
|
||||||
/// Starts listening. This listener may refer to the pollset object beyond
|
// Opaque type used for registered methods.
|
||||||
/// this call, so it is a pointer rather than a reference.
|
struct RegisteredMethod; |
||||||
virtual void Start(grpc_server* server, |
|
||||||
const std::vector<grpc_pollset*>* pollsets) = 0; |
|
||||||
|
|
||||||
/// Returns the channelz node for the listen socket, or null if not
|
// An object to represent the most relevant characteristics of a
|
||||||
/// supported.
|
// newly-allocated call object when using an AllocatingRequestMatcherBatch.
|
||||||
virtual channelz::ListenSocketNode* channelz_listen_socket_node() const = 0; |
struct BatchCallAllocation { |
||||||
|
grpc_experimental_completion_queue_functor* tag; |
||||||
|
grpc_call** call; |
||||||
|
grpc_metadata_array* initial_metadata; |
||||||
|
grpc_call_details* details; |
||||||
|
}; |
||||||
|
|
||||||
/// Sets a closure to be invoked by the listener when its destruction
|
// An object to represent the most relevant characteristics of a
|
||||||
/// is complete.
|
// newly-allocated call object when using an
|
||||||
virtual void SetOnDestroyDone(grpc_closure* on_destroy_done) = 0; |
// AllocatingRequestMatcherRegistered.
|
||||||
}; |
struct RegisteredCallAllocation { |
||||||
|
grpc_experimental_completion_queue_functor* tag; |
||||||
|
grpc_call** call; |
||||||
|
grpc_metadata_array* initial_metadata; |
||||||
|
gpr_timespec* deadline; |
||||||
|
grpc_byte_buffer** optional_payload; |
||||||
|
}; |
||||||
|
|
||||||
} // namespace grpc_core
|
/// Interface for listeners.
|
||||||
|
/// Implementations must override the Orphan() method, which should stop
|
||||||
|
/// listening and initiate destruction of the listener.
|
||||||
|
class ListenerInterface : public Orphanable { |
||||||
|
public: |
||||||
|
virtual ~ListenerInterface() = default; |
||||||
|
|
||||||
/* Add a listener to the server: when the server starts, it will call Start(),
|
/// Starts listening. This listener may refer to the pollset object beyond
|
||||||
and when it shuts down, it will orphan the listener. */ |
/// this call, so it is a pointer rather than a reference.
|
||||||
void grpc_server_add_listener( |
virtual void Start(Server* server, |
||||||
grpc_server* server, |
const std::vector<grpc_pollset*>* pollsets) = 0; |
||||||
grpc_core::OrphanablePtr<grpc_core::ServerListenerInterface> listener); |
|
||||||
|
|
||||||
/* Setup a transport - creates a channel stack, binds the transport to the
|
/// Returns the channelz node for the listen socket, or null if not
|
||||||
server */ |
/// supported.
|
||||||
void grpc_server_setup_transport( |
virtual channelz::ListenSocketNode* channelz_listen_socket_node() const = 0; |
||||||
grpc_server* server, grpc_transport* transport, |
|
||||||
grpc_pollset* accepting_pollset, const grpc_channel_args* args, |
|
||||||
const grpc_core::RefCountedPtr<grpc_core::channelz::SocketNode>& |
|
||||||
socket_node, |
|
||||||
grpc_resource_user* resource_user = nullptr); |
|
||||||
|
|
||||||
grpc_core::channelz::ServerNode* grpc_server_get_channelz_node( |
/// Sets a closure to be invoked by the listener when its destruction
|
||||||
grpc_server* server); |
/// is complete.
|
||||||
|
virtual void SetOnDestroyDone(grpc_closure* on_destroy_done) = 0; |
||||||
|
}; |
||||||
|
|
||||||
const grpc_channel_args* grpc_server_get_channel_args(grpc_server* server); |
explicit Server(const grpc_channel_args* args); |
||||||
|
~Server(); |
||||||
|
|
||||||
grpc_resource_user* grpc_server_get_default_resource_user(grpc_server* server); |
void Orphan() override; |
||||||
|
|
||||||
bool grpc_server_has_open_connections(grpc_server* server); |
const grpc_channel_args* channel_args() const { return channel_args_; } |
||||||
|
grpc_resource_user* default_resource_user() const { |
||||||
|
return default_resource_user_; |
||||||
|
} |
||||||
|
channelz::ServerNode* channelz_node() const { return channelz_node_.get(); } |
||||||
|
|
||||||
// Do not call this before grpc_server_start. Returns the pollsets. The vector
|
// Do not call this before Start(). Returns the pollsets. The
|
||||||
// itself is immutable, but the pollsets inside are mutable. The result is valid
|
// vector itself is immutable, but the pollsets inside are mutable. The
|
||||||
// for the lifetime of the server.
|
// result is valid for the lifetime of the server.
|
||||||
const std::vector<grpc_pollset*>& grpc_server_get_pollsets(grpc_server* server); |
const std::vector<grpc_pollset*>& pollsets() const { return pollsets_; } |
||||||
|
|
||||||
namespace grpc_core { |
bool HasOpenConnections(); |
||||||
|
|
||||||
// An object to represent the most relevant characteristics of a newly-allocated
|
// Adds a listener to the server. When the server starts, it will call
|
||||||
// call object when using an AllocatingRequestMatcherBatch
|
// the listener's Start() method, and when it shuts down, it will orphan
|
||||||
struct ServerBatchCallAllocation { |
// the listener.
|
||||||
grpc_experimental_completion_queue_functor* tag; |
void AddListener(OrphanablePtr<ListenerInterface> listener); |
||||||
grpc_call** call; |
|
||||||
grpc_metadata_array* initial_metadata; |
|
||||||
grpc_call_details* details; |
|
||||||
}; |
|
||||||
|
|
||||||
// An object to represent the most relevant characteristics of a newly-allocated
|
// Starts listening for connections.
|
||||||
// call object when using an AllocatingRequestMatcherRegistered
|
void Start(); |
||||||
struct ServerRegisteredCallAllocation { |
|
||||||
grpc_experimental_completion_queue_functor* tag; |
// Sets up a transport. Creates a channel stack and binds the transport to
|
||||||
grpc_call** call; |
// the server. Called from the listener when a new connection is accepted.
|
||||||
grpc_metadata_array* initial_metadata; |
void SetupTransport(grpc_transport* transport, |
||||||
gpr_timespec* deadline; |
grpc_pollset* accepting_pollset, |
||||||
grpc_byte_buffer** optional_payload; |
const grpc_channel_args* args, |
||||||
}; |
const RefCountedPtr<channelz::SocketNode>& socket_node, |
||||||
|
grpc_resource_user* resource_user = nullptr); |
||||||
|
|
||||||
|
void RegisterCompletionQueue(grpc_completion_queue* cq); |
||||||
|
|
||||||
|
// Functions to specify that a specific registered method or the unregistered
|
||||||
|
// collection should use a specific allocator for request matching.
|
||||||
|
void SetRegisteredMethodAllocator( |
||||||
|
grpc_completion_queue* cq, void* method_tag, |
||||||
|
std::function<RegisteredCallAllocation()> allocator); |
||||||
|
void SetBatchMethodAllocator(grpc_completion_queue* cq, |
||||||
|
std::function<BatchCallAllocation()> allocator); |
||||||
|
|
||||||
|
RegisteredMethod* RegisterMethod( |
||||||
|
const char* method, const char* host, |
||||||
|
grpc_server_register_method_payload_handling payload_handling, |
||||||
|
uint32_t flags); |
||||||
|
|
||||||
|
grpc_call_error RequestCall(grpc_call** call, grpc_call_details* details, |
||||||
|
grpc_metadata_array* request_metadata, |
||||||
|
grpc_completion_queue* cq_bound_to_call, |
||||||
|
grpc_completion_queue* cq_for_notification, |
||||||
|
void* tag); |
||||||
|
|
||||||
|
grpc_call_error RequestRegisteredCall( |
||||||
|
RegisteredMethod* rm, grpc_call** call, gpr_timespec* deadline, |
||||||
|
grpc_metadata_array* request_metadata, |
||||||
|
grpc_byte_buffer** optional_payload, |
||||||
|
grpc_completion_queue* cq_bound_to_call, |
||||||
|
grpc_completion_queue* cq_for_notification, void* tag_new); |
||||||
|
|
||||||
|
void ShutdownAndNotify(grpc_completion_queue* cq, void* tag); |
||||||
|
|
||||||
|
void CancelAllCalls(); |
||||||
|
|
||||||
|
private: |
||||||
|
struct RequestedCall; |
||||||
|
|
||||||
|
struct ChannelRegisteredMethod { |
||||||
|
RegisteredMethod* server_registered_method = nullptr; |
||||||
|
uint32_t flags; |
||||||
|
bool has_host; |
||||||
|
ExternallyManagedSlice method; |
||||||
|
ExternallyManagedSlice host; |
||||||
|
}; |
||||||
|
|
||||||
|
class RequestMatcherInterface; |
||||||
|
class RealRequestMatcher; |
||||||
|
class AllocatingRequestMatcherBase; |
||||||
|
class AllocatingRequestMatcherBatch; |
||||||
|
class AllocatingRequestMatcherRegistered; |
||||||
|
|
||||||
|
class ChannelData { |
||||||
|
public: |
||||||
|
ChannelData() = default; |
||||||
|
~ChannelData(); |
||||||
|
|
||||||
|
void InitTransport(RefCountedPtr<Server> server, grpc_channel* channel, |
||||||
|
size_t cq_idx, grpc_transport* transport, |
||||||
|
intptr_t channelz_socket_uuid); |
||||||
|
|
||||||
|
RefCountedPtr<Server> server() const { return server_; } |
||||||
|
grpc_channel* channel() const { return channel_; } |
||||||
|
size_t cq_idx() const { return cq_idx_; } |
||||||
|
|
||||||
|
ChannelRegisteredMethod* GetRegisteredMethod(const grpc_slice& host, |
||||||
|
const grpc_slice& path, |
||||||
|
bool is_idempotent); |
||||||
|
|
||||||
|
// Filter vtable functions.
|
||||||
|
static grpc_error* InitChannelElement(grpc_channel_element* elem, |
||||||
|
grpc_channel_element_args* args); |
||||||
|
static void DestroyChannelElement(grpc_channel_element* elem); |
||||||
|
|
||||||
|
private: |
||||||
|
class ConnectivityWatcher; |
||||||
|
|
||||||
|
static void AcceptStream(void* arg, grpc_transport* /*transport*/, |
||||||
|
const void* transport_server_data); |
||||||
|
|
||||||
|
void Destroy(); |
||||||
|
|
||||||
|
static void FinishDestroy(void* arg, grpc_error* error); |
||||||
|
|
||||||
|
RefCountedPtr<Server> server_; |
||||||
|
grpc_channel* channel_; |
||||||
|
// The index into Server::cqs_ of the CQ used as a starting point for
|
||||||
|
// where to publish new incoming calls.
|
||||||
|
size_t cq_idx_; |
||||||
|
absl::optional<std::list<ChannelData*>::iterator> list_position_; |
||||||
|
// A hash-table of the methods and hosts of the registered methods.
|
||||||
|
// TODO(vjpai): Convert this to an STL map type as opposed to a direct
|
||||||
|
// bucket implementation. (Consider performance impact, hash function to
|
||||||
|
// use, etc.)
|
||||||
|
std::unique_ptr<std::vector<ChannelRegisteredMethod>> registered_methods_; |
||||||
|
uint32_t registered_method_max_probes_; |
||||||
|
grpc_closure finish_destroy_channel_closure_; |
||||||
|
intptr_t channelz_socket_uuid_; |
||||||
|
}; |
||||||
|
|
||||||
|
class CallData { |
||||||
|
public: |
||||||
|
enum class CallState { |
||||||
|
NOT_STARTED, // Waiting for metadata.
|
||||||
|
PENDING, // Initial metadata read, not flow controlled in yet.
|
||||||
|
ACTIVATED, // Flow controlled in, on completion queue.
|
||||||
|
ZOMBIED, // Cancelled before being queued.
|
||||||
|
}; |
||||||
|
|
||||||
|
CallData(grpc_call_element* elem, const grpc_call_element_args& args, |
||||||
|
RefCountedPtr<Server> server); |
||||||
|
~CallData(); |
||||||
|
|
||||||
|
// Starts the recv_initial_metadata batch on the call.
|
||||||
|
// Invoked from ChannelData::AcceptStream().
|
||||||
|
void Start(grpc_call_element* elem); |
||||||
|
|
||||||
|
void SetState(CallState state); |
||||||
|
|
||||||
|
// Attempts to move from PENDING to ACTIVATED state. Returns true
|
||||||
|
// on success.
|
||||||
|
bool MaybeActivate(); |
||||||
|
|
||||||
|
// Publishes an incoming call to the application after it has been
|
||||||
|
// matched.
|
||||||
|
void Publish(size_t cq_idx, RequestedCall* rc); |
||||||
|
|
||||||
|
void KillZombie(); |
||||||
|
|
||||||
// Functions to specify that a specific registered method or the unregistered
|
void FailCallCreation(); |
||||||
// collection should use a specific allocator for request matching.
|
|
||||||
void SetServerRegisteredMethodAllocator( |
// Filter vtable functions.
|
||||||
grpc_server* server, grpc_completion_queue* cq, void* method_tag, |
static grpc_error* InitCallElement(grpc_call_element* elem, |
||||||
std::function<ServerRegisteredCallAllocation()> allocator); |
const grpc_call_element_args* args); |
||||||
void SetServerBatchMethodAllocator( |
static void DestroyCallElement(grpc_call_element* elem, |
||||||
grpc_server* server, grpc_completion_queue* cq, |
const grpc_call_final_info* /*final_info*/, |
||||||
std::function<ServerBatchCallAllocation()> allocator); |
grpc_closure* /*ignored*/); |
||||||
|
static void StartTransportStreamOpBatch( |
||||||
|
grpc_call_element* elem, grpc_transport_stream_op_batch* batch); |
||||||
|
|
||||||
|
private: |
||||||
|
// Helper functions for handling calls at the top of the call stack.
|
||||||
|
static void RecvInitialMetadataBatchComplete(void* arg, grpc_error* error); |
||||||
|
void StartNewRpc(grpc_call_element* elem); |
||||||
|
static void PublishNewRpc(void* arg, grpc_error* error); |
||||||
|
|
||||||
|
// Functions used inside the call stack.
|
||||||
|
void StartTransportStreamOpBatchImpl(grpc_call_element* elem, |
||||||
|
grpc_transport_stream_op_batch* batch); |
||||||
|
static void RecvInitialMetadataReady(void* arg, grpc_error* error); |
||||||
|
static void RecvTrailingMetadataReady(void* arg, grpc_error* error); |
||||||
|
|
||||||
|
RefCountedPtr<Server> server_; |
||||||
|
|
||||||
|
grpc_call* call_; |
||||||
|
|
||||||
|
Atomic<CallState> state_{CallState::NOT_STARTED}; |
||||||
|
|
||||||
|
absl::optional<grpc_slice> path_; |
||||||
|
absl::optional<grpc_slice> host_; |
||||||
|
grpc_millis deadline_ = GRPC_MILLIS_INF_FUTURE; |
||||||
|
|
||||||
|
grpc_completion_queue* cq_new_ = nullptr; |
||||||
|
|
||||||
|
RequestMatcherInterface* matcher_ = nullptr; |
||||||
|
grpc_byte_buffer* payload_ = nullptr; |
||||||
|
|
||||||
|
grpc_closure kill_zombie_closure_; |
||||||
|
|
||||||
|
grpc_metadata_array initial_metadata_ = |
||||||
|
grpc_metadata_array(); // Zero-initialize the C struct.
|
||||||
|
grpc_closure recv_initial_metadata_batch_complete_; |
||||||
|
|
||||||
|
grpc_metadata_batch* recv_initial_metadata_ = nullptr; |
||||||
|
uint32_t recv_initial_metadata_flags_ = 0; |
||||||
|
grpc_closure recv_initial_metadata_ready_; |
||||||
|
grpc_closure* original_recv_initial_metadata_ready_; |
||||||
|
grpc_error* recv_initial_metadata_error_ = GRPC_ERROR_NONE; |
||||||
|
|
||||||
|
bool seen_recv_trailing_metadata_ready_ = false; |
||||||
|
grpc_closure recv_trailing_metadata_ready_; |
||||||
|
grpc_closure* original_recv_trailing_metadata_ready_; |
||||||
|
grpc_error* recv_trailing_metadata_error_ = GRPC_ERROR_NONE; |
||||||
|
|
||||||
|
grpc_closure publish_; |
||||||
|
|
||||||
|
CallCombiner* call_combiner_; |
||||||
|
}; |
||||||
|
|
||||||
|
struct Listener { |
||||||
|
explicit Listener(OrphanablePtr<ListenerInterface> l) |
||||||
|
: listener(std::move(l)) {} |
||||||
|
OrphanablePtr<ListenerInterface> listener; |
||||||
|
grpc_closure destroy_done; |
||||||
|
}; |
||||||
|
|
||||||
|
struct ShutdownTag { |
||||||
|
ShutdownTag(void* tag_arg, grpc_completion_queue* cq_arg) |
||||||
|
: tag(tag_arg), cq(cq_arg) {} |
||||||
|
void* const tag; |
||||||
|
grpc_completion_queue* const cq; |
||||||
|
grpc_cq_completion completion; |
||||||
|
}; |
||||||
|
|
||||||
|
static void ListenerDestroyDone(void* arg, grpc_error* error); |
||||||
|
|
||||||
|
static void DoneShutdownEvent(void* server, |
||||||
|
grpc_cq_completion* /*completion*/) { |
||||||
|
static_cast<Server*>(server)->Unref(); |
||||||
|
} |
||||||
|
|
||||||
|
static void DoneRequestEvent(void* req, grpc_cq_completion* completion); |
||||||
|
|
||||||
|
void FailCall(size_t cq_idx, RequestedCall* rc, grpc_error* error); |
||||||
|
grpc_call_error QueueRequestedCall(size_t cq_idx, RequestedCall* rc); |
||||||
|
|
||||||
|
void MaybeFinishShutdown(); |
||||||
|
|
||||||
|
void KillPendingWorkLocked(grpc_error* error); |
||||||
|
|
||||||
|
static grpc_call_error ValidateServerRequest( |
||||||
|
grpc_completion_queue* cq_for_notification, void* tag, |
||||||
|
grpc_byte_buffer** optional_payload, RegisteredMethod* rm); |
||||||
|
grpc_call_error ValidateServerRequestAndCq( |
||||||
|
size_t* cq_idx, grpc_completion_queue* cq_for_notification, void* tag, |
||||||
|
grpc_byte_buffer** optional_payload, RegisteredMethod* rm); |
||||||
|
|
||||||
|
std::vector<grpc_channel*> GetChannelsLocked() const; |
||||||
|
|
||||||
|
grpc_channel_args* const channel_args_; |
||||||
|
grpc_resource_user* default_resource_user_ = nullptr; |
||||||
|
RefCountedPtr<channelz::ServerNode> channelz_node_; |
||||||
|
|
||||||
|
std::vector<grpc_completion_queue*> cqs_; |
||||||
|
std::vector<grpc_pollset*> pollsets_; |
||||||
|
bool started_ = false; |
||||||
|
|
||||||
|
// The two following mutexes control access to server-state.
|
||||||
|
// mu_global_ controls access to non-call-related state (e.g., channel state).
|
||||||
|
// mu_call_ controls access to call-related state (e.g., the call lists).
|
||||||
|
//
|
||||||
|
// If they are ever required to be nested, you must lock mu_global_
|
||||||
|
// before mu_call_. This is currently used in shutdown processing
|
||||||
|
// (ShutdownAndNotify() and MaybeFinishShutdown()).
|
||||||
|
Mutex mu_global_; // mutex for server and channel state
|
||||||
|
Mutex mu_call_; // mutex for call-specific state
|
||||||
|
|
||||||
|
// startup synchronization: flag is protected by mu_global_, signals whether
|
||||||
|
// we are doing the listener start routine or not.
|
||||||
|
bool starting_ = false; |
||||||
|
CondVar starting_cv_; |
||||||
|
|
||||||
|
std::vector<std::unique_ptr<RegisteredMethod>> registered_methods_; |
||||||
|
|
||||||
|
// Request matcher for unregistered methods.
|
||||||
|
std::unique_ptr<RequestMatcherInterface> unregistered_request_matcher_; |
||||||
|
|
||||||
|
std::atomic_bool shutdown_flag_{false}; |
||||||
|
bool shutdown_published_ = false; |
||||||
|
std::vector<ShutdownTag> shutdown_tags_; |
||||||
|
|
||||||
|
std::list<ChannelData*> channels_; |
||||||
|
|
||||||
|
std::list<Listener> listeners_; |
||||||
|
size_t listeners_destroyed_ = 0; |
||||||
|
|
||||||
|
// The last time we printed a shutdown progress message.
|
||||||
|
gpr_timespec last_shutdown_message_time_; |
||||||
|
}; |
||||||
|
|
||||||
} // namespace grpc_core
|
} // namespace grpc_core
|
||||||
|
|
||||||
|
struct grpc_server { |
||||||
|
grpc_core::OrphanablePtr<grpc_core::Server> core_server; |
||||||
|
}; |
||||||
|
|
||||||
#endif /* GRPC_CORE_LIB_SURFACE_SERVER_H */ |
#endif /* GRPC_CORE_LIB_SURFACE_SERVER_H */ |
||||||
|
Loading…
Reference in new issue