aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/core/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/lib')
-rw-r--r--src/core/lib/channel/channel_stack.cc51
-rw-r--r--src/core/lib/channel/channel_stack.h4
-rw-r--r--src/core/lib/channel/channel_stack_builder.cc3
-rw-r--r--src/core/lib/channel/channel_stack_builder.h2
-rw-r--r--src/core/lib/channel/channel_trace.cc128
-rw-r--r--src/core/lib/channel/channel_trace.h46
-rw-r--r--src/core/lib/channel/channel_trace_registry.cc80
-rw-r--r--src/core/lib/channel/channel_trace_registry.h43
-rw-r--r--src/core/lib/channel/channelz.cc286
-rw-r--r--src/core/lib/channel/channelz.h243
-rw-r--r--src/core/lib/channel/channelz_registry.cc217
-rw-r--r--src/core/lib/channel/channelz_registry.h93
-rw-r--r--src/core/lib/channel/connected_channel.cc9
-rw-r--r--src/core/lib/channel/handshaker.cc26
-rw-r--r--src/core/lib/debug/stats.h7
-rw-r--r--src/core/lib/debug/stats_data.cc5
-rw-r--r--src/core/lib/debug/stats_data.h120
-rw-r--r--src/core/lib/debug/stats_data.yaml6
-rw-r--r--src/core/lib/debug/stats_data_bq_schema.sql2
-rw-r--r--src/core/lib/debug/trace.h20
-rw-r--r--src/core/lib/gpr/alloc.h28
-rw-r--r--src/core/lib/gpr/arena.cc83
-rw-r--r--src/core/lib/gpr/fork.cc78
-rw-r--r--src/core/lib/gpr/string.cc28
-rw-r--r--src/core/lib/gpr/string.h10
-rw-r--r--src/core/lib/gprpp/abstract.h7
-rw-r--r--src/core/lib/gprpp/fork.cc268
-rw-r--r--src/core/lib/gprpp/fork.h88
-rw-r--r--src/core/lib/gprpp/inlined_vector.h60
-rw-r--r--src/core/lib/gprpp/memory.h12
-rw-r--r--src/core/lib/gprpp/mutex_lock.h (renamed from src/core/lib/iomgr/ev_epollsig_linux.h)31
-rw-r--r--src/core/lib/gprpp/orphanable.h14
-rw-r--r--src/core/lib/gprpp/ref_counted.h14
-rw-r--r--src/core/lib/gprpp/ref_counted_ptr.h73
-rw-r--r--src/core/lib/gprpp/thd.h3
-rw-r--r--src/core/lib/gprpp/thd_posix.cc59
-rw-r--r--src/core/lib/gprpp/thd_windows.cc7
-rw-r--r--src/core/lib/http/httpcli.cc2
-rw-r--r--src/core/lib/iomgr/buffer_list.cc134
-rw-r--r--src/core/lib/iomgr/buffer_list.h96
-rw-r--r--src/core/lib/iomgr/call_combiner.h85
-rw-r--r--src/core/lib/iomgr/cfstream_handle.cc185
-rw-r--r--src/core/lib/iomgr/cfstream_handle.h80
-rw-r--r--src/core/lib/iomgr/closure.h5
-rw-r--r--src/core/lib/iomgr/combiner.cc21
-rw-r--r--src/core/lib/iomgr/combiner.h2
-rw-r--r--src/core/lib/iomgr/endpoint.cc4
-rw-r--r--src/core/lib/iomgr/endpoint.h8
-rw-r--r--src/core/lib/iomgr/endpoint_cfstream.cc372
-rw-r--r--src/core/lib/iomgr/endpoint_cfstream.h49
-rw-r--r--src/core/lib/iomgr/endpoint_pair_posix.cc4
-rw-r--r--src/core/lib/iomgr/error.cc59
-rw-r--r--src/core/lib/iomgr/error.h13
-rw-r--r--src/core/lib/iomgr/error_cfstream.cc52
-rw-r--r--src/core/lib/iomgr/error_cfstream.h31
-rw-r--r--src/core/lib/iomgr/ev_epoll1_linux.cc141
-rw-r--r--src/core/lib/iomgr/ev_epollex_linux.cc284
-rw-r--r--src/core/lib/iomgr/ev_epollsig_linux.cc1735
-rw-r--r--src/core/lib/iomgr/ev_poll_posix.cc193
-rw-r--r--src/core/lib/iomgr/ev_posix.cc115
-rw-r--r--src/core/lib/iomgr/ev_posix.h54
-rw-r--r--src/core/lib/iomgr/exec_ctx.cc24
-rw-r--r--src/core/lib/iomgr/exec_ctx.h68
-rw-r--r--src/core/lib/iomgr/executor.cc416
-rw-r--r--src/core/lib/iomgr/executor.h98
-rw-r--r--src/core/lib/iomgr/fork_posix.cc60
-rw-r--r--src/core/lib/iomgr/internal_errqueue.cc (renamed from src/core/lib/gpr/fork.h)27
-rw-r--r--src/core/lib/iomgr/internal_errqueue.h83
-rw-r--r--src/core/lib/iomgr/iocp_windows.cc13
-rw-r--r--src/core/lib/iomgr/iomgr.cc2
-rw-r--r--src/core/lib/iomgr/iomgr.h5
-rw-r--r--src/core/lib/iomgr/iomgr_posix.cc4
-rw-r--r--src/core/lib/iomgr/iomgr_posix_cfstream.cc75
-rw-r--r--src/core/lib/iomgr/is_epollexclusive_available.cc1
-rw-r--r--src/core/lib/iomgr/lockfree_event.cc6
-rw-r--r--src/core/lib/iomgr/polling_entity.cc13
-rw-r--r--src/core/lib/iomgr/pollset_custom.cc4
-rw-r--r--src/core/lib/iomgr/port.h60
-rw-r--r--src/core/lib/iomgr/resolve_address.h2
-rw-r--r--src/core/lib/iomgr/resolve_address_posix.cc7
-rw-r--r--src/core/lib/iomgr/resolve_address_windows.cc5
-rw-r--r--src/core/lib/iomgr/resource_quota.cc80
-rw-r--r--src/core/lib/iomgr/resource_quota.h16
-rw-r--r--src/core/lib/iomgr/sockaddr_posix.h2
-rw-r--r--src/core/lib/iomgr/socket_factory_posix.cc2
-rw-r--r--src/core/lib/iomgr/socket_mutator.cc2
-rw-r--r--src/core/lib/iomgr/socket_mutator.h2
-rw-r--r--src/core/lib/iomgr/socket_utils.h9
-rw-r--r--src/core/lib/iomgr/socket_utils_common_posix.cc96
-rw-r--r--src/core/lib/iomgr/socket_utils_posix.h7
-rw-r--r--src/core/lib/iomgr/socket_utils_uv.cc4
-rw-r--r--src/core/lib/iomgr/socket_utils_windows.cc4
-rw-r--r--src/core/lib/iomgr/socket_windows.cc33
-rw-r--r--src/core/lib/iomgr/socket_windows.h6
-rw-r--r--src/core/lib/iomgr/tcp_client_cfstream.cc216
-rw-r--r--src/core/lib/iomgr/tcp_client_custom.cc6
-rw-r--r--src/core/lib/iomgr/tcp_client_posix.cc16
-rw-r--r--src/core/lib/iomgr/tcp_custom.cc4
-rw-r--r--src/core/lib/iomgr/tcp_posix.cc347
-rw-r--r--src/core/lib/iomgr/tcp_posix.h3
-rw-r--r--src/core/lib/iomgr/tcp_server_posix.cc20
-rw-r--r--src/core/lib/iomgr/tcp_server_utils_posix_common.cc9
-rw-r--r--src/core/lib/iomgr/tcp_windows.cc6
-rw-r--r--src/core/lib/iomgr/tcp_windows.h2
-rw-r--r--src/core/lib/iomgr/timer.h7
-rw-r--r--src/core/lib/iomgr/timer_generic.cc159
-rw-r--r--src/core/lib/iomgr/timer_manager.cc5
-rw-r--r--src/core/lib/iomgr/udp_server.cc5
-rw-r--r--src/core/lib/json/json.cc13
-rw-r--r--src/core/lib/json/json.h5
-rw-r--r--src/core/lib/security/context/security_context.cc16
-rw-r--r--src/core/lib/security/context/security_context.h8
-rw-r--r--src/core/lib/security/credentials/alts/check_gcp_environment_linux.cc5
-rw-r--r--src/core/lib/security/credentials/alts/grpc_alts_credentials_client_options.cc3
-rw-r--r--src/core/lib/security/credentials/alts/grpc_alts_credentials_server_options.cc3
-rw-r--r--src/core/lib/security/credentials/google_default/google_default_credentials.cc116
-rw-r--r--src/core/lib/security/credentials/google_default/google_default_credentials.h10
-rw-r--r--src/core/lib/security/credentials/jwt/json_token.h2
-rw-r--r--src/core/lib/security/credentials/jwt/jwt_verifier.cc2
-rw-r--r--src/core/lib/security/credentials/local/local_credentials.cc77
-rw-r--r--src/core/lib/security/credentials/local/local_credentials.h40
-rw-r--r--src/core/lib/security/credentials/oauth2/oauth2_credentials.cc18
-rw-r--r--src/core/lib/security/credentials/oauth2/oauth2_credentials.h2
-rw-r--r--src/core/lib/security/credentials/ssl/ssl_credentials.cc20
-rw-r--r--src/core/lib/security/security_connector/alts_security_connector.cc3
-rw-r--r--src/core/lib/security/security_connector/load_system_roots.h29
-rw-r--r--src/core/lib/security/security_connector/load_system_roots_fallback.cc32
-rw-r--r--src/core/lib/security/security_connector/load_system_roots_linux.cc165
-rw-r--r--src/core/lib/security/security_connector/load_system_roots_linux.h44
-rw-r--r--src/core/lib/security/security_connector/local_security_connector.cc245
-rw-r--r--src/core/lib/security/security_connector/local_security_connector.h58
-rw-r--r--src/core/lib/security/security_connector/security_connector.cc69
-rw-r--r--src/core/lib/security/security_connector/security_connector.h1
-rw-r--r--src/core/lib/security/transport/client_auth_filter.cc10
-rw-r--r--src/core/lib/security/transport/secure_endpoint.cc4
-rw-r--r--src/core/lib/security/transport/security_handshaker.cc2
-rw-r--r--src/core/lib/security/transport/server_auth_filter.cc68
-rw-r--r--src/core/lib/security/util/json_util.cc4
-rw-r--r--src/core/lib/slice/slice_buffer.cc18
-rw-r--r--src/core/lib/surface/call.cc533
-rw-r--r--src/core/lib/surface/call.h8
-rw-r--r--src/core/lib/surface/channel.cc73
-rw-r--r--src/core/lib/surface/channel.h4
-rw-r--r--src/core/lib/surface/completion_queue.cc174
-rw-r--r--src/core/lib/surface/completion_queue.h5
-rw-r--r--src/core/lib/surface/completion_queue_factory.cc17
-rw-r--r--src/core/lib/surface/init.cc15
-rw-r--r--src/core/lib/surface/init.h1
-rw-r--r--src/core/lib/surface/server.cc80
-rw-r--r--src/core/lib/surface/server.h4
-rw-r--r--src/core/lib/surface/version.cc2
-rw-r--r--src/core/lib/transport/byte_stream.cc2
-rw-r--r--src/core/lib/transport/metadata.cc18
-rw-r--r--src/core/lib/transport/metadata.h3
-rw-r--r--src/core/lib/transport/metadata_batch.cc4
-rw-r--r--src/core/lib/transport/metadata_batch.h2
-rw-r--r--src/core/lib/transport/service_config.cc4
-rw-r--r--src/core/lib/transport/service_config.h6
-rw-r--r--src/core/lib/transport/static_metadata.cc27
-rw-r--r--src/core/lib/transport/static_metadata.h2
-rw-r--r--src/core/lib/transport/transport.cc32
-rw-r--r--src/core/lib/transport/transport.h36
-rw-r--r--src/core/lib/transport/transport_op_string.cc9
163 files changed, 6779 insertions, 3393 deletions
diff --git a/src/core/lib/channel/channel_stack.cc b/src/core/lib/channel/channel_stack.cc
index a9459b150d..056fcd93de 100644
--- a/src/core/lib/channel/channel_stack.cc
+++ b/src/core/lib/channel/channel_stack.cc
@@ -21,6 +21,7 @@
#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
#include "src/core/lib/channel/channel_stack.h"
+#include "src/core/lib/gpr/alloc.h"
#include <stdlib.h>
#include <string.h>
@@ -43,16 +44,12 @@ grpc_core::TraceFlag grpc_trace_channel(false, "channel");
per-filter memory, aligned to GPR_MAX_ALIGNMENT
} */
-/* Given a size, round up to the next multiple of sizeof(void*) */
-#define ROUND_UP_TO_ALIGNMENT_SIZE(x) \
- (((x) + GPR_MAX_ALIGNMENT - 1u) & ~(GPR_MAX_ALIGNMENT - 1u))
-
size_t grpc_channel_stack_size(const grpc_channel_filter** filters,
size_t filter_count) {
/* always need the header, and size for the channel elements */
- size_t size =
- ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(grpc_channel_stack)) +
- ROUND_UP_TO_ALIGNMENT_SIZE(filter_count * sizeof(grpc_channel_element));
+ size_t size = GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(grpc_channel_stack)) +
+ GPR_ROUND_UP_TO_ALIGNMENT_SIZE(filter_count *
+ sizeof(grpc_channel_element));
size_t i;
GPR_ASSERT((GPR_MAX_ALIGNMENT & (GPR_MAX_ALIGNMENT - 1)) == 0 &&
@@ -60,19 +57,19 @@ size_t grpc_channel_stack_size(const grpc_channel_filter** filters,
/* add the size for each filter */
for (i = 0; i < filter_count; i++) {
- size += ROUND_UP_TO_ALIGNMENT_SIZE(filters[i]->sizeof_channel_data);
+ size += GPR_ROUND_UP_TO_ALIGNMENT_SIZE(filters[i]->sizeof_channel_data);
}
return size;
}
-#define CHANNEL_ELEMS_FROM_STACK(stk) \
- ((grpc_channel_element*)((char*)(stk) + ROUND_UP_TO_ALIGNMENT_SIZE( \
+#define CHANNEL_ELEMS_FROM_STACK(stk) \
+ ((grpc_channel_element*)((char*)(stk) + GPR_ROUND_UP_TO_ALIGNMENT_SIZE( \
sizeof(grpc_channel_stack))))
-#define CALL_ELEMS_FROM_STACK(stk) \
- ((grpc_call_element*)((char*)(stk) + \
- ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(grpc_call_stack))))
+#define CALL_ELEMS_FROM_STACK(stk) \
+ ((grpc_call_element*)((char*)(stk) + GPR_ROUND_UP_TO_ALIGNMENT_SIZE( \
+ sizeof(grpc_call_stack))))
grpc_channel_element* grpc_channel_stack_element(
grpc_channel_stack* channel_stack, size_t index) {
@@ -95,8 +92,8 @@ grpc_error* grpc_channel_stack_init(
const grpc_channel_args* channel_args, grpc_transport* optional_transport,
const char* name, grpc_channel_stack* stack) {
size_t call_size =
- ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(grpc_call_stack)) +
- ROUND_UP_TO_ALIGNMENT_SIZE(filter_count * sizeof(grpc_call_element));
+ GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(grpc_call_stack)) +
+ GPR_ROUND_UP_TO_ALIGNMENT_SIZE(filter_count * sizeof(grpc_call_element));
grpc_channel_element* elems;
grpc_channel_element_args args;
char* user_data;
@@ -106,9 +103,9 @@ grpc_error* grpc_channel_stack_init(
GRPC_STREAM_REF_INIT(&stack->refcount, initial_refs, destroy, destroy_arg,
name);
elems = CHANNEL_ELEMS_FROM_STACK(stack);
- user_data =
- (reinterpret_cast<char*>(elems)) +
- ROUND_UP_TO_ALIGNMENT_SIZE(filter_count * sizeof(grpc_channel_element));
+ user_data = (reinterpret_cast<char*>(elems)) +
+ GPR_ROUND_UP_TO_ALIGNMENT_SIZE(filter_count *
+ sizeof(grpc_channel_element));
/* init per-filter data */
grpc_error* first_error = GRPC_ERROR_NONE;
@@ -128,8 +125,9 @@ grpc_error* grpc_channel_stack_init(
GRPC_ERROR_UNREF(error);
}
}
- user_data += ROUND_UP_TO_ALIGNMENT_SIZE(filters[i]->sizeof_channel_data);
- call_size += ROUND_UP_TO_ALIGNMENT_SIZE(filters[i]->sizeof_call_data);
+ user_data +=
+ GPR_ROUND_UP_TO_ALIGNMENT_SIZE(filters[i]->sizeof_channel_data);
+ call_size += GPR_ROUND_UP_TO_ALIGNMENT_SIZE(filters[i]->sizeof_call_data);
}
GPR_ASSERT(user_data > (char*)stack);
@@ -166,7 +164,7 @@ grpc_error* grpc_call_stack_init(grpc_channel_stack* channel_stack,
destroy_arg, "CALL_STACK");
call_elems = CALL_ELEMS_FROM_STACK(elem_args->call_stack);
user_data = (reinterpret_cast<char*>(call_elems)) +
- ROUND_UP_TO_ALIGNMENT_SIZE(count * sizeof(grpc_call_element));
+ GPR_ROUND_UP_TO_ALIGNMENT_SIZE(count * sizeof(grpc_call_element));
/* init per-filter data */
grpc_error* first_error = GRPC_ERROR_NONE;
@@ -184,7 +182,7 @@ grpc_error* grpc_call_stack_init(grpc_channel_stack* channel_stack,
}
}
user_data +=
- ROUND_UP_TO_ALIGNMENT_SIZE(call_elems[i].filter->sizeof_call_data);
+ GPR_ROUND_UP_TO_ALIGNMENT_SIZE(call_elems[i].filter->sizeof_call_data);
}
return first_error;
}
@@ -193,18 +191,13 @@ void grpc_call_stack_set_pollset_or_pollset_set(grpc_call_stack* call_stack,
grpc_polling_entity* pollent) {
size_t count = call_stack->count;
grpc_call_element* call_elems;
- char* user_data;
size_t i;
call_elems = CALL_ELEMS_FROM_STACK(call_stack);
- user_data = (reinterpret_cast<char*>(call_elems)) +
- ROUND_UP_TO_ALIGNMENT_SIZE(count * sizeof(grpc_call_element));
/* init per-filter data */
for (i = 0; i < count; i++) {
call_elems[i].filter->set_pollset_or_pollset_set(&call_elems[i], pollent);
- user_data +=
- ROUND_UP_TO_ALIGNMENT_SIZE(call_elems[i].filter->sizeof_call_data);
}
}
@@ -248,11 +241,11 @@ grpc_channel_stack* grpc_channel_stack_from_top_element(
grpc_channel_element* elem) {
return reinterpret_cast<grpc_channel_stack*>(
reinterpret_cast<char*>(elem) -
- ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(grpc_channel_stack)));
+ GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(grpc_channel_stack)));
}
grpc_call_stack* grpc_call_stack_from_top_element(grpc_call_element* elem) {
return reinterpret_cast<grpc_call_stack*>(
reinterpret_cast<char*>(elem) -
- ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(grpc_call_stack)));
+ GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(grpc_call_stack)));
}
diff --git a/src/core/lib/channel/channel_stack.h b/src/core/lib/channel/channel_stack.h
index 4bf8218664..35c3fb01ea 100644
--- a/src/core/lib/channel/channel_stack.h
+++ b/src/core/lib/channel/channel_stack.h
@@ -82,7 +82,7 @@ typedef struct {
typedef struct {
grpc_call_stats stats;
grpc_status_code final_status;
- const char** error_string;
+ const char* error_string;
} grpc_call_final_info;
/* Channel filters specify:
@@ -138,7 +138,7 @@ typedef struct {
is_first, is_last designate this elements position in the stack, and are
useful for asserting correct configuration by upper layer code.
The filter does not need to do any chaining.
- Implementations may assume that elem->call_data is all zeros. */
+ Implementations may assume that elem->channel_data is all zeros. */
grpc_error* (*init_channel_elem)(grpc_channel_element* elem,
grpc_channel_element_args* args);
/* Destroy per channel data.
diff --git a/src/core/lib/channel/channel_stack_builder.cc b/src/core/lib/channel/channel_stack_builder.cc
index 8a72449034..df5a783631 100644
--- a/src/core/lib/channel/channel_stack_builder.cc
+++ b/src/core/lib/channel/channel_stack_builder.cc
@@ -25,9 +25,6 @@
#include <grpc/support/alloc.h>
#include <grpc/support/string_util.h>
-grpc_core::TraceFlag grpc_trace_channel_stack_builder(false,
- "channel_stack_builder");
-
typedef struct filter_node {
struct filter_node* next;
struct filter_node* prev;
diff --git a/src/core/lib/channel/channel_stack_builder.h b/src/core/lib/channel/channel_stack_builder.h
index c9a170bc88..9196de9378 100644
--- a/src/core/lib/channel/channel_stack_builder.h
+++ b/src/core/lib/channel/channel_stack_builder.h
@@ -155,6 +155,4 @@ grpc_error* grpc_channel_stack_builder_finish(
/// Destroy the builder without creating a channel stack
void grpc_channel_stack_builder_destroy(grpc_channel_stack_builder* builder);
-extern grpc_core::TraceFlag grpc_trace_channel_stack_builder;
-
#endif /* GRPC_CORE_LIB_CHANNEL_CHANNEL_STACK_BUILDER_H */
diff --git a/src/core/lib/channel/channel_trace.cc b/src/core/lib/channel/channel_trace.cc
index 654300cd32..cfb2faba51 100644
--- a/src/core/lib/channel/channel_trace.cc
+++ b/src/core/lib/channel/channel_trace.cc
@@ -28,7 +28,6 @@
#include <stdlib.h>
#include <string.h>
-#include "src/core/lib/channel/channel_trace_registry.h"
#include "src/core/lib/channel/status_util.h"
#include "src/core/lib/gpr/string.h"
#include "src/core/lib/gpr/useful.h"
@@ -40,17 +39,16 @@
#include "src/core/lib/transport/error_utils.h"
namespace grpc_core {
+namespace channelz {
-ChannelTrace::TraceEvent::TraceEvent(
- Severity severity, grpc_slice data,
- RefCountedPtr<ChannelTrace> referenced_tracer, ReferencedType type)
+ChannelTrace::TraceEvent::TraceEvent(Severity severity, grpc_slice data,
+ RefCountedPtr<BaseNode> referenced_entity)
: severity_(severity),
data_(data),
timestamp_(grpc_millis_to_timespec(grpc_core::ExecCtx::Get()->Now(),
GPR_CLOCK_REALTIME)),
next_(nullptr),
- referenced_tracer_(std::move(referenced_tracer)),
- referenced_type_(type) {}
+ referenced_entity_(std::move(referenced_entity)) {}
ChannelTrace::TraceEvent::TraceEvent(Severity severity, grpc_slice data)
: severity_(severity),
@@ -62,15 +60,13 @@ ChannelTrace::TraceEvent::TraceEvent(Severity severity, grpc_slice data)
ChannelTrace::TraceEvent::~TraceEvent() { grpc_slice_unref_internal(data_); }
ChannelTrace::ChannelTrace(size_t max_events)
- : channel_uuid_(-1),
- num_events_logged_(0),
+ : num_events_logged_(0),
list_size_(0),
max_list_size_(max_events),
head_trace_(nullptr),
tail_trace_(nullptr) {
if (max_list_size_ == 0) return; // tracing is disabled if max_events == 0
gpr_mu_init(&tracer_mu_);
- channel_uuid_ = grpc_channel_trace_registry_register_channel_trace(this);
time_created_ = grpc_millis_to_timespec(grpc_core::ExecCtx::Get()->Now(),
GPR_CLOCK_REALTIME);
}
@@ -83,12 +79,9 @@ ChannelTrace::~ChannelTrace() {
it = it->next();
Delete<TraceEvent>(to_free);
}
- grpc_channel_trace_registry_unregister_channel_trace(channel_uuid_);
gpr_mu_destroy(&tracer_mu_);
}
-intptr_t ChannelTrace::GetUuid() const { return channel_uuid_; }
-
void ChannelTrace::AddTraceEventHelper(TraceEvent* new_trace_event) {
++num_events_logged_;
// first event case
@@ -115,58 +108,17 @@ void ChannelTrace::AddTraceEvent(Severity severity, grpc_slice data) {
AddTraceEventHelper(New<TraceEvent>(severity, data));
}
-void ChannelTrace::AddTraceEventReferencingChannel(
+void ChannelTrace::AddTraceEventWithReference(
Severity severity, grpc_slice data,
- RefCountedPtr<ChannelTrace> referenced_tracer) {
+ RefCountedPtr<BaseNode> referenced_entity) {
if (max_list_size_ == 0) return; // tracing is disabled if max_events == 0
// create and fill up the new event
AddTraceEventHelper(
- New<TraceEvent>(severity, data, std::move(referenced_tracer), Channel));
-}
-
-void ChannelTrace::AddTraceEventReferencingSubchannel(
- Severity severity, grpc_slice data,
- RefCountedPtr<ChannelTrace> referenced_tracer) {
- if (max_list_size_ == 0) return; // tracing is disabled if max_events == 0
- // create and fill up the new event
- AddTraceEventHelper(New<TraceEvent>(
- severity, data, std::move(referenced_tracer), Subchannel));
+ New<TraceEvent>(severity, data, std::move(referenced_entity)));
}
namespace {
-// returns an allocated string that represents tm according to RFC-3339, and,
-// more specifically, follows:
-// https://developers.google.com/protocol-buffers/docs/proto3#json
-//
-// "Uses RFC 3339, where generated output will always be Z-normalized and uses
-// 0, 3, 6 or 9 fractional digits."
-char* fmt_time(gpr_timespec tm) {
- char time_buffer[35];
- char ns_buffer[11]; // '.' + 9 digits of precision
- struct tm* tm_info = localtime((const time_t*)&tm.tv_sec);
- strftime(time_buffer, sizeof(time_buffer), "%Y-%m-%dT%H:%M:%S", tm_info);
- snprintf(ns_buffer, 11, ".%09d", tm.tv_nsec);
- // This loop trims off trailing zeros by inserting a null character that the
- // right point. We iterate in chunks of three because we want 0, 3, 6, or 9
- // fractional digits.
- for (int i = 7; i >= 1; i -= 3) {
- if (ns_buffer[i] == '0' && ns_buffer[i + 1] == '0' &&
- ns_buffer[i + 2] == '0') {
- ns_buffer[i] = '\0';
- // Edge case in which all fractional digits were 0.
- if (i == 1) {
- ns_buffer[0] = '\0';
- }
- } else {
- break;
- }
- }
- char* full_time_str;
- gpr_asprintf(&full_time_str, "%s%sZ", time_buffer, ns_buffer);
- return full_time_str;
-}
-
const char* severity_string(ChannelTrace::Severity severity) {
switch (severity) {
case ChannelTrace::Severity::Info:
@@ -190,50 +142,52 @@ void ChannelTrace::TraceEvent::RenderTraceEvent(grpc_json* json) const {
json_iterator = grpc_json_create_child(json_iterator, json, "severity",
severity_string(severity_),
GRPC_JSON_STRING, false);
- json_iterator =
- grpc_json_create_child(json_iterator, json, "timestamp",
- fmt_time(timestamp_), GRPC_JSON_STRING, true);
- if (referenced_tracer_ != nullptr) {
+ json_iterator = grpc_json_create_child(json_iterator, json, "timestamp",
+ gpr_format_timespec(timestamp_),
+ GRPC_JSON_STRING, true);
+ if (referenced_entity_ != nullptr) {
+ const bool is_channel =
+ (referenced_entity_->type() == BaseNode::EntityType::kTopLevelChannel ||
+ referenced_entity_->type() == BaseNode::EntityType::kInternalChannel);
char* uuid_str;
- gpr_asprintf(&uuid_str, "%" PRIdPTR, referenced_tracer_->channel_uuid_);
+ gpr_asprintf(&uuid_str, "%" PRIdPTR, referenced_entity_->uuid());
grpc_json* child_ref = grpc_json_create_child(
- json_iterator, json,
- (referenced_type_ == Channel) ? "channelRef" : "subchannelRef", nullptr,
- GRPC_JSON_OBJECT, false);
+ json_iterator, json, is_channel ? "channelRef" : "subchannelRef",
+ nullptr, GRPC_JSON_OBJECT, false);
json_iterator = grpc_json_create_child(
- nullptr, child_ref,
- (referenced_type_ == Channel) ? "channelId" : "subchannelId", uuid_str,
+ nullptr, child_ref, is_channel ? "channelId" : "subchannelId", uuid_str,
GRPC_JSON_STRING, true);
json_iterator = child_ref;
}
}
-char* ChannelTrace::RenderTrace() const {
+grpc_json* ChannelTrace::RenderJson() const {
if (!max_list_size_)
return nullptr; // tracing is disabled if max_events == 0
grpc_json* json = grpc_json_create(GRPC_JSON_OBJECT);
- char* num_events_logged_str;
- gpr_asprintf(&num_events_logged_str, "%" PRId64, num_events_logged_);
grpc_json* json_iterator = nullptr;
- json_iterator =
- grpc_json_create_child(json_iterator, json, "numEventsLogged",
- num_events_logged_str, GRPC_JSON_STRING, true);
- json_iterator =
- grpc_json_create_child(json_iterator, json, "creationTime",
- fmt_time(time_created_), GRPC_JSON_STRING, true);
- grpc_json* events = grpc_json_create_child(json_iterator, json, "events",
- nullptr, GRPC_JSON_ARRAY, false);
- json_iterator = nullptr;
- TraceEvent* it = head_trace_;
- while (it != nullptr) {
- json_iterator = grpc_json_create_child(json_iterator, events, nullptr,
- nullptr, GRPC_JSON_OBJECT, false);
- it->RenderTraceEvent(json_iterator);
- it = it->next();
+ if (num_events_logged_ > 0) {
+ json_iterator = grpc_json_add_number_string_child(
+ json, json_iterator, "numEventsLogged", num_events_logged_);
+ }
+ json_iterator = grpc_json_create_child(
+ json_iterator, json, "creationTimestamp",
+ gpr_format_timespec(time_created_), GRPC_JSON_STRING, true);
+ // only add in the event list if it is non-empty.
+ if (num_events_logged_ > 0) {
+ grpc_json* events = grpc_json_create_child(json_iterator, json, "events",
+ nullptr, GRPC_JSON_ARRAY, false);
+ json_iterator = nullptr;
+ TraceEvent* it = head_trace_;
+ while (it != nullptr) {
+ json_iterator = grpc_json_create_child(json_iterator, events, nullptr,
+ nullptr, GRPC_JSON_OBJECT, false);
+ it->RenderTraceEvent(json_iterator);
+ it = it->next();
+ }
}
- char* json_str = grpc_json_dump_to_string(json, 0);
- grpc_json_destroy(json);
- return json_str;
+ return json;
}
+} // namespace channelz
} // namespace grpc_core
diff --git a/src/core/lib/channel/channel_trace.h b/src/core/lib/channel/channel_trace.h
index 1df1e585f2..94fea20b45 100644
--- a/src/core/lib/channel/channel_trace.h
+++ b/src/core/lib/channel/channel_trace.h
@@ -28,18 +28,18 @@
#include "src/core/lib/json/json.h"
namespace grpc_core {
+namespace channelz {
+
+class BaseNode;
// Object used to hold live data for a channel. This data is exposed via the
// channelz service:
// https://github.com/grpc/proposal/blob/master/A14-channelz.md
-class ChannelTrace : public RefCounted<ChannelTrace> {
+class ChannelTrace {
public:
ChannelTrace(size_t max_events);
~ChannelTrace();
- // returns the tracer's uuid
- intptr_t GetUuid() const;
-
enum Severity {
Unset = 0, // never to be used
Info, // we start at 1 to avoid using proto default values
@@ -55,39 +55,28 @@ class ChannelTrace : public RefCounted<ChannelTrace> {
void AddTraceEvent(Severity severity, grpc_slice data);
// Adds a new trace event to the tracing object. This trace event refers to a
- // an event on a child of the channel. For example, if this channel has
- // created a new subchannel, then it would record that with a TraceEvent
- // referencing the new subchannel.
+ // an event that concerns a different channelz entity. For example, if this
+ // channel has created a new subchannel, then it would record that with
+ // a TraceEvent referencing the new subchannel.
//
- // TODO(ncteisen): Once channelz is implemented, the events should reference
- // the overall channelz object, not just the ChannelTrace object.
// TODO(ncteisen): as this call is used more and more throughout the gRPC
// stack, determine if it makes more sense to accept a char* instead of a
// slice.
- void AddTraceEventReferencingChannel(
- Severity severity, grpc_slice data,
- RefCountedPtr<ChannelTrace> referenced_tracer);
- void AddTraceEventReferencingSubchannel(
- Severity severity, grpc_slice data,
- RefCountedPtr<ChannelTrace> referenced_tracer);
+ void AddTraceEventWithReference(Severity severity, grpc_slice data,
+ RefCountedPtr<BaseNode> referenced_entity);
- // Returns the tracing data rendered as a grpc json string.
- // The string is owned by the caller and must be freed.
- char* RenderTrace() const;
+ // Creates and returns the raw grpc_json object, so a parent channelz
+ // object may incorporate the json before rendering.
+ grpc_json* RenderJson() const;
private:
- // Types of objects that can be references by trace events.
- enum ReferencedType { Channel, Subchannel };
// Private class to encapsulate all the data and bookkeeping needed for a
// a trace event.
class TraceEvent {
public:
- // Constructor for a TraceEvent that references a different channel.
- // TODO(ncteisen): once channelz is implemented, this should reference the
- // overall channelz object, not just the ChannelTrace object
+ // Constructor for a TraceEvent that references a channel.
TraceEvent(Severity severity, grpc_slice data,
- RefCountedPtr<ChannelTrace> referenced_tracer,
- ReferencedType type);
+ RefCountedPtr<BaseNode> referenced_entity_);
// Constructor for a TraceEvent that does not reverence a different
// channel.
@@ -109,17 +98,13 @@ class ChannelTrace : public RefCounted<ChannelTrace> {
gpr_timespec timestamp_;
TraceEvent* next_;
// the tracer object for the (sub)channel that this trace event refers to.
- RefCountedPtr<ChannelTrace> referenced_tracer_;
- // the type that the referenced tracer points to. Unused if this trace
- // does not point to any channel or subchannel
- ReferencedType referenced_type_;
+ RefCountedPtr<BaseNode> referenced_entity_;
}; // TraceEvent
// Internal helper to add and link in a trace event
void AddTraceEventHelper(TraceEvent* new_trace_event);
gpr_mu tracer_mu_;
- intptr_t channel_uuid_;
uint64_t num_events_logged_;
size_t list_size_;
size_t max_list_size_;
@@ -128,6 +113,7 @@ class ChannelTrace : public RefCounted<ChannelTrace> {
gpr_timespec time_created_;
};
+} // namespace channelz
} // namespace grpc_core
#endif /* GRPC_CORE_LIB_CHANNEL_CHANNEL_TRACE_H */
diff --git a/src/core/lib/channel/channel_trace_registry.cc b/src/core/lib/channel/channel_trace_registry.cc
deleted file mode 100644
index 6c82431467..0000000000
--- a/src/core/lib/channel/channel_trace_registry.cc
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- *
- * Copyright 2017 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/impl/codegen/port_platform.h>
-
-#include "src/core/lib/avl/avl.h"
-#include "src/core/lib/channel/channel_trace.h"
-#include "src/core/lib/channel/channel_trace_registry.h"
-#include "src/core/lib/gpr/useful.h"
-
-#include <grpc/support/alloc.h>
-#include <grpc/support/log.h>
-
-// file global lock and avl.
-static gpr_mu g_mu;
-static grpc_avl g_avl;
-static gpr_atm g_uuid = 0;
-
-// avl vtable for uuid (intptr_t) -> ChannelTrace
-// this table is only looking, it does not own anything.
-static void destroy_intptr(void* not_used, void* user_data) {}
-static void* copy_intptr(void* key, void* user_data) { return key; }
-static long compare_intptr(void* key1, void* key2, void* user_data) {
- return GPR_ICMP(key1, key2);
-}
-
-static void destroy_channel_trace(void* trace, void* user_data) {}
-static void* copy_channel_trace(void* trace, void* user_data) { return trace; }
-static const grpc_avl_vtable avl_vtable = {
- destroy_intptr, copy_intptr, compare_intptr, destroy_channel_trace,
- copy_channel_trace};
-
-void grpc_channel_trace_registry_init() {
- gpr_mu_init(&g_mu);
- g_avl = grpc_avl_create(&avl_vtable);
-}
-
-void grpc_channel_trace_registry_shutdown() {
- grpc_avl_unref(g_avl, nullptr);
- gpr_mu_destroy(&g_mu);
-}
-
-intptr_t grpc_channel_trace_registry_register_channel_trace(
- grpc_core::ChannelTrace* channel_trace) {
- intptr_t prior = gpr_atm_no_barrier_fetch_add(&g_uuid, 1);
- gpr_mu_lock(&g_mu);
- g_avl = grpc_avl_add(g_avl, (void*)prior, channel_trace, nullptr);
- gpr_mu_unlock(&g_mu);
- return prior;
-}
-
-void grpc_channel_trace_registry_unregister_channel_trace(intptr_t uuid) {
- gpr_mu_lock(&g_mu);
- g_avl = grpc_avl_remove(g_avl, (void*)uuid, nullptr);
- gpr_mu_unlock(&g_mu);
-}
-
-grpc_core::ChannelTrace* grpc_channel_trace_registry_get_channel_trace(
- intptr_t uuid) {
- gpr_mu_lock(&g_mu);
- grpc_core::ChannelTrace* ret = static_cast<grpc_core::ChannelTrace*>(
- grpc_avl_get(g_avl, (void*)uuid, nullptr));
- gpr_mu_unlock(&g_mu);
- return ret;
-}
diff --git a/src/core/lib/channel/channel_trace_registry.h b/src/core/lib/channel/channel_trace_registry.h
deleted file mode 100644
index 391ecba7de..0000000000
--- a/src/core/lib/channel/channel_trace_registry.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- *
- * Copyright 2017 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_CHANNEL_CHANNEL_TRACE_REGISTRY_H
-#define GRPC_CORE_LIB_CHANNEL_CHANNEL_TRACE_REGISTRY_H
-
-#include <grpc/impl/codegen/port_platform.h>
-
-#include "src/core/lib/channel/channel_trace.h"
-
-#include <stdint.h>
-
-// TODO(ncteisen): convert this file to C++
-
-void grpc_channel_trace_registry_init();
-void grpc_channel_trace_registry_shutdown();
-
-// globally registers a ChannelTrace. Returns its unique uuid
-intptr_t grpc_channel_trace_registry_register_channel_trace(
- grpc_core::ChannelTrace* channel_trace);
-// globally unregisters the ChannelTrace that is associated to uuid.
-void grpc_channel_trace_registry_unregister_channel_trace(intptr_t uuid);
-// if object with uuid has previously been registered, returns the ChannelTrace
-// associated with that uuid. Else returns nullptr.
-grpc_core::ChannelTrace* grpc_channel_trace_registry_get_channel_trace(
- intptr_t uuid);
-
-#endif /* GRPC_CORE_LIB_CHANNEL_CHANNEL_TRACE_REGISTRY_H */
diff --git a/src/core/lib/channel/channelz.cc b/src/core/lib/channel/channelz.cc
new file mode 100644
index 0000000000..339c827525
--- /dev/null
+++ b/src/core/lib/channel/channelz.cc
@@ -0,0 +1,286 @@
+/*
+ *
+ * Copyright 2017 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/impl/codegen/port_platform.h>
+
+#include "src/core/lib/channel/channelz.h"
+
+#include <grpc/grpc.h>
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+#include <grpc/support/string_util.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "src/core/lib/channel/channelz_registry.h"
+#include "src/core/lib/channel/status_util.h"
+#include "src/core/lib/gpr/string.h"
+#include "src/core/lib/gpr/useful.h"
+#include "src/core/lib/gprpp/memory.h"
+#include "src/core/lib/iomgr/error.h"
+#include "src/core/lib/slice/slice_internal.h"
+#include "src/core/lib/surface/channel.h"
+#include "src/core/lib/transport/error_utils.h"
+
+namespace grpc_core {
+namespace channelz {
+
+BaseNode::BaseNode(EntityType type)
+ : type_(type), uuid_(ChannelzRegistry::Register(this)) {}
+
+BaseNode::~BaseNode() { ChannelzRegistry::Unregister(uuid_); }
+
+char* BaseNode::RenderJsonString() {
+ grpc_json* json = RenderJson();
+ GPR_ASSERT(json != nullptr);
+ char* json_str = grpc_json_dump_to_string(json, 0);
+ grpc_json_destroy(json);
+ return json_str;
+}
+
+CallCountingHelper::CallCountingHelper() {
+ gpr_atm_no_barrier_store(&last_call_started_millis_,
+ (gpr_atm)ExecCtx::Get()->Now());
+}
+
+CallCountingHelper::~CallCountingHelper() {}
+
+void CallCountingHelper::RecordCallStarted() {
+ gpr_atm_no_barrier_fetch_add(&calls_started_, static_cast<gpr_atm>(1));
+ gpr_atm_no_barrier_store(&last_call_started_millis_,
+ (gpr_atm)ExecCtx::Get()->Now());
+}
+
+void CallCountingHelper::PopulateCallCounts(grpc_json* json) {
+ grpc_json* json_iterator = nullptr;
+ if (calls_started_ != 0) {
+ json_iterator = grpc_json_add_number_string_child(
+ json, json_iterator, "callsStarted", calls_started_);
+ }
+ if (calls_succeeded_ != 0) {
+ json_iterator = grpc_json_add_number_string_child(
+ json, json_iterator, "callsSucceeded", calls_succeeded_);
+ }
+ if (calls_failed_) {
+ json_iterator = grpc_json_add_number_string_child(
+ json, json_iterator, "callsFailed", calls_failed_);
+ }
+ if (calls_started_ != 0) {
+ gpr_timespec ts =
+ grpc_millis_to_timespec(last_call_started_millis_, GPR_CLOCK_REALTIME);
+ json_iterator =
+ grpc_json_create_child(json_iterator, json, "lastCallStartedTimestamp",
+ gpr_format_timespec(ts), GRPC_JSON_STRING, true);
+ }
+}
+
+ChannelNode::ChannelNode(grpc_channel* channel, size_t channel_tracer_max_nodes,
+ bool is_top_level_channel)
+ : BaseNode(is_top_level_channel ? EntityType::kTopLevelChannel
+ : EntityType::kInternalChannel),
+ channel_(channel),
+ target_(UniquePtr<char>(grpc_channel_get_target(channel_))),
+ trace_(channel_tracer_max_nodes) {}
+
+ChannelNode::~ChannelNode() {}
+
+grpc_json* ChannelNode::RenderJson() {
+ // We need to track these three json objects to build our object
+ grpc_json* top_level_json = grpc_json_create(GRPC_JSON_OBJECT);
+ grpc_json* json = top_level_json;
+ grpc_json* json_iterator = nullptr;
+ // create and fill the ref child
+ json_iterator = grpc_json_create_child(json_iterator, json, "ref", nullptr,
+ GRPC_JSON_OBJECT, false);
+ json = json_iterator;
+ json_iterator = nullptr;
+ json_iterator = grpc_json_add_number_string_child(json, json_iterator,
+ "channelId", uuid());
+ // reset json iterators to top level object
+ json = top_level_json;
+ json_iterator = nullptr;
+ // create and fill the data child.
+ grpc_json* data = grpc_json_create_child(json_iterator, json, "data", nullptr,
+ GRPC_JSON_OBJECT, false);
+ json = data;
+ json_iterator = nullptr;
+ // template method. Child classes may override this to add their specific
+ // functionality.
+ PopulateConnectivityState(json);
+ // populate the target.
+ GPR_ASSERT(target_.get() != nullptr);
+ grpc_json_create_child(nullptr, json, "target", target_.get(),
+ GRPC_JSON_STRING, false);
+ // fill in the channel trace if applicable
+ grpc_json* trace_json = trace_.RenderJson();
+ if (trace_json != nullptr) {
+ trace_json->key = "trace"; // this object is named trace in channelz.proto
+ grpc_json_link_child(json, trace_json, nullptr);
+ }
+ // ask CallCountingHelper to populate trace and call count data.
+ call_counter_.PopulateCallCounts(json);
+ json = top_level_json;
+ // template method. Child classes may override this to add their specific
+ // functionality.
+ PopulateChildRefs(json);
+ return top_level_json;
+}
+
+RefCountedPtr<ChannelNode> ChannelNode::MakeChannelNode(
+ grpc_channel* channel, size_t channel_tracer_max_nodes,
+ bool is_top_level_channel) {
+ return MakeRefCounted<grpc_core::channelz::ChannelNode>(
+ channel, channel_tracer_max_nodes, is_top_level_channel);
+}
+
+ServerNode::ServerNode(size_t channel_tracer_max_nodes)
+ : BaseNode(EntityType::kServer), trace_(channel_tracer_max_nodes) {}
+
+ServerNode::~ServerNode() {}
+
+grpc_json* ServerNode::RenderJson() {
+ // We need to track these three json objects to build our object
+ grpc_json* top_level_json = grpc_json_create(GRPC_JSON_OBJECT);
+ grpc_json* json = top_level_json;
+ grpc_json* json_iterator = nullptr;
+ // create and fill the ref child
+ json_iterator = grpc_json_create_child(json_iterator, json, "ref", nullptr,
+ GRPC_JSON_OBJECT, false);
+ json = json_iterator;
+ json_iterator = nullptr;
+ json_iterator = grpc_json_add_number_string_child(json, json_iterator,
+ "serverId", uuid());
+ // reset json iterators to top level object
+ json = top_level_json;
+ json_iterator = nullptr;
+ // create and fill the data child.
+ grpc_json* data = grpc_json_create_child(json_iterator, json, "data", nullptr,
+ GRPC_JSON_OBJECT, false);
+ json = data;
+ json_iterator = nullptr;
+ // fill in the channel trace if applicable
+ grpc_json* trace_json = trace_.RenderJson();
+ if (trace_json != nullptr) {
+ trace_json->key = "trace"; // this object is named trace in channelz.proto
+ grpc_json_link_child(json, trace_json, nullptr);
+ }
+ // ask CallCountingHelper to populate trace and call count data.
+ call_counter_.PopulateCallCounts(json);
+ return top_level_json;
+}
+
+SocketNode::SocketNode() : BaseNode(EntityType::kSocket) {}
+
+void SocketNode::RecordStreamStartedFromLocal() {
+ gpr_atm_no_barrier_fetch_add(&streams_started_, static_cast<gpr_atm>(1));
+ gpr_atm_no_barrier_store(&last_local_stream_created_millis_,
+ (gpr_atm)ExecCtx::Get()->Now());
+}
+
+void SocketNode::RecordStreamStartedFromRemote() {
+ gpr_atm_no_barrier_fetch_add(&streams_started_, static_cast<gpr_atm>(1));
+ gpr_atm_no_barrier_store(&last_remote_stream_created_millis_,
+ (gpr_atm)ExecCtx::Get()->Now());
+}
+
+void SocketNode::RecordMessagesSent(uint32_t num_sent) {
+ gpr_atm_no_barrier_fetch_add(&messages_sent_, static_cast<gpr_atm>(num_sent));
+ gpr_atm_no_barrier_store(&last_message_sent_millis_,
+ (gpr_atm)ExecCtx::Get()->Now());
+}
+
+void SocketNode::RecordMessageReceived() {
+ gpr_atm_no_barrier_fetch_add(&messages_received_, static_cast<gpr_atm>(1));
+ gpr_atm_no_barrier_store(&last_message_received_millis_,
+ (gpr_atm)ExecCtx::Get()->Now());
+}
+
+grpc_json* SocketNode::RenderJson() {
+ // We need to track these three json objects to build our object
+ grpc_json* top_level_json = grpc_json_create(GRPC_JSON_OBJECT);
+ grpc_json* json = top_level_json;
+ grpc_json* json_iterator = nullptr;
+ // create and fill the ref child
+ json_iterator = grpc_json_create_child(json_iterator, json, "ref", nullptr,
+ GRPC_JSON_OBJECT, false);
+ json = json_iterator;
+ json_iterator = nullptr;
+ json_iterator = grpc_json_add_number_string_child(json, json_iterator,
+ "socketId", uuid());
+ // reset json iterators to top level object
+ json = top_level_json;
+ json_iterator = nullptr;
+ // create and fill the data child.
+ grpc_json* data = grpc_json_create_child(json_iterator, json, "data", nullptr,
+ GRPC_JSON_OBJECT, false);
+ json = data;
+ json_iterator = nullptr;
+ gpr_timespec ts;
+ if (streams_started_ != 0) {
+ json_iterator = grpc_json_add_number_string_child(
+ json, json_iterator, "streamsStarted", streams_started_);
+ if (last_local_stream_created_millis_ != 0) {
+ ts = grpc_millis_to_timespec(last_local_stream_created_millis_,
+ GPR_CLOCK_REALTIME);
+ json_iterator = grpc_json_create_child(
+ json_iterator, json, "lastLocalStreamCreatedTimestamp",
+ gpr_format_timespec(ts), GRPC_JSON_STRING, true);
+ }
+ if (last_remote_stream_created_millis_ != 0) {
+ ts = grpc_millis_to_timespec(last_remote_stream_created_millis_,
+ GPR_CLOCK_REALTIME);
+ json_iterator = grpc_json_create_child(
+ json_iterator, json, "lastRemoteStreamCreatedTimestamp",
+ gpr_format_timespec(ts), GRPC_JSON_STRING, true);
+ }
+ }
+ if (streams_succeeded_ != 0) {
+ json_iterator = grpc_json_add_number_string_child(
+ json, json_iterator, "streamsSucceeded", streams_succeeded_);
+ }
+ if (streams_failed_) {
+ json_iterator = grpc_json_add_number_string_child(
+ json, json_iterator, "streamsFailed", streams_failed_);
+ }
+ if (messages_sent_ != 0) {
+ json_iterator = grpc_json_add_number_string_child(
+ json, json_iterator, "messagesSent", messages_sent_);
+ ts = grpc_millis_to_timespec(last_message_sent_millis_, GPR_CLOCK_REALTIME);
+ json_iterator =
+ grpc_json_create_child(json_iterator, json, "lastMessageSentTimestamp",
+ gpr_format_timespec(ts), GRPC_JSON_STRING, true);
+ }
+ if (messages_received_ != 0) {
+ json_iterator = grpc_json_add_number_string_child(
+ json, json_iterator, "messagesReceived", messages_received_);
+ ts = grpc_millis_to_timespec(last_message_received_millis_,
+ GPR_CLOCK_REALTIME);
+ json_iterator = grpc_json_create_child(
+ json_iterator, json, "lastMessageReceivedTimestamp",
+ gpr_format_timespec(ts), GRPC_JSON_STRING, true);
+ }
+ if (keepalives_sent_ != 0) {
+ json_iterator = grpc_json_add_number_string_child(
+ json, json_iterator, "keepAlivesSent", keepalives_sent_);
+ }
+ return top_level_json;
+}
+
+} // namespace channelz
+} // namespace grpc_core
diff --git a/src/core/lib/channel/channelz.h b/src/core/lib/channel/channelz.h
new file mode 100644
index 0000000000..b7ae101238
--- /dev/null
+++ b/src/core/lib/channel/channelz.h
@@ -0,0 +1,243 @@
+/*
+ *
+ * Copyright 2018 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_CHANNEL_CHANNELZ_H
+#define GRPC_CORE_LIB_CHANNEL_CHANNELZ_H
+
+#include <grpc/impl/codegen/port_platform.h>
+
+#include <grpc/grpc.h>
+
+#include "src/core/lib/channel/channel_trace.h"
+#include "src/core/lib/gprpp/manual_constructor.h"
+#include "src/core/lib/gprpp/ref_counted.h"
+#include "src/core/lib/gprpp/ref_counted_ptr.h"
+#include "src/core/lib/iomgr/error.h"
+#include "src/core/lib/iomgr/exec_ctx.h"
+#include "src/core/lib/json/json.h"
+
+// Channel arg key for client channel factory.
+#define GRPC_ARG_CHANNELZ_CHANNEL_NODE_CREATION_FUNC \
+ "grpc.channelz_channel_node_creation_func"
+
+// Channel arg key to signal that the channel is an internal channel.
+#define GRPC_ARG_CHANNELZ_CHANNEL_IS_INTERNAL_CHANNEL \
+ "grpc.channelz_channel_is_internal_channel"
+
+namespace grpc_core {
+namespace channelz {
+
+namespace testing {
+class CallCountingHelperPeer;
+class ChannelNodePeer;
+} // namespace testing
+
+// base class for all channelz entities
+class BaseNode : public RefCounted<BaseNode> {
+ public:
+ // There are only four high level channelz entities. However, to support
+ // GetTopChannelsRequest, we split the Channel entity into two different
+ // types. All children of BaseNode must be one of these types.
+ enum class EntityType {
+ kTopLevelChannel,
+ kInternalChannel,
+ kSubchannel,
+ kServer,
+ kSocket,
+ };
+
+ explicit BaseNode(EntityType type);
+ virtual ~BaseNode();
+
+ // All children must implement this function.
+ virtual grpc_json* RenderJson() GRPC_ABSTRACT;
+
+ // Renders the json and returns allocated string that must be freed by the
+ // caller.
+ char* RenderJsonString();
+
+ EntityType type() const { return type_; }
+ intptr_t uuid() const { return uuid_; }
+
+ private:
+ const EntityType type_;
+ const intptr_t uuid_;
+};
+
+// This class is a helper class for channelz entities that deal with Channels,
+// Subchannels, and Servers, since those have similar proto definitions.
+// This class has the ability to:
+// - track calls_{started,succeeded,failed}
+// - track last_call_started_timestamp
+// - perform rendering of the above items
+class CallCountingHelper {
+ public:
+ CallCountingHelper();
+ ~CallCountingHelper();
+
+ void RecordCallStarted();
+ void RecordCallFailed() {
+ gpr_atm_no_barrier_fetch_add(&calls_failed_, static_cast<gpr_atm>(1));
+ }
+ void RecordCallSucceeded() {
+ gpr_atm_no_barrier_fetch_add(&calls_succeeded_, static_cast<gpr_atm>(1));
+ }
+
+ // Common rendering of the call count data and last_call_started_timestamp.
+ void PopulateCallCounts(grpc_json* json);
+
+ private:
+ // testing peer friend.
+ friend class testing::CallCountingHelperPeer;
+
+ gpr_atm calls_started_ = 0;
+ gpr_atm calls_succeeded_ = 0;
+ gpr_atm calls_failed_ = 0;
+ gpr_atm last_call_started_millis_ = 0;
+};
+
+// Handles channelz bookkeeping for channels
+class ChannelNode : public BaseNode {
+ public:
+ static RefCountedPtr<ChannelNode> MakeChannelNode(
+ grpc_channel* channel, size_t channel_tracer_max_nodes,
+ bool is_top_level_channel);
+
+ ChannelNode(grpc_channel* channel, size_t channel_tracer_max_nodes,
+ bool is_top_level_channel);
+ ~ChannelNode() override;
+
+ grpc_json* RenderJson() override;
+
+ // template methods. RenderJSON uses these methods to render its JSON
+ // representation. These are virtual so that children classes may provide
+ // their specific mechanism for populating these parts of the channelz
+ // object.
+ //
+ // ChannelNode does not have a notion of connectivity state or child refs,
+ // so it leaves these implementations blank.
+ //
+ // This is utilizing the template method design pattern.
+ //
+ // TODO(ncteisen): remove these template methods in favor of manual traversal
+ // and mutation of the grpc_json object.
+ virtual void PopulateConnectivityState(grpc_json* json) {}
+ virtual void PopulateChildRefs(grpc_json* json) {}
+
+ void MarkChannelDestroyed() {
+ GPR_ASSERT(channel_ != nullptr);
+ channel_ = nullptr;
+ }
+
+ bool ChannelIsDestroyed() { return channel_ == nullptr; }
+
+ // proxy methods to composed classes.
+ void AddTraceEvent(ChannelTrace::Severity severity, grpc_slice data) {
+ trace_.AddTraceEvent(severity, data);
+ }
+ void AddTraceEventWithReference(ChannelTrace::Severity severity,
+ grpc_slice data,
+ RefCountedPtr<BaseNode> referenced_channel) {
+ trace_.AddTraceEventWithReference(severity, data,
+ std::move(referenced_channel));
+ }
+ void RecordCallStarted() { call_counter_.RecordCallStarted(); }
+ void RecordCallFailed() { call_counter_.RecordCallFailed(); }
+ void RecordCallSucceeded() { call_counter_.RecordCallSucceeded(); }
+
+ private:
+ // to allow the channel trace test to access trace_.
+ friend class testing::ChannelNodePeer;
+ grpc_channel* channel_ = nullptr;
+ UniquePtr<char> target_;
+ CallCountingHelper call_counter_;
+ ChannelTrace trace_;
+};
+
+// Handles channelz bookkeeping for servers
+class ServerNode : public BaseNode {
+ public:
+ explicit ServerNode(size_t channel_tracer_max_nodes);
+ ~ServerNode() override;
+
+ grpc_json* RenderJson() override;
+
+ // proxy methods to composed classes.
+ void AddTraceEvent(ChannelTrace::Severity severity, grpc_slice data) {
+ trace_.AddTraceEvent(severity, data);
+ }
+ void AddTraceEventWithReference(ChannelTrace::Severity severity,
+ grpc_slice data,
+ RefCountedPtr<BaseNode> referenced_channel) {
+ trace_.AddTraceEventWithReference(severity, data,
+ std::move(referenced_channel));
+ }
+ void RecordCallStarted() { call_counter_.RecordCallStarted(); }
+ void RecordCallFailed() { call_counter_.RecordCallFailed(); }
+ void RecordCallSucceeded() { call_counter_.RecordCallSucceeded(); }
+
+ private:
+ CallCountingHelper call_counter_;
+ ChannelTrace trace_;
+};
+
+// Handles channelz bookkeeping for sockets
+class SocketNode : public BaseNode {
+ public:
+ SocketNode();
+ ~SocketNode() override {}
+
+ grpc_json* RenderJson() override;
+
+ void RecordStreamStartedFromLocal();
+ void RecordStreamStartedFromRemote();
+ void RecordStreamSucceeded() {
+ gpr_atm_no_barrier_fetch_add(&streams_succeeded_, static_cast<gpr_atm>(1));
+ }
+ void RecordStreamFailed() {
+ gpr_atm_no_barrier_fetch_add(&streams_failed_, static_cast<gpr_atm>(1));
+ }
+ void RecordMessagesSent(uint32_t num_sent);
+ void RecordMessageReceived();
+ void RecordKeepaliveSent() {
+ gpr_atm_no_barrier_fetch_add(&keepalives_sent_, static_cast<gpr_atm>(1));
+ }
+
+ private:
+ gpr_atm streams_started_ = 0;
+ gpr_atm streams_succeeded_ = 0;
+ gpr_atm streams_failed_ = 0;
+ gpr_atm messages_sent_ = 0;
+ gpr_atm messages_received_ = 0;
+ gpr_atm keepalives_sent_ = 0;
+ gpr_atm last_local_stream_created_millis_ = 0;
+ gpr_atm last_remote_stream_created_millis_ = 0;
+ gpr_atm last_message_sent_millis_ = 0;
+ gpr_atm last_message_received_millis_ = 0;
+ UniquePtr<char> peer_string_;
+};
+
+// Creation functions
+
+typedef RefCountedPtr<ChannelNode> (*ChannelNodeCreationFunc)(grpc_channel*,
+ size_t, bool);
+
+} // namespace channelz
+} // namespace grpc_core
+
+#endif /* GRPC_CORE_LIB_CHANNEL_CHANNELZ_H */
diff --git a/src/core/lib/channel/channelz_registry.cc b/src/core/lib/channel/channelz_registry.cc
new file mode 100644
index 0000000000..841f1c6104
--- /dev/null
+++ b/src/core/lib/channel/channelz_registry.cc
@@ -0,0 +1,217 @@
+/*
+ *
+ * Copyright 2017 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/impl/codegen/port_platform.h>
+
+#include "src/core/lib/channel/channel_trace.h"
+#include "src/core/lib/channel/channelz.h"
+#include "src/core/lib/channel/channelz_registry.h"
+#include "src/core/lib/gpr/useful.h"
+#include "src/core/lib/gprpp/memory.h"
+#include "src/core/lib/gprpp/mutex_lock.h"
+
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+#include <grpc/support/sync.h>
+
+#include <cstring>
+
+namespace grpc_core {
+namespace channelz {
+namespace {
+
+// singleton instance of the registry.
+ChannelzRegistry* g_channelz_registry = nullptr;
+
+} // anonymous namespace
+
+void ChannelzRegistry::Init() { g_channelz_registry = New<ChannelzRegistry>(); }
+
+void ChannelzRegistry::Shutdown() { Delete(g_channelz_registry); }
+
+ChannelzRegistry* ChannelzRegistry::Default() {
+ GPR_DEBUG_ASSERT(g_channelz_registry != nullptr);
+ return g_channelz_registry;
+}
+
+ChannelzRegistry::ChannelzRegistry() { gpr_mu_init(&mu_); }
+
+ChannelzRegistry::~ChannelzRegistry() { gpr_mu_destroy(&mu_); }
+
+intptr_t ChannelzRegistry::InternalRegister(BaseNode* node) {
+ MutexLock lock(&mu_);
+ entities_.push_back(node);
+ intptr_t uuid = entities_.size();
+ return uuid;
+}
+
+void ChannelzRegistry::InternalUnregister(intptr_t uuid) {
+ GPR_ASSERT(uuid >= 1);
+ MutexLock lock(&mu_);
+ GPR_ASSERT(static_cast<size_t>(uuid) <= entities_.size());
+ entities_[uuid - 1] = nullptr;
+}
+
+BaseNode* ChannelzRegistry::InternalGet(intptr_t uuid) {
+ MutexLock lock(&mu_);
+ if (uuid < 1 || uuid > static_cast<intptr_t>(entities_.size())) {
+ return nullptr;
+ }
+ return entities_[uuid - 1];
+}
+
+char* ChannelzRegistry::InternalGetTopChannels(intptr_t start_channel_id) {
+ grpc_json* top_level_json = grpc_json_create(GRPC_JSON_OBJECT);
+ grpc_json* json = top_level_json;
+ grpc_json* json_iterator = nullptr;
+ InlinedVector<BaseNode*, 10> top_level_channels;
+ // uuids index into entities one-off (idx 0 is really uuid 1, since 0 is
+ // reserved). However, we want to support requests coming in with
+ // start_channel_id=0, which signifies "give me everything." Hence this
+ // funky looking line below.
+ size_t start_idx = start_channel_id == 0 ? 0 : start_channel_id - 1;
+ for (size_t i = start_idx; i < entities_.size(); ++i) {
+ if (entities_[i] != nullptr &&
+ entities_[i]->type() ==
+ grpc_core::channelz::BaseNode::EntityType::kTopLevelChannel) {
+ top_level_channels.push_back(entities_[i]);
+ }
+ }
+ if (!top_level_channels.empty()) {
+ // create list of channels
+ grpc_json* array_parent = grpc_json_create_child(
+ nullptr, json, "channel", nullptr, GRPC_JSON_ARRAY, false);
+ for (size_t i = 0; i < top_level_channels.size(); ++i) {
+ grpc_json* channel_json = top_level_channels[i]->RenderJson();
+ json_iterator =
+ grpc_json_link_child(array_parent, channel_json, json_iterator);
+ }
+ }
+ // For now we do not have any pagination rules. In the future we could
+ // pick a constant for max_channels_sent for a GetTopChannels request.
+ // Tracking: https://github.com/grpc/grpc/issues/16019.
+ json_iterator = grpc_json_create_child(nullptr, json, "end", nullptr,
+ GRPC_JSON_TRUE, false);
+ char* json_str = grpc_json_dump_to_string(top_level_json, 0);
+ grpc_json_destroy(top_level_json);
+ return json_str;
+}
+
+char* ChannelzRegistry::InternalGetServers(intptr_t start_server_id) {
+ grpc_json* top_level_json = grpc_json_create(GRPC_JSON_OBJECT);
+ grpc_json* json = top_level_json;
+ grpc_json* json_iterator = nullptr;
+ InlinedVector<BaseNode*, 10> servers;
+ // uuids index into entities one-off (idx 0 is really uuid 1, since 0 is
+ // reserved). However, we want to support requests coming in with
+ // start_server_id=0, which signifies "give me everything."
+ size_t start_idx = start_server_id == 0 ? 0 : start_server_id - 1;
+ for (size_t i = start_idx; i < entities_.size(); ++i) {
+ if (entities_[i] != nullptr &&
+ entities_[i]->type() ==
+ grpc_core::channelz::BaseNode::EntityType::kServer) {
+ servers.push_back(entities_[i]);
+ }
+ }
+ if (!servers.empty()) {
+ // create list of servers
+ grpc_json* array_parent = grpc_json_create_child(
+ nullptr, json, "server", nullptr, GRPC_JSON_ARRAY, false);
+ for (size_t i = 0; i < servers.size(); ++i) {
+ grpc_json* server_json = servers[i]->RenderJson();
+ json_iterator =
+ grpc_json_link_child(array_parent, server_json, json_iterator);
+ }
+ }
+ // For now we do not have any pagination rules. In the future we could
+ // pick a constant for max_channels_sent for a GetServers request.
+ // Tracking: https://github.com/grpc/grpc/issues/16019.
+ json_iterator = grpc_json_create_child(nullptr, json, "end", nullptr,
+ GRPC_JSON_TRUE, false);
+ char* json_str = grpc_json_dump_to_string(top_level_json, 0);
+ grpc_json_destroy(top_level_json);
+ return json_str;
+}
+
+} // namespace channelz
+} // namespace grpc_core
+
+char* grpc_channelz_get_top_channels(intptr_t start_channel_id) {
+ return grpc_core::channelz::ChannelzRegistry::GetTopChannels(
+ start_channel_id);
+}
+
+char* grpc_channelz_get_servers(intptr_t start_server_id) {
+ return grpc_core::channelz::ChannelzRegistry::GetServers(start_server_id);
+}
+
+char* grpc_channelz_get_channel(intptr_t channel_id) {
+ grpc_core::channelz::BaseNode* channel_node =
+ grpc_core::channelz::ChannelzRegistry::Get(channel_id);
+ if (channel_node == nullptr ||
+ (channel_node->type() !=
+ grpc_core::channelz::BaseNode::EntityType::kTopLevelChannel &&
+ channel_node->type() !=
+ grpc_core::channelz::BaseNode::EntityType::kInternalChannel)) {
+ return nullptr;
+ }
+ grpc_json* top_level_json = grpc_json_create(GRPC_JSON_OBJECT);
+ grpc_json* json = top_level_json;
+ grpc_json* channel_json = channel_node->RenderJson();
+ channel_json->key = "channel";
+ grpc_json_link_child(json, channel_json, nullptr);
+ char* json_str = grpc_json_dump_to_string(top_level_json, 0);
+ grpc_json_destroy(top_level_json);
+ return json_str;
+}
+
+char* grpc_channelz_get_subchannel(intptr_t subchannel_id) {
+ grpc_core::channelz::BaseNode* subchannel_node =
+ grpc_core::channelz::ChannelzRegistry::Get(subchannel_id);
+ if (subchannel_node == nullptr ||
+ subchannel_node->type() !=
+ grpc_core::channelz::BaseNode::EntityType::kSubchannel) {
+ return nullptr;
+ }
+ grpc_json* top_level_json = grpc_json_create(GRPC_JSON_OBJECT);
+ grpc_json* json = top_level_json;
+ grpc_json* subchannel_json = subchannel_node->RenderJson();
+ subchannel_json->key = "subchannel";
+ grpc_json_link_child(json, subchannel_json, nullptr);
+ char* json_str = grpc_json_dump_to_string(top_level_json, 0);
+ grpc_json_destroy(top_level_json);
+ return json_str;
+}
+
+char* grpc_channelz_get_socket(intptr_t socket_id) {
+ grpc_core::channelz::BaseNode* socket_node =
+ grpc_core::channelz::ChannelzRegistry::Get(socket_id);
+ if (socket_node == nullptr ||
+ socket_node->type() !=
+ grpc_core::channelz::BaseNode::EntityType::kSocket) {
+ return nullptr;
+ }
+ grpc_json* top_level_json = grpc_json_create(GRPC_JSON_OBJECT);
+ grpc_json* json = top_level_json;
+ grpc_json* socket_json = socket_node->RenderJson();
+ socket_json->key = "socket";
+ grpc_json_link_child(json, socket_json, nullptr);
+ char* json_str = grpc_json_dump_to_string(top_level_json, 0);
+ grpc_json_destroy(top_level_json);
+ return json_str;
+}
diff --git a/src/core/lib/channel/channelz_registry.h b/src/core/lib/channel/channelz_registry.h
new file mode 100644
index 0000000000..d0d660600d
--- /dev/null
+++ b/src/core/lib/channel/channelz_registry.h
@@ -0,0 +1,93 @@
+/*
+ *
+ * Copyright 2017 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_CHANNEL_CHANNELZ_REGISTRY_H
+#define GRPC_CORE_LIB_CHANNEL_CHANNELZ_REGISTRY_H
+
+#include <grpc/impl/codegen/port_platform.h>
+
+#include "src/core/lib/channel/channel_trace.h"
+#include "src/core/lib/channel/channelz.h"
+#include "src/core/lib/gprpp/inlined_vector.h"
+
+#include <stdint.h>
+
+namespace grpc_core {
+namespace channelz {
+
+// singleton registry object to track all objects that are needed to support
+// channelz bookkeeping. All objects share globally distributed uuids.
+class ChannelzRegistry {
+ public:
+ // To be called in grpc_init()
+ static void Init();
+
+ // To be called in grpc_shutdown();
+ static void Shutdown();
+
+ static intptr_t Register(BaseNode* node) {
+ return Default()->InternalRegister(node);
+ }
+ static void Unregister(intptr_t uuid) { Default()->InternalUnregister(uuid); }
+ static BaseNode* Get(intptr_t uuid) { return Default()->InternalGet(uuid); }
+
+ // Returns the allocated JSON string that represents the proto
+ // GetTopChannelsResponse as per channelz.proto.
+ static char* GetTopChannels(intptr_t start_channel_id) {
+ return Default()->InternalGetTopChannels(start_channel_id);
+ }
+
+ // Returns the allocated JSON string that represents the proto
+ // GetServersResponse as per channelz.proto.
+ static char* GetServers(intptr_t start_server_id) {
+ return Default()->InternalGetServers(start_server_id);
+ }
+
+ private:
+ GPRC_ALLOW_CLASS_TO_USE_NON_PUBLIC_NEW
+ GPRC_ALLOW_CLASS_TO_USE_NON_PUBLIC_DELETE
+
+ ChannelzRegistry();
+ ~ChannelzRegistry();
+
+ // Returned the singleton instance of ChannelzRegistry;
+ static ChannelzRegistry* Default();
+
+ // globally registers an Entry. Returns its unique uuid
+ intptr_t InternalRegister(BaseNode* node);
+
+ // globally unregisters the object that is associated to uuid. Also does
+ // sanity check that an object doesn't try to unregister the wrong type.
+ void InternalUnregister(intptr_t uuid);
+
+ // if object with uuid has previously been registered as the correct type,
+ // returns the void* associated with that uuid. Else returns nullptr.
+ BaseNode* InternalGet(intptr_t uuid);
+
+ char* InternalGetTopChannels(intptr_t start_channel_id);
+ char* InternalGetServers(intptr_t start_server_id);
+
+ // protects entities_ and uuid_
+ gpr_mu mu_;
+ InlinedVector<BaseNode*, 20> entities_;
+};
+
+} // namespace channelz
+} // namespace grpc_core
+
+#endif /* GRPC_CORE_LIB_CHANNEL_CHANNELZ_REGISTRY_H */
diff --git a/src/core/lib/channel/connected_channel.cc b/src/core/lib/channel/connected_channel.cc
index ddd3029402..e2ea334ded 100644
--- a/src/core/lib/channel/connected_channel.cc
+++ b/src/core/lib/channel/connected_channel.cc
@@ -51,6 +51,7 @@ typedef struct connected_channel_call_data {
callback_state on_complete[6]; // Max number of pending batches.
callback_state recv_initial_metadata_ready;
callback_state recv_message_ready;
+ callback_state recv_trailing_metadata_ready;
} call_data;
static void run_in_call_combiner(void* arg, grpc_error* error) {
@@ -111,6 +112,12 @@ static void con_start_transport_stream_op_batch(
intercept_callback(calld, state, false, "recv_message_ready",
&batch->payload->recv_message.recv_message_ready);
}
+ if (batch->recv_trailing_metadata) {
+ callback_state* state = &calld->recv_trailing_metadata_ready;
+ intercept_callback(
+ calld, state, false, "recv_trailing_metadata_ready",
+ &batch->payload->recv_trailing_metadata.recv_trailing_metadata_ready);
+ }
if (batch->cancel_stream) {
// There can be more than one cancellation batch in flight at any
// given time, so we can't just pick out a fixed index into
@@ -121,7 +128,7 @@ static void con_start_transport_stream_op_batch(
static_cast<callback_state*>(gpr_malloc(sizeof(*state)));
intercept_callback(calld, state, true, "on_complete (cancel_stream)",
&batch->on_complete);
- } else {
+ } else if (batch->on_complete != nullptr) {
callback_state* state = get_state_for_batch(calld, batch);
intercept_callback(calld, state, false, "on_complete", &batch->on_complete);
}
diff --git a/src/core/lib/channel/handshaker.cc b/src/core/lib/channel/handshaker.cc
index 2faeb64cb6..ad3250b7e9 100644
--- a/src/core/lib/channel/handshaker.cc
+++ b/src/core/lib/channel/handshaker.cc
@@ -28,6 +28,7 @@
#include "src/core/lib/channel/handshaker.h"
#include "src/core/lib/debug/trace.h"
#include "src/core/lib/iomgr/timer.h"
+#include "src/core/lib/slice/slice_internal.h"
grpc_core::TraceFlag grpc_handshaker_trace(false, "handshaker");
@@ -220,8 +221,31 @@ static bool call_next_handshaker_locked(grpc_handshake_manager* mgr,
// callback. Otherwise, call the next handshaker.
if (error != GRPC_ERROR_NONE || mgr->shutdown || mgr->args.exit_early ||
mgr->index == mgr->count) {
+ if (error == GRPC_ERROR_NONE && mgr->shutdown) {
+ error = GRPC_ERROR_CREATE_FROM_STATIC_STRING("handshaker shutdown");
+ // It is possible that the endpoint has already been destroyed by
+ // a shutdown call while this callback was sitting on the ExecCtx
+ // with no error.
+ if (mgr->args.endpoint != nullptr) {
+ // TODO(roth): It is currently necessary to shutdown endpoints
+ // before destroying then, even when we know that there are no
+ // pending read/write callbacks. This should be fixed, at which
+ // point this can be removed.
+ grpc_endpoint_shutdown(mgr->args.endpoint, GRPC_ERROR_REF(error));
+ grpc_endpoint_destroy(mgr->args.endpoint);
+ mgr->args.endpoint = nullptr;
+ grpc_channel_args_destroy(mgr->args.args);
+ mgr->args.args = nullptr;
+ grpc_slice_buffer_destroy_internal(mgr->args.read_buffer);
+ gpr_free(mgr->args.read_buffer);
+ mgr->args.read_buffer = nullptr;
+ }
+ }
if (grpc_handshaker_trace.enabled()) {
- gpr_log(GPR_INFO, "handshake_manager %p: handshaking complete", mgr);
+ gpr_log(GPR_INFO,
+ "handshake_manager %p: handshaking complete -- scheduling "
+ "on_handshake_done with error=%s",
+ mgr, grpc_error_string(error));
}
// Cancel deadline timer, since we're invoking the on_handshake_done
// callback now.
diff --git a/src/core/lib/debug/stats.h b/src/core/lib/debug/stats.h
index 749665262a..9e88ad7000 100644
--- a/src/core/lib/debug/stats.h
+++ b/src/core/lib/debug/stats.h
@@ -35,6 +35,9 @@ extern grpc_stats_data* grpc_stats_per_cpu_storage;
#define GRPC_THREAD_STATS_DATA() \
(&grpc_stats_per_cpu_storage[grpc_core::ExecCtx::Get()->starting_cpu()])
+/* Only collect stats if GRPC_COLLECT_STATS is defined or it is a debug build.
+ */
+#if defined(GRPC_COLLECT_STATS) || !defined(NDEBUG)
#define GRPC_STATS_INC_COUNTER(ctr) \
(gpr_atm_no_barrier_fetch_add(&GRPC_THREAD_STATS_DATA()->counters[(ctr)], 1))
@@ -42,6 +45,10 @@ extern grpc_stats_data* grpc_stats_per_cpu_storage;
(gpr_atm_no_barrier_fetch_add( \
&GRPC_THREAD_STATS_DATA()->histograms[histogram##_FIRST_SLOT + (index)], \
1))
+#else /* defined(GRPC_COLLECT_STATS) || !defined(NDEBUG) */
+#define GRPC_STATS_INC_COUNTER(ctr)
+#define GRPC_STATS_INC_HISTOGRAM(histogram, index)
+#endif /* defined(GRPC_COLLECT_STATS) || !defined(NDEBUG) */
void grpc_stats_init(void);
void grpc_stats_shutdown(void);
diff --git a/src/core/lib/debug/stats_data.cc b/src/core/lib/debug/stats_data.cc
index 309ece94bb..f8c27db0a8 100644
--- a/src/core/lib/debug/stats_data.cc
+++ b/src/core/lib/debug/stats_data.cc
@@ -40,6 +40,8 @@ const char* grpc_stats_counter_name[GRPC_STATS_COUNTER_COUNT] = {
"pollset_kick_wakeup_fd",
"pollset_kick_wakeup_cv",
"pollset_kick_own_thread",
+ "syscall_epoll_ctl",
+ "pollset_fd_cache_hits",
"histogram_slow_lookups",
"syscall_write",
"syscall_read",
@@ -144,6 +146,9 @@ const char* grpc_stats_counter_doc[GRPC_STATS_COUNTER_COUNT] = {
"polling wakeup (only valid for epoll1 right now)",
"How many times could a polling wakeup be satisfied by keeping the waking "
"thread awake? (only valid for epoll1 right now)",
+ "Number of epoll_ctl calls made (only valid for epollex right now)",
+ "Number of epoll_ctl calls skipped because the fd was cached as already "
+ "being added. (only valid for epollex right now)",
"Number of times histogram increments went through the slow (binary "
"search) path",
"Number of write syscalls (or equivalent - eg sendmsg) made by this "
diff --git a/src/core/lib/debug/stats_data.h b/src/core/lib/debug/stats_data.h
index da1266ad73..1f3861f494 100644
--- a/src/core/lib/debug/stats_data.h
+++ b/src/core/lib/debug/stats_data.h
@@ -41,6 +41,8 @@ typedef enum {
GRPC_STATS_COUNTER_POLLSET_KICK_WAKEUP_FD,
GRPC_STATS_COUNTER_POLLSET_KICK_WAKEUP_CV,
GRPC_STATS_COUNTER_POLLSET_KICK_OWN_THREAD,
+ GRPC_STATS_COUNTER_SYSCALL_EPOLL_CTL,
+ GRPC_STATS_COUNTER_POLLSET_FD_CACHE_HITS,
GRPC_STATS_COUNTER_HISTOGRAM_SLOW_LOOKUPS,
GRPC_STATS_COUNTER_SYSCALL_WRITE,
GRPC_STATS_COUNTER_SYSCALL_READ,
@@ -174,6 +176,7 @@ typedef enum {
GRPC_STATS_HISTOGRAM_SERVER_CQS_CHECKED_BUCKETS = 8,
GRPC_STATS_HISTOGRAM_BUCKETS = 840
} grpc_stats_histogram_constants;
+#if defined(GRPC_COLLECT_STATS) || !defined(NDEBUG)
#define GRPC_STATS_INC_CLIENT_CALLS_CREATED() \
GRPC_STATS_INC_COUNTER(GRPC_STATS_COUNTER_CLIENT_CALLS_CREATED)
#define GRPC_STATS_INC_SERVER_CALLS_CREATED() \
@@ -202,6 +205,10 @@ typedef enum {
GRPC_STATS_INC_COUNTER(GRPC_STATS_COUNTER_POLLSET_KICK_WAKEUP_CV)
#define GRPC_STATS_INC_POLLSET_KICK_OWN_THREAD() \
GRPC_STATS_INC_COUNTER(GRPC_STATS_COUNTER_POLLSET_KICK_OWN_THREAD)
+#define GRPC_STATS_INC_SYSCALL_EPOLL_CTL() \
+ GRPC_STATS_INC_COUNTER(GRPC_STATS_COUNTER_SYSCALL_EPOLL_CTL)
+#define GRPC_STATS_INC_POLLSET_FD_CACHE_HITS() \
+ GRPC_STATS_INC_COUNTER(GRPC_STATS_COUNTER_POLLSET_FD_CACHE_HITS)
#define GRPC_STATS_INC_HISTOGRAM_SLOW_LOOKUPS() \
GRPC_STATS_INC_COUNTER(GRPC_STATS_COUNTER_HISTOGRAM_SLOW_LOOKUPS)
#define GRPC_STATS_INC_SYSCALL_WRITE() \
@@ -427,6 +434,119 @@ void grpc_stats_inc_http2_send_flowctl_per_write(int x);
#define GRPC_STATS_INC_SERVER_CQS_CHECKED(value) \
grpc_stats_inc_server_cqs_checked((int)(value))
void grpc_stats_inc_server_cqs_checked(int x);
+#else
+#define GRPC_STATS_INC_CLIENT_CALLS_CREATED()
+#define GRPC_STATS_INC_SERVER_CALLS_CREATED()
+#define GRPC_STATS_INC_CQS_CREATED()
+#define GRPC_STATS_INC_CLIENT_CHANNELS_CREATED()
+#define GRPC_STATS_INC_CLIENT_SUBCHANNELS_CREATED()
+#define GRPC_STATS_INC_SERVER_CHANNELS_CREATED()
+#define GRPC_STATS_INC_SYSCALL_POLL()
+#define GRPC_STATS_INC_SYSCALL_WAIT()
+#define GRPC_STATS_INC_POLLSET_KICK()
+#define GRPC_STATS_INC_POLLSET_KICKED_WITHOUT_POLLER()
+#define GRPC_STATS_INC_POLLSET_KICKED_AGAIN()
+#define GRPC_STATS_INC_POLLSET_KICK_WAKEUP_FD()
+#define GRPC_STATS_INC_POLLSET_KICK_WAKEUP_CV()
+#define GRPC_STATS_INC_POLLSET_KICK_OWN_THREAD()
+#define GRPC_STATS_INC_SYSCALL_EPOLL_CTL()
+#define GRPC_STATS_INC_POLLSET_FD_CACHE_HITS()
+#define GRPC_STATS_INC_HISTOGRAM_SLOW_LOOKUPS()
+#define GRPC_STATS_INC_SYSCALL_WRITE()
+#define GRPC_STATS_INC_SYSCALL_READ()
+#define GRPC_STATS_INC_TCP_BACKUP_POLLERS_CREATED()
+#define GRPC_STATS_INC_TCP_BACKUP_POLLER_POLLS()
+#define GRPC_STATS_INC_HTTP2_OP_BATCHES()
+#define GRPC_STATS_INC_HTTP2_OP_CANCEL()
+#define GRPC_STATS_INC_HTTP2_OP_SEND_INITIAL_METADATA()
+#define GRPC_STATS_INC_HTTP2_OP_SEND_MESSAGE()
+#define GRPC_STATS_INC_HTTP2_OP_SEND_TRAILING_METADATA()
+#define GRPC_STATS_INC_HTTP2_OP_RECV_INITIAL_METADATA()
+#define GRPC_STATS_INC_HTTP2_OP_RECV_MESSAGE()
+#define GRPC_STATS_INC_HTTP2_OP_RECV_TRAILING_METADATA()
+#define GRPC_STATS_INC_HTTP2_SETTINGS_WRITES()
+#define GRPC_STATS_INC_HTTP2_PINGS_SENT()
+#define GRPC_STATS_INC_HTTP2_WRITES_BEGUN()
+#define GRPC_STATS_INC_HTTP2_WRITES_OFFLOADED()
+#define GRPC_STATS_INC_HTTP2_WRITES_CONTINUED()
+#define GRPC_STATS_INC_HTTP2_PARTIAL_WRITES()
+#define GRPC_STATS_INC_HTTP2_INITIATE_WRITE_DUE_TO_INITIAL_WRITE()
+#define GRPC_STATS_INC_HTTP2_INITIATE_WRITE_DUE_TO_START_NEW_STREAM()
+#define GRPC_STATS_INC_HTTP2_INITIATE_WRITE_DUE_TO_SEND_MESSAGE()
+#define GRPC_STATS_INC_HTTP2_INITIATE_WRITE_DUE_TO_SEND_INITIAL_METADATA()
+#define GRPC_STATS_INC_HTTP2_INITIATE_WRITE_DUE_TO_SEND_TRAILING_METADATA()
+#define GRPC_STATS_INC_HTTP2_INITIATE_WRITE_DUE_TO_RETRY_SEND_PING()
+#define GRPC_STATS_INC_HTTP2_INITIATE_WRITE_DUE_TO_CONTINUE_PINGS()
+#define GRPC_STATS_INC_HTTP2_INITIATE_WRITE_DUE_TO_GOAWAY_SENT()
+#define GRPC_STATS_INC_HTTP2_INITIATE_WRITE_DUE_TO_RST_STREAM()
+#define GRPC_STATS_INC_HTTP2_INITIATE_WRITE_DUE_TO_CLOSE_FROM_API()
+#define GRPC_STATS_INC_HTTP2_INITIATE_WRITE_DUE_TO_STREAM_FLOW_CONTROL()
+#define GRPC_STATS_INC_HTTP2_INITIATE_WRITE_DUE_TO_TRANSPORT_FLOW_CONTROL()
+#define GRPC_STATS_INC_HTTP2_INITIATE_WRITE_DUE_TO_SEND_SETTINGS()
+#define GRPC_STATS_INC_HTTP2_INITIATE_WRITE_DUE_TO_BDP_ESTIMATOR_PING()
+#define GRPC_STATS_INC_HTTP2_INITIATE_WRITE_DUE_TO_FLOW_CONTROL_UNSTALLED_BY_SETTING()
+#define GRPC_STATS_INC_HTTP2_INITIATE_WRITE_DUE_TO_FLOW_CONTROL_UNSTALLED_BY_UPDATE()
+#define GRPC_STATS_INC_HTTP2_INITIATE_WRITE_DUE_TO_APPLICATION_PING()
+#define GRPC_STATS_INC_HTTP2_INITIATE_WRITE_DUE_TO_KEEPALIVE_PING()
+#define GRPC_STATS_INC_HTTP2_INITIATE_WRITE_DUE_TO_TRANSPORT_FLOW_CONTROL_UNSTALLED()
+#define GRPC_STATS_INC_HTTP2_INITIATE_WRITE_DUE_TO_PING_RESPONSE()
+#define GRPC_STATS_INC_HTTP2_INITIATE_WRITE_DUE_TO_FORCE_RST_STREAM()
+#define GRPC_STATS_INC_HTTP2_SPURIOUS_WRITES_BEGUN()
+#define GRPC_STATS_INC_HPACK_RECV_INDEXED()
+#define GRPC_STATS_INC_HPACK_RECV_LITHDR_INCIDX()
+#define GRPC_STATS_INC_HPACK_RECV_LITHDR_INCIDX_V()
+#define GRPC_STATS_INC_HPACK_RECV_LITHDR_NOTIDX()
+#define GRPC_STATS_INC_HPACK_RECV_LITHDR_NOTIDX_V()
+#define GRPC_STATS_INC_HPACK_RECV_LITHDR_NVRIDX()
+#define GRPC_STATS_INC_HPACK_RECV_LITHDR_NVRIDX_V()
+#define GRPC_STATS_INC_HPACK_RECV_UNCOMPRESSED()
+#define GRPC_STATS_INC_HPACK_RECV_HUFFMAN()
+#define GRPC_STATS_INC_HPACK_RECV_BINARY()
+#define GRPC_STATS_INC_HPACK_RECV_BINARY_BASE64()
+#define GRPC_STATS_INC_HPACK_SEND_INDEXED()
+#define GRPC_STATS_INC_HPACK_SEND_LITHDR_INCIDX()
+#define GRPC_STATS_INC_HPACK_SEND_LITHDR_INCIDX_V()
+#define GRPC_STATS_INC_HPACK_SEND_LITHDR_NOTIDX()
+#define GRPC_STATS_INC_HPACK_SEND_LITHDR_NOTIDX_V()
+#define GRPC_STATS_INC_HPACK_SEND_LITHDR_NVRIDX()
+#define GRPC_STATS_INC_HPACK_SEND_LITHDR_NVRIDX_V()
+#define GRPC_STATS_INC_HPACK_SEND_UNCOMPRESSED()
+#define GRPC_STATS_INC_HPACK_SEND_HUFFMAN()
+#define GRPC_STATS_INC_HPACK_SEND_BINARY()
+#define GRPC_STATS_INC_HPACK_SEND_BINARY_BASE64()
+#define GRPC_STATS_INC_COMBINER_LOCKS_INITIATED()
+#define GRPC_STATS_INC_COMBINER_LOCKS_SCHEDULED_ITEMS()
+#define GRPC_STATS_INC_COMBINER_LOCKS_SCHEDULED_FINAL_ITEMS()
+#define GRPC_STATS_INC_COMBINER_LOCKS_OFFLOADED()
+#define GRPC_STATS_INC_CALL_COMBINER_LOCKS_INITIATED()
+#define GRPC_STATS_INC_CALL_COMBINER_LOCKS_SCHEDULED_ITEMS()
+#define GRPC_STATS_INC_CALL_COMBINER_SET_NOTIFY_ON_CANCEL()
+#define GRPC_STATS_INC_CALL_COMBINER_CANCELLED()
+#define GRPC_STATS_INC_EXECUTOR_SCHEDULED_SHORT_ITEMS()
+#define GRPC_STATS_INC_EXECUTOR_SCHEDULED_LONG_ITEMS()
+#define GRPC_STATS_INC_EXECUTOR_SCHEDULED_TO_SELF()
+#define GRPC_STATS_INC_EXECUTOR_WAKEUP_INITIATED()
+#define GRPC_STATS_INC_EXECUTOR_QUEUE_DRAINED()
+#define GRPC_STATS_INC_EXECUTOR_PUSH_RETRIES()
+#define GRPC_STATS_INC_SERVER_REQUESTED_CALLS()
+#define GRPC_STATS_INC_SERVER_SLOWPATH_REQUESTS_QUEUED()
+#define GRPC_STATS_INC_CQ_EV_QUEUE_TRYLOCK_FAILURES()
+#define GRPC_STATS_INC_CQ_EV_QUEUE_TRYLOCK_SUCCESSES()
+#define GRPC_STATS_INC_CQ_EV_QUEUE_TRANSIENT_POP_FAILURES()
+#define GRPC_STATS_INC_CALL_INITIAL_SIZE(value)
+#define GRPC_STATS_INC_POLL_EVENTS_RETURNED(value)
+#define GRPC_STATS_INC_TCP_WRITE_SIZE(value)
+#define GRPC_STATS_INC_TCP_WRITE_IOV_SIZE(value)
+#define GRPC_STATS_INC_TCP_READ_SIZE(value)
+#define GRPC_STATS_INC_TCP_READ_OFFER(value)
+#define GRPC_STATS_INC_TCP_READ_OFFER_IOV_SIZE(value)
+#define GRPC_STATS_INC_HTTP2_SEND_MESSAGE_SIZE(value)
+#define GRPC_STATS_INC_HTTP2_SEND_INITIAL_METADATA_PER_WRITE(value)
+#define GRPC_STATS_INC_HTTP2_SEND_MESSAGE_PER_WRITE(value)
+#define GRPC_STATS_INC_HTTP2_SEND_TRAILING_METADATA_PER_WRITE(value)
+#define GRPC_STATS_INC_HTTP2_SEND_FLOWCTL_PER_WRITE(value)
+#define GRPC_STATS_INC_SERVER_CQS_CHECKED(value)
+#endif /* defined(GRPC_COLLECT_STATS) || !defined(NDEBUG) */
extern const int grpc_stats_histo_buckets[13];
extern const int grpc_stats_histo_start[13];
extern const int* const grpc_stats_histo_bucket_boundaries[13];
diff --git a/src/core/lib/debug/stats_data.yaml b/src/core/lib/debug/stats_data.yaml
index af4553028e..775b09df74 100644
--- a/src/core/lib/debug/stats_data.yaml
+++ b/src/core/lib/debug/stats_data.yaml
@@ -63,6 +63,12 @@
doc: How many times could a polling wakeup be satisfied by keeping the waking
thread awake?
(only valid for epoll1 right now)
+# polling
+- counter: syscall_epoll_ctl
+ doc: Number of epoll_ctl calls made (only valid for epollex right now)
+- counter: pollset_fd_cache_hits
+ doc: Number of epoll_ctl calls skipped because the fd was cached as
+ already being added. (only valid for epollex right now)
# stats system
- counter: histogram_slow_lookups
doc: Number of times histogram increments went through the slow
diff --git a/src/core/lib/debug/stats_data_bq_schema.sql b/src/core/lib/debug/stats_data_bq_schema.sql
index 04b6d471f6..7d1ab1dae9 100644
--- a/src/core/lib/debug/stats_data_bq_schema.sql
+++ b/src/core/lib/debug/stats_data_bq_schema.sql
@@ -12,6 +12,8 @@ pollset_kicked_again_per_iteration:FLOAT,
pollset_kick_wakeup_fd_per_iteration:FLOAT,
pollset_kick_wakeup_cv_per_iteration:FLOAT,
pollset_kick_own_thread_per_iteration:FLOAT,
+syscall_epoll_ctl_per_iteration:FLOAT,
+pollset_fd_cache_hits_per_iteration:FLOAT,
histogram_slow_lookups_per_iteration:FLOAT,
syscall_write_per_iteration:FLOAT,
syscall_read_per_iteration:FLOAT,
diff --git a/src/core/lib/debug/trace.h b/src/core/lib/debug/trace.h
index 28157c6383..fe6301a3fc 100644
--- a/src/core/lib/debug/trace.h
+++ b/src/core/lib/debug/trace.h
@@ -57,14 +57,14 @@ class TraceFlag {
const char* name() const { return name_; }
-// This following define may be commented out to ensure that the compiler
-// deletes any "if (tracer.enabled()) {...}" codeblocks. This is useful to
-// test the performance impact tracers have on the system.
-//
-// #define COMPILE_OUT_ALL_TRACERS_IN_OPT_BUILD
-#ifdef COMPILE_OUT_ALL_TRACERS_IN_OPT_BUILD
- bool enabled() { return false; }
-#else
+// Use the symbol GRPC_USE_TRACERS to determine if tracers will be enabled in
+// opt builds (tracers are always on in dbg builds). The default in OSS is for
+// tracers to be on since we support binary distributions of gRPC for the
+// wrapped language (wr don't want to force recompilation to get tracing).
+// Internally, however, for performance reasons, we compile them out by
+// default, since internal build systems make recompiling trivial.
+#define GRPC_USE_TRACERS // tracers on by default in OSS
+#if defined(GRPC_USE_TRACERS) || !defined(NDEBUG)
bool enabled() {
#ifdef GRPC_THREADSAFE_TRACER
return gpr_atm_no_barrier_load(&value_) != 0;
@@ -72,7 +72,9 @@ class TraceFlag {
return value_;
#endif // GRPC_THREADSAFE_TRACER
}
-#endif // COMPILE_OUT_ALL_TRACERS_IN_OPT_BUILD
+#else
+ bool enabled() { return false; }
+#endif /* defined(GRPC_USE_TRACERS) || !defined(NDEBUG) */
private:
friend void grpc_core::testing::grpc_tracer_enable_flag(TraceFlag* flag);
diff --git a/src/core/lib/gpr/alloc.h b/src/core/lib/gpr/alloc.h
new file mode 100644
index 0000000000..762b51bf66
--- /dev/null
+++ b/src/core/lib/gpr/alloc.h
@@ -0,0 +1,28 @@
+/*
+ *
+ * Copyright 2018 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_GPR_ALLOC_H
+#define GRPC_CORE_LIB_GPR_ALLOC_H
+
+#include <grpc/support/port_platform.h>
+
+/// Given a size, round up to the next multiple of sizeof(void*).
+#define GPR_ROUND_UP_TO_ALIGNMENT_SIZE(x) \
+ (((x) + GPR_MAX_ALIGNMENT - 1u) & ~(GPR_MAX_ALIGNMENT - 1u))
+
+#endif /* GRPC_CORE_LIB_GPR_ALLOC_H */
diff --git a/src/core/lib/gpr/arena.cc b/src/core/lib/gpr/arena.cc
index b02c5b9fb6..77f9357146 100644
--- a/src/core/lib/gpr/arena.cc
+++ b/src/core/lib/gpr/arena.cc
@@ -25,6 +25,9 @@
#include <grpc/support/alloc.h>
#include <grpc/support/atm.h>
#include <grpc/support/log.h>
+#include <grpc/support/sync.h>
+
+#include "src/core/lib/gpr/alloc.h"
// Uncomment this to use a simple arena that simply allocates the
// requested amount of memory for each call to gpr_arena_alloc(). This
@@ -34,8 +37,6 @@
#ifdef SIMPLE_ARENA_FOR_DEBUGGING
-#include <grpc/support/sync.h>
-
struct gpr_arena {
gpr_mu mu;
void** ptrs;
@@ -74,18 +75,19 @@ void* gpr_arena_alloc(gpr_arena* arena, size_t size) {
// arena API to C++, we should consider replacing gpr_arena_alloc() with a
// template that takes the type of the value being allocated, which
// would allow us to use the alignment actually needed by the caller.
-#define ROUND_UP_TO_ALIGNMENT_SIZE(x) \
- (((x) + GPR_MAX_ALIGNMENT - 1u) & ~(GPR_MAX_ALIGNMENT - 1u))
typedef struct zone {
- size_t size_begin;
- size_t size_end;
- gpr_atm next_atm;
+ zone* next;
} zone;
struct gpr_arena {
- gpr_atm size_so_far;
+ // Keep track of the total used size. We use this in our call sizing
+ // historesis.
+ gpr_atm total_used;
+ size_t initial_zone_size;
zone initial_zone;
+ zone* last_zone;
+ gpr_mu arena_growth_mutex;
};
static void* zalloc_aligned(size_t size) {
@@ -95,19 +97,22 @@ static void* zalloc_aligned(size_t size) {
}
gpr_arena* gpr_arena_create(size_t initial_size) {
- initial_size = ROUND_UP_TO_ALIGNMENT_SIZE(initial_size);
+ initial_size = GPR_ROUND_UP_TO_ALIGNMENT_SIZE(initial_size);
gpr_arena* a = static_cast<gpr_arena*>(zalloc_aligned(
- ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(gpr_arena)) + initial_size));
- a->initial_zone.size_end = initial_size;
+ GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(gpr_arena)) + initial_size));
+ a->initial_zone_size = initial_size;
+ a->last_zone = &a->initial_zone;
+ gpr_mu_init(&a->arena_growth_mutex);
return a;
}
size_t gpr_arena_destroy(gpr_arena* arena) {
- gpr_atm size = gpr_atm_no_barrier_load(&arena->size_so_far);
- zone* z = (zone*)gpr_atm_no_barrier_load(&arena->initial_zone.next_atm);
+ gpr_mu_destroy(&arena->arena_growth_mutex);
+ gpr_atm size = gpr_atm_no_barrier_load(&arena->total_used);
+ zone* z = arena->initial_zone.next;
gpr_free_aligned(arena);
while (z) {
- zone* next_z = (zone*)gpr_atm_no_barrier_load(&z->next_atm);
+ zone* next_z = z->next;
gpr_free_aligned(z);
z = next_z;
}
@@ -115,38 +120,26 @@ size_t gpr_arena_destroy(gpr_arena* arena) {
}
void* gpr_arena_alloc(gpr_arena* arena, size_t size) {
- size = ROUND_UP_TO_ALIGNMENT_SIZE(size);
- size_t start = static_cast<size_t>(
- gpr_atm_no_barrier_fetch_add(&arena->size_so_far, size));
- zone* z = &arena->initial_zone;
- while (start > z->size_end) {
- zone* next_z = (zone*)gpr_atm_acq_load(&z->next_atm);
- if (next_z == nullptr) {
- size_t next_z_size =
- static_cast<size_t>(gpr_atm_no_barrier_load(&arena->size_so_far));
- next_z = static_cast<zone*>(zalloc_aligned(
- ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(zone)) + next_z_size));
- next_z->size_begin = z->size_end;
- next_z->size_end = z->size_end + next_z_size;
- if (!gpr_atm_rel_cas(&z->next_atm, static_cast<gpr_atm>(NULL),
- (gpr_atm)next_z)) {
- gpr_free_aligned(next_z);
- next_z = (zone*)gpr_atm_acq_load(&z->next_atm);
- }
- }
- z = next_z;
- }
- if (start + size > z->size_end) {
- return gpr_arena_alloc(arena, size);
+ size = GPR_ROUND_UP_TO_ALIGNMENT_SIZE(size);
+ size_t begin = gpr_atm_no_barrier_fetch_add(&arena->total_used, size);
+ if (begin + size <= arena->initial_zone_size) {
+ return reinterpret_cast<char*>(arena) +
+ GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(gpr_arena)) + begin;
+ } else {
+ // If the allocation isn't able to end in the initial zone, create a new
+ // zone for this allocation, and any unused space in the initial zone is
+ // wasted. This overflowing and wasting is uncommon because of our arena
+ // sizing historesis (that is, most calls should have a large enough initial
+ // zone and will not need to grow the arena).
+ gpr_mu_lock(&arena->arena_growth_mutex);
+ zone* z = static_cast<zone*>(
+ zalloc_aligned(GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(zone)) + size));
+ arena->last_zone->next = z;
+ arena->last_zone = z;
+ gpr_mu_unlock(&arena->arena_growth_mutex);
+ return reinterpret_cast<char*>(z) +
+ GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(zone));
}
- GPR_ASSERT(start >= z->size_begin);
- GPR_ASSERT(start + size <= z->size_end);
- char* ptr = (z == &arena->initial_zone)
- ? reinterpret_cast<char*>(arena) +
- ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(gpr_arena))
- : reinterpret_cast<char*>(z) +
- ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(zone));
- return ptr + start - z->size_begin;
}
#endif // SIMPLE_ARENA_FOR_DEBUGGING
diff --git a/src/core/lib/gpr/fork.cc b/src/core/lib/gpr/fork.cc
deleted file mode 100644
index 812522b058..0000000000
--- a/src/core/lib/gpr/fork.cc
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- *
- * Copyright 2017 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/gpr/fork.h"
-
-#include <string.h>
-
-#include <grpc/support/alloc.h>
-
-#include "src/core/lib/gpr/env.h"
-#include "src/core/lib/gpr/useful.h"
-
-/*
- * NOTE: FORKING IS NOT GENERALLY SUPPORTED, THIS IS ONLY INTENDED TO WORK
- * AROUND VERY SPECIFIC USE CASES.
- */
-
-static int override_fork_support_enabled = -1;
-static int fork_support_enabled;
-
-void grpc_fork_support_init() {
-#ifdef GRPC_ENABLE_FORK_SUPPORT
- fork_support_enabled = 1;
-#else
- fork_support_enabled = 0;
-#endif
- bool env_var_set = false;
- char* env = gpr_getenv("GRPC_ENABLE_FORK_SUPPORT");
- if (env != nullptr) {
- static const char* truthy[] = {"yes", "Yes", "YES", "true",
- "True", "TRUE", "1"};
- static const char* falsey[] = {"no", "No", "NO", "false",
- "False", "FALSE", "0"};
- for (size_t i = 0; i < GPR_ARRAY_SIZE(truthy); i++) {
- if (0 == strcmp(env, truthy[i])) {
- fork_support_enabled = 1;
- env_var_set = true;
- break;
- }
- }
- if (!env_var_set) {
- for (size_t i = 0; i < GPR_ARRAY_SIZE(falsey); i++) {
- if (0 == strcmp(env, falsey[i])) {
- fork_support_enabled = 0;
- env_var_set = true;
- break;
- }
- }
- }
- gpr_free(env);
- }
- if (override_fork_support_enabled != -1) {
- fork_support_enabled = override_fork_support_enabled;
- }
-}
-
-int grpc_fork_support_enabled() { return fork_support_enabled; }
-
-void grpc_enable_fork_support(int enable) {
- override_fork_support_enabled = enable;
-}
diff --git a/src/core/lib/gpr/string.cc b/src/core/lib/gpr/string.cc
index ef2a6900b4..0a76fc1f54 100644
--- a/src/core/lib/gpr/string.cc
+++ b/src/core/lib/gpr/string.cc
@@ -23,8 +23,10 @@
#include <ctype.h>
#include <limits.h>
#include <stddef.h>
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <time.h>
#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
@@ -54,6 +56,32 @@ typedef struct {
char* data;
} dump_out;
+char* gpr_format_timespec(gpr_timespec tm) {
+ char time_buffer[35];
+ char ns_buffer[11]; // '.' + 9 digits of precision
+ struct tm* tm_info = localtime((const time_t*)&tm.tv_sec);
+ strftime(time_buffer, sizeof(time_buffer), "%Y-%m-%dT%H:%M:%S", tm_info);
+ snprintf(ns_buffer, 11, ".%09d", tm.tv_nsec);
+ // This loop trims off trailing zeros by inserting a null character that the
+ // right point. We iterate in chunks of three because we want 0, 3, 6, or 9
+ // fractional digits.
+ for (int i = 7; i >= 1; i -= 3) {
+ if (ns_buffer[i] == '0' && ns_buffer[i + 1] == '0' &&
+ ns_buffer[i + 2] == '0') {
+ ns_buffer[i] = '\0';
+ // Edge case in which all fractional digits were 0.
+ if (i == 1) {
+ ns_buffer[0] = '\0';
+ }
+ } else {
+ break;
+ }
+ }
+ char* full_time_str;
+ gpr_asprintf(&full_time_str, "%s%sZ", time_buffer, ns_buffer);
+ return full_time_str;
+}
+
static dump_out dump_out_create(void) {
dump_out r = {0, 0, nullptr};
return r;
diff --git a/src/core/lib/gpr/string.h b/src/core/lib/gpr/string.h
index 2e8a4898d9..ce51fe4632 100644
--- a/src/core/lib/gpr/string.h
+++ b/src/core/lib/gpr/string.h
@@ -21,6 +21,8 @@
#include <grpc/support/port_platform.h>
+#include <grpc/impl/codegen/gpr_types.h>
+
#include <stdbool.h>
#include <stddef.h>
@@ -81,6 +83,14 @@ char* gpr_strjoin_sep(const char** strs, size_t nstrs, const char* sep,
void gpr_string_split(const char* input, const char* sep, char*** strs,
size_t* nstrs);
+/* Returns an allocated string that represents tm according to RFC-3339, and,
+ more specifically, follows:
+ https://developers.google.com/protocol-buffers/docs/proto3#json
+
+ Uses RFC 3339, where generated output will always be Z-normalized and uses
+ 0, 3, 6 or 9 fractional digits. */
+char* gpr_format_timespec(gpr_timespec);
+
/* A vector of strings... for building up a final string one piece at a time */
typedef struct {
char** strs;
diff --git a/src/core/lib/gprpp/abstract.h b/src/core/lib/gprpp/abstract.h
index cc96edc49b..5b7018e07e 100644
--- a/src/core/lib/gprpp/abstract.h
+++ b/src/core/lib/gprpp/abstract.h
@@ -28,7 +28,10 @@
// gRPC currently can't depend on libstdc++, so we can't use "= 0" for
// pure virtual methods. Instead, we use this macro.
-#define GRPC_ABSTRACT \
- { GPR_ASSERT(false); }
+#define GRPC_ABSTRACT \
+ { \
+ gpr_log(GPR_ERROR, "Function marked GRPC_ABSTRACT was not implemented"); \
+ GPR_ASSERT(false); \
+ }
#endif /* GRPC_CORE_LIB_GPRPP_ABSTRACT_H */
diff --git a/src/core/lib/gprpp/fork.cc b/src/core/lib/gprpp/fork.cc
new file mode 100644
index 0000000000..3b9c16510a
--- /dev/null
+++ b/src/core/lib/gprpp/fork.cc
@@ -0,0 +1,268 @@
+/*
+ *
+ * Copyright 2017 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/gprpp/fork.h"
+
+#include <string.h>
+
+#include <grpc/support/alloc.h>
+#include <grpc/support/sync.h>
+#include <grpc/support/time.h>
+
+#include "src/core/lib/gpr/env.h"
+#include "src/core/lib/gpr/useful.h"
+#include "src/core/lib/gprpp/memory.h"
+
+/*
+ * NOTE: FORKING IS NOT GENERALLY SUPPORTED, THIS IS ONLY INTENDED TO WORK
+ * AROUND VERY SPECIFIC USE CASES.
+ */
+
+namespace grpc_core {
+namespace internal {
+// The exec_ctx_count has 2 modes, blocked and unblocked.
+// When unblocked, the count is 2-indexed; exec_ctx_count=2 indicates
+// 0 active ExecCtxs, exex_ctx_count=3 indicates 1 active ExecCtxs...
+
+// When blocked, the exec_ctx_count is 0-indexed. Note that ExecCtx
+// creation can only be blocked if there is exactly 1 outstanding ExecCtx,
+// meaning that BLOCKED and UNBLOCKED counts partition the integers
+#define UNBLOCKED(n) (n + 2)
+#define BLOCKED(n) (n)
+
+class ExecCtxState {
+ public:
+ ExecCtxState() : fork_complete_(true) {
+ gpr_mu_init(&mu_);
+ gpr_cv_init(&cv_);
+ gpr_atm_no_barrier_store(&count_, UNBLOCKED(0));
+ }
+
+ void IncExecCtxCount() {
+ gpr_atm count = gpr_atm_no_barrier_load(&count_);
+ while (true) {
+ if (count <= BLOCKED(1)) {
+ // This only occurs if we are trying to fork. Wait until the fork()
+ // operation completes before allowing new ExecCtxs.
+ gpr_mu_lock(&mu_);
+ if (gpr_atm_no_barrier_load(&count_) <= BLOCKED(1)) {
+ while (!fork_complete_) {
+ gpr_cv_wait(&cv_, &mu_, gpr_inf_future(GPR_CLOCK_REALTIME));
+ }
+ }
+ gpr_mu_unlock(&mu_);
+ } else if (gpr_atm_no_barrier_cas(&count_, count, count + 1)) {
+ break;
+ }
+ count = gpr_atm_no_barrier_load(&count_);
+ }
+ }
+
+ void DecExecCtxCount() { gpr_atm_no_barrier_fetch_add(&count_, -1); }
+
+ bool BlockExecCtx() {
+ // Assumes there is an active ExecCtx when this function is called
+ if (gpr_atm_no_barrier_cas(&count_, UNBLOCKED(1), BLOCKED(1))) {
+ gpr_mu_lock(&mu_);
+ fork_complete_ = false;
+ gpr_mu_unlock(&mu_);
+ return true;
+ }
+ return false;
+ }
+
+ void AllowExecCtx() {
+ gpr_mu_lock(&mu_);
+ gpr_atm_no_barrier_store(&count_, UNBLOCKED(0));
+ fork_complete_ = true;
+ gpr_cv_broadcast(&cv_);
+ gpr_mu_unlock(&mu_);
+ }
+
+ ~ExecCtxState() {
+ gpr_mu_destroy(&mu_);
+ gpr_cv_destroy(&cv_);
+ }
+
+ private:
+ bool fork_complete_;
+ gpr_mu mu_;
+ gpr_cv cv_;
+ gpr_atm count_;
+};
+
+class ThreadState {
+ public:
+ ThreadState() : awaiting_threads_(false), threads_done_(false), count_(0) {
+ gpr_mu_init(&mu_);
+ gpr_cv_init(&cv_);
+ }
+
+ void IncThreadCount() {
+ gpr_mu_lock(&mu_);
+ count_++;
+ gpr_mu_unlock(&mu_);
+ }
+
+ void DecThreadCount() {
+ gpr_mu_lock(&mu_);
+ count_--;
+ if (awaiting_threads_ && count_ == 0) {
+ threads_done_ = true;
+ gpr_cv_signal(&cv_);
+ }
+ gpr_mu_unlock(&mu_);
+ }
+ void AwaitThreads() {
+ gpr_mu_lock(&mu_);
+ awaiting_threads_ = true;
+ threads_done_ = (count_ == 0);
+ while (!threads_done_) {
+ gpr_cv_wait(&cv_, &mu_, gpr_inf_future(GPR_CLOCK_REALTIME));
+ }
+ awaiting_threads_ = true;
+ gpr_mu_unlock(&mu_);
+ }
+
+ ~ThreadState() {
+ gpr_mu_destroy(&mu_);
+ gpr_cv_destroy(&cv_);
+ }
+
+ private:
+ bool awaiting_threads_;
+ bool threads_done_;
+ gpr_mu mu_;
+ gpr_cv cv_;
+ int count_;
+};
+
+} // namespace
+
+void Fork::GlobalInit() {
+ if (!override_enabled_) {
+#ifdef GRPC_ENABLE_FORK_SUPPORT
+ support_enabled_ = true;
+#else
+ support_enabled_ = false;
+#endif
+ bool env_var_set = false;
+ char* env = gpr_getenv("GRPC_ENABLE_FORK_SUPPORT");
+ if (env != nullptr) {
+ static const char* truthy[] = {"yes", "Yes", "YES", "true",
+ "True", "TRUE", "1"};
+ static const char* falsey[] = {"no", "No", "NO", "false",
+ "False", "FALSE", "0"};
+ for (size_t i = 0; i < GPR_ARRAY_SIZE(truthy); i++) {
+ if (0 == strcmp(env, truthy[i])) {
+ support_enabled_ = true;
+ env_var_set = true;
+ break;
+ }
+ }
+ if (!env_var_set) {
+ for (size_t i = 0; i < GPR_ARRAY_SIZE(falsey); i++) {
+ if (0 == strcmp(env, falsey[i])) {
+ support_enabled_ = false;
+ env_var_set = true;
+ break;
+ }
+ }
+ }
+ gpr_free(env);
+ }
+ }
+ if (support_enabled_) {
+ exec_ctx_state_ = grpc_core::New<internal::ExecCtxState>();
+ thread_state_ = grpc_core::New<internal::ThreadState>();
+ }
+}
+
+void Fork::GlobalShutdown() {
+ if (support_enabled_) {
+ grpc_core::Delete(exec_ctx_state_);
+ grpc_core::Delete(thread_state_);
+ }
+}
+
+bool Fork::Enabled() { return support_enabled_; }
+
+// Testing Only
+void Fork::Enable(bool enable) {
+ override_enabled_ = true;
+ support_enabled_ = enable;
+}
+
+void Fork::IncExecCtxCount() {
+ if (support_enabled_) {
+ exec_ctx_state_->IncExecCtxCount();
+ }
+}
+
+void Fork::DecExecCtxCount() {
+ if (support_enabled_) {
+ exec_ctx_state_->DecExecCtxCount();
+ }
+}
+
+void Fork::SetResetChildPollingEngineFunc(
+ Fork::child_postfork_func reset_child_polling_engine) {
+ reset_child_polling_engine_ = reset_child_polling_engine;
+}
+Fork::child_postfork_func Fork::GetResetChildPollingEngineFunc() {
+ return reset_child_polling_engine_;
+}
+
+bool Fork::BlockExecCtx() {
+ if (support_enabled_) {
+ return exec_ctx_state_->BlockExecCtx();
+ }
+ return false;
+}
+
+void Fork::AllowExecCtx() {
+ if (support_enabled_) {
+ exec_ctx_state_->AllowExecCtx();
+ }
+}
+
+void Fork::IncThreadCount() {
+ if (support_enabled_) {
+ thread_state_->IncThreadCount();
+ }
+}
+
+void Fork::DecThreadCount() {
+ if (support_enabled_) {
+ thread_state_->DecThreadCount();
+ }
+}
+void Fork::AwaitThreads() {
+ if (support_enabled_) {
+ thread_state_->AwaitThreads();
+ }
+}
+
+internal::ExecCtxState* Fork::exec_ctx_state_ = nullptr;
+internal::ThreadState* Fork::thread_state_ = nullptr;
+bool Fork::support_enabled_ = false;
+bool Fork::override_enabled_ = false;
+Fork::child_postfork_func Fork::reset_child_polling_engine_ = nullptr;
+} // namespace grpc_core
diff --git a/src/core/lib/gprpp/fork.h b/src/core/lib/gprpp/fork.h
new file mode 100644
index 0000000000..5a7404f0d9
--- /dev/null
+++ b/src/core/lib/gprpp/fork.h
@@ -0,0 +1,88 @@
+/*
+ *
+ * Copyright 2017 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_GPRPP_FORK_H
+#define GRPC_CORE_LIB_GPRPP_FORK_H
+
+/*
+ * NOTE: FORKING IS NOT GENERALLY SUPPORTED, THIS IS ONLY INTENDED TO WORK
+ * AROUND VERY SPECIFIC USE CASES.
+ */
+
+namespace grpc_core {
+
+namespace internal {
+class ExecCtxState;
+class ThreadState;
+} // namespace internal
+
+class Fork {
+ public:
+ typedef void (*child_postfork_func)(void);
+
+ static void GlobalInit();
+ static void GlobalShutdown();
+
+ // Returns true if fork suppport is enabled, false otherwise
+ static bool Enabled();
+
+ // Increment the count of active ExecCtxs.
+ // Will block until a pending fork is complete if one is in progress.
+ static void IncExecCtxCount();
+
+ // Decrement the count of active ExecCtxs
+ static void DecExecCtxCount();
+
+ // Provide a function that will be invoked in the child's postfork handler to
+ // reset the polling engine's internal state.
+ static void SetResetChildPollingEngineFunc(
+ child_postfork_func reset_child_polling_engine);
+ static child_postfork_func GetResetChildPollingEngineFunc();
+
+ // Check if there is a single active ExecCtx
+ // (the one used to invoke this function). If there are more,
+ // return false. Otherwise, return true and block creation of
+ // more ExecCtx s until AlloWExecCtx() is called
+ //
+ static bool BlockExecCtx();
+ static void AllowExecCtx();
+
+ // Increment the count of active threads.
+ static void IncThreadCount();
+
+ // Decrement the count of active threads.
+ static void DecThreadCount();
+
+ // Await all core threads to be joined.
+ static void AwaitThreads();
+
+ // Test only: overrides environment variables/compile flags
+ // Must be called before grpc_init()
+ static void Enable(bool enable);
+
+ private:
+ static internal::ExecCtxState* exec_ctx_state_;
+ static internal::ThreadState* thread_state_;
+ static bool support_enabled_;
+ static bool override_enabled_;
+ static child_postfork_func reset_child_polling_engine_;
+};
+
+} // namespace grpc_core
+
+#endif /* GRPC_CORE_LIB_GPRPP_FORK_H */
diff --git a/src/core/lib/gprpp/inlined_vector.h b/src/core/lib/gprpp/inlined_vector.h
index f36f6cb706..76e2f0a785 100644
--- a/src/core/lib/gprpp/inlined_vector.h
+++ b/src/core/lib/gprpp/inlined_vector.h
@@ -22,6 +22,7 @@
#include <grpc/support/port_platform.h>
#include <cassert>
+#include <cstring>
#include "src/core/lib/gprpp/memory.h"
@@ -50,9 +51,33 @@ class InlinedVector {
InlinedVector() { init_data(); }
~InlinedVector() { destroy_elements(); }
- // For now, we do not support copying.
- InlinedVector(const InlinedVector&) = delete;
- InlinedVector& operator=(const InlinedVector&) = delete;
+ // copy constructor
+ InlinedVector(const InlinedVector& v) {
+ init_data();
+ copy_from(v);
+ }
+
+ InlinedVector& operator=(const InlinedVector& v) {
+ if (this != &v) {
+ clear();
+ copy_from(v);
+ }
+ return *this;
+ }
+
+ // move constructor
+ InlinedVector(InlinedVector&& v) {
+ init_data();
+ move_from(v);
+ }
+
+ InlinedVector& operator=(InlinedVector&& v) {
+ if (this != &v) {
+ clear();
+ move_from(v);
+ }
+ return *this;
+ }
T* data() {
return dynamic_ != nullptr ? dynamic_ : reinterpret_cast<T*>(inline_);
@@ -98,7 +123,36 @@ class InlinedVector {
void push_back(T&& value) { emplace_back(std::move(value)); }
+ void copy_from(const InlinedVector& v) {
+ // if v is allocated, copy over the buffer.
+ if (v.dynamic_ != nullptr) {
+ reserve(v.capacity_);
+ memcpy(dynamic_, v.dynamic_, v.size_ * sizeof(T));
+ } else {
+ memcpy(inline_, v.inline_, v.size_ * sizeof(T));
+ }
+ // copy over metadata
+ size_ = v.size_;
+ capacity_ = v.capacity_;
+ }
+
+ void move_from(InlinedVector& v) {
+ // if v is allocated, then we steal its buffer, else we copy it.
+ if (v.dynamic_ != nullptr) {
+ dynamic_ = v.dynamic_;
+ } else {
+ memcpy(inline_, v.inline_, v.size_ * sizeof(T));
+ }
+ // copy over metadata
+ size_ = v.size_;
+ capacity_ = v.capacity_;
+ // null out the original
+ v.init_data();
+ }
+
size_t size() const { return size_; }
+ bool empty() const { return size_ == 0; }
+
size_t capacity() const { return capacity_; }
void clear() {
diff --git a/src/core/lib/gprpp/memory.h b/src/core/lib/gprpp/memory.h
index ba2f546675..e90bedcd9b 100644
--- a/src/core/lib/gprpp/memory.h
+++ b/src/core/lib/gprpp/memory.h
@@ -27,6 +27,17 @@
#include <memory>
#include <utility>
+// Add this to a class that want to use Delete(), but has a private or
+// protected destructor.
+#define GPRC_ALLOW_CLASS_TO_USE_NON_PUBLIC_DELETE \
+ template <typename T> \
+ friend void grpc_core::Delete(T*);
+// Add this to a class that want to use New(), but has a private or
+// protected constructor.
+#define GPRC_ALLOW_CLASS_TO_USE_NON_PUBLIC_NEW \
+ template <typename T, typename... Args> \
+ friend T* grpc_core::New(Args&&...);
+
namespace grpc_core {
// The alignment of memory returned by gpr_malloc().
@@ -44,6 +55,7 @@ inline T* New(Args&&... args) {
// Alternative to delete, since we cannot use it (for fear of libstdc++)
template <typename T>
inline void Delete(T* p) {
+ if (p == nullptr) return;
p->~T();
if (alignof(T) > kAlignmentForDefaultAllocationInBytes) {
gpr_free_aligned(p);
diff --git a/src/core/lib/iomgr/ev_epollsig_linux.h b/src/core/lib/gprpp/mutex_lock.h
index 2ba2f0a63b..54751d5fe4 100644
--- a/src/core/lib/iomgr/ev_epollsig_linux.h
+++ b/src/core/lib/gprpp/mutex_lock.h
@@ -1,6 +1,6 @@
/*
*
- * Copyright 2015 gRPC authors.
+ * Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,20 +16,27 @@
*
*/
-#ifndef GRPC_CORE_LIB_IOMGR_EV_EPOLLSIG_LINUX_H
-#define GRPC_CORE_LIB_IOMGR_EV_EPOLLSIG_LINUX_H
+#ifndef GRPC_CORE_LIB_GPRPP_MUTEX_LOCK_H
+#define GRPC_CORE_LIB_GPRPP_MUTEX_LOCK_H
#include <grpc/support/port_platform.h>
-#include "src/core/lib/iomgr/ev_posix.h"
-#include "src/core/lib/iomgr/port.h"
+#include <grpc/support/sync.h>
-const grpc_event_engine_vtable* grpc_init_epollsig_linux(bool explicit_request);
+namespace grpc_core {
-#ifdef GRPC_LINUX_EPOLL_CREATE1
-void* grpc_fd_get_polling_island(grpc_fd* fd);
-void* grpc_pollset_get_polling_island(grpc_pollset* ps);
-bool grpc_are_polling_islands_equal(void* p, void* q);
-#endif /* defined(GRPC_LINUX_EPOLL_CREATE1) */
+class MutexLock {
+ public:
+ explicit MutexLock(gpr_mu* mu) : mu_(mu) { gpr_mu_lock(mu); }
+ ~MutexLock() { gpr_mu_unlock(mu_); }
-#endif /* GRPC_CORE_LIB_IOMGR_EV_EPOLLSIG_LINUX_H */
+ MutexLock(const MutexLock&) = delete;
+ MutexLock& operator=(const MutexLock&) = delete;
+
+ private:
+ gpr_mu* const mu_;
+};
+
+} // namespace grpc_core
+
+#endif /* GRPC_CORE_LIB_GPRPP_MUTEX_LOCK_H */
diff --git a/src/core/lib/gprpp/orphanable.h b/src/core/lib/gprpp/orphanable.h
index 73a73995c7..3123e3f5a3 100644
--- a/src/core/lib/gprpp/orphanable.h
+++ b/src/core/lib/gprpp/orphanable.h
@@ -83,12 +83,11 @@ class InternallyRefCounted : public Orphanable {
GRPC_ABSTRACT_BASE_CLASS
protected:
- // Allow Delete() to access destructor.
- template <typename T>
- friend void Delete(T*);
+ GPRC_ALLOW_CLASS_TO_USE_NON_PUBLIC_DELETE
// Allow RefCountedPtr<> to access Unref() and IncrementRefCount().
- friend class RefCountedPtr<Child>;
+ template <typename T>
+ friend class RefCountedPtr;
InternallyRefCounted() { gpr_ref_init(&refs_, 1); }
virtual ~InternallyRefCounted() {}
@@ -128,12 +127,11 @@ class InternallyRefCountedWithTracing : public Orphanable {
GRPC_ABSTRACT_BASE_CLASS
protected:
- // Allow Delete() to access destructor.
- template <typename T>
- friend void Delete(T*);
+ GPRC_ALLOW_CLASS_TO_USE_NON_PUBLIC_DELETE
// Allow RefCountedPtr<> to access Unref() and IncrementRefCount().
- friend class RefCountedPtr<Child>;
+ template <typename T>
+ friend class RefCountedPtr;
InternallyRefCountedWithTracing()
: InternallyRefCountedWithTracing(static_cast<TraceFlag*>(nullptr)) {}
diff --git a/src/core/lib/gprpp/ref_counted.h b/src/core/lib/gprpp/ref_counted.h
index c67e3f315c..03c293f6ed 100644
--- a/src/core/lib/gprpp/ref_counted.h
+++ b/src/core/lib/gprpp/ref_counted.h
@@ -65,9 +65,7 @@ class RefCounted {
GRPC_ABSTRACT_BASE_CLASS
protected:
- // Allow Delete() to access destructor.
- template <typename T>
- friend void Delete(T*);
+ GPRC_ALLOW_CLASS_TO_USE_NON_PUBLIC_DELETE
RefCounted() { gpr_ref_init(&refs_, 1); }
@@ -75,7 +73,8 @@ class RefCounted {
private:
// Allow RefCountedPtr<> to access IncrementRefCount().
- friend class RefCountedPtr<Child>;
+ template <typename T>
+ friend class RefCountedPtr;
void IncrementRefCount() { gpr_ref(&refs_); }
@@ -135,9 +134,7 @@ class RefCountedWithTracing {
GRPC_ABSTRACT_BASE_CLASS
protected:
- // Allow Delete() to access destructor.
- template <typename T>
- friend void Delete(T*);
+ GPRC_ALLOW_CLASS_TO_USE_NON_PUBLIC_DELETE
RefCountedWithTracing()
: RefCountedWithTracing(static_cast<TraceFlag*>(nullptr)) {}
@@ -156,7 +153,8 @@ class RefCountedWithTracing {
private:
// Allow RefCountedPtr<> to access IncrementRefCount().
- friend class RefCountedPtr<Child>;
+ template <typename T>
+ friend class RefCountedPtr;
void IncrementRefCount() { gpr_ref(&refs_); }
diff --git a/src/core/lib/gprpp/ref_counted_ptr.h b/src/core/lib/gprpp/ref_counted_ptr.h
index 388e2ec410..c2dfbdd90f 100644
--- a/src/core/lib/gprpp/ref_counted_ptr.h
+++ b/src/core/lib/gprpp/ref_counted_ptr.h
@@ -36,25 +36,49 @@ class RefCountedPtr {
RefCountedPtr(std::nullptr_t) {}
// If value is non-null, we take ownership of a ref to it.
- explicit RefCountedPtr(T* value) { value_ = value; }
+ template <typename Y>
+ explicit RefCountedPtr(Y* value) {
+ value_ = value;
+ }
- // Move support.
+ // Move ctors.
RefCountedPtr(RefCountedPtr&& other) {
value_ = other.value_;
other.value_ = nullptr;
}
+ template <typename Y>
+ RefCountedPtr(RefCountedPtr<Y>&& other) {
+ value_ = other.value_;
+ other.value_ = nullptr;
+ }
+
+ // Move assignment.
RefCountedPtr& operator=(RefCountedPtr&& other) {
if (value_ != nullptr) value_->Unref();
value_ = other.value_;
other.value_ = nullptr;
return *this;
}
+ template <typename Y>
+ RefCountedPtr& operator=(RefCountedPtr<Y>&& other) {
+ if (value_ != nullptr) value_->Unref();
+ value_ = other.value_;
+ other.value_ = nullptr;
+ return *this;
+ }
- // Copy support.
+ // Copy ctors.
RefCountedPtr(const RefCountedPtr& other) {
if (other.value_ != nullptr) other.value_->IncrementRefCount();
value_ = other.value_;
}
+ template <typename Y>
+ RefCountedPtr(const RefCountedPtr<Y>& other) {
+ if (other.value_ != nullptr) other.value_->IncrementRefCount();
+ value_ = other.value_;
+ }
+
+ // Copy assignment.
RefCountedPtr& operator=(const RefCountedPtr& other) {
// Note: Order of reffing and unreffing is important here in case value_
// and other.value_ are the same object.
@@ -63,17 +87,32 @@ class RefCountedPtr {
value_ = other.value_;
return *this;
}
+ template <typename Y>
+ RefCountedPtr& operator=(const RefCountedPtr<Y>& other) {
+ // Note: Order of reffing and unreffing is important here in case value_
+ // and other.value_ are the same object.
+ if (other.value_ != nullptr) other.value_->IncrementRefCount();
+ if (value_ != nullptr) value_->Unref();
+ value_ = other.value_;
+ return *this;
+ }
~RefCountedPtr() {
if (value_ != nullptr) value_->Unref();
}
// If value is non-null, we take ownership of a ref to it.
- void reset(T* value = nullptr) {
+ template <typename Y>
+ void reset(Y* value) {
if (value_ != nullptr) value_->Unref();
value_ = value;
}
+ void reset() {
+ if (value_ != nullptr) value_->Unref();
+ value_ = nullptr;
+ }
+
// TODO(roth): This method exists solely as a transition mechanism to allow
// us to pass a ref to idiomatic C code that does not use RefCountedPtr<>.
// Once all of our code has been converted to idiomatic C++, this
@@ -89,16 +128,34 @@ class RefCountedPtr {
T& operator*() const { return *value_; }
T* operator->() const { return value_; }
- bool operator==(const RefCountedPtr& other) const {
+ template <typename Y>
+ bool operator==(const RefCountedPtr<Y>& other) const {
return value_ == other.value_;
}
- bool operator==(const T* other) const { return value_ == other; }
- bool operator!=(const RefCountedPtr& other) const {
+
+ template <typename Y>
+ bool operator==(const Y* other) const {
+ return value_ == other;
+ }
+
+ bool operator==(std::nullptr_t) const { return value_ == nullptr; }
+
+ template <typename Y>
+ bool operator!=(const RefCountedPtr<Y>& other) const {
return value_ != other.value_;
}
- bool operator!=(const T* other) const { return value_ != other; }
+
+ template <typename Y>
+ bool operator!=(const Y* other) const {
+ return value_ != other;
+ }
+
+ bool operator!=(std::nullptr_t) const { return value_ != nullptr; }
private:
+ template <typename Y>
+ friend class RefCountedPtr;
+
T* value_ = nullptr;
};
diff --git a/src/core/lib/gprpp/thd.h b/src/core/lib/gprpp/thd.h
index 05c7ded45f..caf0652c1a 100644
--- a/src/core/lib/gprpp/thd.h
+++ b/src/core/lib/gprpp/thd.h
@@ -111,9 +111,6 @@ class Thread {
}
};
- static void Init();
- static bool AwaitAll(gpr_timespec deadline);
-
private:
Thread(const Thread&) = delete;
Thread& operator=(const Thread&) = delete;
diff --git a/src/core/lib/gprpp/thd_posix.cc b/src/core/lib/gprpp/thd_posix.cc
index 2f6c2edcae..2751b221a8 100644
--- a/src/core/lib/gprpp/thd_posix.cc
+++ b/src/core/lib/gprpp/thd_posix.cc
@@ -32,17 +32,12 @@
#include <stdlib.h>
#include <string.h>
-#include "src/core/lib/gpr/fork.h"
#include "src/core/lib/gpr/useful.h"
+#include "src/core/lib/gprpp/fork.h"
#include "src/core/lib/gprpp/memory.h"
namespace grpc_core {
namespace {
-gpr_mu g_mu;
-gpr_cv g_cv;
-int g_thread_count;
-int g_awaiting_threads;
-
class ThreadInternalsPosix;
struct thd_arg {
ThreadInternalsPosix* thread;
@@ -68,7 +63,7 @@ class ThreadInternalsPosix
info->body = thd_body;
info->arg = arg;
info->name = thd_name;
- inc_thd_count();
+ grpc_core::Fork::IncThreadCount();
GPR_ASSERT(pthread_attr_init(&attr) == 0);
GPR_ASSERT(pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE) ==
@@ -103,17 +98,17 @@ class ThreadInternalsPosix
gpr_mu_unlock(&arg.thread->mu_);
(*arg.body)(arg.arg);
- dec_thd_count();
+ grpc_core::Fork::DecThreadCount();
return nullptr;
},
info) == 0);
GPR_ASSERT(pthread_attr_destroy(&attr) == 0);
- if (!success) {
+ if (!(*success)) {
/* don't use gpr_free, as this was allocated using malloc (see above) */
free(info);
- dec_thd_count();
+ grpc_core::Fork::DecThreadCount();
}
};
@@ -132,29 +127,6 @@ class ThreadInternalsPosix
void Join() override { pthread_join(pthread_id_, nullptr); }
private:
- /*****************************************
- * Only used when fork support is enabled
- */
-
- static void inc_thd_count() {
- if (grpc_fork_support_enabled()) {
- gpr_mu_lock(&g_mu);
- g_thread_count++;
- gpr_mu_unlock(&g_mu);
- }
- }
-
- static void dec_thd_count() {
- if (grpc_fork_support_enabled()) {
- gpr_mu_lock(&g_mu);
- g_thread_count--;
- if (g_awaiting_threads && g_thread_count == 0) {
- gpr_cv_signal(&g_cv);
- }
- gpr_mu_unlock(&g_mu);
- }
- }
-
gpr_mu mu_;
gpr_cv ready_;
bool started_;
@@ -180,27 +152,6 @@ Thread::Thread(const char* thd_name, void (*thd_body)(void* arg), void* arg,
*success = outcome;
}
}
-
-void Thread::Init() {
- gpr_mu_init(&g_mu);
- gpr_cv_init(&g_cv);
- g_thread_count = 0;
- g_awaiting_threads = 0;
-}
-
-bool Thread::AwaitAll(gpr_timespec deadline) {
- gpr_mu_lock(&g_mu);
- g_awaiting_threads = 1;
- int res = 0;
- while ((g_thread_count > 0) &&
- (gpr_time_cmp(gpr_now(GPR_CLOCK_REALTIME), deadline) < 0)) {
- res = gpr_cv_wait(&g_cv, &g_mu, deadline);
- }
- g_awaiting_threads = 0;
- gpr_mu_unlock(&g_mu);
- return res == 0;
-}
-
} // namespace grpc_core
// The following is in the external namespace as it is exposed as C89 API
diff --git a/src/core/lib/gprpp/thd_windows.cc b/src/core/lib/gprpp/thd_windows.cc
index 59ea02f3d2..71584fd358 100644
--- a/src/core/lib/gprpp/thd_windows.cc
+++ b/src/core/lib/gprpp/thd_windows.cc
@@ -131,13 +131,6 @@ class ThreadInternalsWindows
namespace grpc_core {
-void Thread::Init() {}
-
-bool Thread::AwaitAll(gpr_timespec deadline) {
- // TODO: Consider adding this if needed
- return false;
-}
-
Thread::Thread(const char* thd_name, void (*thd_body)(void* arg), void* arg,
bool* success) {
bool outcome = false;
diff --git a/src/core/lib/http/httpcli.cc b/src/core/lib/http/httpcli.cc
index 12060074c5..3bd7a2ce59 100644
--- a/src/core/lib/http/httpcli.cc
+++ b/src/core/lib/http/httpcli.cc
@@ -163,7 +163,7 @@ static void done_write(void* arg, grpc_error* error) {
static void start_write(internal_request* req) {
grpc_slice_ref_internal(req->request_text);
grpc_slice_buffer_add(&req->outgoing, req->request_text);
- grpc_endpoint_write(req->ep, &req->outgoing, &req->done_write);
+ grpc_endpoint_write(req->ep, &req->outgoing, &req->done_write, nullptr);
}
static void on_handshake_done(void* arg, grpc_endpoint* ep) {
diff --git a/src/core/lib/iomgr/buffer_list.cc b/src/core/lib/iomgr/buffer_list.cc
new file mode 100644
index 0000000000..6ada23db1c
--- /dev/null
+++ b/src/core/lib/iomgr/buffer_list.cc
@@ -0,0 +1,134 @@
+/*
+ *
+ * Copyright 2018 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/iomgr/buffer_list.h"
+#include "src/core/lib/iomgr/port.h"
+
+#include <grpc/support/log.h>
+
+#ifdef GRPC_LINUX_ERRQUEUE
+#include <time.h>
+
+#include "src/core/lib/gprpp/memory.h"
+
+namespace grpc_core {
+void TracedBuffer::AddNewEntry(TracedBuffer** head, uint32_t seq_no,
+ void* arg) {
+ GPR_DEBUG_ASSERT(head != nullptr);
+ TracedBuffer* new_elem = New<TracedBuffer>(seq_no, arg);
+ /* Store the current time as the sendmsg time. */
+ new_elem->ts_.sendmsg_time = gpr_now(GPR_CLOCK_REALTIME);
+ if (*head == nullptr) {
+ *head = new_elem;
+ return;
+ }
+ /* Append at the end. */
+ TracedBuffer* ptr = *head;
+ while (ptr->next_ != nullptr) {
+ ptr = ptr->next_;
+ }
+ ptr->next_ = new_elem;
+}
+
+namespace {
+/** Fills gpr_timespec gts based on values from timespec ts */
+void fill_gpr_from_timestamp(gpr_timespec* gts, const struct timespec* ts) {
+ gts->tv_sec = ts->tv_sec;
+ gts->tv_nsec = static_cast<int32_t>(ts->tv_nsec);
+ gts->clock_type = GPR_CLOCK_REALTIME;
+}
+
+/** The saved callback function that will be invoked when we get all the
+ * timestamps that we are going to get for a TracedBuffer. */
+void (*timestamps_callback)(void*, grpc_core::Timestamps*,
+ grpc_error* shutdown_err);
+} /* namespace */
+
+void TracedBuffer::ProcessTimestamp(TracedBuffer** head,
+ struct sock_extended_err* serr,
+ struct scm_timestamping* tss) {
+ GPR_DEBUG_ASSERT(head != nullptr);
+ TracedBuffer* elem = *head;
+ TracedBuffer* next = nullptr;
+ while (elem != nullptr) {
+ /* The byte number refers to the sequence number of the last byte which this
+ * timestamp relates to. */
+ if (serr->ee_data >= elem->seq_no_) {
+ switch (serr->ee_info) {
+ case SCM_TSTAMP_SCHED:
+ fill_gpr_from_timestamp(&(elem->ts_.scheduled_time), &(tss->ts[0]));
+ elem = elem->next_;
+ break;
+ case SCM_TSTAMP_SND:
+ fill_gpr_from_timestamp(&(elem->ts_.sent_time), &(tss->ts[0]));
+ elem = elem->next_;
+ break;
+ case SCM_TSTAMP_ACK:
+ fill_gpr_from_timestamp(&(elem->ts_.acked_time), &(tss->ts[0]));
+ /* Got all timestamps. Do the callback and free this TracedBuffer.
+ * The thing below can be passed by value if we don't want the
+ * restriction on the lifetime. */
+ timestamps_callback(elem->arg_, &(elem->ts_), GRPC_ERROR_NONE);
+ next = elem->next_;
+ Delete<TracedBuffer>(elem);
+ *head = elem = next;
+ break;
+ default:
+ abort();
+ }
+ } else {
+ break;
+ }
+ }
+}
+
+void TracedBuffer::Shutdown(TracedBuffer** head, grpc_error* shutdown_err) {
+ GPR_DEBUG_ASSERT(head != nullptr);
+ TracedBuffer* elem = *head;
+ while (elem != nullptr) {
+ if (timestamps_callback) {
+ timestamps_callback(elem->arg_, &(elem->ts_), shutdown_err);
+ }
+ auto* next = elem->next_;
+ Delete<TracedBuffer>(elem);
+ elem = next;
+ }
+ *head = nullptr;
+ GRPC_ERROR_UNREF(shutdown_err);
+}
+
+void grpc_tcp_set_write_timestamps_callback(void (*fn)(void*,
+ grpc_core::Timestamps*,
+ grpc_error* error)) {
+ timestamps_callback = fn;
+}
+} /* namespace grpc_core */
+
+#else /* GRPC_LINUX_ERRQUEUE */
+
+namespace grpc_core {
+void grpc_tcp_set_write_timestamps_callback(void (*fn)(void*,
+ grpc_core::Timestamps*,
+ grpc_error* error)) {
+ gpr_log(GPR_DEBUG, "Timestamps callback is not enabled for this platform");
+}
+} /* namespace grpc_core */
+
+#endif /* GRPC_LINUX_ERRQUEUE */
diff --git a/src/core/lib/iomgr/buffer_list.h b/src/core/lib/iomgr/buffer_list.h
new file mode 100644
index 0000000000..cbbf50a657
--- /dev/null
+++ b/src/core/lib/iomgr/buffer_list.h
@@ -0,0 +1,96 @@
+/*
+ *
+ * Copyright 2018 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_IOMGR_BUFFER_LIST_H
+#define GRPC_CORE_LIB_IOMGR_BUFFER_LIST_H
+
+#include <grpc/support/port_platform.h>
+
+#include "src/core/lib/iomgr/port.h"
+
+#include <grpc/support/time.h>
+
+#include "src/core/lib/gprpp/memory.h"
+#include "src/core/lib/iomgr/error.h"
+#include "src/core/lib/iomgr/internal_errqueue.h"
+
+namespace grpc_core {
+struct Timestamps {
+ /* TODO(yashykt): This would also need to store OPTSTAT once support is added
+ */
+ gpr_timespec sendmsg_time;
+ gpr_timespec scheduled_time;
+ gpr_timespec sent_time;
+ gpr_timespec acked_time;
+};
+
+/** TracedBuffer is a class to keep track of timestamps for a specific buffer in
+ * the TCP layer. We are only tracking timestamps for Linux kernels and hence
+ * this class would only be used by Linux platforms. For all other platforms,
+ * TracedBuffer would be an empty class.
+ *
+ * The timestamps collected are according to grpc_core::Timestamps declared
+ * above.
+ *
+ * A TracedBuffer list is kept track of using the head element of the list. If
+ * the head element of the list is nullptr, then the list is empty.
+ */
+#ifdef GRPC_LINUX_ERRQUEUE
+class TracedBuffer {
+ public:
+ /** Add a new entry in the TracedBuffer list pointed to by head. Also saves
+ * sendmsg_time with the current timestamp. */
+ static void AddNewEntry(grpc_core::TracedBuffer** head, uint32_t seq_no,
+ void* arg);
+
+ /** Processes a received timestamp based on sock_extended_err and
+ * scm_timestamping structures. It will invoke the timestamps callback if the
+ * timestamp type is SCM_TSTAMP_ACK. */
+ static void ProcessTimestamp(grpc_core::TracedBuffer** head,
+ struct sock_extended_err* serr,
+ struct scm_timestamping* tss);
+
+ /** Cleans the list by calling the callback for each traced buffer in the list
+ * with timestamps that it has. */
+ static void Shutdown(grpc_core::TracedBuffer** head,
+ grpc_error* shutdown_err);
+
+ private:
+ GPRC_ALLOW_CLASS_TO_USE_NON_PUBLIC_NEW
+
+ TracedBuffer(int seq_no, void* arg)
+ : seq_no_(seq_no), arg_(arg), next_(nullptr) {}
+
+ uint32_t seq_no_; /* The sequence number for the last byte in the buffer */
+ void* arg_; /* The arg to pass to timestamps_callback */
+ grpc_core::Timestamps ts_; /* The timestamps corresponding to this buffer */
+ grpc_core::TracedBuffer* next_; /* The next TracedBuffer in the list */
+};
+#else /* GRPC_LINUX_ERRQUEUE */
+class TracedBuffer {};
+#endif /* GRPC_LINUX_ERRQUEUE */
+
+/** Sets the callback function to call when timestamps for a write are
+ * collected. The callback does not own a reference to error. */
+void grpc_tcp_set_write_timestamps_callback(void (*fn)(void*,
+ grpc_core::Timestamps*,
+ grpc_error* error));
+
+}; /* namespace grpc_core */
+
+#endif /* GRPC_CORE_LIB_IOMGR_BUFFER_LIST_H */
diff --git a/src/core/lib/iomgr/call_combiner.h b/src/core/lib/iomgr/call_combiner.h
index 0ccd08ea57..6f7ddd4043 100644
--- a/src/core/lib/iomgr/call_combiner.h
+++ b/src/core/lib/iomgr/call_combiner.h
@@ -26,6 +26,7 @@
#include <grpc/support/atm.h>
#include "src/core/lib/gpr/mpscq.h"
+#include "src/core/lib/gprpp/inlined_vector.h"
#include "src/core/lib/iomgr/closure.h"
// A simple, lock-free mechanism for serializing activity related to a
@@ -101,7 +102,10 @@ void grpc_call_combiner_stop(grpc_call_combiner* call_combiner,
/// If \a closure is NULL, then no closure will be invoked on
/// cancellation; this effectively unregisters the previously set closure.
/// However, most filters will not need to explicitly unregister their
-/// callbacks, as this is done automatically when the call is destroyed.
+/// callbacks, as this is done automatically when the call is destroyed. Filters
+/// that schedule the cancellation closure on ExecCtx do not need to take a ref
+/// on the call stack to guarantee closure liveness. This is done by explicitly
+/// flushing ExecCtx after the unregistration during call destruction.
void grpc_call_combiner_set_notify_on_cancel(grpc_call_combiner* call_combiner,
grpc_closure* closure);
@@ -109,4 +113,83 @@ void grpc_call_combiner_set_notify_on_cancel(grpc_call_combiner* call_combiner,
void grpc_call_combiner_cancel(grpc_call_combiner* call_combiner,
grpc_error* error);
+namespace grpc_core {
+
+// Helper for running a list of closures in a call combiner.
+//
+// Each callback running in the call combiner will eventually be
+// returned to the surface, at which point the surface will yield the
+// call combiner. So when we are running in the call combiner and have
+// more than one callback to return to the surface, we need to re-enter
+// the call combiner for all but one of those callbacks.
+class CallCombinerClosureList {
+ public:
+ CallCombinerClosureList() {}
+
+ // Adds a closure to the list. The closure must eventually result in
+ // the call combiner being yielded.
+ void Add(grpc_closure* closure, grpc_error* error, const char* reason) {
+ closures_.emplace_back(closure, error, reason);
+ }
+
+ // Runs all closures in the call combiner and yields the call combiner.
+ //
+ // All but one of the closures in the list will be scheduled via
+ // GRPC_CALL_COMBINER_START(), and the remaining closure will be
+ // scheduled via GRPC_CLOSURE_SCHED(), which will eventually result in
+ // yielding the call combiner. If the list is empty, then the call
+ // combiner will be yielded immediately.
+ void RunClosures(grpc_call_combiner* call_combiner) {
+ if (closures_.empty()) {
+ GRPC_CALL_COMBINER_STOP(call_combiner, "no closures to schedule");
+ return;
+ }
+ for (size_t i = 1; i < closures_.size(); ++i) {
+ auto& closure = closures_[i];
+ GRPC_CALL_COMBINER_START(call_combiner, closure.closure, closure.error,
+ closure.reason);
+ }
+ if (grpc_call_combiner_trace.enabled()) {
+ gpr_log(GPR_INFO,
+ "CallCombinerClosureList executing closure while already "
+ "holding call_combiner %p: closure=%p error=%s reason=%s",
+ call_combiner, closures_[0].closure,
+ grpc_error_string(closures_[0].error), closures_[0].reason);
+ }
+ // This will release the call combiner.
+ GRPC_CLOSURE_SCHED(closures_[0].closure, closures_[0].error);
+ closures_.clear();
+ }
+
+ // Runs all closures in the call combiner, but does NOT yield the call
+ // combiner. All closures will be scheduled via GRPC_CALL_COMBINER_START().
+ void RunClosuresWithoutYielding(grpc_call_combiner* call_combiner) {
+ for (size_t i = 0; i < closures_.size(); ++i) {
+ auto& closure = closures_[i];
+ GRPC_CALL_COMBINER_START(call_combiner, closure.closure, closure.error,
+ closure.reason);
+ }
+ closures_.clear();
+ }
+
+ size_t size() const { return closures_.size(); }
+
+ private:
+ struct CallCombinerClosure {
+ grpc_closure* closure;
+ grpc_error* error;
+ const char* reason;
+
+ CallCombinerClosure(grpc_closure* closure, grpc_error* error,
+ const char* reason)
+ : closure(closure), error(error), reason(reason) {}
+ };
+
+ // There are generally a maximum of 6 closures to run in the call
+ // combiner, one for each pending op.
+ InlinedVector<CallCombinerClosure, 6> closures_;
+};
+
+} // namespace grpc_core
+
#endif /* GRPC_CORE_LIB_IOMGR_CALL_COMBINER_H */
diff --git a/src/core/lib/iomgr/cfstream_handle.cc b/src/core/lib/iomgr/cfstream_handle.cc
new file mode 100644
index 0000000000..827fd24831
--- /dev/null
+++ b/src/core/lib/iomgr/cfstream_handle.cc
@@ -0,0 +1,185 @@
+/*
+ *
+ * Copyright 2018 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/iomgr/port.h"
+
+#ifdef GRPC_CFSTREAM
+#import <CoreFoundation/CoreFoundation.h>
+#import "src/core/lib/iomgr/cfstream_handle.h"
+
+#include <grpc/support/atm.h>
+#include <grpc/support/sync.h>
+
+#include "src/core/lib/debug/trace.h"
+#include "src/core/lib/iomgr/closure.h"
+#include "src/core/lib/iomgr/exec_ctx.h"
+
+extern grpc_core::TraceFlag grpc_tcp_trace;
+
+void* CFStreamHandle::Retain(void* info) {
+ CFStreamHandle* handle = static_cast<CFStreamHandle*>(info);
+ CFSTREAM_HANDLE_REF(handle, "retain");
+ return info;
+}
+
+void CFStreamHandle::Release(void* info) {
+ CFStreamHandle* handle = static_cast<CFStreamHandle*>(info);
+ CFSTREAM_HANDLE_UNREF(handle, "release");
+}
+
+CFStreamHandle* CFStreamHandle::CreateStreamHandle(
+ CFReadStreamRef read_stream, CFWriteStreamRef write_stream) {
+ return new CFStreamHandle(read_stream, write_stream);
+}
+
+void CFStreamHandle::ReadCallback(CFReadStreamRef stream,
+ CFStreamEventType type,
+ void* client_callback_info) {
+ CFStreamHandle* handle = static_cast<CFStreamHandle*>(client_callback_info);
+ CFSTREAM_HANDLE_REF(handle, "read callback");
+ dispatch_async(
+ dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+ grpc_core::ExecCtx exec_ctx;
+ if (grpc_tcp_trace.enabled()) {
+ gpr_log(GPR_DEBUG, "CFStream ReadCallback (%p, %p, %lu, %p)", handle,
+ stream, type, client_callback_info);
+ }
+ switch (type) {
+ case kCFStreamEventOpenCompleted:
+ handle->open_event_.SetReady();
+ break;
+ case kCFStreamEventHasBytesAvailable:
+ case kCFStreamEventEndEncountered:
+ handle->read_event_.SetReady();
+ break;
+ case kCFStreamEventErrorOccurred:
+ handle->open_event_.SetReady();
+ handle->read_event_.SetReady();
+ break;
+ default:
+ GPR_UNREACHABLE_CODE(return );
+ }
+ CFSTREAM_HANDLE_UNREF(handle, "read callback");
+ });
+}
+void CFStreamHandle::WriteCallback(CFWriteStreamRef stream,
+ CFStreamEventType type,
+ void* clientCallBackInfo) {
+ CFStreamHandle* handle = static_cast<CFStreamHandle*>(clientCallBackInfo);
+ CFSTREAM_HANDLE_REF(handle, "write callback");
+ dispatch_async(
+ dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+ grpc_core::ExecCtx exec_ctx;
+ if (grpc_tcp_trace.enabled()) {
+ gpr_log(GPR_DEBUG, "CFStream WriteCallback (%p, %p, %lu, %p)", handle,
+ stream, type, clientCallBackInfo);
+ }
+ switch (type) {
+ case kCFStreamEventOpenCompleted:
+ handle->open_event_.SetReady();
+ break;
+ case kCFStreamEventCanAcceptBytes:
+ case kCFStreamEventEndEncountered:
+ handle->write_event_.SetReady();
+ break;
+ case kCFStreamEventErrorOccurred:
+ handle->open_event_.SetReady();
+ handle->write_event_.SetReady();
+ break;
+ default:
+ GPR_UNREACHABLE_CODE(return );
+ }
+ CFSTREAM_HANDLE_UNREF(handle, "write callback");
+ });
+}
+
+CFStreamHandle::CFStreamHandle(CFReadStreamRef read_stream,
+ CFWriteStreamRef write_stream) {
+ gpr_ref_init(&refcount_, 1);
+ open_event_.InitEvent();
+ read_event_.InitEvent();
+ write_event_.InitEvent();
+ CFStreamClientContext ctx = {0, static_cast<void*>(this),
+ CFStreamHandle::Retain, CFStreamHandle::Release,
+ nil};
+ CFReadStreamSetClient(
+ read_stream,
+ kCFStreamEventOpenCompleted | kCFStreamEventHasBytesAvailable |
+ kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered,
+ CFStreamHandle::ReadCallback, &ctx);
+ CFWriteStreamSetClient(
+ write_stream,
+ kCFStreamEventOpenCompleted | kCFStreamEventCanAcceptBytes |
+ kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered,
+ CFStreamHandle::WriteCallback, &ctx);
+ CFReadStreamScheduleWithRunLoop(read_stream, CFRunLoopGetMain(),
+ kCFRunLoopCommonModes);
+ CFWriteStreamScheduleWithRunLoop(write_stream, CFRunLoopGetMain(),
+ kCFRunLoopCommonModes);
+}
+
+CFStreamHandle::~CFStreamHandle() {
+ open_event_.DestroyEvent();
+ read_event_.DestroyEvent();
+ write_event_.DestroyEvent();
+}
+
+void CFStreamHandle::NotifyOnOpen(grpc_closure* closure) {
+ open_event_.NotifyOn(closure);
+}
+
+void CFStreamHandle::NotifyOnRead(grpc_closure* closure) {
+ read_event_.NotifyOn(closure);
+}
+
+void CFStreamHandle::NotifyOnWrite(grpc_closure* closure) {
+ write_event_.NotifyOn(closure);
+}
+
+void CFStreamHandle::Shutdown(grpc_error* error) {
+ open_event_.SetShutdown(GRPC_ERROR_REF(error));
+ read_event_.SetShutdown(GRPC_ERROR_REF(error));
+ write_event_.SetShutdown(GRPC_ERROR_REF(error));
+ GRPC_ERROR_UNREF(error);
+}
+
+void CFStreamHandle::Ref(const char* file, int line, const char* reason) {
+ if (grpc_tcp_trace.enabled()) {
+ gpr_atm val = gpr_atm_no_barrier_load(&refcount_.count);
+ gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG,
+ "CFStream Handle ref %p : %s %" PRIdPTR " -> %" PRIdPTR, this,
+ reason, val, val + 1);
+ }
+ gpr_ref(&refcount_);
+}
+
+void CFStreamHandle::Unref(const char* file, int line, const char* reason) {
+ if (grpc_tcp_trace.enabled()) {
+ gpr_atm val = gpr_atm_no_barrier_load(&refcount_.count);
+ gpr_log(GPR_ERROR,
+ "CFStream Handle unref %p : %s %" PRIdPTR " -> %" PRIdPTR, this,
+ reason, val, val - 1);
+ }
+ if (gpr_unref(&refcount_)) {
+ delete this;
+ }
+}
+
+#endif
diff --git a/src/core/lib/iomgr/cfstream_handle.h b/src/core/lib/iomgr/cfstream_handle.h
new file mode 100644
index 0000000000..4258e72431
--- /dev/null
+++ b/src/core/lib/iomgr/cfstream_handle.h
@@ -0,0 +1,80 @@
+/*
+ *
+ * Copyright 2018 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.
+ *
+ */
+
+/* The CFStream handle acts as an event synchronization entity for
+ * read/write/open/error/eos events happening on CFStream streams. */
+
+#ifndef GRPC_CORE_LIB_IOMGR_CFSTREAM_HANDLE_H
+#define GRPC_CORE_LIB_IOMGR_CFSTREAM_HANDLE_H
+
+#include <grpc/support/port_platform.h>
+
+#include "src/core/lib/iomgr/port.h"
+
+#ifdef GRPC_CFSTREAM
+#import <CoreFoundation/CoreFoundation.h>
+
+#include "src/core/lib/iomgr/closure.h"
+#include "src/core/lib/iomgr/lockfree_event.h"
+
+class CFStreamHandle final {
+ public:
+ static CFStreamHandle* CreateStreamHandle(CFReadStreamRef read_stream,
+ CFWriteStreamRef write_stream);
+ ~CFStreamHandle();
+ CFStreamHandle(const CFReadStreamRef& ref) = delete;
+ CFStreamHandle(CFReadStreamRef&& ref) = delete;
+ CFStreamHandle& operator=(const CFStreamHandle& rhs) = delete;
+
+ void NotifyOnOpen(grpc_closure* closure);
+ void NotifyOnRead(grpc_closure* closure);
+ void NotifyOnWrite(grpc_closure* closure);
+ void Shutdown(grpc_error* error);
+
+ void Ref(const char* file = "", int line = 0, const char* reason = nullptr);
+ void Unref(const char* file = "", int line = 0, const char* reason = nullptr);
+
+ private:
+ CFStreamHandle(CFReadStreamRef read_stream, CFWriteStreamRef write_stream);
+ static void ReadCallback(CFReadStreamRef stream, CFStreamEventType type,
+ void* client_callback_info);
+ static void WriteCallback(CFWriteStreamRef stream, CFStreamEventType type,
+ void* client_callback_info);
+ static void* Retain(void* info);
+ static void Release(void* info);
+
+ grpc_core::LockfreeEvent open_event_;
+ grpc_core::LockfreeEvent read_event_;
+ grpc_core::LockfreeEvent write_event_;
+
+ gpr_refcount refcount_;
+};
+
+#ifdef DEBUG
+#define CFSTREAM_HANDLE_REF(handle, reason) \
+ (handle)->Ref(__FILE__, __LINE__, (reason))
+#define CFSTREAM_HANDLE_UNREF(handle, reason) \
+ (handle)->Unref(__FILE__, __LINE__, (reason))
+#else
+#define CFSTREAM_HANDLE_REF(handle, reason) (handle)->Ref()
+#define CFSTREAM_HANDLE_UNREF(handle, reason) (handle)->Unref()
+#endif
+
+#endif
+
+#endif /* GRPC_CORE_LIB_IOMGR_CFSTREAM_HANDLE_H */
diff --git a/src/core/lib/iomgr/closure.h b/src/core/lib/iomgr/closure.h
index 34a494485d..f14c723844 100644
--- a/src/core/lib/iomgr/closure.h
+++ b/src/core/lib/iomgr/closure.h
@@ -283,9 +283,10 @@ inline void grpc_closure_sched(grpc_closure* c, grpc_error* error) {
if (c->scheduled) {
gpr_log(GPR_ERROR,
"Closure already scheduled. (closure: %p, created: [%s:%d], "
- "previously scheduled at: [%s: %d] run?: %s",
+ "previously scheduled at: [%s: %d], newly scheduled at [%s: %d], "
+ "run?: %s",
c, c->file_created, c->line_created, c->file_initiated,
- c->line_initiated, c->run ? "true" : "false");
+ c->line_initiated, file, line, c->run ? "true" : "false");
abort();
}
c->scheduled = true;
diff --git a/src/core/lib/iomgr/combiner.cc b/src/core/lib/iomgr/combiner.cc
index 9429842eb8..7c0062eb4e 100644
--- a/src/core/lib/iomgr/combiner.cc
+++ b/src/core/lib/iomgr/combiner.cc
@@ -31,7 +31,7 @@
#include "src/core/lib/iomgr/executor.h"
#include "src/core/lib/profiling/timers.h"
-grpc_core::TraceFlag grpc_combiner_trace(false, "combiner");
+grpc_core::DebugOnlyTraceFlag grpc_combiner_trace(false, "combiner");
#define GRPC_COMBINER_TRACE(fn) \
do { \
@@ -63,11 +63,12 @@ struct grpc_combiner {
gpr_refcount refs;
};
+static void combiner_run(grpc_closure* closure, grpc_error* error);
static void combiner_exec(grpc_closure* closure, grpc_error* error);
static void combiner_finally_exec(grpc_closure* closure, grpc_error* error);
static const grpc_closure_scheduler_vtable scheduler = {
- combiner_exec, combiner_exec, "combiner:immediately"};
+ combiner_run, combiner_exec, "combiner:immediately"};
static const grpc_closure_scheduler_vtable finally_scheduler = {
combiner_finally_exec, combiner_finally_exec, "combiner:finally"};
@@ -343,6 +344,22 @@ static void combiner_finally_exec(grpc_closure* closure, grpc_error* error) {
grpc_closure_list_append(&lock->final_list, closure, error);
}
+static void combiner_run(grpc_closure* closure, grpc_error* error) {
+ grpc_combiner* lock = COMBINER_FROM_CLOSURE_SCHEDULER(closure, scheduler);
+#ifndef NDEBUG
+ closure->scheduled = false;
+ GRPC_COMBINER_TRACE(gpr_log(
+ GPR_DEBUG,
+ "Combiner:%p grpc_combiner_run closure:%p created [%s:%d] run [%s:%d]",
+ lock, closure, closure->file_created, closure->line_created,
+ closure->file_initiated, closure->line_initiated));
+#endif
+ GPR_ASSERT(grpc_core::ExecCtx::Get()->combiner_data()->active_combiner ==
+ lock);
+ closure->cb(closure->cb_arg, error);
+ GRPC_ERROR_UNREF(error);
+}
+
static void enqueue_finally(void* closure, grpc_error* error) {
combiner_finally_exec(static_cast<grpc_closure*>(closure),
GRPC_ERROR_REF(error));
diff --git a/src/core/lib/iomgr/combiner.h b/src/core/lib/iomgr/combiner.h
index 0d63e468df..3c947bf9d6 100644
--- a/src/core/lib/iomgr/combiner.h
+++ b/src/core/lib/iomgr/combiner.h
@@ -61,6 +61,6 @@ grpc_closure_scheduler* grpc_combiner_finally_scheduler(grpc_combiner* lock);
bool grpc_combiner_continue_exec_ctx();
-extern grpc_core::TraceFlag grpc_combiner_trace;
+extern grpc_core::DebugOnlyTraceFlag grpc_combiner_trace;
#endif /* GRPC_CORE_LIB_IOMGR_COMBINER_H */
diff --git a/src/core/lib/iomgr/endpoint.cc b/src/core/lib/iomgr/endpoint.cc
index 92e7930111..44fb47e19d 100644
--- a/src/core/lib/iomgr/endpoint.cc
+++ b/src/core/lib/iomgr/endpoint.cc
@@ -28,8 +28,8 @@ void grpc_endpoint_read(grpc_endpoint* ep, grpc_slice_buffer* slices,
}
void grpc_endpoint_write(grpc_endpoint* ep, grpc_slice_buffer* slices,
- grpc_closure* cb) {
- ep->vtable->write(ep, slices, cb);
+ grpc_closure* cb, void* arg) {
+ ep->vtable->write(ep, slices, cb, arg);
}
void grpc_endpoint_add_to_pollset(grpc_endpoint* ep, grpc_pollset* pollset) {
diff --git a/src/core/lib/iomgr/endpoint.h b/src/core/lib/iomgr/endpoint.h
index 15db1649fa..1f590a80ca 100644
--- a/src/core/lib/iomgr/endpoint.h
+++ b/src/core/lib/iomgr/endpoint.h
@@ -33,10 +33,12 @@
typedef struct grpc_endpoint grpc_endpoint;
typedef struct grpc_endpoint_vtable grpc_endpoint_vtable;
+class Timestamps;
struct grpc_endpoint_vtable {
void (*read)(grpc_endpoint* ep, grpc_slice_buffer* slices, grpc_closure* cb);
- void (*write)(grpc_endpoint* ep, grpc_slice_buffer* slices, grpc_closure* cb);
+ void (*write)(grpc_endpoint* ep, grpc_slice_buffer* slices, grpc_closure* cb,
+ void* arg);
void (*add_to_pollset)(grpc_endpoint* ep, grpc_pollset* pollset);
void (*add_to_pollset_set)(grpc_endpoint* ep, grpc_pollset_set* pollset);
void (*delete_from_pollset_set)(grpc_endpoint* ep, grpc_pollset_set* pollset);
@@ -70,9 +72,11 @@ int grpc_endpoint_get_fd(grpc_endpoint* ep);
\a slices may be mutated at will by the endpoint until cb is called.
No guarantee is made to the content of slices after a write EXCEPT that
it is a valid slice buffer.
+ \a arg is platform specific. It is currently only used by TCP on linux
+ platforms as an argument that would be forwarded to the timestamps callback.
*/
void grpc_endpoint_write(grpc_endpoint* ep, grpc_slice_buffer* slices,
- grpc_closure* cb);
+ grpc_closure* cb, void* arg);
/* Causes any pending and future read/write callbacks to run immediately with
success==0 */
diff --git a/src/core/lib/iomgr/endpoint_cfstream.cc b/src/core/lib/iomgr/endpoint_cfstream.cc
new file mode 100644
index 0000000000..df2cf508c8
--- /dev/null
+++ b/src/core/lib/iomgr/endpoint_cfstream.cc
@@ -0,0 +1,372 @@
+/*
+ *
+ * Copyright 2018 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/iomgr/port.h"
+
+#ifdef GRPC_CFSTREAM_ENDPOINT
+
+#import <CoreFoundation/CoreFoundation.h>
+#import "src/core/lib/iomgr/endpoint_cfstream.h"
+
+#include <grpc/slice_buffer.h>
+#include <grpc/support/alloc.h>
+#include <grpc/support/string_util.h>
+
+#include "src/core/lib/gpr/string.h"
+#include "src/core/lib/iomgr/cfstream_handle.h"
+#include "src/core/lib/iomgr/closure.h"
+#include "src/core/lib/iomgr/endpoint.h"
+#include "src/core/lib/iomgr/error_cfstream.h"
+#include "src/core/lib/slice/slice_internal.h"
+#include "src/core/lib/slice/slice_string_helpers.h"
+
+extern grpc_core::TraceFlag grpc_tcp_trace;
+
+typedef struct {
+ grpc_endpoint base;
+ gpr_refcount refcount;
+
+ CFReadStreamRef read_stream;
+ CFWriteStreamRef write_stream;
+ CFStreamHandle* stream_sync;
+
+ grpc_closure* read_cb;
+ grpc_closure* write_cb;
+ grpc_slice_buffer* read_slices;
+ grpc_slice_buffer* write_slices;
+
+ grpc_closure read_action;
+ grpc_closure write_action;
+
+ char* peer_string;
+ grpc_resource_user* resource_user;
+ grpc_resource_user_slice_allocator slice_allocator;
+} CFStreamEndpoint;
+
+static void CFStreamFree(CFStreamEndpoint* ep) {
+ grpc_resource_user_unref(ep->resource_user);
+ CFRelease(ep->read_stream);
+ CFRelease(ep->write_stream);
+ CFSTREAM_HANDLE_UNREF(ep->stream_sync, "free");
+ gpr_free(ep->peer_string);
+ gpr_free(ep);
+}
+
+#ifndef NDEBUG
+#define EP_REF(ep, reason) CFStreamRef((ep), (reason), __FILE__, __LINE__)
+#define EP_UNREF(ep, reason) CFStreamUnref((ep), (reason), __FILE__, __LINE__)
+static void CFStreamUnref(CFStreamEndpoint* ep, const char* reason,
+ const char* file, int line) {
+ if (grpc_tcp_trace.enabled()) {
+ gpr_atm val = gpr_atm_no_barrier_load(&ep->refcount.count);
+ gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG,
+ "CFStream endpoint unref %p : %s %" PRIdPTR " -> %" PRIdPTR, ep,
+ reason, val, val - 1);
+ }
+ if (gpr_unref(&ep->refcount)) {
+ CFStreamFree(ep);
+ }
+}
+static void CFStreamRef(CFStreamEndpoint* ep, const char* reason,
+ const char* file, int line) {
+ if (grpc_tcp_trace.enabled()) {
+ gpr_atm val = gpr_atm_no_barrier_load(&ep->refcount.count);
+ gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG,
+ "CFStream endpoint ref %p : %s %" PRIdPTR " -> %" PRIdPTR, ep,
+ reason, val, val + 1);
+ }
+ gpr_ref(&ep->refcount);
+}
+#else
+#define EP_REF(ep, reason) CFStreamRef((ep))
+#define EP_UNREF(ep, reason) CFStreamUnref((ep))
+static void CFStreamUnref(CFStreamEndpoint* ep) {
+ if (gpr_unref(&ep->refcount)) {
+ CFStreamFree(ep);
+ }
+}
+static void CFStreamRef(CFStreamEndpoint* ep) { gpr_ref(&ep->refcount); }
+#endif
+
+static grpc_error* CFStreamAnnotateError(grpc_error* src_error,
+ CFStreamEndpoint* ep) {
+ return grpc_error_set_str(
+ grpc_error_set_int(src_error, GRPC_ERROR_INT_GRPC_STATUS,
+ GRPC_STATUS_UNAVAILABLE),
+ GRPC_ERROR_STR_TARGET_ADDRESS,
+ grpc_slice_from_copied_string(ep->peer_string));
+}
+
+static void CallReadCb(CFStreamEndpoint* ep, grpc_error* error) {
+ if (grpc_tcp_trace.enabled()) {
+ gpr_log(GPR_DEBUG, "CFStream endpoint:%p call_read_cb %p %p:%p", ep,
+ ep->read_cb, ep->read_cb->cb, ep->read_cb->cb_arg);
+ size_t i;
+ const char* str = grpc_error_string(error);
+ gpr_log(GPR_DEBUG, "read: error=%s", str);
+
+ for (i = 0; i < ep->read_slices->count; i++) {
+ char* dump = grpc_dump_slice(ep->read_slices->slices[i],
+ GPR_DUMP_HEX | GPR_DUMP_ASCII);
+ gpr_log(GPR_DEBUG, "READ %p (peer=%s): %s", ep, ep->peer_string, dump);
+ gpr_free(dump);
+ }
+ }
+ grpc_closure* cb = ep->read_cb;
+ ep->read_cb = nullptr;
+ ep->read_slices = nullptr;
+ GRPC_CLOSURE_SCHED(cb, error);
+}
+
+static void CallWriteCb(CFStreamEndpoint* ep, grpc_error* error) {
+ if (grpc_tcp_trace.enabled()) {
+ gpr_log(GPR_DEBUG, "CFStream endpoint:%p call_write_cb %p %p:%p", ep,
+ ep->write_cb, ep->write_cb->cb, ep->write_cb->cb_arg);
+ const char* str = grpc_error_string(error);
+ gpr_log(GPR_DEBUG, "write: error=%s", str);
+ }
+ grpc_closure* cb = ep->write_cb;
+ ep->write_cb = nullptr;
+ ep->write_slices = nullptr;
+ GRPC_CLOSURE_SCHED(cb, error);
+}
+
+static void ReadAction(void* arg, grpc_error* error) {
+ CFStreamEndpoint* ep = static_cast<CFStreamEndpoint*>(arg);
+ GPR_ASSERT(ep->read_cb != nullptr);
+ if (error) {
+ grpc_slice_buffer_reset_and_unref_internal(ep->read_slices);
+ CallReadCb(ep, GRPC_ERROR_REF(error));
+ EP_UNREF(ep, "read");
+ return;
+ }
+
+ GPR_ASSERT(ep->read_slices->count == 1);
+ grpc_slice slice = ep->read_slices->slices[0];
+ size_t len = GRPC_SLICE_LENGTH(slice);
+ CFIndex read_size =
+ CFReadStreamRead(ep->read_stream, GRPC_SLICE_START_PTR(slice), len);
+ if (read_size == -1) {
+ grpc_slice_buffer_reset_and_unref_internal(ep->read_slices);
+ CFErrorRef stream_error = CFReadStreamCopyError(ep->read_stream);
+ if (stream_error != nullptr) {
+ error = CFStreamAnnotateError(
+ GRPC_ERROR_CREATE_FROM_CFERROR(stream_error, "Read error"), ep);
+ CFRelease(stream_error);
+ } else {
+ error = GRPC_ERROR_CREATE_FROM_STATIC_STRING("Read error");
+ }
+ CallReadCb(ep, error);
+ EP_UNREF(ep, "read");
+ } else if (read_size == 0) {
+ grpc_slice_buffer_reset_and_unref_internal(ep->read_slices);
+ CallReadCb(ep,
+ CFStreamAnnotateError(
+ GRPC_ERROR_CREATE_FROM_STATIC_STRING("Socket closed"), ep));
+ EP_UNREF(ep, "read");
+ } else {
+ if (read_size < len) {
+ grpc_slice_buffer_trim_end(ep->read_slices, len - read_size, nullptr);
+ }
+ CallReadCb(ep, GRPC_ERROR_NONE);
+ EP_UNREF(ep, "read");
+ }
+}
+
+static void WriteAction(void* arg, grpc_error* error) {
+ CFStreamEndpoint* ep = static_cast<CFStreamEndpoint*>(arg);
+ GPR_ASSERT(ep->write_cb != nullptr);
+ if (error) {
+ grpc_slice_buffer_reset_and_unref_internal(ep->write_slices);
+ CallWriteCb(ep, GRPC_ERROR_REF(error));
+ EP_UNREF(ep, "write");
+ return;
+ }
+
+ grpc_slice slice = grpc_slice_buffer_take_first(ep->write_slices);
+ size_t slice_len = GRPC_SLICE_LENGTH(slice);
+ CFIndex write_size = CFWriteStreamWrite(
+ ep->write_stream, GRPC_SLICE_START_PTR(slice), slice_len);
+ if (write_size == -1) {
+ grpc_slice_buffer_reset_and_unref_internal(ep->write_slices);
+ CFErrorRef stream_error = CFWriteStreamCopyError(ep->write_stream);
+ if (stream_error != nullptr) {
+ error = CFStreamAnnotateError(
+ GRPC_ERROR_CREATE_FROM_CFERROR(stream_error, "write failed."), ep);
+ CFRelease(stream_error);
+ } else {
+ error = GRPC_ERROR_CREATE_FROM_STATIC_STRING("write failed.");
+ }
+ CallWriteCb(ep, error);
+ EP_UNREF(ep, "write");
+ } else {
+ if (write_size < GRPC_SLICE_LENGTH(slice)) {
+ grpc_slice_buffer_undo_take_first(
+ ep->write_slices, grpc_slice_sub(slice, write_size, slice_len));
+ }
+ if (ep->write_slices->length > 0) {
+ ep->stream_sync->NotifyOnWrite(&ep->write_action);
+ } else {
+ CallWriteCb(ep, GRPC_ERROR_NONE);
+ EP_UNREF(ep, "write");
+ }
+
+ if (grpc_tcp_trace.enabled()) {
+ grpc_slice trace_slice = grpc_slice_sub(slice, 0, write_size);
+ char* dump = grpc_dump_slice(trace_slice, GPR_DUMP_HEX | GPR_DUMP_ASCII);
+ gpr_log(GPR_DEBUG, "WRITE %p (peer=%s): %s", ep, ep->peer_string, dump);
+ gpr_free(dump);
+ grpc_slice_unref_internal(trace_slice);
+ }
+ }
+ grpc_slice_unref_internal(slice);
+}
+
+static void CFStreamReadAllocationDone(void* arg, grpc_error* error) {
+ CFStreamEndpoint* ep = static_cast<CFStreamEndpoint*>(arg);
+ if (error == GRPC_ERROR_NONE) {
+ ep->stream_sync->NotifyOnRead(&ep->read_action);
+ } else {
+ grpc_slice_buffer_reset_and_unref_internal(ep->read_slices);
+ CallReadCb(ep, error);
+ EP_UNREF(ep, "read");
+ }
+}
+
+static void CFStreamRead(grpc_endpoint* ep, grpc_slice_buffer* slices,
+ grpc_closure* cb) {
+ CFStreamEndpoint* ep_impl = reinterpret_cast<CFStreamEndpoint*>(ep);
+ if (grpc_tcp_trace.enabled()) {
+ gpr_log(GPR_DEBUG, "CFStream endpoint:%p read (%p, %p) length:%zu", ep_impl,
+ slices, cb, slices->length);
+ }
+ GPR_ASSERT(ep_impl->read_cb == nullptr);
+ ep_impl->read_cb = cb;
+ ep_impl->read_slices = slices;
+ grpc_slice_buffer_reset_and_unref_internal(slices);
+ grpc_resource_user_alloc_slices(&ep_impl->slice_allocator,
+ GRPC_TCP_DEFAULT_READ_SLICE_SIZE, 1,
+ ep_impl->read_slices);
+ EP_REF(ep_impl, "read");
+}
+
+static void CFStreamWrite(grpc_endpoint* ep, grpc_slice_buffer* slices,
+ grpc_closure* cb, void* arg) {
+ CFStreamEndpoint* ep_impl = reinterpret_cast<CFStreamEndpoint*>(ep);
+ if (grpc_tcp_trace.enabled()) {
+ gpr_log(GPR_DEBUG, "CFStream endpoint:%p write (%p, %p) length:%zu",
+ ep_impl, slices, cb, slices->length);
+ }
+ GPR_ASSERT(ep_impl->write_cb == nullptr);
+ ep_impl->write_cb = cb;
+ ep_impl->write_slices = slices;
+ EP_REF(ep_impl, "write");
+ ep_impl->stream_sync->NotifyOnWrite(&ep_impl->write_action);
+}
+
+void CFStreamShutdown(grpc_endpoint* ep, grpc_error* why) {
+ CFStreamEndpoint* ep_impl = reinterpret_cast<CFStreamEndpoint*>(ep);
+ if (grpc_tcp_trace.enabled()) {
+ gpr_log(GPR_DEBUG, "CFStream endpoint:%p shutdown (%p)", ep_impl, why);
+ }
+ CFReadStreamClose(ep_impl->read_stream);
+ CFWriteStreamClose(ep_impl->write_stream);
+ ep_impl->stream_sync->Shutdown(why);
+ grpc_resource_user_shutdown(ep_impl->resource_user);
+ if (grpc_tcp_trace.enabled()) {
+ gpr_log(GPR_DEBUG, "CFStream endpoint:%p shutdown DONE (%p)", ep_impl, why);
+ }
+}
+
+void CFStreamDestroy(grpc_endpoint* ep) {
+ CFStreamEndpoint* ep_impl = reinterpret_cast<CFStreamEndpoint*>(ep);
+ if (grpc_tcp_trace.enabled()) {
+ gpr_log(GPR_DEBUG, "CFStream endpoint:%p destroy", ep_impl);
+ }
+ EP_UNREF(ep_impl, "destroy");
+}
+
+grpc_resource_user* CFStreamGetResourceUser(grpc_endpoint* ep) {
+ CFStreamEndpoint* ep_impl = reinterpret_cast<CFStreamEndpoint*>(ep);
+ return ep_impl->resource_user;
+}
+
+char* CFStreamGetPeer(grpc_endpoint* ep) {
+ CFStreamEndpoint* ep_impl = reinterpret_cast<CFStreamEndpoint*>(ep);
+ return gpr_strdup(ep_impl->peer_string);
+}
+
+int CFStreamGetFD(grpc_endpoint* ep) { return 0; }
+
+void CFStreamAddToPollset(grpc_endpoint* ep, grpc_pollset* pollset) {}
+void CFStreamAddToPollsetSet(grpc_endpoint* ep, grpc_pollset_set* pollset) {}
+void CFStreamDeleteFromPollsetSet(grpc_endpoint* ep,
+ grpc_pollset_set* pollset) {}
+
+static const grpc_endpoint_vtable vtable = {CFStreamRead,
+ CFStreamWrite,
+ CFStreamAddToPollset,
+ CFStreamAddToPollsetSet,
+ CFStreamDeleteFromPollsetSet,
+ CFStreamShutdown,
+ CFStreamDestroy,
+ CFStreamGetResourceUser,
+ CFStreamGetPeer,
+ CFStreamGetFD};
+
+grpc_endpoint* grpc_cfstream_endpoint_create(
+ CFReadStreamRef read_stream, CFWriteStreamRef write_stream,
+ const char* peer_string, grpc_resource_quota* resource_quota,
+ CFStreamHandle* stream_sync) {
+ CFStreamEndpoint* ep_impl =
+ static_cast<CFStreamEndpoint*>(gpr_malloc(sizeof(CFStreamEndpoint)));
+ if (grpc_tcp_trace.enabled()) {
+ gpr_log(GPR_DEBUG,
+ "CFStream endpoint:%p create readStream:%p writeStream: %p",
+ ep_impl, read_stream, write_stream);
+ }
+ ep_impl->base.vtable = &vtable;
+ gpr_ref_init(&ep_impl->refcount, 1);
+ ep_impl->read_stream = read_stream;
+ ep_impl->write_stream = write_stream;
+ CFRetain(read_stream);
+ CFRetain(write_stream);
+ ep_impl->stream_sync = stream_sync;
+ CFSTREAM_HANDLE_REF(ep_impl->stream_sync, "endpoint create");
+
+ ep_impl->peer_string = gpr_strdup(peer_string);
+ ep_impl->read_cb = nil;
+ ep_impl->write_cb = nil;
+ ep_impl->read_slices = nil;
+ ep_impl->write_slices = nil;
+ GRPC_CLOSURE_INIT(&ep_impl->read_action, ReadAction,
+ static_cast<void*>(ep_impl), grpc_schedule_on_exec_ctx);
+ GRPC_CLOSURE_INIT(&ep_impl->write_action, WriteAction,
+ static_cast<void*>(ep_impl), grpc_schedule_on_exec_ctx);
+ ep_impl->resource_user =
+ grpc_resource_user_create(resource_quota, peer_string);
+ grpc_resource_user_slice_allocator_init(&ep_impl->slice_allocator,
+ ep_impl->resource_user,
+ CFStreamReadAllocationDone, ep_impl);
+
+ return &ep_impl->base;
+}
+
+#endif /* GRPC_CFSTREAM_ENDPOINT */
diff --git a/src/core/lib/iomgr/endpoint_cfstream.h b/src/core/lib/iomgr/endpoint_cfstream.h
new file mode 100644
index 0000000000..ef957c1f11
--- /dev/null
+++ b/src/core/lib/iomgr/endpoint_cfstream.h
@@ -0,0 +1,49 @@
+/*
+ *
+ * Copyright 2018 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_IOMGR_ENDPOINT_CFSTREAM_H
+#define GRPC_CORE_LIB_IOMGR_ENDPOINT_CFSTREAM_H
+/*
+ Low level TCP "bottom half" implementation, for use by transports built on
+ top of a TCP connection.
+
+ Note that this file does not (yet) include APIs for creating the socket in
+ the first place.
+
+ All calls passing slice transfer ownership of a slice refcount unless
+ otherwise specified.
+*/
+
+#include <grpc/support/port_platform.h>
+
+#ifdef GRPC_CFSTREAM
+
+#import <CoreFoundation/CoreFoundation.h>
+
+#include "src/core/lib/debug/trace.h"
+#include "src/core/lib/iomgr/cfstream_handle.h"
+#include "src/core/lib/iomgr/endpoint.h"
+
+grpc_endpoint* grpc_cfstream_endpoint_create(
+ CFReadStreamRef read_stream, CFWriteStreamRef write_stream,
+ const char* peer_string, grpc_resource_quota* resource_quota,
+ CFStreamHandle* stream_sync);
+
+#endif /* GRPC_CFSTREAM */
+
+#endif /* GRPC_CORE_LIB_IOMGR_ENDPOINT_CFSTREAM_H */
diff --git a/src/core/lib/iomgr/endpoint_pair_posix.cc b/src/core/lib/iomgr/endpoint_pair_posix.cc
index 49850ab3a1..3afbfd7254 100644
--- a/src/core/lib/iomgr/endpoint_pair_posix.cc
+++ b/src/core/lib/iomgr/endpoint_pair_posix.cc
@@ -59,11 +59,11 @@ grpc_endpoint_pair grpc_iomgr_create_endpoint_pair(const char* name,
grpc_core::ExecCtx exec_ctx;
gpr_asprintf(&final_name, "%s:client", name);
- p.client = grpc_tcp_create(grpc_fd_create(sv[1], final_name), args,
+ p.client = grpc_tcp_create(grpc_fd_create(sv[1], final_name, true), args,
"socketpair-server");
gpr_free(final_name);
gpr_asprintf(&final_name, "%s:server", name);
- p.server = grpc_tcp_create(grpc_fd_create(sv[0], final_name), args,
+ p.server = grpc_tcp_create(grpc_fd_create(sv[0], final_name, true), args,
"socketpair-client");
gpr_free(final_name);
diff --git a/src/core/lib/iomgr/error.cc b/src/core/lib/iomgr/error.cc
index 4088cf612e..146a539027 100644
--- a/src/core/lib/iomgr/error.cc
+++ b/src/core/lib/iomgr/error.cc
@@ -312,6 +312,12 @@ static void internal_add_error(grpc_error** err, grpc_error* new_err) {
// It is very common to include and extra int and string in an error
#define SURPLUS_CAPACITY (2 * SLOTS_PER_INT + SLOTS_PER_TIME)
+static bool g_error_creation_allowed = true;
+
+void grpc_disable_error_creation() { g_error_creation_allowed = false; }
+
+void grpc_enable_error_creation() { g_error_creation_allowed = true; }
+
grpc_error* grpc_error_create(const char* file, int line, grpc_slice desc,
grpc_error** referencing,
size_t num_referencing) {
@@ -326,6 +332,12 @@ grpc_error* grpc_error_create(const char* file, int line, grpc_slice desc,
return GRPC_ERROR_OOM;
}
#ifndef NDEBUG
+ if (!g_error_creation_allowed) {
+ gpr_log(GPR_ERROR,
+ "Error creation occurred when error creation was disabled [%s:%d]",
+ file, line);
+ abort();
+ }
if (grpc_trace_error_refcount.enabled()) {
gpr_log(GPR_DEBUG, "%p create [%s:%d]", err, file, line);
}
@@ -442,7 +454,7 @@ typedef struct {
grpc_status_code code;
const char* msg;
} special_error_status_map;
-static special_error_status_map error_status_map[] = {
+static const special_error_status_map error_status_map[] = {
{GRPC_ERROR_NONE, GRPC_STATUS_OK, ""},
{GRPC_ERROR_CANCELLED, GRPC_STATUS_CANCELLED, "Cancelled"},
{GRPC_ERROR_OOM, GRPC_STATUS_RESOURCE_EXHAUSTED, "Out of memory"},
@@ -451,15 +463,13 @@ static special_error_status_map error_status_map[] = {
bool grpc_error_get_int(grpc_error* err, grpc_error_ints which, intptr_t* p) {
GPR_TIMER_SCOPE("grpc_error_get_int", 0);
if (grpc_error_is_special(err)) {
- if (which == GRPC_ERROR_INT_GRPC_STATUS) {
- for (size_t i = 0; i < GPR_ARRAY_SIZE(error_status_map); i++) {
- if (error_status_map[i].error == err) {
- if (p != nullptr) *p = error_status_map[i].code;
- return true;
- }
+ for (size_t i = 0; i < GPR_ARRAY_SIZE(error_status_map); i++) {
+ if (error_status_map[i].error == err) {
+ if (which != GRPC_ERROR_INT_GRPC_STATUS) return false;
+ if (p != nullptr) *p = error_status_map[i].code;
+ return true;
}
}
- return false;
}
uint8_t slot = err->ints[which];
if (slot != UINT8_MAX) {
@@ -480,15 +490,13 @@ grpc_error* grpc_error_set_str(grpc_error* src, grpc_error_strs which,
bool grpc_error_get_str(grpc_error* err, grpc_error_strs which,
grpc_slice* str) {
if (grpc_error_is_special(err)) {
- if (which == GRPC_ERROR_STR_GRPC_MESSAGE) {
- for (size_t i = 0; i < GPR_ARRAY_SIZE(error_status_map); i++) {
- if (error_status_map[i].error == err) {
- *str = grpc_slice_from_static_string(error_status_map[i].msg);
- return true;
- }
+ for (size_t i = 0; i < GPR_ARRAY_SIZE(error_status_map); i++) {
+ if (error_status_map[i].error == err) {
+ if (which != GRPC_ERROR_STR_GRPC_MESSAGE) return false;
+ *str = grpc_slice_from_static_string(error_status_map[i].msg);
+ return true;
}
}
- return false;
}
uint8_t slot = err->strs[which];
if (slot != UINT8_MAX) {
@@ -501,9 +509,24 @@ bool grpc_error_get_str(grpc_error* err, grpc_error_strs which,
grpc_error* grpc_error_add_child(grpc_error* src, grpc_error* child) {
GPR_TIMER_SCOPE("grpc_error_add_child", 0);
- grpc_error* new_err = copy_error_and_unref(src);
- internal_add_error(&new_err, child);
- return new_err;
+ if (src != GRPC_ERROR_NONE) {
+ if (child == GRPC_ERROR_NONE) {
+ /* \a child is empty. Simply return the ref to \a src */
+ return src;
+ } else if (child != src) {
+ grpc_error* new_err = copy_error_and_unref(src);
+ internal_add_error(&new_err, child);
+ return new_err;
+ } else {
+ /* \a src and \a child are the same. Drop one of the references and return
+ * the other */
+ GRPC_ERROR_UNREF(child);
+ return src;
+ }
+ } else {
+ /* \a src is empty. Simply return the ref to \a child */
+ return child;
+ }
}
static const char* no_error_string = "\"No Error\"";
diff --git a/src/core/lib/iomgr/error.h b/src/core/lib/iomgr/error.h
index f8cae4da82..49f4029bc2 100644
--- a/src/core/lib/iomgr/error.h
+++ b/src/core/lib/iomgr/error.h
@@ -123,6 +123,11 @@ typedef enum {
#define GRPC_ERROR_OOM ((grpc_error*)2)
#define GRPC_ERROR_CANCELLED ((grpc_error*)4)
+// debug only toggles that allow for a sanity to check that ensures we will
+// never create any errors in the per-RPC hotpath.
+void grpc_disable_error_creation();
+void grpc_enable_error_creation();
+
const char* grpc_error_string(grpc_error* error);
/// Create an error - but use GRPC_ERROR_CREATE instead
@@ -180,8 +185,16 @@ bool grpc_error_get_str(grpc_error* error, grpc_error_strs which,
/// error occurring. Allows root causing high level errors from lower level
/// errors that contributed to them. The src error takes ownership of the
/// child error.
+///
+/// Edge Conditions -
+/// 1) If either of \a src or \a child is GRPC_ERROR_NONE, returns a reference
+/// to the other argument. 2) If both \a src and \a child are GRPC_ERROR_NONE,
+/// returns GRPC_ERROR_NONE. 3) If \a src and \a child point to the same error,
+/// returns a single reference. (Note that, 2 references should have been
+/// received to the error in this case.)
grpc_error* grpc_error_add_child(grpc_error* src,
grpc_error* child) GRPC_MUST_USE_RESULT;
+
grpc_error* grpc_os_error(const char* file, int line, int err,
const char* call_name) GRPC_MUST_USE_RESULT;
diff --git a/src/core/lib/iomgr/error_cfstream.cc b/src/core/lib/iomgr/error_cfstream.cc
new file mode 100644
index 0000000000..d7af8c377f
--- /dev/null
+++ b/src/core/lib/iomgr/error_cfstream.cc
@@ -0,0 +1,52 @@
+/*
+ *
+ * Copyright 2018 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>
+
+#ifdef GRPC_CFSTREAM
+#include <CoreFoundation/CoreFoundation.h>
+
+#include <grpc/support/alloc.h>
+#include <grpc/support/string_util.h>
+
+#include "src/core/lib/iomgr/error.h"
+
+#define MAX_ERROR_DESCRIPTION 256
+
+grpc_error* grpc_error_create_from_cferror(const char* file, int line,
+ void* arg, const char* custom_desc) {
+ CFErrorRef error = static_cast<CFErrorRef>(arg);
+ char buf_domain[MAX_ERROR_DESCRIPTION];
+ char buf_desc[MAX_ERROR_DESCRIPTION];
+ char* error_msg;
+ CFErrorDomain domain = CFErrorGetDomain((error));
+ CFIndex code = CFErrorGetCode((error));
+ CFStringRef desc = CFErrorCopyDescription((error));
+ CFStringGetCString(domain, buf_domain, MAX_ERROR_DESCRIPTION,
+ kCFStringEncodingUTF8);
+ CFStringGetCString(desc, buf_desc, MAX_ERROR_DESCRIPTION,
+ kCFStringEncodingUTF8);
+ gpr_asprintf(&error_msg, "%s (error domain:%s, code:%ld, description:%s)",
+ custom_desc, buf_domain, code, buf_desc);
+ CFRelease(desc);
+ grpc_error* return_error = grpc_error_create(
+ file, line, grpc_slice_from_copied_string(error_msg), NULL, 0);
+ gpr_free(error_msg);
+ return return_error;
+}
+#endif /* GRPC_CFSTREAM */
diff --git a/src/core/lib/iomgr/error_cfstream.h b/src/core/lib/iomgr/error_cfstream.h
new file mode 100644
index 0000000000..06ab751329
--- /dev/null
+++ b/src/core/lib/iomgr/error_cfstream.h
@@ -0,0 +1,31 @@
+/*
+ *
+ * Copyright 2018 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_IOMGR_ERROR_CFSTREAM_H
+#define GRPC_CORE_LIB_IOMGR_ERROR_CFSTREAM_H
+
+#ifdef GRPC_CFSTREAM
+// Create an error from Apple Core Foundation CFError object
+#define GRPC_ERROR_CREATE_FROM_CFERROR(error, desc) \
+ grpc_error_create_from_cferror(__FILE__, __LINE__, \
+ static_cast<void*>((error)), (desc))
+grpc_error* grpc_error_create_from_cferror(const char* file, int line,
+ void* arg, const char* desc);
+#endif /* GRPC_CFSTREAM */
+
+#endif /* GRPC_CORE_LIB_IOMGR_ERROR_CFSTREAM_H */
diff --git a/src/core/lib/iomgr/ev_epoll1_linux.cc b/src/core/lib/iomgr/ev_epoll1_linux.cc
index e5db1be0e0..aa5016bd8f 100644
--- a/src/core/lib/iomgr/ev_epoll1_linux.cc
+++ b/src/core/lib/iomgr/ev_epoll1_linux.cc
@@ -131,19 +131,26 @@ static void epoll_set_shutdown() {
* Fd Declarations
*/
+/* Only used when GRPC_ENABLE_FORK_SUPPORT=1 */
+struct grpc_fork_fd_list {
+ grpc_fd* fd;
+ grpc_fd* next;
+ grpc_fd* prev;
+};
+
struct grpc_fd {
int fd;
grpc_core::ManualConstructor<grpc_core::LockfreeEvent> read_closure;
grpc_core::ManualConstructor<grpc_core::LockfreeEvent> write_closure;
+ grpc_core::ManualConstructor<grpc_core::LockfreeEvent> error_closure;
struct grpc_fd* freelist_next;
- /* The pollset that last noticed that the fd is readable. The actual type
- * stored in this is (grpc_pollset *) */
- gpr_atm read_notifier_pollset;
-
grpc_iomgr_object iomgr_object;
+
+ /* Only used when GRPC_ENABLE_FORK_SUPPORT=1 */
+ grpc_fork_fd_list* fork_fd_list;
};
static void fd_global_init(void);
@@ -259,6 +266,10 @@ static bool append_error(grpc_error** composite, grpc_error* error,
static grpc_fd* fd_freelist = nullptr;
static gpr_mu fd_freelist_mu;
+/* Only used when GRPC_ENABLE_FORK_SUPPORT=1 */
+static grpc_fd* fork_fd_list_head = nullptr;
+static gpr_mu fork_fd_list_mu;
+
static void fd_global_init(void) { gpr_mu_init(&fd_freelist_mu); }
static void fd_global_shutdown(void) {
@@ -272,7 +283,39 @@ static void fd_global_shutdown(void) {
gpr_mu_destroy(&fd_freelist_mu);
}
-static grpc_fd* fd_create(int fd, const char* name) {
+static void fork_fd_list_add_grpc_fd(grpc_fd* fd) {
+ if (grpc_core::Fork::Enabled()) {
+ gpr_mu_lock(&fork_fd_list_mu);
+ fd->fork_fd_list =
+ static_cast<grpc_fork_fd_list*>(gpr_malloc(sizeof(grpc_fork_fd_list)));
+ fd->fork_fd_list->next = fork_fd_list_head;
+ fd->fork_fd_list->prev = nullptr;
+ if (fork_fd_list_head != nullptr) {
+ fork_fd_list_head->fork_fd_list->prev = fd;
+ }
+ fork_fd_list_head = fd;
+ gpr_mu_unlock(&fork_fd_list_mu);
+ }
+}
+
+static void fork_fd_list_remove_grpc_fd(grpc_fd* fd) {
+ if (grpc_core::Fork::Enabled()) {
+ gpr_mu_lock(&fork_fd_list_mu);
+ if (fork_fd_list_head == fd) {
+ fork_fd_list_head = fd->fork_fd_list->next;
+ }
+ if (fd->fork_fd_list->prev != nullptr) {
+ fd->fork_fd_list->prev->fork_fd_list->next = fd->fork_fd_list->next;
+ }
+ if (fd->fork_fd_list->next != nullptr) {
+ fd->fork_fd_list->next->fork_fd_list->prev = fd->fork_fd_list->prev;
+ }
+ gpr_free(fd->fork_fd_list);
+ gpr_mu_unlock(&fork_fd_list_mu);
+ }
+}
+
+static grpc_fd* fd_create(int fd, const char* name, bool track_err) {
grpc_fd* new_fd = nullptr;
gpr_mu_lock(&fd_freelist_mu);
@@ -286,18 +329,19 @@ static grpc_fd* fd_create(int fd, const char* name) {
new_fd = static_cast<grpc_fd*>(gpr_malloc(sizeof(grpc_fd)));
new_fd->read_closure.Init();
new_fd->write_closure.Init();
+ new_fd->error_closure.Init();
}
-
new_fd->fd = fd;
new_fd->read_closure->InitEvent();
new_fd->write_closure->InitEvent();
- gpr_atm_no_barrier_store(&new_fd->read_notifier_pollset, (gpr_atm)NULL);
+ new_fd->error_closure->InitEvent();
new_fd->freelist_next = nullptr;
char* fd_name;
gpr_asprintf(&fd_name, "%s fd=%d", name, fd);
grpc_iomgr_register_object(&new_fd->iomgr_object, fd_name);
+ fork_fd_list_add_grpc_fd(new_fd);
#ifndef NDEBUG
if (grpc_trace_fd_refcount.enabled()) {
gpr_log(GPR_DEBUG, "FD %d %p create %s", fd, new_fd, fd_name);
@@ -307,7 +351,13 @@ static grpc_fd* fd_create(int fd, const char* name) {
struct epoll_event ev;
ev.events = static_cast<uint32_t>(EPOLLIN | EPOLLOUT | EPOLLET);
- ev.data.ptr = new_fd;
+ /* Use the least significant bit of ev.data.ptr to store track_err. We expect
+ * the addresses to be word aligned. We need to store track_err to avoid
+ * synchronization issues when accessing it after receiving an event.
+ * Accessing fd would be a data race there because the fd might have been
+ * returned to the free list at that point. */
+ ev.data.ptr = reinterpret_cast<void*>(reinterpret_cast<intptr_t>(new_fd) |
+ (track_err ? 1 : 0));
if (epoll_ctl(g_epoll_set.epfd, EPOLL_CTL_ADD, fd, &ev) != 0) {
gpr_log(GPR_ERROR, "epoll_ctl failed: %s", strerror(errno));
}
@@ -327,6 +377,7 @@ static void fd_shutdown_internal(grpc_fd* fd, grpc_error* why,
shutdown(fd->fd, SHUT_RDWR);
}
fd->write_closure->SetShutdown(GRPC_ERROR_REF(why));
+ fd->error_closure->SetShutdown(GRPC_ERROR_REF(why));
}
GRPC_ERROR_UNREF(why);
}
@@ -337,7 +388,7 @@ static void fd_shutdown(grpc_fd* fd, grpc_error* why) {
}
static void fd_orphan(grpc_fd* fd, grpc_closure* on_done, int* release_fd,
- bool already_closed, const char* reason) {
+ const char* reason) {
grpc_error* error = GRPC_ERROR_NONE;
bool is_release_fd = (release_fd != nullptr);
@@ -350,15 +401,17 @@ static void fd_orphan(grpc_fd* fd, grpc_closure* on_done, int* release_fd,
descriptor fd->fd (but we still own the grpc_fd structure). */
if (is_release_fd) {
*release_fd = fd->fd;
- } else if (!already_closed) {
+ } else {
close(fd->fd);
}
GRPC_CLOSURE_SCHED(on_done, GRPC_ERROR_REF(error));
grpc_iomgr_unregister_object(&fd->iomgr_object);
+ fork_fd_list_remove_grpc_fd(fd);
fd->read_closure->DestroyEvent();
fd->write_closure->DestroyEvent();
+ fd->error_closure->DestroyEvent();
gpr_mu_lock(&fd_freelist_mu);
fd->freelist_next = fd_freelist;
@@ -366,11 +419,6 @@ static void fd_orphan(grpc_fd* fd, grpc_closure* on_done, int* release_fd,
gpr_mu_unlock(&fd_freelist_mu);
}
-static grpc_pollset* fd_get_read_notifier_pollset(grpc_fd* fd) {
- gpr_atm notifier = gpr_atm_acq_load(&fd->read_notifier_pollset);
- return (grpc_pollset*)notifier;
-}
-
static bool fd_is_shutdown(grpc_fd* fd) {
return fd->read_closure->IsShutdown();
}
@@ -383,14 +431,16 @@ static void fd_notify_on_write(grpc_fd* fd, grpc_closure* closure) {
fd->write_closure->NotifyOn(closure);
}
-static void fd_become_readable(grpc_fd* fd, grpc_pollset* notifier) {
- fd->read_closure->SetReady();
- /* Use release store to match with acquire load in fd_get_read_notifier */
- gpr_atm_rel_store(&fd->read_notifier_pollset, (gpr_atm)notifier);
+static void fd_notify_on_error(grpc_fd* fd, grpc_closure* closure) {
+ fd->error_closure->NotifyOn(closure);
}
+static void fd_become_readable(grpc_fd* fd) { fd->read_closure->SetReady(); }
+
static void fd_become_writable(grpc_fd* fd) { fd->write_closure->SetReady(); }
+static void fd_has_errors(grpc_fd* fd) { fd->error_closure->SetReady(); }
+
/*******************************************************************************
* Pollset Definitions
*/
@@ -611,16 +661,25 @@ static grpc_error* process_epoll_events(grpc_pollset* pollset) {
append_error(&error, grpc_wakeup_fd_consume_wakeup(&global_wakeup_fd),
err_desc);
} else {
- grpc_fd* fd = static_cast<grpc_fd*>(data_ptr);
- bool cancel = (ev->events & (EPOLLERR | EPOLLHUP)) != 0;
+ grpc_fd* fd = reinterpret_cast<grpc_fd*>(
+ reinterpret_cast<intptr_t>(data_ptr) & ~static_cast<intptr_t>(1));
+ bool track_err =
+ reinterpret_cast<intptr_t>(data_ptr) & static_cast<intptr_t>(1);
+ bool cancel = (ev->events & EPOLLHUP) != 0;
+ bool error = (ev->events & EPOLLERR) != 0;
bool read_ev = (ev->events & (EPOLLIN | EPOLLPRI)) != 0;
bool write_ev = (ev->events & EPOLLOUT) != 0;
+ bool err_fallback = error && !track_err;
- if (read_ev || cancel) {
- fd_become_readable(fd, pollset);
+ if (error && !err_fallback) {
+ fd_has_errors(fd);
}
- if (write_ev || cancel) {
+ if (read_ev || cancel || err_fallback) {
+ fd_become_readable(fd);
+ }
+
+ if (write_ev || cancel || err_fallback) {
fd_become_writable(fd);
}
}
@@ -1179,10 +1238,15 @@ static void shutdown_engine(void) {
fd_global_shutdown();
pollset_global_shutdown();
epoll_set_shutdown();
+ if (grpc_core::Fork::Enabled()) {
+ gpr_mu_destroy(&fork_fd_list_mu);
+ grpc_core::Fork::SetResetChildPollingEngineFunc(nullptr);
+ }
}
static const grpc_event_engine_vtable vtable = {
sizeof(grpc_pollset),
+ true,
fd_create,
fd_wrapped_fd,
@@ -1190,8 +1254,11 @@ static const grpc_event_engine_vtable vtable = {
fd_shutdown,
fd_notify_on_read,
fd_notify_on_write,
+ fd_notify_on_error,
+ fd_become_readable,
+ fd_become_writable,
+ fd_has_errors,
fd_is_shutdown,
- fd_get_read_notifier_pollset,
pollset_init,
pollset_shutdown,
@@ -1212,6 +1279,21 @@ static const grpc_event_engine_vtable vtable = {
shutdown_engine,
};
+/* Called by the child process's post-fork handler to close open fds, including
+ * the global epoll fd. This allows gRPC to shutdown in the child process
+ * without interfering with connections or RPCs ongoing in the parent. */
+static void reset_event_manager_on_fork() {
+ gpr_mu_lock(&fork_fd_list_mu);
+ while (fork_fd_list_head != nullptr) {
+ close(fork_fd_list_head->fd);
+ fork_fd_list_head->fd = -1;
+ fork_fd_list_head = fork_fd_list_head->fork_fd_list->next;
+ }
+ gpr_mu_unlock(&fork_fd_list_mu);
+ shutdown_engine();
+ grpc_init_epoll1_linux(true);
+}
+
/* It is possible that GLIBC has epoll but the underlying kernel doesn't.
* Create epoll_fd (epoll_set_init() takes care of that) to make sure epoll
* support is available */
@@ -1233,16 +1315,21 @@ const grpc_event_engine_vtable* grpc_init_epoll1_linux(bool explicit_request) {
return nullptr;
}
+ if (grpc_core::Fork::Enabled()) {
+ gpr_mu_init(&fork_fd_list_mu);
+ grpc_core::Fork::SetResetChildPollingEngineFunc(
+ reset_event_manager_on_fork);
+ }
return &vtable;
}
#else /* defined(GRPC_LINUX_EPOLL) */
-#if defined(GRPC_POSIX_SOCKET)
+#if defined(GRPC_POSIX_SOCKET_EV_EPOLL1)
#include "src/core/lib/iomgr/ev_epoll1_linux.h"
/* If GRPC_LINUX_EPOLL is not defined, it means epoll is not available. Return
* NULL */
const grpc_event_engine_vtable* grpc_init_epoll1_linux(bool explicit_request) {
return nullptr;
}
-#endif /* defined(GRPC_POSIX_SOCKET) */
+#endif /* defined(GRPC_POSIX_SOCKET_EV_EPOLL1) */
#endif /* !defined(GRPC_LINUX_EPOLL) */
diff --git a/src/core/lib/iomgr/ev_epollex_linux.cc b/src/core/lib/iomgr/ev_epollex_linux.cc
index 98369ddd6e..b082634af1 100644
--- a/src/core/lib/iomgr/ev_epollex_linux.cc
+++ b/src/core/lib/iomgr/ev_epollex_linux.cc
@@ -33,6 +33,7 @@
#include <poll.h>
#include <pthread.h>
#include <string.h>
+#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <unistd.h>
@@ -45,6 +46,7 @@
#include "src/core/lib/gpr/tls.h"
#include "src/core/lib/gpr/useful.h"
#include "src/core/lib/gprpp/manual_constructor.h"
+#include "src/core/lib/gprpp/mutex_lock.h"
#include "src/core/lib/iomgr/block_annotate.h"
#include "src/core/lib/iomgr/iomgr_internal.h"
#include "src/core/lib/iomgr/is_epollexclusive_available.h"
@@ -63,6 +65,7 @@
// a keepalive ping timeout issue. We may want to revert https://github
// .com/grpc/grpc/pull/14943 once we figure out the root cause.
#define MAX_EPOLL_EVENTS_HANDLED_EACH_POLL_CALL 16
+#define MAX_FDS_IN_CACHE 32
grpc_core::DebugOnlyTraceFlag grpc_trace_pollable_refcount(false,
"pollable_refcount");
@@ -75,6 +78,18 @@ typedef enum { PO_MULTI, PO_FD, PO_EMPTY } pollable_type;
typedef struct pollable pollable;
+typedef struct cached_fd {
+ // Set to the grpc_fd's salt value. See 'salt' variable' in grpc_fd for more
+ // details
+ intptr_t salt;
+
+ // The underlying fd
+ int fd;
+
+ // A recency time counter that helps to determine the LRU fd in the cache
+ uint64_t last_used;
+} cached_fd;
+
/// A pollable is something that can be polled: it has an epoll set to poll on,
/// and a wakeup fd for kicks
/// There are three broad types:
@@ -90,8 +105,10 @@ struct pollable {
int epfd;
grpc_wakeup_fd wakeup;
- // only for type fd... one ref to the owner fd
- grpc_fd* owner_fd;
+ // The following are relevant only for type PO_FD
+ grpc_fd* owner_fd; // Set to the owner_fd if the type is PO_FD
+ gpr_mu owner_orphan_mu; // Synchronizes access to owner_orphaned field
+ bool owner_orphaned; // Is the owner fd orphaned
grpc_pollset_set* pollset_set;
pollable* next;
@@ -103,6 +120,33 @@ struct pollable {
int event_cursor;
int event_count;
struct epoll_event events[MAX_EPOLL_EVENTS];
+
+ // We may be calling pollable_add_fd() on the same (pollable, fd) multiple
+ // times. To prevent pollable_add_fd() from making multiple sys calls to
+ // epoll_ctl() to add the fd, we maintain a cache of what fds are already
+ // present in the underlying epoll-set.
+ //
+ // Since this is not a correctness issue, we do not need to maintain all the
+ // fds in the cache. Hence we just use an LRU cache of size 'MAX_FDS_IN_CACHE'
+ //
+ // NOTE: An ideal implementation of this should do the following:
+ // 1) Add fds to the cache in pollable_add_fd() function (i.e whenever the fd
+ // is added to the pollable's epoll set)
+ // 2) Remove the fd from the cache whenever the fd is removed from the
+ // underlying epoll set (i.e whenever fd_orphan() is called).
+ //
+ // Implementing (2) above (i.e removing fds from cache on fd_orphan) adds a
+ // lot of complexity since an fd can be present in multiple pollables. So our
+ // implementation ONLY DOES (1) and NOT (2).
+ //
+ // The cache_fd.salt variable helps here to maintain correctness (it serves as
+ // an epoch that differentiates one grpc_fd from the other even though both of
+ // them may have the same fd number)
+ //
+ // The following implements LRU-eviction cache of fds in this pollable
+ cached_fd fd_cache[MAX_FDS_IN_CACHE];
+ int fd_cache_size;
+ uint64_t fd_cache_counter; // Recency timer tick counter
};
static const char* pollable_type_string(pollable_type t) {
@@ -145,12 +189,24 @@ static void pollable_unref(pollable* p, int line, const char* reason);
* Fd Declarations
*/
+// Monotonically increasing Epoch counter that is assinged to each grpc_fd. See
+// the description of 'salt' variable in 'grpc_fd' for more details
+// TODO: (sreek/kpayson) gpr_atm is intptr_t which may not be wide-enough on
+// 32-bit systems. Change this to int_64 - atleast on 32-bit systems
+static gpr_atm g_fd_salt;
+
struct grpc_fd {
int fd;
- /* refst format:
- bit 0 : 1=Active / 0=Orphaned
- bits 1-n : refcount
- Ref/Unref by two to avoid altering the orphaned bit */
+
+ // Since fd numbers can be reused (after old fds are closed), this serves as
+ // an epoch that uniquely identifies this fd (i.e the pair (salt, fd) is
+ // unique (until the salt counter (i.e g_fd_salt) overflows)
+ intptr_t salt;
+
+ // refst format:
+ // bit 0 : 1=Active / 0=Orphaned
+ // bits 1-n : refcount
+ // Ref/Unref by two to avoid altering the orphaned bit
gpr_atm refst;
gpr_mu orphan_mu;
@@ -160,15 +216,15 @@ struct grpc_fd {
grpc_core::ManualConstructor<grpc_core::LockfreeEvent> read_closure;
grpc_core::ManualConstructor<grpc_core::LockfreeEvent> write_closure;
+ grpc_core::ManualConstructor<grpc_core::LockfreeEvent> error_closure;
struct grpc_fd* freelist_next;
grpc_closure* on_done_closure;
- /* The pollset that last noticed that the fd is readable. The actual type
- * stored in this is (grpc_pollset *) */
- gpr_atm read_notifier_pollset;
-
grpc_iomgr_object iomgr_object;
+
+ // Do we need to track EPOLLERR events separately?
+ bool track_err;
};
static void fd_global_init(void);
@@ -281,20 +337,44 @@ static void ref_by(grpc_fd* fd, int n) {
GPR_ASSERT(gpr_atm_no_barrier_fetch_add(&fd->refst, n) > 0);
}
+#ifndef NDEBUG
+#define INVALIDATE_FD(fd) invalidate_fd(fd)
+/* Since an fd is never really destroyed (i.e gpr_free() is not called), it is
+ * hard to cases where fd fields are accessed even after calling fd_destroy().
+ * The following invalidates fd fields to make catching such errors easier */
+static void invalidate_fd(grpc_fd* fd) {
+ fd->fd = -1;
+ fd->salt = -1;
+ gpr_atm_no_barrier_store(&fd->refst, -1);
+ memset(&fd->orphan_mu, -1, sizeof(fd->orphan_mu));
+ memset(&fd->pollable_mu, -1, sizeof(fd->pollable_mu));
+ fd->pollable_obj = nullptr;
+ fd->on_done_closure = nullptr;
+ memset(&fd->iomgr_object, -1, sizeof(fd->iomgr_object));
+ fd->track_err = false;
+}
+#else
+#define INVALIDATE_FD(fd)
+#endif
+
+/* Uninitialize and add to the freelist */
static void fd_destroy(void* arg, grpc_error* error) {
grpc_fd* fd = static_cast<grpc_fd*>(arg);
- /* Add the fd to the freelist */
grpc_iomgr_unregister_object(&fd->iomgr_object);
POLLABLE_UNREF(fd->pollable_obj, "fd_pollable");
gpr_mu_destroy(&fd->pollable_mu);
gpr_mu_destroy(&fd->orphan_mu);
- gpr_mu_lock(&fd_freelist_mu);
- fd->freelist_next = fd_freelist;
- fd_freelist = fd;
fd->read_closure->DestroyEvent();
fd->write_closure->DestroyEvent();
+ fd->error_closure->DestroyEvent();
+ INVALIDATE_FD(fd);
+
+ /* Add the fd to the freelist */
+ gpr_mu_lock(&fd_freelist_mu);
+ fd->freelist_next = fd_freelist;
+ fd_freelist = fd;
gpr_mu_unlock(&fd_freelist_mu);
}
@@ -333,7 +413,7 @@ static void fd_global_shutdown(void) {
gpr_mu_destroy(&fd_freelist_mu);
}
-static grpc_fd* fd_create(int fd, const char* name) {
+static grpc_fd* fd_create(int fd, const char* name, bool track_err) {
grpc_fd* new_fd = nullptr;
gpr_mu_lock(&fd_freelist_mu);
@@ -347,17 +427,18 @@ static grpc_fd* fd_create(int fd, const char* name) {
new_fd = static_cast<grpc_fd*>(gpr_malloc(sizeof(grpc_fd)));
new_fd->read_closure.Init();
new_fd->write_closure.Init();
+ new_fd->error_closure.Init();
}
- gpr_mu_init(&new_fd->pollable_mu);
+ new_fd->fd = fd;
+ new_fd->salt = gpr_atm_no_barrier_fetch_add(&g_fd_salt, 1);
+ gpr_atm_rel_store(&new_fd->refst, (gpr_atm)1);
gpr_mu_init(&new_fd->orphan_mu);
+ gpr_mu_init(&new_fd->pollable_mu);
new_fd->pollable_obj = nullptr;
- gpr_atm_rel_store(&new_fd->refst, (gpr_atm)1);
- new_fd->fd = fd;
new_fd->read_closure->InitEvent();
new_fd->write_closure->InitEvent();
- gpr_atm_no_barrier_store(&new_fd->read_notifier_pollset, (gpr_atm)NULL);
-
+ new_fd->error_closure->InitEvent();
new_fd->freelist_next = nullptr;
new_fd->on_done_closure = nullptr;
@@ -370,6 +451,8 @@ static grpc_fd* fd_create(int fd, const char* name) {
}
#endif
gpr_free(fd_name);
+
+ new_fd->track_err = track_err;
return new_fd;
}
@@ -379,24 +462,36 @@ static int fd_wrapped_fd(grpc_fd* fd) {
}
static void fd_orphan(grpc_fd* fd, grpc_closure* on_done, int* release_fd,
- bool already_closed, const char* reason) {
- bool is_fd_closed = already_closed;
+ const char* reason) {
+ bool is_fd_closed = false;
gpr_mu_lock(&fd->orphan_mu);
+ // Get the fd->pollable_obj and set the owner_orphaned on that pollable to
+ // true so that the pollable will no longer access its owner_fd field.
+ gpr_mu_lock(&fd->pollable_mu);
+ pollable* pollable_obj = fd->pollable_obj;
+ gpr_mu_unlock(&fd->pollable_mu);
+
+ if (pollable_obj) {
+ gpr_mu_lock(&pollable_obj->owner_orphan_mu);
+ pollable_obj->owner_orphaned = true;
+ }
+
fd->on_done_closure = on_done;
/* If release_fd is not NULL, we should be relinquishing control of the file
descriptor fd->fd (but we still own the grpc_fd structure). */
if (release_fd != nullptr) {
*release_fd = fd->fd;
- } else if (!is_fd_closed) {
+ } else {
close(fd->fd);
is_fd_closed = true;
}
+ // TODO(sreek): handle fd removal (where is_fd_closed=false)
if (!is_fd_closed) {
- gpr_log(GPR_DEBUG, "TODO: handle fd removal?");
+ GRPC_FD_TRACE("epoll_fd %p (%d) was orphaned but not closed.", fd, fd->fd);
}
/* Remove the active status but keep referenced. We want this grpc_fd struct
@@ -405,16 +500,15 @@ static void fd_orphan(grpc_fd* fd, grpc_closure* on_done, int* release_fd,
GRPC_CLOSURE_SCHED(fd->on_done_closure, GRPC_ERROR_NONE);
+ if (pollable_obj) {
+ gpr_mu_unlock(&pollable_obj->owner_orphan_mu);
+ }
+
gpr_mu_unlock(&fd->orphan_mu);
UNREF_BY(fd, 2, reason); /* Drop the reference */
}
-static grpc_pollset* fd_get_read_notifier_pollset(grpc_fd* fd) {
- gpr_atm notifier = gpr_atm_acq_load(&fd->read_notifier_pollset);
- return (grpc_pollset*)notifier;
-}
-
static bool fd_is_shutdown(grpc_fd* fd) {
return fd->read_closure->IsShutdown();
}
@@ -422,8 +516,14 @@ static bool fd_is_shutdown(grpc_fd* fd) {
/* Might be called multiple times */
static void fd_shutdown(grpc_fd* fd, grpc_error* why) {
if (fd->read_closure->SetShutdown(GRPC_ERROR_REF(why))) {
- shutdown(fd->fd, SHUT_RDWR);
+ if (shutdown(fd->fd, SHUT_RDWR)) {
+ if (errno != ENOTCONN) {
+ gpr_log(GPR_ERROR, "Error shutting down fd %d. errno: %d",
+ grpc_fd_wrapped_fd(fd), errno);
+ }
+ }
fd->write_closure->SetShutdown(GRPC_ERROR_REF(why));
+ fd->error_closure->SetShutdown(GRPC_ERROR_REF(why));
}
GRPC_ERROR_UNREF(why);
}
@@ -436,6 +536,10 @@ static void fd_notify_on_write(grpc_fd* fd, grpc_closure* closure) {
fd->write_closure->NotifyOn(closure);
}
+static void fd_notify_on_error(grpc_fd* fd, grpc_closure* closure) {
+ fd->error_closure->NotifyOn(closure);
+}
+
/*******************************************************************************
* Pollable Definitions
*/
@@ -479,11 +583,15 @@ static grpc_error* pollable_create(pollable_type type, pollable** p) {
gpr_mu_init(&(*p)->mu);
(*p)->epfd = epfd;
(*p)->owner_fd = nullptr;
+ gpr_mu_init(&(*p)->owner_orphan_mu);
+ (*p)->owner_orphaned = false;
(*p)->pollset_set = nullptr;
(*p)->next = (*p)->prev = *p;
(*p)->root_worker = nullptr;
(*p)->event_cursor = 0;
(*p)->event_count = 0;
+ (*p)->fd_cache_size = 0;
+ (*p)->fd_cache_counter = 0;
return GRPC_ERROR_NONE;
}
@@ -516,6 +624,7 @@ static void pollable_unref(pollable* p, int line, const char* reason) {
GRPC_FD_TRACE("pollable_unref: Closing epfd: %d", p->epfd);
close(p->epfd);
grpc_wakeup_fd_destroy(&p->wakeup);
+ gpr_mu_destroy(&p->owner_orphan_mu);
gpr_free(p);
}
}
@@ -524,6 +633,38 @@ static grpc_error* pollable_add_fd(pollable* p, grpc_fd* fd) {
grpc_error* error = GRPC_ERROR_NONE;
static const char* err_desc = "pollable_add_fd";
const int epfd = p->epfd;
+ gpr_mu_lock(&p->mu);
+ p->fd_cache_counter++;
+
+ // Handle the case of overflow for our cache counter by
+ // reseting the recency-counter on all cache objects
+ if (p->fd_cache_counter == 0) {
+ for (int i = 0; i < p->fd_cache_size; i++) {
+ p->fd_cache[i].last_used = 0;
+ }
+ }
+
+ int lru_idx = 0;
+ for (int i = 0; i < p->fd_cache_size; i++) {
+ if (p->fd_cache[i].fd == fd->fd && p->fd_cache[i].salt == fd->salt) {
+ GRPC_STATS_INC_POLLSET_FD_CACHE_HITS();
+ p->fd_cache[i].last_used = p->fd_cache_counter;
+ gpr_mu_unlock(&p->mu);
+ return GRPC_ERROR_NONE;
+ } else if (p->fd_cache[i].last_used < p->fd_cache[lru_idx].last_used) {
+ lru_idx = i;
+ }
+ }
+
+ // Add to cache
+ if (p->fd_cache_size < MAX_FDS_IN_CACHE) {
+ lru_idx = p->fd_cache_size;
+ p->fd_cache_size++;
+ }
+ p->fd_cache[lru_idx].fd = fd->fd;
+ p->fd_cache[lru_idx].salt = fd->salt;
+ p->fd_cache[lru_idx].last_used = p->fd_cache_counter;
+ gpr_mu_unlock(&p->mu);
if (grpc_polling_trace.enabled()) {
gpr_log(GPR_INFO, "add fd %p (%d) to pollable %p", fd, fd->fd, p);
@@ -532,7 +673,13 @@ static grpc_error* pollable_add_fd(pollable* p, grpc_fd* fd) {
struct epoll_event ev_fd;
ev_fd.events =
static_cast<uint32_t>(EPOLLET | EPOLLIN | EPOLLOUT | EPOLLEXCLUSIVE);
- ev_fd.data.ptr = fd;
+ /* Use the second least significant bit of ev_fd.data.ptr to store track_err
+ * to avoid synchronization issues when accessing it after receiving an event.
+ * Accessing fd would be a data race there because the fd might have been
+ * returned to the free list at that point. */
+ ev_fd.data.ptr = reinterpret_cast<void*>(reinterpret_cast<intptr_t>(fd) |
+ (fd->track_err ? 2 : 0));
+ GRPC_STATS_INC_SYSCALL_EPOLL_CTL();
if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd->fd, &ev_fd) != 0) {
switch (errno) {
case EEXIST:
@@ -589,7 +736,7 @@ static void pollset_maybe_finish_shutdown(grpc_pollset* pollset) {
static grpc_error* kick_one_worker(grpc_pollset_worker* specific_worker) {
GPR_TIMER_SCOPE("kick_one_worker", 0);
pollable* p = specific_worker->pollable_obj;
- grpc_core::mu_guard lock(&p->mu);
+ grpc_core::MutexLock lock(&p->mu);
GPR_ASSERT(specific_worker != nullptr);
if (specific_worker->kicked) {
if (grpc_polling_trace.enabled()) {
@@ -718,24 +865,21 @@ static int poll_deadline_to_millis_timeout(grpc_millis millis) {
return static_cast<int>(delta);
}
-static void fd_become_readable(grpc_fd* fd, grpc_pollset* notifier) {
- fd->read_closure->SetReady();
-
- /* Note, it is possible that fd_become_readable might be called twice with
- different 'notifier's when an fd becomes readable and it is in two epoll
- sets (This can happen briefly during polling island merges). In such cases
- it does not really matter which notifer is set as the read_notifier_pollset
- (They would both point to the same polling island anyway) */
- /* Use release store to match with acquire load in fd_get_read_notifier */
- gpr_atm_rel_store(&fd->read_notifier_pollset, (gpr_atm)notifier);
-}
+static void fd_become_readable(grpc_fd* fd) { fd->read_closure->SetReady(); }
static void fd_become_writable(grpc_fd* fd) { fd->write_closure->SetReady(); }
-static grpc_error* fd_get_or_become_pollable(grpc_fd* fd, pollable** p) {
+static void fd_has_errors(grpc_fd* fd) { fd->error_closure->SetReady(); }
+
+/* Get the pollable_obj attached to this fd. If none is attached, create a new
+ * pollable object (of type PO_FD), attach it to the fd and return it
+ *
+ * Note that if a pollable object is already attached to the fd, it may be of
+ * either PO_FD or PO_MULTI type */
+static grpc_error* get_fd_pollable(grpc_fd* fd, pollable** p) {
gpr_mu_lock(&fd->pollable_mu);
grpc_error* error = GRPC_ERROR_NONE;
- static const char* err_desc = "fd_get_or_become_pollable";
+ static const char* err_desc = "get_fd_pollable";
if (fd->pollable_obj == nullptr) {
if (append_error(&error, pollable_create(PO_FD, &fd->pollable_obj),
err_desc)) {
@@ -800,20 +944,28 @@ static grpc_error* pollable_process_events(grpc_pollset* pollset,
(intptr_t)data_ptr)),
err_desc);
} else {
- grpc_fd* fd = static_cast<grpc_fd*>(data_ptr);
- bool cancel = (ev->events & (EPOLLERR | EPOLLHUP)) != 0;
+ grpc_fd* fd =
+ reinterpret_cast<grpc_fd*>(reinterpret_cast<intptr_t>(data_ptr) & ~2);
+ bool track_err = reinterpret_cast<intptr_t>(data_ptr) & 2;
+ bool cancel = (ev->events & EPOLLHUP) != 0;
+ bool error = (ev->events & EPOLLERR) != 0;
bool read_ev = (ev->events & (EPOLLIN | EPOLLPRI)) != 0;
bool write_ev = (ev->events & EPOLLOUT) != 0;
+ bool err_fallback = error && !track_err;
+
if (grpc_polling_trace.enabled()) {
gpr_log(GPR_INFO,
"PS:%p got fd %p: cancel=%d read=%d "
"write=%d",
pollset, fd, cancel, read_ev, write_ev);
}
- if (read_ev || cancel) {
- fd_become_readable(fd, pollset);
+ if (error && !err_fallback) {
+ fd_has_errors(fd);
+ }
+ if (read_ev || cancel || err_fallback) {
+ fd_become_readable(fd);
}
- if (write_ev || cancel) {
+ if (write_ev || cancel || err_fallback) {
fd_become_writable(fd);
}
}
@@ -1018,7 +1170,7 @@ static grpc_error* pollset_work(grpc_pollset* pollset,
#endif
if (grpc_polling_trace.enabled()) {
gpr_log(GPR_INFO,
- "PS:%p work hdl=%p worker=%p now=%" PRIdPTR " deadline=%" PRIdPTR
+ "PS:%p work hdl=%p worker=%p now=%" PRId64 " deadline=%" PRId64
" kwp=%d pollable=%p",
pollset, worker_hdl, WORKER_PTR, grpc_core::ExecCtx::Get()->Now(),
deadline, pollset->kicked_without_poller, pollset->active_pollable);
@@ -1064,7 +1216,7 @@ static grpc_error* pollset_transition_pollable_from_empty_to_fd_locked(
}
append_error(&error, pollset_kick_all(pollset), err_desc);
POLLABLE_UNREF(pollset->active_pollable, "pollset");
- append_error(&error, fd_get_or_become_pollable(fd, &pollset->active_pollable),
+ append_error(&error, get_fd_pollable(fd, &pollset->active_pollable),
err_desc);
return error;
}
@@ -1108,9 +1260,8 @@ static grpc_error* pollset_add_fd_locked(grpc_pollset* pollset, grpc_fd* fd) {
error = pollset_transition_pollable_from_empty_to_fd_locked(pollset, fd);
break;
case PO_FD:
- gpr_mu_lock(&po_at_start->owner_fd->orphan_mu);
- if ((gpr_atm_no_barrier_load(&pollset->active_pollable->owner_fd->refst) &
- 1) == 0) {
+ gpr_mu_lock(&po_at_start->owner_orphan_mu);
+ if (po_at_start->owner_orphaned) {
error =
pollset_transition_pollable_from_empty_to_fd_locked(pollset, fd);
} else {
@@ -1118,7 +1269,7 @@ static grpc_error* pollset_add_fd_locked(grpc_pollset* pollset, grpc_fd* fd) {
error =
pollset_transition_pollable_from_fd_to_multi_locked(pollset, fd);
}
- gpr_mu_unlock(&po_at_start->owner_fd->orphan_mu);
+ gpr_mu_unlock(&po_at_start->owner_orphan_mu);
break;
case PO_MULTI:
error = pollable_add_fd(pollset->active_pollable, fd);
@@ -1154,16 +1305,17 @@ static grpc_error* pollset_as_multipollable_locked(grpc_pollset* pollset,
append_error(&error, pollset_kick_all(pollset), err_desc);
break;
case PO_FD:
- gpr_mu_lock(&po_at_start->owner_fd->orphan_mu);
- if ((gpr_atm_no_barrier_load(&pollset->active_pollable->owner_fd->refst) &
- 1) == 0) {
+ gpr_mu_lock(&po_at_start->owner_orphan_mu);
+ if (po_at_start->owner_orphaned) {
+ // Unlock before Unref'ing the pollable
+ gpr_mu_unlock(&po_at_start->owner_orphan_mu);
POLLABLE_UNREF(pollset->active_pollable, "pollset");
error = pollable_create(PO_MULTI, &pollset->active_pollable);
} else {
error = pollset_transition_pollable_from_fd_to_multi_locked(pollset,
nullptr);
+ gpr_mu_unlock(&po_at_start->owner_orphan_mu);
}
- gpr_mu_unlock(&po_at_start->owner_fd->orphan_mu);
break;
case PO_MULTI:
break;
@@ -1393,7 +1545,7 @@ static void pollset_set_add_pollset_set(grpc_pollset_set* a,
gpr_mu_unlock(b_mu);
}
// try to do the least copying possible
- // TODO(ctiller): there's probably a better heuristic here
+ // TODO(sreek): there's probably a better heuristic here
const size_t a_size = a->fd_count + a->pollset_count;
const size_t b_size = b->fd_count + b->pollset_count;
if (b_size > a_size) {
@@ -1455,6 +1607,7 @@ static void shutdown_engine(void) {
static const grpc_event_engine_vtable vtable = {
sizeof(grpc_pollset),
+ true,
fd_create,
fd_wrapped_fd,
@@ -1462,8 +1615,11 @@ static const grpc_event_engine_vtable vtable = {
fd_shutdown,
fd_notify_on_read,
fd_notify_on_write,
+ fd_notify_on_error,
+ fd_become_readable,
+ fd_become_writable,
+ fd_has_errors,
fd_is_shutdown,
- fd_get_read_notifier_pollset,
pollset_init,
pollset_shutdown,
@@ -1508,7 +1664,7 @@ const grpc_event_engine_vtable* grpc_init_epollex_linux(
}
#else /* defined(GRPC_LINUX_EPOLL_CREATE1) */
-#if defined(GRPC_POSIX_SOCKET)
+#if defined(GRPC_POSIX_SOCKET_EV_EPOLLEX)
#include "src/core/lib/iomgr/ev_epollex_linux.h"
/* If GRPC_LINUX_EPOLL_CREATE1 is not defined, it means
epoll_create1 is not available. Return NULL */
@@ -1516,6 +1672,6 @@ const grpc_event_engine_vtable* grpc_init_epollex_linux(
bool explicitly_requested) {
return nullptr;
}
-#endif /* defined(GRPC_POSIX_SOCKET) */
+#endif /* defined(GRPC_POSIX_SOCKET_EV_EPOLLEX) */
#endif /* !defined(GRPC_LINUX_EPOLL_CREATE1) */
diff --git a/src/core/lib/iomgr/ev_epollsig_linux.cc b/src/core/lib/iomgr/ev_epollsig_linux.cc
deleted file mode 100644
index 494bc71c1d..0000000000
--- a/src/core/lib/iomgr/ev_epollsig_linux.cc
+++ /dev/null
@@ -1,1735 +0,0 @@
-/*
- *
- * Copyright 2016 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/iomgr/port.h"
-
-#include <grpc/grpc_posix.h>
-#include <grpc/support/log.h>
-
-/* This polling engine is only relevant on linux kernels supporting epoll() */
-#ifdef GRPC_LINUX_EPOLL_CREATE1
-
-#include "src/core/lib/iomgr/ev_epollsig_linux.h"
-
-#include <assert.h>
-#include <errno.h>
-#include <limits.h>
-#include <poll.h>
-#include <pthread.h>
-#include <signal.h>
-#include <string.h>
-#include <sys/epoll.h>
-#include <sys/socket.h>
-#include <unistd.h>
-
-#include <grpc/support/alloc.h>
-#include <grpc/support/string_util.h>
-
-#include "src/core/lib/debug/stats.h"
-#include "src/core/lib/gpr/tls.h"
-#include "src/core/lib/gpr/useful.h"
-#include "src/core/lib/gprpp/manual_constructor.h"
-#include "src/core/lib/iomgr/block_annotate.h"
-#include "src/core/lib/iomgr/ev_posix.h"
-#include "src/core/lib/iomgr/iomgr_internal.h"
-#include "src/core/lib/iomgr/lockfree_event.h"
-#include "src/core/lib/iomgr/timer.h"
-#include "src/core/lib/iomgr/wakeup_fd_posix.h"
-#include "src/core/lib/profiling/timers.h"
-
-#define GRPC_POLLSET_KICK_BROADCAST ((grpc_pollset_worker*)1)
-
-#define GRPC_POLLING_TRACE(...) \
- if (grpc_polling_trace.enabled()) { \
- gpr_log(GPR_INFO, __VA_ARGS__); \
- }
-
-static int grpc_wakeup_signal = -1;
-static bool is_grpc_wakeup_signal_initialized = false;
-
-/* Implements the function defined in grpc_posix.h. This function might be
- * called before even calling grpc_init() to set either a different signal to
- * use. If signum == -1, then the use of signals is disabled */
-void grpc_use_signal(int signum) {
- grpc_wakeup_signal = signum;
- is_grpc_wakeup_signal_initialized = true;
-
- if (grpc_wakeup_signal < 0) {
- gpr_log(GPR_INFO,
- "Use of signals is disabled. Epoll engine will not be used");
- } else {
- gpr_log(GPR_INFO, "epoll engine will be using signal: %d",
- grpc_wakeup_signal);
- }
-}
-
-struct polling_island;
-
-typedef enum {
- POLL_OBJ_FD,
- POLL_OBJ_POLLSET,
- POLL_OBJ_POLLSET_SET
-} poll_obj_type;
-
-typedef struct poll_obj {
-#ifndef NDEBUG
- poll_obj_type obj_type;
-#endif
- gpr_mu mu;
- struct polling_island* pi;
-} poll_obj;
-
-const char* poll_obj_string(poll_obj_type po_type) {
- switch (po_type) {
- case POLL_OBJ_FD:
- return "fd";
- case POLL_OBJ_POLLSET:
- return "pollset";
- case POLL_OBJ_POLLSET_SET:
- return "pollset_set";
- }
-
- GPR_UNREACHABLE_CODE(return "UNKNOWN");
-}
-
- /*******************************************************************************
- * Fd Declarations
- */
-
-#define FD_FROM_PO(po) ((grpc_fd*)(po))
-
-struct grpc_fd {
- poll_obj po;
-
- int fd;
- /* refst format:
- bit 0 : 1=Active / 0=Orphaned
- bits 1-n : refcount
- Ref/Unref by two to avoid altering the orphaned bit */
- gpr_atm refst;
-
- /* The fd is either closed or we relinquished control of it. In either
- cases, this indicates that the 'fd' on this structure is no longer
- valid */
- bool orphaned;
-
- grpc_core::ManualConstructor<grpc_core::LockfreeEvent> read_closure;
- grpc_core::ManualConstructor<grpc_core::LockfreeEvent> write_closure;
-
- struct grpc_fd* freelist_next;
- grpc_closure* on_done_closure;
-
- /* The pollset that last noticed that the fd is readable. The actual type
- * stored in this is (grpc_pollset *) */
- gpr_atm read_notifier_pollset;
-
- grpc_iomgr_object iomgr_object;
-};
-
-/* Reference counting for fds */
-#ifndef NDEBUG
-static void fd_ref(grpc_fd* fd, const char* reason, const char* file, int line);
-static void fd_unref(grpc_fd* fd, const char* reason, const char* file,
- int line);
-#define GRPC_FD_REF(fd, reason) fd_ref(fd, reason, __FILE__, __LINE__)
-#define GRPC_FD_UNREF(fd, reason) fd_unref(fd, reason, __FILE__, __LINE__)
-#else
-static void fd_ref(grpc_fd* fd);
-static void fd_unref(grpc_fd* fd);
-#define GRPC_FD_REF(fd, reason) fd_ref(fd)
-#define GRPC_FD_UNREF(fd, reason) fd_unref(fd)
-#endif
-
-static void fd_global_init(void);
-static void fd_global_shutdown(void);
-
-/*******************************************************************************
- * Polling island Declarations
- */
-
-#ifndef NDEBUG
-
-#define PI_ADD_REF(p, r) pi_add_ref_dbg((p), (r), __FILE__, __LINE__)
-#define PI_UNREF(p, r) pi_unref_dbg((p), (r), __FILE__, __LINE__)
-
-#else
-
-#define PI_ADD_REF(p, r) pi_add_ref((p))
-#define PI_UNREF(p, r) pi_unref((p))
-
-#endif
-
-/* This is also used as grpc_workqueue (by directly casing it) */
-typedef struct polling_island {
- gpr_mu mu;
- /* Ref count. Use PI_ADD_REF() and PI_UNREF() macros to increment/decrement
- the refcount.
- Once the ref count becomes zero, this structure is destroyed which means
- we should ensure that there is never a scenario where a PI_ADD_REF() is
- racing with a PI_UNREF() that just made the ref_count zero. */
- gpr_atm ref_count;
-
- /* Pointer to the polling_island this merged into.
- * merged_to value is only set once in polling_island's lifetime (and that too
- * only if the island is merged with another island). Because of this, we can
- * use gpr_atm type here so that we can do atomic access on this and reduce
- * lock contention on 'mu' mutex.
- *
- * Note that if this field is not NULL (i.e not 0), all the remaining fields
- * (except mu and ref_count) are invalid and must be ignored. */
- gpr_atm merged_to;
-
- /* Number of threads currently polling on this island */
- gpr_atm poller_count;
-
- /* The fd of the underlying epoll set */
- int epoll_fd;
-
- /* The file descriptors in the epoll set */
- size_t fd_cnt;
- size_t fd_capacity;
- grpc_fd** fds;
-} polling_island;
-
-/*******************************************************************************
- * Pollset Declarations
- */
-struct grpc_pollset_worker {
- /* Thread id of this worker */
- pthread_t pt_id;
-
- /* Used to prevent a worker from getting kicked multiple times */
- gpr_atm is_kicked;
- struct grpc_pollset_worker* next;
- struct grpc_pollset_worker* prev;
-};
-
-struct grpc_pollset {
- poll_obj po;
-
- grpc_pollset_worker root_worker;
- bool kicked_without_pollers;
-
- bool shutting_down; /* Is the pollset shutting down ? */
- bool finish_shutdown_called; /* Is the 'finish_shutdown_locked()' called ? */
- grpc_closure* shutdown_done; /* Called after after shutdown is complete */
-};
-
-/*******************************************************************************
- * Pollset-set Declarations
- */
-struct grpc_pollset_set {
- poll_obj po;
-};
-
-/*******************************************************************************
- * Common helpers
- */
-
-static bool append_error(grpc_error** composite, grpc_error* error,
- const char* desc) {
- if (error == GRPC_ERROR_NONE) return true;
- if (*composite == GRPC_ERROR_NONE) {
- *composite = GRPC_ERROR_CREATE_FROM_COPIED_STRING(desc);
- }
- *composite = grpc_error_add_child(*composite, error);
- return false;
-}
-
-/*******************************************************************************
- * Polling island Definitions
- */
-
-/* The wakeup fd that is used to wake up all threads in a Polling island. This
- is useful in the polling island merge operation where we need to wakeup all
- the threads currently polling the smaller polling island (so that they can
- start polling the new/merged polling island)
-
- NOTE: This fd is initialized to be readable and MUST NOT be consumed i.e the
- threads that woke up MUST NOT call grpc_wakeup_fd_consume_wakeup() */
-static grpc_wakeup_fd polling_island_wakeup_fd;
-
-/* The polling island being polled right now.
- See comments in workqueue_maybe_wakeup for why this is tracked. */
-static __thread polling_island* g_current_thread_polling_island;
-
-/* Forward declaration */
-static void polling_island_delete(polling_island* pi);
-
-#ifdef GRPC_TSAN
-/* Currently TSAN may incorrectly flag data races between epoll_ctl and
- epoll_wait for any grpc_fd structs that are added to the epoll set via
- epoll_ctl and are returned (within a very short window) via epoll_wait().
-
- To work-around this race, we establish a happens-before relation between
- the code just-before epoll_ctl() and the code after epoll_wait() by using
- this atomic */
-gpr_atm g_epoll_sync;
-#endif /* defined(GRPC_TSAN) */
-
-static void pi_add_ref(polling_island* pi);
-static void pi_unref(polling_island* pi);
-
-#ifndef NDEBUG
-static void pi_add_ref_dbg(polling_island* pi, const char* reason,
- const char* file, int line) {
- if (grpc_polling_trace.enabled()) {
- gpr_atm old_cnt = gpr_atm_acq_load(&pi->ref_count);
- gpr_log(GPR_INFO,
- "Add ref pi: %p, old:%" PRIdPTR " -> new:%" PRIdPTR
- " (%s) - (%s, %d)",
- pi, old_cnt, old_cnt + 1, reason, file, line);
- }
- pi_add_ref(pi);
-}
-
-static void pi_unref_dbg(polling_island* pi, const char* reason,
- const char* file, int line) {
- if (grpc_polling_trace.enabled()) {
- gpr_atm old_cnt = gpr_atm_acq_load(&pi->ref_count);
- gpr_log(GPR_INFO,
- "Unref pi: %p, old:%" PRIdPTR " -> new:%" PRIdPTR
- " (%s) - (%s, %d)",
- pi, old_cnt, (old_cnt - 1), reason, file, line);
- }
- pi_unref(pi);
-}
-#endif
-
-static void pi_add_ref(polling_island* pi) {
- gpr_atm_no_barrier_fetch_add(&pi->ref_count, 1);
-}
-
-static void pi_unref(polling_island* pi) {
- /* If ref count went to zero, delete the polling island.
- Note that this deletion not be done under a lock. Once the ref count goes
- to zero, we are guaranteed that no one else holds a reference to the
- polling island (and that there is no racing pi_add_ref() call either).
-
- Also, if we are deleting the polling island and the merged_to field is
- non-empty, we should remove a ref to the merged_to polling island
- */
- if (1 == gpr_atm_full_fetch_add(&pi->ref_count, -1)) {
- polling_island* next = (polling_island*)gpr_atm_acq_load(&pi->merged_to);
- polling_island_delete(pi);
- if (next != nullptr) {
- PI_UNREF(next, "pi_delete"); /* Recursive call */
- }
- }
-}
-
-/* The caller is expected to hold pi->mu lock before calling this function */
-static void polling_island_add_fds_locked(polling_island* pi, grpc_fd** fds,
- size_t fd_count, bool add_fd_refs,
- grpc_error** error) {
- int err;
- size_t i;
- struct epoll_event ev;
- char* err_msg;
- const char* err_desc = "polling_island_add_fds";
-
-#ifdef GRPC_TSAN
- /* See the definition of g_epoll_sync for more context */
- gpr_atm_rel_store(&g_epoll_sync, (gpr_atm)0);
-#endif /* defined(GRPC_TSAN) */
-
- for (i = 0; i < fd_count; i++) {
- ev.events = static_cast<uint32_t>(EPOLLIN | EPOLLOUT | EPOLLET);
- ev.data.ptr = fds[i];
- err = epoll_ctl(pi->epoll_fd, EPOLL_CTL_ADD, fds[i]->fd, &ev);
-
- if (err < 0) {
- if (errno != EEXIST) {
- gpr_asprintf(
- &err_msg,
- "epoll_ctl (epoll_fd: %d) add fd: %d failed with error: %d (%s)",
- pi->epoll_fd, fds[i]->fd, errno, strerror(errno));
- append_error(error, GRPC_OS_ERROR(errno, err_msg), err_desc);
- gpr_free(err_msg);
- }
-
- continue;
- }
-
- if (pi->fd_cnt == pi->fd_capacity) {
- pi->fd_capacity = GPR_MAX(pi->fd_capacity + 8, pi->fd_cnt * 3 / 2);
- pi->fds = static_cast<grpc_fd**>(
- gpr_realloc(pi->fds, sizeof(grpc_fd*) * pi->fd_capacity));
- }
-
- pi->fds[pi->fd_cnt++] = fds[i];
- if (add_fd_refs) {
- GRPC_FD_REF(fds[i], "polling_island");
- }
- }
-}
-
-/* The caller is expected to hold pi->mu before calling this */
-static void polling_island_add_wakeup_fd_locked(polling_island* pi,
- grpc_wakeup_fd* wakeup_fd,
- grpc_error** error) {
- struct epoll_event ev;
- int err;
- char* err_msg;
- const char* err_desc = "polling_island_add_wakeup_fd";
-
- ev.events = static_cast<uint32_t>(EPOLLIN | EPOLLET);
- ev.data.ptr = wakeup_fd;
- err = epoll_ctl(pi->epoll_fd, EPOLL_CTL_ADD,
- GRPC_WAKEUP_FD_GET_READ_FD(wakeup_fd), &ev);
- if (err < 0 && errno != EEXIST) {
- gpr_asprintf(&err_msg,
- "epoll_ctl (epoll_fd: %d) add wakeup fd: %d failed with "
- "error: %d (%s)",
- pi->epoll_fd, GRPC_WAKEUP_FD_GET_READ_FD(wakeup_fd), errno,
- strerror(errno));
- append_error(error, GRPC_OS_ERROR(errno, err_msg), err_desc);
- gpr_free(err_msg);
- }
-}
-
-/* The caller is expected to hold pi->mu lock before calling this function */
-static void polling_island_remove_all_fds_locked(polling_island* pi,
- bool remove_fd_refs,
- grpc_error** error) {
- int err;
- size_t i;
- char* err_msg;
- const char* err_desc = "polling_island_remove_fds";
-
- for (i = 0; i < pi->fd_cnt; i++) {
- err = epoll_ctl(pi->epoll_fd, EPOLL_CTL_DEL, pi->fds[i]->fd, nullptr);
- if (err < 0 && errno != ENOENT) {
- gpr_asprintf(&err_msg,
- "epoll_ctl (epoll_fd: %d) delete fds[%zu]: %d failed with "
- "error: %d (%s)",
- pi->epoll_fd, i, pi->fds[i]->fd, errno, strerror(errno));
- append_error(error, GRPC_OS_ERROR(errno, err_msg), err_desc);
- gpr_free(err_msg);
- }
-
- if (remove_fd_refs) {
- GRPC_FD_UNREF(pi->fds[i], "polling_island");
- }
- }
-
- pi->fd_cnt = 0;
-}
-
-/* The caller is expected to hold pi->mu lock before calling this function */
-static void polling_island_remove_fd_locked(polling_island* pi, grpc_fd* fd,
- bool is_fd_closed,
- grpc_error** error) {
- int err;
- size_t i;
- char* err_msg;
- const char* err_desc = "polling_island_remove_fd";
-
- /* If fd is already closed, then it would have been automatically been removed
- from the epoll set */
- if (!is_fd_closed) {
- err = epoll_ctl(pi->epoll_fd, EPOLL_CTL_DEL, fd->fd, nullptr);
- if (err < 0 && errno != ENOENT) {
- gpr_asprintf(
- &err_msg,
- "epoll_ctl (epoll_fd: %d) del fd: %d failed with error: %d (%s)",
- pi->epoll_fd, fd->fd, errno, strerror(errno));
- append_error(error, GRPC_OS_ERROR(errno, err_msg), err_desc);
- gpr_free(err_msg);
- }
- }
-
- for (i = 0; i < pi->fd_cnt; i++) {
- if (pi->fds[i] == fd) {
- pi->fds[i] = pi->fds[--pi->fd_cnt];
- GRPC_FD_UNREF(fd, "polling_island");
- break;
- }
- }
-}
-
-/* Might return NULL in case of an error */
-static polling_island* polling_island_create(grpc_fd* initial_fd,
- grpc_error** error) {
- polling_island* pi = nullptr;
- const char* err_desc = "polling_island_create";
-
- *error = GRPC_ERROR_NONE;
-
- pi = static_cast<polling_island*>(gpr_malloc(sizeof(*pi)));
- gpr_mu_init(&pi->mu);
- pi->fd_cnt = 0;
- pi->fd_capacity = 0;
- pi->fds = nullptr;
- pi->epoll_fd = -1;
-
- gpr_atm_rel_store(&pi->ref_count, 0);
- gpr_atm_rel_store(&pi->poller_count, 0);
- gpr_atm_rel_store(&pi->merged_to, (gpr_atm) nullptr);
-
- pi->epoll_fd = epoll_create1(EPOLL_CLOEXEC);
-
- if (pi->epoll_fd < 0) {
- append_error(error, GRPC_OS_ERROR(errno, "epoll_create1"), err_desc);
- goto done;
- }
-
- if (initial_fd != nullptr) {
- polling_island_add_fds_locked(pi, &initial_fd, 1, true, error);
- }
-
-done:
- if (*error != GRPC_ERROR_NONE) {
- polling_island_delete(pi);
- pi = nullptr;
- }
- return pi;
-}
-
-static void polling_island_delete(polling_island* pi) {
- GPR_ASSERT(pi->fd_cnt == 0);
-
- if (pi->epoll_fd >= 0) {
- close(pi->epoll_fd);
- }
- gpr_mu_destroy(&pi->mu);
- gpr_free(pi->fds);
- gpr_free(pi);
-}
-
-/* Attempts to gets the last polling island in the linked list (liked by the
- * 'merged_to' field). Since this does not lock the polling island, there are no
- * guarantees that the island returned is the last island */
-static polling_island* polling_island_maybe_get_latest(polling_island* pi) {
- polling_island* next = (polling_island*)gpr_atm_acq_load(&pi->merged_to);
- while (next != nullptr) {
- pi = next;
- next = (polling_island*)gpr_atm_acq_load(&pi->merged_to);
- }
-
- return pi;
-}
-
-/* Gets the lock on the *latest* polling island i.e the last polling island in
- the linked list (linked by the 'merged_to' field). Call gpr_mu_unlock on the
- returned polling island's mu.
- Usage: To lock/unlock polling island "pi", do the following:
- polling_island *pi_latest = polling_island_lock(pi);
- ...
- ... critical section ..
- ...
- gpr_mu_unlock(&pi_latest->mu); // NOTE: use pi_latest->mu. NOT pi->mu */
-static polling_island* polling_island_lock(polling_island* pi) {
- polling_island* next = nullptr;
-
- while (true) {
- next = (polling_island*)gpr_atm_acq_load(&pi->merged_to);
- if (next == nullptr) {
- /* Looks like 'pi' is the last node in the linked list but unless we check
- this by holding the pi->mu lock, we cannot be sure (i.e without the
- pi->mu lock, we don't prevent island merges).
- To be absolutely sure, check once more by holding the pi->mu lock */
- gpr_mu_lock(&pi->mu);
- next = (polling_island*)gpr_atm_acq_load(&pi->merged_to);
- if (next == nullptr) {
- /* pi is infact the last node and we have the pi->mu lock. we're done */
- break;
- }
-
- /* pi->merged_to is not NULL i.e pi isn't the last node anymore. pi->mu
- * isn't the lock we are interested in. Continue traversing the list */
- gpr_mu_unlock(&pi->mu);
- }
-
- pi = next;
- }
-
- return pi;
-}
-
-/* Gets the lock on the *latest* polling islands in the linked lists pointed by
- *p and *q (and also updates *p and *q to point to the latest polling islands)
-
- This function is needed because calling the following block of code to obtain
- locks on polling islands (*p and *q) is prone to deadlocks.
- {
- polling_island_lock(*p, true);
- polling_island_lock(*q, true);
- }
-
- Usage/example:
- polling_island *p1;
- polling_island *p2;
- ..
- polling_island_lock_pair(&p1, &p2);
- ..
- .. Critical section with both p1 and p2 locked
- ..
- // Release locks: Always call polling_island_unlock_pair() to release locks
- polling_island_unlock_pair(p1, p2);
-*/
-static void polling_island_lock_pair(polling_island** p, polling_island** q) {
- polling_island* pi_1 = *p;
- polling_island* pi_2 = *q;
- polling_island* next_1 = nullptr;
- polling_island* next_2 = nullptr;
-
- /* The algorithm is simple:
- - Go to the last polling islands in the linked lists *pi_1 and *pi_2 (and
- keep updating pi_1 and pi_2)
- - Then obtain locks on the islands by following a lock order rule of
- locking polling_island with lower address first
- Special case: Before obtaining the locks, check if pi_1 and pi_2 are
- pointing to the same island. If that is the case, we can just call
- polling_island_lock()
- - After obtaining both the locks, double check that the polling islands
- are still the last polling islands in their respective linked lists
- (this is because there might have been polling island merges before
- we got the lock)
- - If the polling islands are the last islands, we are done. If not,
- release the locks and continue the process from the first step */
- while (true) {
- next_1 = (polling_island*)gpr_atm_acq_load(&pi_1->merged_to);
- while (next_1 != nullptr) {
- pi_1 = next_1;
- next_1 = (polling_island*)gpr_atm_acq_load(&pi_1->merged_to);
- }
-
- next_2 = (polling_island*)gpr_atm_acq_load(&pi_2->merged_to);
- while (next_2 != nullptr) {
- pi_2 = next_2;
- next_2 = (polling_island*)gpr_atm_acq_load(&pi_2->merged_to);
- }
-
- if (pi_1 == pi_2) {
- pi_1 = pi_2 = polling_island_lock(pi_1);
- break;
- }
-
- if (pi_1 < pi_2) {
- gpr_mu_lock(&pi_1->mu);
- gpr_mu_lock(&pi_2->mu);
- } else {
- gpr_mu_lock(&pi_2->mu);
- gpr_mu_lock(&pi_1->mu);
- }
-
- next_1 = (polling_island*)gpr_atm_acq_load(&pi_1->merged_to);
- next_2 = (polling_island*)gpr_atm_acq_load(&pi_2->merged_to);
- if (next_1 == nullptr && next_2 == nullptr) {
- break;
- }
-
- gpr_mu_unlock(&pi_1->mu);
- gpr_mu_unlock(&pi_2->mu);
- }
-
- *p = pi_1;
- *q = pi_2;
-}
-
-static void polling_island_unlock_pair(polling_island* p, polling_island* q) {
- if (p == q) {
- gpr_mu_unlock(&p->mu);
- } else {
- gpr_mu_unlock(&p->mu);
- gpr_mu_unlock(&q->mu);
- }
-}
-
-static polling_island* polling_island_merge(polling_island* p,
- polling_island* q,
- grpc_error** error) {
- /* Get locks on both the polling islands */
- polling_island_lock_pair(&p, &q);
-
- if (p != q) {
- /* Make sure that p points to the polling island with fewer fds than q */
- if (p->fd_cnt > q->fd_cnt) {
- GPR_SWAP(polling_island*, p, q);
- }
-
- /* Merge p with q i.e move all the fds from p (The one with fewer fds) to q
- Note that the refcounts on the fds being moved will not change here.
- This is why the last param in the following two functions is 'false') */
- polling_island_add_fds_locked(q, p->fds, p->fd_cnt, false, error);
- polling_island_remove_all_fds_locked(p, false, error);
-
- /* Wakeup all the pollers (if any) on p so that they pickup this change */
- polling_island_add_wakeup_fd_locked(p, &polling_island_wakeup_fd, error);
-
- /* Add the 'merged_to' link from p --> q */
- gpr_atm_rel_store(&p->merged_to, (gpr_atm)q);
- PI_ADD_REF(q, "pi_merge"); /* To account for the new incoming ref from p */
- }
- /* else if p == q, nothing needs to be done */
-
- polling_island_unlock_pair(p, q);
-
- /* Return the merged polling island (Note that no merge would have happened
- if p == q which is ok) */
- return q;
-}
-
-static grpc_error* polling_island_global_init() {
- grpc_error* error = GRPC_ERROR_NONE;
-
- error = grpc_wakeup_fd_init(&polling_island_wakeup_fd);
- if (error == GRPC_ERROR_NONE) {
- error = grpc_wakeup_fd_wakeup(&polling_island_wakeup_fd);
- }
-
- return error;
-}
-
-static void polling_island_global_shutdown() {
- grpc_wakeup_fd_destroy(&polling_island_wakeup_fd);
-}
-
-/*******************************************************************************
- * Fd Definitions
- */
-
-/* We need to keep a freelist not because of any concerns of malloc performance
- * but instead so that implementations with multiple threads in (for example)
- * epoll_wait deal with the race between pollset removal and incoming poll
- * notifications.
- *
- * The problem is that the poller ultimately holds a reference to this
- * object, so it is very difficult to know when is safe to free it, at least
- * without some expensive synchronization.
- *
- * If we keep the object freelisted, in the worst case losing this race just
- * becomes a spurious read notification on a reused fd.
- */
-
-/* The alarm system needs to be able to wakeup 'some poller' sometimes
- * (specifically when a new alarm needs to be triggered earlier than the next
- * alarm 'epoch'). This wakeup_fd gives us something to alert on when such a
- * case occurs. */
-
-static grpc_fd* fd_freelist = nullptr;
-static gpr_mu fd_freelist_mu;
-
-#ifndef NDEBUG
-#define REF_BY(fd, n, reason) ref_by(fd, n, reason, __FILE__, __LINE__)
-#define UNREF_BY(fd, n, reason) unref_by(fd, n, reason, __FILE__, __LINE__)
-static void ref_by(grpc_fd* fd, int n, const char* reason, const char* file,
- int line) {
- if (grpc_trace_fd_refcount.enabled()) {
- gpr_log(GPR_DEBUG,
- "FD %d %p ref %d %" PRIdPTR " -> %" PRIdPTR " [%s; %s:%d]",
- fd->fd, fd, n, gpr_atm_no_barrier_load(&fd->refst),
- gpr_atm_no_barrier_load(&fd->refst) + n, reason, file, line);
- }
-#else
-#define REF_BY(fd, n, reason) ref_by(fd, n)
-#define UNREF_BY(fd, n, reason) unref_by(fd, n)
-static void ref_by(grpc_fd* fd, int n) {
-#endif
- GPR_ASSERT(gpr_atm_no_barrier_fetch_add(&fd->refst, n) > 0);
-}
-
-#ifndef NDEBUG
-static void unref_by(grpc_fd* fd, int n, const char* reason, const char* file,
- int line) {
- if (grpc_trace_fd_refcount.enabled()) {
- gpr_log(GPR_DEBUG,
- "FD %d %p unref %d %" PRIdPTR " -> %" PRIdPTR " [%s; %s:%d]",
- fd->fd, fd, n, gpr_atm_no_barrier_load(&fd->refst),
- gpr_atm_no_barrier_load(&fd->refst) - n, reason, file, line);
- }
-#else
-static void unref_by(grpc_fd* fd, int n) {
-#endif
- gpr_atm old = gpr_atm_full_fetch_add(&fd->refst, -n);
- if (old == n) {
- /* Add the fd to the freelist */
- gpr_mu_lock(&fd_freelist_mu);
- fd->freelist_next = fd_freelist;
- fd_freelist = fd;
- grpc_iomgr_unregister_object(&fd->iomgr_object);
-
- fd->read_closure->DestroyEvent();
- fd->write_closure->DestroyEvent();
-
- gpr_mu_unlock(&fd_freelist_mu);
- } else {
- GPR_ASSERT(old > n);
- }
-}
-
-/* Increment refcount by two to avoid changing the orphan bit */
-#ifndef NDEBUG
-static void fd_ref(grpc_fd* fd, const char* reason, const char* file,
- int line) {
- ref_by(fd, 2, reason, file, line);
-}
-
-static void fd_unref(grpc_fd* fd, const char* reason, const char* file,
- int line) {
- unref_by(fd, 2, reason, file, line);
-}
-#else
-static void fd_ref(grpc_fd* fd) { ref_by(fd, 2); }
-static void fd_unref(grpc_fd* fd) { unref_by(fd, 2); }
-#endif
-
-static void fd_global_init(void) { gpr_mu_init(&fd_freelist_mu); }
-
-static void fd_global_shutdown(void) {
- gpr_mu_lock(&fd_freelist_mu);
- gpr_mu_unlock(&fd_freelist_mu);
- while (fd_freelist != nullptr) {
- grpc_fd* fd = fd_freelist;
- fd_freelist = fd_freelist->freelist_next;
- gpr_mu_destroy(&fd->po.mu);
- gpr_free(fd);
- }
- gpr_mu_destroy(&fd_freelist_mu);
-}
-
-static grpc_fd* fd_create(int fd, const char* name) {
- grpc_fd* new_fd = nullptr;
-
- gpr_mu_lock(&fd_freelist_mu);
- if (fd_freelist != nullptr) {
- new_fd = fd_freelist;
- fd_freelist = fd_freelist->freelist_next;
- }
- gpr_mu_unlock(&fd_freelist_mu);
-
- if (new_fd == nullptr) {
- new_fd = static_cast<grpc_fd*>(gpr_malloc(sizeof(grpc_fd)));
- gpr_mu_init(&new_fd->po.mu);
- new_fd->read_closure.Init();
- new_fd->write_closure.Init();
- }
-
- /* Note: It is not really needed to get the new_fd->po.mu lock here. If this
- * is a newly created fd (or an fd we got from the freelist), no one else
- * would be holding a lock to it anyway. */
- gpr_mu_lock(&new_fd->po.mu);
- new_fd->po.pi = nullptr;
-#ifndef NDEBUG
- new_fd->po.obj_type = POLL_OBJ_FD;
-#endif
-
- gpr_atm_rel_store(&new_fd->refst, (gpr_atm)1);
- new_fd->fd = fd;
- new_fd->orphaned = false;
- new_fd->read_closure->InitEvent();
- new_fd->write_closure->InitEvent();
- gpr_atm_no_barrier_store(&new_fd->read_notifier_pollset, (gpr_atm)NULL);
-
- new_fd->freelist_next = nullptr;
- new_fd->on_done_closure = nullptr;
-
- gpr_mu_unlock(&new_fd->po.mu);
-
- char* fd_name;
- gpr_asprintf(&fd_name, "%s fd=%d", name, fd);
- grpc_iomgr_register_object(&new_fd->iomgr_object, fd_name);
- gpr_free(fd_name);
- return new_fd;
-}
-
-static int fd_wrapped_fd(grpc_fd* fd) {
- int ret_fd = -1;
- gpr_mu_lock(&fd->po.mu);
- if (!fd->orphaned) {
- ret_fd = fd->fd;
- }
- gpr_mu_unlock(&fd->po.mu);
-
- return ret_fd;
-}
-
-static void fd_orphan(grpc_fd* fd, grpc_closure* on_done, int* release_fd,
- bool already_closed, const char* reason) {
- grpc_error* error = GRPC_ERROR_NONE;
- polling_island* unref_pi = nullptr;
-
- gpr_mu_lock(&fd->po.mu);
- fd->on_done_closure = on_done;
-
- /* Remove the active status but keep referenced. We want this grpc_fd struct
- to be alive (and not added to freelist) until the end of this function */
- REF_BY(fd, 1, reason);
-
- /* Remove the fd from the polling island:
- - Get a lock on the latest polling island (i.e the last island in the
- linked list pointed by fd->po.pi). This is the island that
- would actually contain the fd
- - Remove the fd from the latest polling island
- - Unlock the latest polling island
- - Set fd->po.pi to NULL (but remove the ref on the polling island
- before doing this.) */
- if (fd->po.pi != nullptr) {
- polling_island* pi_latest = polling_island_lock(fd->po.pi);
- polling_island_remove_fd_locked(pi_latest, fd, already_closed, &error);
- gpr_mu_unlock(&pi_latest->mu);
-
- unref_pi = fd->po.pi;
- fd->po.pi = nullptr;
- }
-
- /* If release_fd is not NULL, we should be relinquishing control of the file
- descriptor fd->fd (but we still own the grpc_fd structure). */
- if (release_fd != nullptr) {
- *release_fd = fd->fd;
- } else {
- close(fd->fd);
- }
-
- fd->orphaned = true;
-
- GRPC_CLOSURE_SCHED(fd->on_done_closure, GRPC_ERROR_REF(error));
-
- gpr_mu_unlock(&fd->po.mu);
- UNREF_BY(fd, 2, reason); /* Drop the reference */
- if (unref_pi != nullptr) {
- /* Unref stale polling island here, outside the fd lock above.
- The polling island owns a workqueue which owns an fd, and unreffing
- inside the lock can cause an eventual lock loop that makes TSAN very
- unhappy. */
- PI_UNREF(unref_pi, "fd_orphan");
- }
- if (error != GRPC_ERROR_NONE) {
- const char* msg = grpc_error_string(error);
- gpr_log(GPR_DEBUG, "fd_orphan: %s", msg);
- }
- GRPC_ERROR_UNREF(error);
-}
-
-static grpc_pollset* fd_get_read_notifier_pollset(grpc_fd* fd) {
- gpr_atm notifier = gpr_atm_acq_load(&fd->read_notifier_pollset);
- return (grpc_pollset*)notifier;
-}
-
-static bool fd_is_shutdown(grpc_fd* fd) {
- return fd->read_closure->IsShutdown();
-}
-
-/* Might be called multiple times */
-static void fd_shutdown(grpc_fd* fd, grpc_error* why) {
- if (fd->read_closure->SetShutdown(GRPC_ERROR_REF(why))) {
- shutdown(fd->fd, SHUT_RDWR);
- fd->write_closure->SetShutdown(GRPC_ERROR_REF(why));
- }
- GRPC_ERROR_UNREF(why);
-}
-
-static void fd_notify_on_read(grpc_fd* fd, grpc_closure* closure) {
- fd->read_closure->NotifyOn(closure);
-}
-
-static void fd_notify_on_write(grpc_fd* fd, grpc_closure* closure) {
- fd->write_closure->NotifyOn(closure);
-}
-
-/*******************************************************************************
- * Pollset Definitions
- */
-GPR_TLS_DECL(g_current_thread_pollset);
-GPR_TLS_DECL(g_current_thread_worker);
-static __thread bool g_initialized_sigmask;
-static __thread sigset_t g_orig_sigmask;
-
-static void sig_handler(int sig_num) {
-#ifdef GRPC_EPOLL_DEBUG
- gpr_log(GPR_INFO, "Received signal %d", sig_num);
-#endif
-}
-
-static void poller_kick_init() { signal(grpc_wakeup_signal, sig_handler); }
-
-/* Global state management */
-static grpc_error* pollset_global_init(void) {
- gpr_tls_init(&g_current_thread_pollset);
- gpr_tls_init(&g_current_thread_worker);
- poller_kick_init();
- return GRPC_ERROR_NONE;
-}
-
-static void pollset_global_shutdown(void) {
- gpr_tls_destroy(&g_current_thread_pollset);
- gpr_tls_destroy(&g_current_thread_worker);
-}
-
-static grpc_error* pollset_worker_kick(grpc_pollset_worker* worker) {
- grpc_error* err = GRPC_ERROR_NONE;
-
- /* Kick the worker only if it was not already kicked */
- if (gpr_atm_no_barrier_cas(&worker->is_kicked, static_cast<gpr_atm>(0),
- static_cast<gpr_atm>(1))) {
- GRPC_POLLING_TRACE(
- "pollset_worker_kick: Kicking worker: %p (thread id: %ld)",
- (void*)worker, (long int)worker->pt_id);
- int err_num = pthread_kill(worker->pt_id, grpc_wakeup_signal);
- if (err_num != 0) {
- err = GRPC_OS_ERROR(err_num, "pthread_kill");
- }
- }
- return err;
-}
-
-/* Return 1 if the pollset has active threads in pollset_work (pollset must
- * be locked) */
-static int pollset_has_workers(grpc_pollset* p) {
- return p->root_worker.next != &p->root_worker;
-}
-
-static void remove_worker(grpc_pollset* p, grpc_pollset_worker* worker) {
- worker->prev->next = worker->next;
- worker->next->prev = worker->prev;
-}
-
-static grpc_pollset_worker* pop_front_worker(grpc_pollset* p) {
- if (pollset_has_workers(p)) {
- grpc_pollset_worker* w = p->root_worker.next;
- remove_worker(p, w);
- return w;
- } else {
- return nullptr;
- }
-}
-
-static void push_back_worker(grpc_pollset* p, grpc_pollset_worker* worker) {
- worker->next = &p->root_worker;
- worker->prev = worker->next->prev;
- worker->prev->next = worker->next->prev = worker;
-}
-
-static void push_front_worker(grpc_pollset* p, grpc_pollset_worker* worker) {
- worker->prev = &p->root_worker;
- worker->next = worker->prev->next;
- worker->prev->next = worker->next->prev = worker;
-}
-
-/* p->mu must be held before calling this function */
-static grpc_error* pollset_kick(grpc_pollset* p,
- grpc_pollset_worker* specific_worker) {
- GPR_TIMER_SCOPE("pollset_kick", 0);
- grpc_error* error = GRPC_ERROR_NONE;
- GRPC_STATS_INC_POLLSET_KICK();
- const char* err_desc = "Kick Failure";
- grpc_pollset_worker* worker = specific_worker;
- if (worker != nullptr) {
- if (worker == GRPC_POLLSET_KICK_BROADCAST) {
- if (pollset_has_workers(p)) {
- GPR_TIMER_SCOPE("pollset_kick.broadcast", 0);
- for (worker = p->root_worker.next; worker != &p->root_worker;
- worker = worker->next) {
- if (gpr_tls_get(&g_current_thread_worker) != (intptr_t)worker) {
- append_error(&error, pollset_worker_kick(worker), err_desc);
- }
- }
- } else {
- p->kicked_without_pollers = true;
- }
- } else {
- GPR_TIMER_MARK("kicked_specifically", 0);
- if (gpr_tls_get(&g_current_thread_worker) != (intptr_t)worker) {
- append_error(&error, pollset_worker_kick(worker), err_desc);
- }
- }
- } else if (gpr_tls_get(&g_current_thread_pollset) != (intptr_t)p) {
- /* Since worker == NULL, it means that we can kick "any" worker on this
- pollset 'p'. If 'p' happens to be the same pollset this thread is
- currently polling (i.e in pollset_work() function), then there is no need
- to kick any other worker since the current thread can just absorb the
- kick. This is the reason why we enter this case only when
- g_current_thread_pollset is != p */
-
- GPR_TIMER_MARK("kick_anonymous", 0);
- worker = pop_front_worker(p);
- if (worker != nullptr) {
- GPR_TIMER_MARK("finally_kick", 0);
- push_back_worker(p, worker);
- append_error(&error, pollset_worker_kick(worker), err_desc);
- } else {
- GPR_TIMER_MARK("kicked_no_pollers", 0);
- p->kicked_without_pollers = true;
- }
- }
-
- GRPC_LOG_IF_ERROR("pollset_kick", GRPC_ERROR_REF(error));
- return error;
-}
-
-static void pollset_init(grpc_pollset* pollset, gpr_mu** mu) {
- gpr_mu_init(&pollset->po.mu);
- *mu = &pollset->po.mu;
- pollset->po.pi = nullptr;
-#ifndef NDEBUG
- pollset->po.obj_type = POLL_OBJ_POLLSET;
-#endif
-
- pollset->root_worker.next = pollset->root_worker.prev = &pollset->root_worker;
- pollset->kicked_without_pollers = false;
-
- pollset->shutting_down = false;
- pollset->finish_shutdown_called = false;
- pollset->shutdown_done = nullptr;
-}
-
-static int poll_deadline_to_millis_timeout(grpc_millis millis) {
- if (millis == GRPC_MILLIS_INF_FUTURE) return -1;
- grpc_millis delta = millis - grpc_core::ExecCtx::Get()->Now();
- if (delta > INT_MAX)
- return INT_MAX;
- else if (delta < 0)
- return 0;
- else
- return static_cast<int>(delta);
-}
-
-static void fd_become_readable(grpc_fd* fd, grpc_pollset* notifier) {
- fd->read_closure->SetReady();
-
- /* Note, it is possible that fd_become_readable might be called twice with
- different 'notifier's when an fd becomes readable and it is in two epoll
- sets (This can happen briefly during polling island merges). In such cases
- it does not really matter which notifer is set as the read_notifier_pollset
- (They would both point to the same polling island anyway) */
- /* Use release store to match with acquire load in fd_get_read_notifier */
- gpr_atm_rel_store(&fd->read_notifier_pollset, (gpr_atm)notifier);
-}
-
-static void fd_become_writable(grpc_fd* fd) { fd->write_closure->SetReady(); }
-
-static void pollset_release_polling_island(grpc_pollset* ps,
- const char* reason) {
- if (ps->po.pi != nullptr) {
- PI_UNREF(ps->po.pi, reason);
- }
- ps->po.pi = nullptr;
-}
-
-static void finish_shutdown_locked(grpc_pollset* pollset) {
- /* The pollset cannot have any workers if we are at this stage */
- GPR_ASSERT(!pollset_has_workers(pollset));
-
- pollset->finish_shutdown_called = true;
-
- /* Release the ref and set pollset->po.pi to NULL */
- pollset_release_polling_island(pollset, "ps_shutdown");
- GRPC_CLOSURE_SCHED(pollset->shutdown_done, GRPC_ERROR_NONE);
-}
-
-/* pollset->po.mu lock must be held by the caller before calling this */
-static void pollset_shutdown(grpc_pollset* pollset, grpc_closure* closure) {
- GPR_TIMER_SCOPE("pollset_shutdown", 0);
- GPR_ASSERT(!pollset->shutting_down);
- pollset->shutting_down = true;
- pollset->shutdown_done = closure;
- pollset_kick(pollset, GRPC_POLLSET_KICK_BROADCAST);
-
- /* If the pollset has any workers, we cannot call finish_shutdown_locked()
- because it would release the underlying polling island. In such a case, we
- let the last worker call finish_shutdown_locked() from pollset_work() */
- if (!pollset_has_workers(pollset)) {
- GPR_ASSERT(!pollset->finish_shutdown_called);
- GPR_TIMER_MARK("pollset_shutdown.finish_shutdown_locked", 0);
- finish_shutdown_locked(pollset);
- }
-}
-
-/* pollset_shutdown is guaranteed to be called before pollset_destroy. So other
- * than destroying the mutexes, there is nothing special that needs to be done
- * here */
-static void pollset_destroy(grpc_pollset* pollset) {
- GPR_ASSERT(!pollset_has_workers(pollset));
- gpr_mu_destroy(&pollset->po.mu);
-}
-
-#define GRPC_EPOLL_MAX_EVENTS 100
-/* Note: sig_mask contains the signal mask to use *during* epoll_wait() */
-static void pollset_work_and_unlock(grpc_pollset* pollset,
- grpc_pollset_worker* worker, int timeout_ms,
- sigset_t* sig_mask, grpc_error** error) {
- GPR_TIMER_SCOPE("pollset_work_and_unlock", 0);
- struct epoll_event ep_ev[GRPC_EPOLL_MAX_EVENTS];
- int epoll_fd = -1;
- int ep_rv;
- polling_island* pi = nullptr;
- char* err_msg;
- const char* err_desc = "pollset_work_and_unlock";
-
- /* We need to get the epoll_fd to wait on. The epoll_fd is in inside the
- latest polling island pointed by pollset->po.pi
-
- Since epoll_fd is immutable, we can read it without obtaining the polling
- island lock. There is however a possibility that the polling island (from
- which we got the epoll_fd) got merged with another island while we are
- in this function. This is still okay because in such a case, we will wakeup
- right-away from epoll_wait() and pick up the latest polling_island the next
- this function (i.e pollset_work_and_unlock()) is called */
-
- if (pollset->po.pi == nullptr) {
- pollset->po.pi = polling_island_create(nullptr, error);
- if (pollset->po.pi == nullptr) {
- return; /* Fatal error. We cannot continue */
- }
-
- PI_ADD_REF(pollset->po.pi, "ps");
- GRPC_POLLING_TRACE("pollset_work: pollset: %p created new pi: %p",
- (void*)pollset, (void*)pollset->po.pi);
- }
-
- pi = polling_island_maybe_get_latest(pollset->po.pi);
- epoll_fd = pi->epoll_fd;
-
- /* Update the pollset->po.pi since the island being pointed by
- pollset->po.pi maybe older than the one pointed by pi) */
- if (pollset->po.pi != pi) {
- /* Always do PI_ADD_REF before PI_UNREF because PI_UNREF may cause the
- polling island to be deleted */
- PI_ADD_REF(pi, "ps");
- PI_UNREF(pollset->po.pi, "ps");
- pollset->po.pi = pi;
- }
-
- /* Add an extra ref so that the island does not get destroyed (which means
- the epoll_fd won't be closed) while we are are doing an epoll_wait() on the
- epoll_fd */
- PI_ADD_REF(pi, "ps_work");
- gpr_mu_unlock(&pollset->po.mu);
-
- gpr_atm_no_barrier_fetch_add(&pi->poller_count, 1);
- g_current_thread_polling_island = pi;
-
- GRPC_SCHEDULING_START_BLOCKING_REGION;
- GRPC_STATS_INC_SYSCALL_POLL();
- ep_rv =
- epoll_pwait(epoll_fd, ep_ev, GRPC_EPOLL_MAX_EVENTS, timeout_ms, sig_mask);
- GRPC_SCHEDULING_END_BLOCKING_REGION;
- if (ep_rv < 0) {
- if (errno != EINTR) {
- gpr_asprintf(&err_msg,
- "epoll_wait() epoll fd: %d failed with error: %d (%s)",
- epoll_fd, errno, strerror(errno));
- append_error(error, GRPC_OS_ERROR(errno, err_msg), err_desc);
- } else {
- /* We were interrupted. Save an interation by doing a zero timeout
- epoll_wait to see if there are any other events of interest */
- GRPC_POLLING_TRACE("pollset_work: pollset: %p, worker: %p received kick",
- (void*)pollset, (void*)worker);
- ep_rv = epoll_wait(epoll_fd, ep_ev, GRPC_EPOLL_MAX_EVENTS, 0);
- }
- }
-
-#ifdef GRPC_TSAN
- /* See the definition of g_poll_sync for more details */
- gpr_atm_acq_load(&g_epoll_sync);
-#endif /* defined(GRPC_TSAN) */
-
- for (int i = 0; i < ep_rv; ++i) {
- void* data_ptr = ep_ev[i].data.ptr;
- if (data_ptr == &polling_island_wakeup_fd) {
- GRPC_POLLING_TRACE(
- "pollset_work: pollset: %p, worker: %p polling island (epoll_fd: "
- "%d) got merged",
- (void*)pollset, (void*)worker, epoll_fd);
- /* This means that our polling island is merged with a different
- island. We do not have to do anything here since the subsequent call
- to the function pollset_work_and_unlock() will pick up the correct
- epoll_fd */
- } else {
- grpc_fd* fd = static_cast<grpc_fd*>(data_ptr);
- int cancel = ep_ev[i].events & (EPOLLERR | EPOLLHUP);
- int read_ev = ep_ev[i].events & (EPOLLIN | EPOLLPRI);
- int write_ev = ep_ev[i].events & EPOLLOUT;
- if (read_ev || cancel) {
- fd_become_readable(fd, pollset);
- }
- if (write_ev || cancel) {
- fd_become_writable(fd);
- }
- }
- }
-
- g_current_thread_polling_island = nullptr;
- gpr_atm_no_barrier_fetch_add(&pi->poller_count, -1);
-
- GPR_ASSERT(pi != nullptr);
-
- /* Before leaving, release the extra ref we added to the polling island. It
- is important to use "pi" here (i.e our old copy of pollset->po.pi
- that we got before releasing the polling island lock). This is because
- pollset->po.pi pointer might get udpated in other parts of the
- code when there is an island merge while we are doing epoll_wait() above */
- PI_UNREF(pi, "ps_work");
-}
-
-/* pollset->po.mu lock must be held by the caller before calling this.
- The function pollset_work() may temporarily release the lock (pollset->po.mu)
- during the course of its execution but it will always re-acquire the lock and
- ensure that it is held by the time the function returns */
-static grpc_error* pollset_work(grpc_pollset* pollset,
- grpc_pollset_worker** worker_hdl,
- grpc_millis deadline) {
- GPR_TIMER_SCOPE("pollset_work", 0);
- grpc_error* error = GRPC_ERROR_NONE;
- int timeout_ms = poll_deadline_to_millis_timeout(deadline);
-
- sigset_t new_mask;
-
- grpc_pollset_worker worker;
- worker.next = worker.prev = nullptr;
- worker.pt_id = pthread_self();
- gpr_atm_no_barrier_store(&worker.is_kicked, (gpr_atm)0);
-
- if (worker_hdl) *worker_hdl = &worker;
-
- gpr_tls_set(&g_current_thread_pollset, (intptr_t)pollset);
- gpr_tls_set(&g_current_thread_worker, (intptr_t)&worker);
-
- if (pollset->kicked_without_pollers) {
- /* If the pollset was kicked without pollers, pretend that the current
- worker got the kick and skip polling. A kick indicates that there is some
- work that needs attention like an event on the completion queue or an
- alarm */
- GPR_TIMER_MARK("pollset_work.kicked_without_pollers", 0);
- pollset->kicked_without_pollers = 0;
- } else if (!pollset->shutting_down) {
- /* We use the posix-signal with number 'grpc_wakeup_signal' for waking up
- (i.e 'kicking') a worker in the pollset. A 'kick' is a way to inform the
- worker that there is some pending work that needs immediate attention
- (like an event on the completion queue, or a polling island merge that
- results in a new epoll-fd to wait on) and that the worker should not
- spend time waiting in epoll_pwait().
-
- A worker can be kicked anytime from the point it is added to the pollset
- via push_front_worker() (or push_back_worker()) to the point it is
- removed via remove_worker().
- If the worker is kicked before/during it calls epoll_pwait(), it should
- immediately exit from epoll_wait(). If the worker is kicked after it
- returns from epoll_wait(), then nothing really needs to be done.
-
- To accomplish this, we mask 'grpc_wakeup_signal' on this thread at all
- times *except* when it is in epoll_pwait(). This way, the worker never
- misses acting on a kick */
-
- if (!g_initialized_sigmask) {
- sigemptyset(&new_mask);
- sigaddset(&new_mask, grpc_wakeup_signal);
- pthread_sigmask(SIG_BLOCK, &new_mask, &g_orig_sigmask);
- sigdelset(&g_orig_sigmask, grpc_wakeup_signal);
- g_initialized_sigmask = true;
- /* new_mask: The new thread mask which blocks 'grpc_wakeup_signal'.
- This is the mask used at all times *except during
- epoll_wait()*"
- g_orig_sigmask: The thread mask which allows 'grpc_wakeup_signal' and
- this is the mask to use *during epoll_wait()*
-
- The new_mask is set on the worker before it is added to the pollset
- (i.e before it can be kicked) */
- }
-
- push_front_worker(pollset, &worker); /* Add worker to pollset */
-
- pollset_work_and_unlock(pollset, &worker, timeout_ms, &g_orig_sigmask,
- &error);
- grpc_core::ExecCtx::Get()->Flush();
-
- gpr_mu_lock(&pollset->po.mu);
-
- /* Note: There is no need to reset worker.is_kicked to 0 since we are no
- longer going to use this worker */
- remove_worker(pollset, &worker);
- }
-
- /* If we are the last worker on the pollset (i.e pollset_has_workers() is
- false at this point) and the pollset is shutting down, we may have to
- finish the shutdown process by calling finish_shutdown_locked().
- See pollset_shutdown() for more details.
-
- Note: Continuing to access pollset here is safe; it is the caller's
- responsibility to not destroy a pollset when it has outstanding calls to
- pollset_work() */
- if (pollset->shutting_down && !pollset_has_workers(pollset) &&
- !pollset->finish_shutdown_called) {
- GPR_TIMER_MARK("pollset_work.finish_shutdown_locked", 0);
- finish_shutdown_locked(pollset);
-
- gpr_mu_unlock(&pollset->po.mu);
- grpc_core::ExecCtx::Get()->Flush();
- gpr_mu_lock(&pollset->po.mu);
- }
-
- if (worker_hdl) *worker_hdl = nullptr;
-
- gpr_tls_set(&g_current_thread_pollset, (intptr_t)0);
- gpr_tls_set(&g_current_thread_worker, (intptr_t)0);
-
- GRPC_LOG_IF_ERROR("pollset_work", GRPC_ERROR_REF(error));
- return error;
-}
-
-static void add_poll_object(poll_obj* bag, poll_obj_type bag_type,
- poll_obj* item, poll_obj_type item_type) {
- GPR_TIMER_SCOPE("add_poll_object", 0);
-
-#ifndef NDEBUG
- GPR_ASSERT(item->obj_type == item_type);
- GPR_ASSERT(bag->obj_type == bag_type);
-#endif
-
- grpc_error* error = GRPC_ERROR_NONE;
- polling_island* pi_new = nullptr;
-
- gpr_mu_lock(&bag->mu);
- gpr_mu_lock(&item->mu);
-
-retry:
- /*
- * 1) If item->pi and bag->pi are both non-NULL and equal, do nothing
- * 2) If item->pi and bag->pi are both NULL, create a new polling island (with
- * a refcount of 2) and point item->pi and bag->pi to the new island
- * 3) If exactly one of item->pi or bag->pi is NULL, update it to point to
- * the other's non-NULL pi
- * 4) Finally if item->pi and bag-pi are non-NULL and not-equal, merge the
- * polling islands and update item->pi and bag->pi to point to the new
- * island
- */
-
- /* Early out if we are trying to add an 'fd' to a 'bag' but the fd is already
- * orphaned */
- if (item_type == POLL_OBJ_FD && (FD_FROM_PO(item))->orphaned) {
- gpr_mu_unlock(&item->mu);
- gpr_mu_unlock(&bag->mu);
- return;
- }
-
- if (item->pi == bag->pi) {
- pi_new = item->pi;
- if (pi_new == nullptr) {
- /* GPR_ASSERT(item->pi == bag->pi == NULL) */
-
- /* If we are adding an fd to a bag (i.e pollset or pollset_set), then
- * we need to do some extra work to make TSAN happy */
- if (item_type == POLL_OBJ_FD) {
- /* Unlock before creating a new polling island: the polling island will
- create a workqueue which creates a file descriptor, and holding an fd
- lock here can eventually cause a loop to appear to TSAN (making it
- unhappy). We don't think it's a real loop (there's an epoch point
- where that loop possibility disappears), but the advantages of
- keeping TSAN happy outweigh any performance advantage we might have
- by keeping the lock held. */
- gpr_mu_unlock(&item->mu);
- pi_new = polling_island_create(FD_FROM_PO(item), &error);
- gpr_mu_lock(&item->mu);
-
- /* Need to reverify any assumptions made between the initial lock and
- getting to this branch: if they've changed, we need to throw away our
- work and figure things out again. */
- if (item->pi != nullptr) {
- GRPC_POLLING_TRACE(
- "add_poll_object: Raced creating new polling island. pi_new: %p "
- "(fd: %d, %s: %p)",
- (void*)pi_new, FD_FROM_PO(item)->fd, poll_obj_string(bag_type),
- (void*)bag);
- /* No need to lock 'pi_new' here since this is a new polling island
- and no one has a reference to it yet */
- polling_island_remove_all_fds_locked(pi_new, true, &error);
-
- /* Ref and unref so that the polling island gets deleted during unref
- */
- PI_ADD_REF(pi_new, "dance_of_destruction");
- PI_UNREF(pi_new, "dance_of_destruction");
- goto retry;
- }
- } else {
- pi_new = polling_island_create(nullptr, &error);
- }
-
- GRPC_POLLING_TRACE(
- "add_poll_object: Created new polling island. pi_new: %p (%s: %p, "
- "%s: %p)",
- (void*)pi_new, poll_obj_string(item_type), (void*)item,
- poll_obj_string(bag_type), (void*)bag);
- } else {
- GRPC_POLLING_TRACE(
- "add_poll_object: Same polling island. pi: %p (%s, %s)",
- (void*)pi_new, poll_obj_string(item_type), poll_obj_string(bag_type));
- }
- } else if (item->pi == nullptr) {
- /* GPR_ASSERT(bag->pi != NULL) */
- /* Make pi_new point to latest pi*/
- pi_new = polling_island_lock(bag->pi);
-
- if (item_type == POLL_OBJ_FD) {
- grpc_fd* fd = FD_FROM_PO(item);
- polling_island_add_fds_locked(pi_new, &fd, 1, true, &error);
- }
-
- gpr_mu_unlock(&pi_new->mu);
- GRPC_POLLING_TRACE(
- "add_poll_obj: item->pi was NULL. pi_new: %p (item(%s): %p, "
- "bag(%s): %p)",
- (void*)pi_new, poll_obj_string(item_type), (void*)item,
- poll_obj_string(bag_type), (void*)bag);
- } else if (bag->pi == nullptr) {
- /* GPR_ASSERT(item->pi != NULL) */
- /* Make pi_new to point to latest pi */
- pi_new = polling_island_lock(item->pi);
- gpr_mu_unlock(&pi_new->mu);
- GRPC_POLLING_TRACE(
- "add_poll_obj: bag->pi was NULL. pi_new: %p (item(%s): %p, "
- "bag(%s): %p)",
- (void*)pi_new, poll_obj_string(item_type), (void*)item,
- poll_obj_string(bag_type), (void*)bag);
- } else {
- pi_new = polling_island_merge(item->pi, bag->pi, &error);
- GRPC_POLLING_TRACE(
- "add_poll_obj: polling islands merged. pi_new: %p (item(%s): %p, "
- "bag(%s): %p)",
- (void*)pi_new, poll_obj_string(item_type), (void*)item,
- poll_obj_string(bag_type), (void*)bag);
- }
-
- /* At this point, pi_new is the polling island that both item->pi and bag->pi
- MUST be pointing to */
-
- if (item->pi != pi_new) {
- PI_ADD_REF(pi_new, poll_obj_string(item_type));
- if (item->pi != nullptr) {
- PI_UNREF(item->pi, poll_obj_string(item_type));
- }
- item->pi = pi_new;
- }
-
- if (bag->pi != pi_new) {
- PI_ADD_REF(pi_new, poll_obj_string(bag_type));
- if (bag->pi != nullptr) {
- PI_UNREF(bag->pi, poll_obj_string(bag_type));
- }
- bag->pi = pi_new;
- }
-
- gpr_mu_unlock(&item->mu);
- gpr_mu_unlock(&bag->mu);
-
- GRPC_LOG_IF_ERROR("add_poll_object", error);
-}
-
-static void pollset_add_fd(grpc_pollset* pollset, grpc_fd* fd) {
- add_poll_object(&pollset->po, POLL_OBJ_POLLSET, &fd->po, POLL_OBJ_FD);
-}
-
-/*******************************************************************************
- * Pollset-set Definitions
- */
-
-static grpc_pollset_set* pollset_set_create(void) {
- grpc_pollset_set* pss =
- static_cast<grpc_pollset_set*>(gpr_malloc(sizeof(*pss)));
- gpr_mu_init(&pss->po.mu);
- pss->po.pi = nullptr;
-#ifndef NDEBUG
- pss->po.obj_type = POLL_OBJ_POLLSET_SET;
-#endif
- return pss;
-}
-
-static void pollset_set_destroy(grpc_pollset_set* pss) {
- gpr_mu_destroy(&pss->po.mu);
-
- if (pss->po.pi != nullptr) {
- PI_UNREF(pss->po.pi, "pss_destroy");
- }
-
- gpr_free(pss);
-}
-
-static void pollset_set_add_fd(grpc_pollset_set* pss, grpc_fd* fd) {
- add_poll_object(&pss->po, POLL_OBJ_POLLSET_SET, &fd->po, POLL_OBJ_FD);
-}
-
-static void pollset_set_del_fd(grpc_pollset_set* pss, grpc_fd* fd) {
- /* Nothing to do */
-}
-
-static void pollset_set_add_pollset(grpc_pollset_set* pss, grpc_pollset* ps) {
- add_poll_object(&pss->po, POLL_OBJ_POLLSET_SET, &ps->po, POLL_OBJ_POLLSET);
-}
-
-static void pollset_set_del_pollset(grpc_pollset_set* pss, grpc_pollset* ps) {
- /* Nothing to do */
-}
-
-static void pollset_set_add_pollset_set(grpc_pollset_set* bag,
- grpc_pollset_set* item) {
- add_poll_object(&bag->po, POLL_OBJ_POLLSET_SET, &item->po,
- POLL_OBJ_POLLSET_SET);
-}
-
-static void pollset_set_del_pollset_set(grpc_pollset_set* bag,
- grpc_pollset_set* item) {
- /* Nothing to do */
-}
-
-/* Test helper functions
- * */
-void* grpc_fd_get_polling_island(grpc_fd* fd) {
- polling_island* pi;
-
- gpr_mu_lock(&fd->po.mu);
- pi = fd->po.pi;
- gpr_mu_unlock(&fd->po.mu);
-
- return pi;
-}
-
-void* grpc_pollset_get_polling_island(grpc_pollset* ps) {
- polling_island* pi;
-
- gpr_mu_lock(&ps->po.mu);
- pi = ps->po.pi;
- gpr_mu_unlock(&ps->po.mu);
-
- return pi;
-}
-
-bool grpc_are_polling_islands_equal(void* p, void* q) {
- polling_island* p1 = static_cast<polling_island*>(p);
- polling_island* p2 = static_cast<polling_island*>(q);
-
- /* Note: polling_island_lock_pair() may change p1 and p2 to point to the
- latest polling islands in their respective linked lists */
- polling_island_lock_pair(&p1, &p2);
- polling_island_unlock_pair(p1, p2);
-
- return p1 == p2;
-}
-
-/*******************************************************************************
- * Event engine binding
- */
-
-static void shutdown_engine(void) {
- fd_global_shutdown();
- pollset_global_shutdown();
- polling_island_global_shutdown();
-}
-
-static const grpc_event_engine_vtable vtable = {
- sizeof(grpc_pollset),
-
- fd_create,
- fd_wrapped_fd,
- fd_orphan,
- fd_shutdown,
- fd_notify_on_read,
- fd_notify_on_write,
- fd_is_shutdown,
- fd_get_read_notifier_pollset,
-
- pollset_init,
- pollset_shutdown,
- pollset_destroy,
- pollset_work,
- pollset_kick,
- pollset_add_fd,
-
- pollset_set_create,
- pollset_set_destroy,
- pollset_set_add_pollset,
- pollset_set_del_pollset,
- pollset_set_add_pollset_set,
- pollset_set_del_pollset_set,
- pollset_set_add_fd,
- pollset_set_del_fd,
-
- shutdown_engine,
-};
-
-/* It is possible that GLIBC has epoll but the underlying kernel doesn't.
- * Create a dummy epoll_fd to make sure epoll support is available */
-static bool is_epoll_available() {
- int fd = epoll_create1(EPOLL_CLOEXEC);
- if (fd < 0) {
- gpr_log(
- GPR_ERROR,
- "epoll_create1 failed with error: %d. Not using epoll polling engine",
- fd);
- return false;
- }
- close(fd);
- return true;
-}
-
-const grpc_event_engine_vtable* grpc_init_epollsig_linux(
- bool explicit_request) {
- /* If use of signals is disabled, we cannot use epoll engine*/
- if (is_grpc_wakeup_signal_initialized && grpc_wakeup_signal < 0) {
- gpr_log(GPR_ERROR, "Skipping epollsig because use of signals is disabled.");
- return nullptr;
- }
-
- if (!grpc_has_wakeup_fd()) {
- gpr_log(GPR_ERROR, "Skipping epollsig because of no wakeup fd.");
- return nullptr;
- }
-
- if (!is_epoll_available()) {
- gpr_log(GPR_ERROR, "Skipping epollsig because epoll is unavailable.");
- return nullptr;
- }
-
- if (!is_grpc_wakeup_signal_initialized) {
- if (explicit_request) {
- grpc_use_signal(SIGRTMIN + 6);
- } else {
- gpr_log(GPR_ERROR,
- "Skipping epollsig because uninitialized wakeup signal.");
- return nullptr;
- }
- }
-
- fd_global_init();
-
- if (!GRPC_LOG_IF_ERROR("pollset_global_init", pollset_global_init())) {
- return nullptr;
- }
-
- if (!GRPC_LOG_IF_ERROR("polling_island_global_init",
- polling_island_global_init())) {
- return nullptr;
- }
-
- return &vtable;
-}
-
-#else /* defined(GRPC_LINUX_EPOLL_CREATE1) */
-#if defined(GRPC_POSIX_SOCKET)
-#include "src/core/lib/iomgr/ev_epollsig_linux.h"
-/* If GRPC_LINUX_EPOLL_CREATE1 is not defined, it means
- epoll_create1 is not available. Return NULL */
-const grpc_event_engine_vtable* grpc_init_epollsig_linux(
- bool explicit_request) {
- return nullptr;
-}
-#endif /* defined(GRPC_POSIX_SOCKET) */
-
-void grpc_use_signal(int signum) {}
-#endif /* !defined(GRPC_LINUX_EPOLL_CREATE1) */
diff --git a/src/core/lib/iomgr/ev_poll_posix.cc b/src/core/lib/iomgr/ev_poll_posix.cc
index 504787e659..16562538a6 100644
--- a/src/core/lib/iomgr/ev_poll_posix.cc
+++ b/src/core/lib/iomgr/ev_poll_posix.cc
@@ -20,7 +20,7 @@
#include "src/core/lib/iomgr/port.h"
-#ifdef GRPC_POSIX_SOCKET
+#ifdef GRPC_POSIX_SOCKET_EV_POLL
#include "src/core/lib/iomgr/ev_poll_posix.h"
@@ -60,6 +60,19 @@ typedef struct grpc_fd_watcher {
grpc_fd* fd;
} grpc_fd_watcher;
+typedef struct grpc_cached_wakeup_fd grpc_cached_wakeup_fd;
+
+/* Only used when GRPC_ENABLE_FORK_SUPPORT=1 */
+struct grpc_fork_fd_list {
+ /* Only one of fd or cached_wakeup_fd will be set. The unused field will be
+ set to nullptr. */
+ grpc_fd* fd;
+ grpc_cached_wakeup_fd* cached_wakeup_fd;
+
+ grpc_fork_fd_list* next;
+ grpc_fork_fd_list* prev;
+};
+
struct grpc_fd {
int fd;
/* refst format:
@@ -109,10 +122,17 @@ struct grpc_fd {
grpc_iomgr_object iomgr_object;
- /* The pollset that last noticed and notified that the fd is readable */
- grpc_pollset* read_notifier_pollset;
+ /* Only used when GRPC_ENABLE_FORK_SUPPORT=1 */
+ grpc_fork_fd_list* fork_fd_list;
};
+/* True when GRPC_ENABLE_FORK_SUPPORT=1. We do not support fork with poll-cv */
+static bool track_fds_for_fork = false;
+
+/* Only used when GRPC_ENABLE_FORK_SUPPORT=1 */
+static grpc_fork_fd_list* fork_fd_list_head = nullptr;
+static gpr_mu fork_fd_list_mu;
+
/* Begin polling on an fd.
Registers that the given pollset is interested in this fd - so that if read
or writability interest changes, the pollset can be kicked to pick up that
@@ -131,8 +151,7 @@ static uint32_t fd_begin_poll(grpc_fd* fd, grpc_pollset* pollset,
MUST NOT be called with a pollset lock taken
if got_read or got_write are 1, also does the become_{readable,writable} as
appropriate. */
-static void fd_end_poll(grpc_fd_watcher* rec, int got_read, int got_write,
- grpc_pollset* read_notifier_pollset);
+static void fd_end_poll(grpc_fd_watcher* rec, int got_read, int got_write);
/* Return 1 if this fd is orphaned, 0 otherwise */
static bool fd_is_orphaned(grpc_fd* fd);
@@ -160,6 +179,9 @@ static void fd_unref(grpc_fd* fd);
typedef struct grpc_cached_wakeup_fd {
grpc_wakeup_fd fd;
struct grpc_cached_wakeup_fd* next;
+
+ /* Only used when GRPC_ENABLE_FORK_SUPPORT=1 */
+ grpc_fork_fd_list* fork_fd_list;
} grpc_cached_wakeup_fd;
struct grpc_pollset_worker {
@@ -285,9 +307,61 @@ poll_hash_table poll_cache;
grpc_cv_fd_table g_cvfds;
/*******************************************************************************
- * fd_posix.c
+ * functions to track opened fds. No-ops unless track_fds_for_fork is true.
*/
+static void fork_fd_list_remove_node(grpc_fork_fd_list* node) {
+ if (track_fds_for_fork) {
+ gpr_mu_lock(&fork_fd_list_mu);
+ if (fork_fd_list_head == node) {
+ fork_fd_list_head = node->next;
+ }
+ if (node->prev != nullptr) {
+ node->prev->next = node->next;
+ }
+ if (node->next != nullptr) {
+ node->next->prev = node->prev;
+ }
+ gpr_free(node);
+ gpr_mu_unlock(&fork_fd_list_mu);
+ }
+}
+
+static void fork_fd_list_add_node(grpc_fork_fd_list* node) {
+ gpr_mu_lock(&fork_fd_list_mu);
+ node->next = fork_fd_list_head;
+ node->prev = nullptr;
+ if (fork_fd_list_head != nullptr) {
+ fork_fd_list_head->prev = node;
+ }
+ fork_fd_list_head = node;
+ gpr_mu_unlock(&fork_fd_list_mu);
+}
+
+static void fork_fd_list_add_grpc_fd(grpc_fd* fd) {
+ if (track_fds_for_fork) {
+ fd->fork_fd_list =
+ static_cast<grpc_fork_fd_list*>(gpr_malloc(sizeof(grpc_fork_fd_list)));
+ fd->fork_fd_list->fd = fd;
+ fd->fork_fd_list->cached_wakeup_fd = nullptr;
+ fork_fd_list_add_node(fd->fork_fd_list);
+ }
+}
+
+static void fork_fd_list_add_wakeup_fd(grpc_cached_wakeup_fd* fd) {
+ if (track_fds_for_fork) {
+ fd->fork_fd_list =
+ static_cast<grpc_fork_fd_list*>(gpr_malloc(sizeof(grpc_fork_fd_list)));
+ fd->fork_fd_list->cached_wakeup_fd = fd;
+ fd->fork_fd_list->fd = nullptr;
+ fork_fd_list_add_node(fd->fork_fd_list);
+ }
+}
+
+ /*******************************************************************************
+ * fd_posix.c
+ */
+
#ifndef NDEBUG
#define REF_BY(fd, n, reason) ref_by(fd, n, reason, __FILE__, __LINE__)
#define UNREF_BY(fd, n, reason) unref_by(fd, n, reason, __FILE__, __LINE__)
@@ -323,6 +397,7 @@ static void unref_by(grpc_fd* fd, int n) {
if (old == n) {
gpr_mu_destroy(&fd->mu);
grpc_iomgr_unregister_object(&fd->iomgr_object);
+ fork_fd_list_remove_node(fd->fork_fd_list);
if (fd->shutdown) GRPC_ERROR_UNREF(fd->shutdown_error);
gpr_free(fd);
} else {
@@ -330,7 +405,8 @@ static void unref_by(grpc_fd* fd, int n) {
}
}
-static grpc_fd* fd_create(int fd, const char* name) {
+static grpc_fd* fd_create(int fd, const char* name, bool track_err) {
+ GPR_DEBUG_ASSERT(track_err == false);
grpc_fd* r = static_cast<grpc_fd*>(gpr_malloc(sizeof(*r)));
gpr_mu_init(&r->mu);
gpr_atm_rel_store(&r->refst, 1);
@@ -345,12 +421,12 @@ static grpc_fd* fd_create(int fd, const char* name) {
r->closed = 0;
r->released = 0;
gpr_atm_no_barrier_store(&r->pollhup, 0);
- r->read_notifier_pollset = nullptr;
char* name2;
gpr_asprintf(&name2, "%s fd=%d", name, fd);
grpc_iomgr_register_object(&r->iomgr_object, name2);
gpr_free(name2);
+ fork_fd_list_add_grpc_fd(r);
return r;
}
@@ -358,17 +434,6 @@ static bool fd_is_orphaned(grpc_fd* fd) {
return (gpr_atm_acq_load(&fd->refst) & 1) == 0;
}
-/* Return the read-notifier pollset */
-static grpc_pollset* fd_get_read_notifier_pollset(grpc_fd* fd) {
- grpc_pollset* notifier = nullptr;
-
- gpr_mu_lock(&fd->mu);
- notifier = fd->read_notifier_pollset;
- gpr_mu_unlock(&fd->mu);
-
- return notifier;
-}
-
static grpc_error* pollset_kick_locked(grpc_fd_watcher* watcher) {
gpr_mu_lock(&watcher->pollset->mu);
GPR_ASSERT(watcher->worker);
@@ -424,14 +489,12 @@ static int fd_wrapped_fd(grpc_fd* fd) {
}
static void fd_orphan(grpc_fd* fd, grpc_closure* on_done, int* release_fd,
- bool already_closed, const char* reason) {
+ const char* reason) {
fd->on_done_closure = on_done;
fd->released = release_fd != nullptr;
if (release_fd != nullptr) {
*release_fd = fd->fd;
fd->released = true;
- } else if (already_closed) {
- fd->released = true;
}
gpr_mu_lock(&fd->mu);
REF_BY(fd, 1, reason); /* remove active status, but keep referenced */
@@ -513,11 +576,6 @@ static int set_ready_locked(grpc_fd* fd, grpc_closure** st) {
}
}
-static void set_read_notifier_pollset_locked(
- grpc_fd* fd, grpc_pollset* read_notifier_pollset) {
- fd->read_notifier_pollset = read_notifier_pollset;
-}
-
static void fd_shutdown(grpc_fd* fd, grpc_error* why) {
gpr_mu_lock(&fd->mu);
/* only shutdown once */
@@ -553,6 +611,31 @@ static void fd_notify_on_write(grpc_fd* fd, grpc_closure* closure) {
gpr_mu_unlock(&fd->mu);
}
+static void fd_notify_on_error(grpc_fd* fd, grpc_closure* closure) {
+ if (grpc_polling_trace.enabled()) {
+ gpr_log(GPR_ERROR, "Polling engine does not support tracking errors.");
+ }
+ GRPC_CLOSURE_SCHED(closure, GRPC_ERROR_CANCELLED);
+}
+
+static void fd_set_readable(grpc_fd* fd) {
+ gpr_mu_lock(&fd->mu);
+ set_ready_locked(fd, &fd->read_closure);
+ gpr_mu_unlock(&fd->mu);
+}
+
+static void fd_set_writable(grpc_fd* fd) {
+ gpr_mu_lock(&fd->mu);
+ set_ready_locked(fd, &fd->write_closure);
+ gpr_mu_unlock(&fd->mu);
+}
+
+static void fd_set_error(grpc_fd* fd) {
+ if (grpc_polling_trace.enabled()) {
+ gpr_log(GPR_ERROR, "Polling engine does not support tracking errors.");
+ }
+}
+
static uint32_t fd_begin_poll(grpc_fd* fd, grpc_pollset* pollset,
grpc_pollset_worker* worker, uint32_t read_mask,
uint32_t write_mask, grpc_fd_watcher* watcher) {
@@ -604,8 +687,7 @@ static uint32_t fd_begin_poll(grpc_fd* fd, grpc_pollset* pollset,
return mask;
}
-static void fd_end_poll(grpc_fd_watcher* watcher, int got_read, int got_write,
- grpc_pollset* read_notifier_pollset) {
+static void fd_end_poll(grpc_fd_watcher* watcher, int got_read, int got_write) {
int was_polling = 0;
int kick = 0;
grpc_fd* fd = watcher->fd;
@@ -641,9 +723,6 @@ static void fd_end_poll(grpc_fd_watcher* watcher, int got_read, int got_write,
if (set_ready_locked(fd, &fd->read_closure)) {
kick = 1;
}
- if (read_notifier_pollset != nullptr) {
- set_read_notifier_pollset_locked(fd, read_notifier_pollset);
- }
}
if (got_write) {
if (set_ready_locked(fd, &fd->write_closure)) {
@@ -823,6 +902,7 @@ static void pollset_destroy(grpc_pollset* pollset) {
GPR_ASSERT(!pollset_has_workers(pollset));
while (pollset->local_wakeup_cache) {
grpc_cached_wakeup_fd* next = pollset->local_wakeup_cache->next;
+ fork_fd_list_remove_node(pollset->local_wakeup_cache->fork_fd_list);
grpc_wakeup_fd_destroy(&pollset->local_wakeup_cache->fd);
gpr_free(pollset->local_wakeup_cache);
pollset->local_wakeup_cache = next;
@@ -896,6 +976,7 @@ static grpc_error* pollset_work(grpc_pollset* pollset,
worker.wakeup_fd = static_cast<grpc_cached_wakeup_fd*>(
gpr_malloc(sizeof(*worker.wakeup_fd)));
error = grpc_wakeup_fd_init(&worker.wakeup_fd->fd);
+ fork_fd_list_add_wakeup_fd(worker.wakeup_fd);
if (error != GRPC_ERROR_NONE) {
GRPC_LOG_IF_ERROR("pollset_work", GRPC_ERROR_REF(error));
return error;
@@ -993,16 +1074,16 @@ static grpc_error* pollset_work(grpc_pollset* pollset,
for (i = 1; i < pfd_count; i++) {
if (watchers[i].fd == nullptr) {
- fd_end_poll(&watchers[i], 0, 0, nullptr);
+ fd_end_poll(&watchers[i], 0, 0);
} else {
// Wake up all the file descriptors, if we have an invalid one
// we can identify it on the next pollset_work()
- fd_end_poll(&watchers[i], 1, 1, pollset);
+ fd_end_poll(&watchers[i], 1, 1);
}
}
} else if (r == 0) {
for (i = 1; i < pfd_count; i++) {
- fd_end_poll(&watchers[i], 0, 0, nullptr);
+ fd_end_poll(&watchers[i], 0, 0);
}
} else {
if (pfds[0].revents & POLLIN_CHECK) {
@@ -1014,7 +1095,7 @@ static grpc_error* pollset_work(grpc_pollset* pollset,
}
for (i = 1; i < pfd_count; i++) {
if (watchers[i].fd == nullptr) {
- fd_end_poll(&watchers[i], 0, 0, nullptr);
+ fd_end_poll(&watchers[i], 0, 0);
} else {
if (grpc_polling_trace.enabled()) {
gpr_log(GPR_INFO, "%p got_event: %d r:%d w:%d [%d]", pollset,
@@ -1028,7 +1109,7 @@ static grpc_error* pollset_work(grpc_pollset* pollset,
gpr_atm_no_barrier_store(&watchers[i].fd->pollhup, 1);
}
fd_end_poll(&watchers[i], pfds[i].revents & POLLIN_CHECK,
- pfds[i].revents & POLLOUT_CHECK, pollset);
+ pfds[i].revents & POLLOUT_CHECK);
}
}
}
@@ -1706,10 +1787,15 @@ static void shutdown_engine(void) {
if (grpc_cv_wakeup_fds_enabled()) {
global_cv_fd_table_shutdown();
}
+ if (track_fds_for_fork) {
+ gpr_mu_destroy(&fork_fd_list_mu);
+ grpc_core::Fork::SetResetChildPollingEngineFunc(nullptr);
+ }
}
static const grpc_event_engine_vtable vtable = {
sizeof(grpc_pollset),
+ false,
fd_create,
fd_wrapped_fd,
@@ -1717,8 +1803,11 @@ static const grpc_event_engine_vtable vtable = {
fd_shutdown,
fd_notify_on_read,
fd_notify_on_write,
+ fd_notify_on_error,
+ fd_set_readable,
+ fd_set_writable,
+ fd_set_error,
fd_is_shutdown,
- fd_get_read_notifier_pollset,
pollset_init,
pollset_shutdown,
@@ -1739,6 +1828,26 @@ static const grpc_event_engine_vtable vtable = {
shutdown_engine,
};
+/* Called by the child process's post-fork handler to close open fds, including
+ * worker wakeup fds. This allows gRPC to shutdown in the child process without
+ * interfering with connections or RPCs ongoing in the parent. */
+static void reset_event_manager_on_fork() {
+ gpr_mu_lock(&fork_fd_list_mu);
+ while (fork_fd_list_head != nullptr) {
+ if (fork_fd_list_head->fd != nullptr) {
+ close(fork_fd_list_head->fd->fd);
+ fork_fd_list_head->fd->fd = -1;
+ } else {
+ close(fork_fd_list_head->cached_wakeup_fd->fd.read_fd);
+ fork_fd_list_head->cached_wakeup_fd->fd.read_fd = -1;
+ close(fork_fd_list_head->cached_wakeup_fd->fd.write_fd);
+ fork_fd_list_head->cached_wakeup_fd->fd.write_fd = -1;
+ }
+ fork_fd_list_head = fork_fd_list_head->next;
+ }
+ gpr_mu_unlock(&fork_fd_list_mu);
+}
+
const grpc_event_engine_vtable* grpc_init_poll_posix(bool explicit_request) {
if (!grpc_has_wakeup_fd()) {
gpr_log(GPR_ERROR, "Skipping poll because of no wakeup fd.");
@@ -1747,6 +1856,12 @@ const grpc_event_engine_vtable* grpc_init_poll_posix(bool explicit_request) {
if (!GRPC_LOG_IF_ERROR("pollset_global_init", pollset_global_init())) {
return nullptr;
}
+ if (grpc_core::Fork::Enabled()) {
+ track_fds_for_fork = true;
+ gpr_mu_init(&fork_fd_list_mu);
+ grpc_core::Fork::SetResetChildPollingEngineFunc(
+ reset_event_manager_on_fork);
+ }
return &vtable;
}
@@ -1761,4 +1876,4 @@ const grpc_event_engine_vtable* grpc_init_poll_cv_posix(bool explicit_request) {
return &vtable;
}
-#endif
+#endif /* GRPC_POSIX_SOCKET_EV_POLL */
diff --git a/src/core/lib/iomgr/ev_posix.cc b/src/core/lib/iomgr/ev_posix.cc
index 6bd1dc8e50..764fb08142 100644
--- a/src/core/lib/iomgr/ev_posix.cc
+++ b/src/core/lib/iomgr/ev_posix.cc
@@ -20,7 +20,7 @@
#include "src/core/lib/iomgr/port.h"
-#ifdef GRPC_POSIX_SOCKET
+#ifdef GRPC_POSIX_SOCKET_EV
#include "src/core/lib/iomgr/ev_posix.h"
@@ -35,7 +35,6 @@
#include "src/core/lib/gpr/useful.h"
#include "src/core/lib/iomgr/ev_epoll1_linux.h"
#include "src/core/lib/iomgr/ev_epollex_linux.h"
-#include "src/core/lib/iomgr/ev_epollsig_linux.h"
#include "src/core/lib/iomgr/ev_poll_posix.h"
grpc_core::TraceFlag grpc_polling_trace(false,
@@ -59,7 +58,14 @@ grpc_core::DebugOnlyTraceFlag grpc_polling_api_trace(false, "polling_api");
/** Default poll() function - a pointer so that it can be overridden by some
* tests */
+#ifndef GPR_AIX
grpc_poll_function_type grpc_poll_function = poll;
+#else
+int aix_poll(struct pollfd fds[], nfds_t nfds, int timeout) {
+ return poll(fds, nfds, timeout);
+}
+grpc_poll_function_type grpc_poll_function = aix_poll;
+#endif
grpc_wakeup_fd grpc_global_wakeup_fd;
@@ -101,10 +107,28 @@ const grpc_event_engine_vtable* init_non_polling(bool explicit_request) {
}
} // namespace
-static const event_engine_factory g_factories[] = {
- {"epollex", grpc_init_epollex_linux}, {"epoll1", grpc_init_epoll1_linux},
- {"epollsig", grpc_init_epollsig_linux}, {"poll", grpc_init_poll_posix},
- {"poll-cv", grpc_init_poll_cv_posix}, {"none", init_non_polling},
+#define ENGINE_HEAD_CUSTOM "head_custom"
+#define ENGINE_TAIL_CUSTOM "tail_custom"
+
+// The global array of event-engine factories. Each entry is a pair with a name
+// and an event-engine generator function (nullptr if there is no generator
+// registered for this name). The middle entries are the engines predefined by
+// open-source gRPC. The head entries represent an opportunity for specific
+// high-priority custom pollers to be added by the initializer plugins of
+// custom-built gRPC libraries. The tail entries represent the same, but for
+// low-priority custom pollers. The actual poller selected is either the first
+// available one in the list if no specific poller is requested, or the first
+// specific poller that is requested by name in the GRPC_POLL_STRATEGY
+// environment variable if that variable is set (which should be a
+// comma-separated list of one or more event engine names)
+static event_engine_factory g_factories[] = {
+ {ENGINE_HEAD_CUSTOM, nullptr}, {ENGINE_HEAD_CUSTOM, nullptr},
+ {ENGINE_HEAD_CUSTOM, nullptr}, {ENGINE_HEAD_CUSTOM, nullptr},
+ {"epollex", grpc_init_epollex_linux}, {"epoll1", grpc_init_epoll1_linux},
+ {"poll", grpc_init_poll_posix}, {"poll-cv", grpc_init_poll_cv_posix},
+ {"none", init_non_polling}, {ENGINE_TAIL_CUSTOM, nullptr},
+ {ENGINE_TAIL_CUSTOM, nullptr}, {ENGINE_TAIL_CUSTOM, nullptr},
+ {ENGINE_TAIL_CUSTOM, nullptr},
};
static void add(const char* beg, const char* end, char*** ss, size_t* ns) {
@@ -138,7 +162,7 @@ static bool is(const char* want, const char* have) {
static void try_engine(const char* engine) {
for (size_t i = 0; i < GPR_ARRAY_SIZE(g_factories); i++) {
- if (is(engine, g_factories[i].name)) {
+ if (g_factories[i].factory != nullptr && is(engine, g_factories[i].name)) {
if ((g_event_engine = g_factories[i].factory(
0 == strcmp(engine, g_factories[i].name)))) {
g_poll_strategy_name = g_factories[i].name;
@@ -149,14 +173,32 @@ static void try_engine(const char* engine) {
}
}
-/* This should be used for testing purposes ONLY */
-void grpc_set_event_engine_test_only(
- const grpc_event_engine_vtable* ev_engine) {
- g_event_engine = ev_engine;
-}
+/* Call this before calling grpc_event_engine_init() */
+void grpc_register_event_engine_factory(const char* name,
+ event_engine_factory_fn factory,
+ bool add_at_head) {
+ const char* custom_match =
+ add_at_head ? ENGINE_HEAD_CUSTOM : ENGINE_TAIL_CUSTOM;
-const grpc_event_engine_vtable* grpc_get_event_engine_test_only() {
- return g_event_engine;
+ // Overwrite an existing registration if already registered
+ for (size_t i = 0; i < GPR_ARRAY_SIZE(g_factories); i++) {
+ if (0 == strcmp(name, g_factories[i].name)) {
+ g_factories[i].factory = factory;
+ return;
+ }
+ }
+
+ // Otherwise fill in an available custom slot
+ for (size_t i = 0; i < GPR_ARRAY_SIZE(g_factories); i++) {
+ if (0 == strcmp(g_factories[i].name, custom_match)) {
+ g_factories[i].name = name;
+ g_factories[i].factory = factory;
+ return;
+ }
+ }
+
+ // Otherwise fail
+ GPR_ASSERT(false);
}
/* Call this only after calling grpc_event_engine_init() */
@@ -193,10 +235,20 @@ void grpc_event_engine_shutdown(void) {
g_event_engine = nullptr;
}
-grpc_fd* grpc_fd_create(int fd, const char* name) {
- GRPC_POLLING_API_TRACE("fd_create(%d, %s)", fd, name);
- GRPC_FD_TRACE("fd_create(%d, %s)", fd, name);
- return g_event_engine->fd_create(fd, name);
+bool grpc_event_engine_can_track_errors(void) {
+/* Only track errors if platform supports errqueue. */
+#ifdef GRPC_LINUX_ERRQUEUE
+ return g_event_engine->can_track_err;
+#else
+ return false;
+#endif /* GRPC_LINUX_ERRQUEUE */
+}
+
+grpc_fd* grpc_fd_create(int fd, const char* name, bool track_err) {
+ GRPC_POLLING_API_TRACE("fd_create(%d, %s, %d)", fd, name, track_err);
+ GRPC_FD_TRACE("fd_create(%d, %s, %d)", fd, name, track_err);
+ return g_event_engine->fd_create(fd, name,
+ track_err && g_event_engine->can_track_err);
}
int grpc_fd_wrapped_fd(grpc_fd* fd) {
@@ -204,13 +256,12 @@ int grpc_fd_wrapped_fd(grpc_fd* fd) {
}
void grpc_fd_orphan(grpc_fd* fd, grpc_closure* on_done, int* release_fd,
- bool already_closed, const char* reason) {
- GRPC_POLLING_API_TRACE("fd_orphan(%d, %p, %p, %d, %s)",
- grpc_fd_wrapped_fd(fd), on_done, release_fd,
- already_closed, reason);
+ const char* reason) {
+ GRPC_POLLING_API_TRACE("fd_orphan(%d, %p, %p, %s)", grpc_fd_wrapped_fd(fd),
+ on_done, release_fd, reason);
GRPC_FD_TRACE("grpc_fd_orphan, fd:%d closed", grpc_fd_wrapped_fd(fd));
- g_event_engine->fd_orphan(fd, on_done, release_fd, already_closed, reason);
+ g_event_engine->fd_orphan(fd, on_done, release_fd, reason);
}
void grpc_fd_shutdown(grpc_fd* fd, grpc_error* why) {
@@ -231,6 +282,16 @@ void grpc_fd_notify_on_write(grpc_fd* fd, grpc_closure* closure) {
g_event_engine->fd_notify_on_write(fd, closure);
}
+void grpc_fd_notify_on_error(grpc_fd* fd, grpc_closure* closure) {
+ g_event_engine->fd_notify_on_error(fd, closure);
+}
+
+void grpc_fd_set_readable(grpc_fd* fd) { g_event_engine->fd_set_readable(fd); }
+
+void grpc_fd_set_writable(grpc_fd* fd) { g_event_engine->fd_set_writable(fd); }
+
+void grpc_fd_set_error(grpc_fd* fd) { g_event_engine->fd_set_error(fd); }
+
static size_t pollset_size(void) { return g_event_engine->pollset_size; }
static void pollset_init(grpc_pollset* pollset, gpr_mu** mu) {
@@ -251,10 +312,10 @@ static void pollset_destroy(grpc_pollset* pollset) {
static grpc_error* pollset_work(grpc_pollset* pollset,
grpc_pollset_worker** worker,
grpc_millis deadline) {
- GRPC_POLLING_API_TRACE("pollset_work(%p, %" PRIdPTR ") begin", pollset,
+ GRPC_POLLING_API_TRACE("pollset_work(%p, %" PRId64 ") begin", pollset,
deadline);
grpc_error* err = g_event_engine->pollset_work(pollset, worker, deadline);
- GRPC_POLLING_API_TRACE("pollset_work(%p, %" PRIdPTR ") end", pollset,
+ GRPC_POLLING_API_TRACE("pollset_work(%p, %" PRId64 ") end", pollset,
deadline);
return err;
}
@@ -334,4 +395,6 @@ void grpc_pollset_set_del_fd(grpc_pollset_set* pollset_set, grpc_fd* fd) {
g_event_engine->pollset_set_del_fd(pollset_set, fd);
}
-#endif // GRPC_POSIX_SOCKET
+void grpc_use_signal(int signum) {}
+
+#endif // GRPC_POSIX_SOCKET_EV
diff --git a/src/core/lib/iomgr/ev_posix.h b/src/core/lib/iomgr/ev_posix.h
index 82cbce9a7b..b8fb8f534b 100644
--- a/src/core/lib/iomgr/ev_posix.h
+++ b/src/core/lib/iomgr/ev_posix.h
@@ -41,16 +41,20 @@ typedef struct grpc_fd grpc_fd;
typedef struct grpc_event_engine_vtable {
size_t pollset_size;
+ bool can_track_err;
- grpc_fd* (*fd_create)(int fd, const char* name);
+ grpc_fd* (*fd_create)(int fd, const char* name, bool track_err);
int (*fd_wrapped_fd)(grpc_fd* fd);
void (*fd_orphan)(grpc_fd* fd, grpc_closure* on_done, int* release_fd,
- bool already_closed, const char* reason);
+ const char* reason);
void (*fd_shutdown)(grpc_fd* fd, grpc_error* why);
void (*fd_notify_on_read)(grpc_fd* fd, grpc_closure* closure);
void (*fd_notify_on_write)(grpc_fd* fd, grpc_closure* closure);
+ void (*fd_notify_on_error)(grpc_fd* fd, grpc_closure* closure);
+ void (*fd_set_readable)(grpc_fd* fd);
+ void (*fd_set_writable)(grpc_fd* fd);
+ void (*fd_set_error)(grpc_fd* fd);
bool (*fd_is_shutdown)(grpc_fd* fd);
- grpc_pollset* (*fd_get_read_notifier_pollset)(grpc_fd* fd);
void (*pollset_init)(grpc_pollset* pollset, gpr_mu** mu);
void (*pollset_shutdown)(grpc_pollset* pollset, grpc_closure* closure);
@@ -78,16 +82,31 @@ typedef struct grpc_event_engine_vtable {
void (*shutdown_engine)(void);
} grpc_event_engine_vtable;
+/* register a new event engine factory */
+void grpc_register_event_engine_factory(
+ const char* name, const grpc_event_engine_vtable* (*factory)(bool),
+ bool add_at_head);
+
void grpc_event_engine_init(void);
void grpc_event_engine_shutdown(void);
/* Return the name of the poll strategy */
const char* grpc_get_poll_strategy_name();
+/* Returns true if polling engine can track errors separately, false otherwise.
+ * If this is true, fd can be created with track_err set. After this, error
+ * events will be reported using fd_notify_on_error. If it is not set, errors
+ * will continue to be reported through fd_notify_on_read and
+ * fd_notify_on_write.
+ */
+bool grpc_event_engine_can_track_errors();
+
/* Create a wrapped file descriptor.
Requires fd is a non-blocking file descriptor.
+ \a track_err if true means that error events would be tracked separately
+ using grpc_fd_notify_on_error. Currently, valid only for linux systems.
This takes ownership of closing fd. */
-grpc_fd* grpc_fd_create(int fd, const char* name);
+grpc_fd* grpc_fd_create(int fd, const char* name, bool track_err);
/* Return the wrapped fd, or -1 if it has been released or closed. */
int grpc_fd_wrapped_fd(grpc_fd* fd);
@@ -100,7 +119,7 @@ int grpc_fd_wrapped_fd(grpc_fd* fd);
notify_on_write.
MUST NOT be called with a pollset lock taken */
void grpc_fd_orphan(grpc_fd* fd, grpc_closure* on_done, int* release_fd,
- bool already_closed, const char* reason);
+ const char* reason);
/* Has grpc_fd_shutdown been called on an fd? */
bool grpc_fd_is_shutdown(grpc_fd* fd);
@@ -126,8 +145,24 @@ void grpc_fd_notify_on_read(grpc_fd* fd, grpc_closure* closure);
/* Exactly the same semantics as above, except based on writable events. */
void grpc_fd_notify_on_write(grpc_fd* fd, grpc_closure* closure);
-/* Return the read notifier pollset from the fd */
-grpc_pollset* grpc_fd_get_read_notifier_pollset(grpc_fd* fd);
+/* Exactly the same semantics as above, except based on error events. track_err
+ * needs to have been set on grpc_fd_create */
+void grpc_fd_notify_on_error(grpc_fd* fd, grpc_closure* closure);
+
+/* Forcibly set the fd to be readable, resulting in the closure registered with
+ * grpc_fd_notify_on_read being invoked.
+ */
+void grpc_fd_set_readable(grpc_fd* fd);
+
+/* Forcibly set the fd to be writable, resulting in the closure registered with
+ * grpc_fd_notify_on_write being invoked.
+ */
+void grpc_fd_set_writable(grpc_fd* fd);
+
+/* Forcibly set the fd to have errored, resulting in the closure registered with
+ * grpc_fd_notify_on_error being invoked.
+ */
+void grpc_fd_set_error(grpc_fd* fd);
/* pollset_posix functions */
@@ -143,9 +178,4 @@ void grpc_pollset_set_del_fd(grpc_pollset_set* pollset_set, grpc_fd* fd);
typedef int (*grpc_poll_function_type)(struct pollfd*, nfds_t, int);
extern grpc_poll_function_type grpc_poll_function;
-/* WARNING: The following two functions should be used for testing purposes
- * ONLY */
-void grpc_set_event_engine_test_only(const grpc_event_engine_vtable*);
-const grpc_event_engine_vtable* grpc_get_event_engine_test_only();
-
#endif /* GRPC_CORE_LIB_IOMGR_EV_POSIX_H */
diff --git a/src/core/lib/iomgr/exec_ctx.cc b/src/core/lib/iomgr/exec_ctx.cc
index 2f544b20ab..d68fa0714b 100644
--- a/src/core/lib/iomgr/exec_ctx.cc
+++ b/src/core/lib/iomgr/exec_ctx.cc
@@ -53,24 +53,24 @@ static void exec_ctx_sched(grpc_closure* closure, grpc_error* error) {
static gpr_timespec g_start_time;
-static gpr_atm timespec_to_atm_round_down(gpr_timespec ts) {
+static grpc_millis timespec_to_millis_round_down(gpr_timespec ts) {
ts = gpr_time_sub(ts, g_start_time);
double x = GPR_MS_PER_SEC * static_cast<double>(ts.tv_sec) +
static_cast<double>(ts.tv_nsec) / GPR_NS_PER_MS;
if (x < 0) return 0;
- if (x > GPR_ATM_MAX) return GPR_ATM_MAX;
- return static_cast<gpr_atm>(x);
+ if (x > GRPC_MILLIS_INF_FUTURE) return GRPC_MILLIS_INF_FUTURE;
+ return static_cast<grpc_millis>(x);
}
-static gpr_atm timespec_to_atm_round_up(gpr_timespec ts) {
+static grpc_millis timespec_to_millis_round_up(gpr_timespec ts) {
ts = gpr_time_sub(ts, g_start_time);
double x = GPR_MS_PER_SEC * static_cast<double>(ts.tv_sec) +
static_cast<double>(ts.tv_nsec) / GPR_NS_PER_MS +
static_cast<double>(GPR_NS_PER_SEC - 1) /
static_cast<double>(GPR_NS_PER_SEC);
if (x < 0) return 0;
- if (x > GPR_ATM_MAX) return GPR_ATM_MAX;
- return static_cast<gpr_atm>(x);
+ if (x > GRPC_MILLIS_INF_FUTURE) return GRPC_MILLIS_INF_FUTURE;
+ return static_cast<grpc_millis>(x);
}
gpr_timespec grpc_millis_to_timespec(grpc_millis millis,
@@ -92,12 +92,12 @@ gpr_timespec grpc_millis_to_timespec(grpc_millis millis,
}
grpc_millis grpc_timespec_to_millis_round_down(gpr_timespec ts) {
- return timespec_to_atm_round_down(
+ return timespec_to_millis_round_down(
gpr_convert_clock_type(ts, g_start_time.clock_type));
}
grpc_millis grpc_timespec_to_millis_round_up(gpr_timespec ts) {
- return timespec_to_atm_round_up(
+ return timespec_to_millis_round_up(
gpr_convert_clock_type(ts, g_start_time.clock_type));
}
@@ -109,6 +109,12 @@ grpc_closure_scheduler* grpc_schedule_on_exec_ctx = &exec_ctx_scheduler;
namespace grpc_core {
GPR_TLS_CLASS_DEF(ExecCtx::exec_ctx_);
+// WARNING: for testing purposes only!
+void ExecCtx::TestOnlyGlobalInit(gpr_timespec new_val) {
+ g_start_time = new_val;
+ gpr_tls_init(&exec_ctx_);
+}
+
void ExecCtx::GlobalInit(void) {
g_start_time = gpr_now(GPR_CLOCK_MONOTONIC);
gpr_tls_init(&exec_ctx_);
@@ -138,7 +144,7 @@ bool ExecCtx::Flush() {
grpc_millis ExecCtx::Now() {
if (!now_is_valid_) {
- now_ = timespec_to_atm_round_down(gpr_now(GPR_CLOCK_MONOTONIC));
+ now_ = timespec_to_millis_round_down(gpr_now(GPR_CLOCK_MONOTONIC));
now_is_valid_ = true;
}
return now_;
diff --git a/src/core/lib/iomgr/exec_ctx.h b/src/core/lib/iomgr/exec_ctx.h
index 72d0ae58c1..f3528d527a 100644
--- a/src/core/lib/iomgr/exec_ctx.h
+++ b/src/core/lib/iomgr/exec_ctx.h
@@ -26,12 +26,13 @@
#include <grpc/support/log.h>
#include "src/core/lib/gpr/tls.h"
+#include "src/core/lib/gprpp/fork.h"
#include "src/core/lib/iomgr/closure.h"
-typedef gpr_atm grpc_millis;
+typedef int64_t grpc_millis;
-#define GRPC_MILLIS_INF_FUTURE GPR_ATM_MAX
-#define GRPC_MILLIS_INF_PAST GPR_ATM_MIN
+#define GRPC_MILLIS_INF_FUTURE INT64_MAX
+#define GRPC_MILLIS_INF_PAST INT64_MIN
/** A workqueue represents a list of work to be executed asynchronously.
Forward declared here to avoid a circular dependency with workqueue.h. */
@@ -44,6 +45,9 @@ typedef struct grpc_combiner grpc_combiner;
/* The exec_ctx's thread is (potentially) owned by a call or channel: care
should be given to not delete said call/channel from this exec_ctx */
#define GRPC_EXEC_CTX_FLAG_THREAD_RESOURCE_LOOP 2
+/* This exec ctx was initialized by an internal thread, and should not
+ be counted by fork handlers */
+#define GRPC_EXEC_CTX_FLAG_IS_INTERNAL_THREAD 4
extern grpc_closure_scheduler* grpc_schedule_on_exec_ctx;
@@ -79,30 +83,45 @@ namespace grpc_core {
* - Exactly one instance of ExecCtx must be created per thread. Instances must
* always be called exec_ctx.
* - Do not pass exec_ctx as a parameter to a function. Always access it using
- * grpc_core::ExecCtx::Get()
+ * grpc_core::ExecCtx::Get().
*/
class ExecCtx {
public:
/** Default Constructor */
- ExecCtx() : flags_(GRPC_EXEC_CTX_FLAG_IS_FINISHED) { Set(this); }
+ ExecCtx() : flags_(GRPC_EXEC_CTX_FLAG_IS_FINISHED) {
+ grpc_core::Fork::IncExecCtxCount();
+ Set(this);
+ }
/** Parameterised Constructor */
- ExecCtx(uintptr_t fl) : flags_(fl) { Set(this); }
+ ExecCtx(uintptr_t fl) : flags_(fl) {
+ if (!(GRPC_EXEC_CTX_FLAG_IS_INTERNAL_THREAD & flags_)) {
+ grpc_core::Fork::IncExecCtxCount();
+ }
+ Set(this);
+ }
/** Destructor */
virtual ~ExecCtx() {
flags_ |= GRPC_EXEC_CTX_FLAG_IS_FINISHED;
Flush();
Set(last_exec_ctx_);
+ if (!(GRPC_EXEC_CTX_FLAG_IS_INTERNAL_THREAD & flags_)) {
+ grpc_core::Fork::DecExecCtxCount();
+ }
}
/** Disallow copy and assignment operators */
ExecCtx(const ExecCtx&) = delete;
ExecCtx& operator=(const ExecCtx&) = delete;
- /** Return starting_cpu */
+ /** Return starting_cpu. This is only required for stats collection and is
+ * hence only defined if GRPC_COLLECT_STATS is enabled.
+ */
+#if defined(GRPC_COLLECT_STATS) || !defined(NDEBUG)
unsigned starting_cpu() const { return starting_cpu_; }
+#endif /* defined(GRPC_COLLECT_STATS) || !defined(NDEBUG) */
struct CombinerData {
/* currently active combiner: updated only via combiner.c */
@@ -128,12 +147,14 @@ class ExecCtx {
/** Flush any work that has been enqueued onto this grpc_exec_ctx.
* Caller must guarantee that no interfering locks are held.
- * Returns true if work was performed, false otherwise. */
+ * Returns true if work was performed, false otherwise.
+ */
bool Flush();
/** Returns true if we'd like to leave this execution context as soon as
-possible: useful for deciding whether to do something more or not depending
-on outside context */
+ * possible: useful for deciding whether to do something more or not
+ * depending on outside context.
+ */
bool IsReadyToFinish() {
if ((flags_ & GRPC_EXEC_CTX_FLAG_IS_FINISHED) == 0) {
if (CheckReadyToFinish()) {
@@ -147,12 +168,14 @@ on outside context */
}
/** Returns the stored current time relative to start if valid,
- * otherwise refreshes the stored time, sets it valid and returns the new
- * value */
+ * otherwise refreshes the stored time, sets it valid and returns the new
+ * value.
+ */
grpc_millis Now();
/** Invalidates the stored time value. A new time value will be set on calling
- * Now() */
+ * Now().
+ */
void InvalidateNow() { now_is_valid_ = false; }
/** To be used only by shutdown code in iomgr */
@@ -162,20 +185,22 @@ on outside context */
}
/** To be used only for testing.
- * Sets the now value
+ * Sets the now value.
*/
void TestOnlySetNow(grpc_millis new_val) {
now_ = new_val;
now_is_valid_ = true;
}
- /** Global initialization for ExecCtx. Called by iomgr */
+ static void TestOnlyGlobalInit(gpr_timespec new_val);
+
+ /** Global initialization for ExecCtx. Called by iomgr. */
static void GlobalInit(void);
- /** Global shutdown for ExecCtx. Called by iomgr */
+ /** Global shutdown for ExecCtx. Called by iomgr. */
static void GlobalShutdown(void) { gpr_tls_destroy(&exec_ctx_); }
- /** Gets pointer to current exec_ctx */
+ /** Gets pointer to current exec_ctx. */
static ExecCtx* Get() {
return reinterpret_cast<ExecCtx*>(gpr_tls_get(&exec_ctx_));
}
@@ -185,19 +210,22 @@ on outside context */
}
protected:
- /** Check if ready to finish */
+ /** Check if ready to finish. */
virtual bool CheckReadyToFinish() { return false; }
- /** Disallow delete on ExecCtx */
+ /** Disallow delete on ExecCtx. */
static void operator delete(void* p) { abort(); }
private:
- /** Set exec_ctx_ to exec_ctx */
+ /** Set exec_ctx_ to exec_ctx. */
grpc_closure_list closure_list_ = GRPC_CLOSURE_LIST_INIT;
CombinerData combiner_data_ = {nullptr, nullptr};
uintptr_t flags_;
+
+#if defined(GRPC_COLLECT_STATS) || !defined(NDEBUG)
unsigned starting_cpu_ = gpr_cpu_current_cpu();
+#endif /* defined(GRPC_COLLECT_STATS) || !defined(NDEBUG) */
bool now_is_valid_ = false;
grpc_millis now_ = 0;
diff --git a/src/core/lib/iomgr/executor.cc b/src/core/lib/iomgr/executor.cc
index f19f8cf20d..45d96b80eb 100644
--- a/src/core/lib/iomgr/executor.cc
+++ b/src/core/lib/iomgr/executor.cc
@@ -28,52 +28,49 @@
#include <grpc/support/sync.h>
#include "src/core/lib/debug/stats.h"
-#include "src/core/lib/gpr/spinlock.h"
#include "src/core/lib/gpr/tls.h"
#include "src/core/lib/gpr/useful.h"
-#include "src/core/lib/gprpp/thd.h"
+#include "src/core/lib/gprpp/memory.h"
#include "src/core/lib/iomgr/exec_ctx.h"
#define MAX_DEPTH 2
-typedef struct {
- gpr_mu mu;
- gpr_cv cv;
- grpc_closure_list elems;
- size_t depth;
- bool shutdown;
- bool queued_long_job;
- grpc_core::Thread thd;
-} thread_state;
-
-static thread_state* g_thread_state;
-static size_t g_max_threads;
-static gpr_atm g_cur_threads;
-static gpr_spinlock g_adding_thread_lock = GPR_SPINLOCK_STATIC_INITIALIZER;
+#define EXECUTOR_TRACE(format, ...) \
+ if (executor_trace.enabled()) { \
+ gpr_log(GPR_INFO, "EXECUTOR " format, __VA_ARGS__); \
+ }
-GPR_TLS_DECL(g_this_thread_state);
+#define EXECUTOR_TRACE0(str) \
+ if (executor_trace.enabled()) { \
+ gpr_log(GPR_INFO, "EXECUTOR " str); \
+ }
grpc_core::TraceFlag executor_trace(false, "executor");
-static void executor_thread(void* arg);
+GPR_TLS_DECL(g_this_thread_state);
+
+GrpcExecutor::GrpcExecutor(const char* name) : name_(name) {
+ adding_thread_lock_ = GPR_SPINLOCK_STATIC_INITIALIZER;
+ gpr_atm_rel_store(&num_threads_, 0);
+ max_threads_ = GPR_MAX(1, 2 * gpr_cpu_num_cores());
+}
-static size_t run_closures(grpc_closure_list list) {
+void GrpcExecutor::Init() { SetThreading(true); }
+
+size_t GrpcExecutor::RunClosures(const char* executor_name,
+ grpc_closure_list list) {
size_t n = 0;
grpc_closure* c = list.head;
while (c != nullptr) {
grpc_closure* next = c->next_data.next;
grpc_error* error = c->error_data.error;
- if (executor_trace.enabled()) {
-#ifndef NDEBUG
- gpr_log(GPR_DEBUG, "EXECUTOR: run %p [created by %s:%d]", c,
- c->file_created, c->line_created);
-#else
- gpr_log(GPR_INFO, "EXECUTOR: run %p", c);
-#endif
- }
#ifndef NDEBUG
+ EXECUTOR_TRACE("(%s) run %p [created by %s:%d]", executor_name, c,
+ c->file_created, c->line_created);
c->scheduled = false;
+#else
+ EXECUTOR_TRACE("(%s) run %p", executor_name, c);
#endif
c->cb(c->cb_arg, error);
GRPC_ERROR_UNREF(error);
@@ -85,217 +82,352 @@ static size_t run_closures(grpc_closure_list list) {
return n;
}
-bool grpc_executor_is_threaded() {
- return gpr_atm_no_barrier_load(&g_cur_threads) > 0;
+bool GrpcExecutor::IsThreaded() const {
+ return gpr_atm_acq_load(&num_threads_) > 0;
}
-void grpc_executor_set_threading(bool threading) {
- gpr_atm cur_threads = gpr_atm_no_barrier_load(&g_cur_threads);
+void GrpcExecutor::SetThreading(bool threading) {
+ gpr_atm curr_num_threads = gpr_atm_acq_load(&num_threads_);
+ EXECUTOR_TRACE("(%s) SetThreading(%d) begin", name_, threading);
+
if (threading) {
- if (cur_threads > 0) return;
- g_max_threads = GPR_MAX(1, 2 * gpr_cpu_num_cores());
- gpr_atm_no_barrier_store(&g_cur_threads, 1);
+ if (curr_num_threads > 0) {
+ EXECUTOR_TRACE("(%s) SetThreading(true). curr_num_threads == 0", name_);
+ return;
+ }
+
+ GPR_ASSERT(num_threads_ == 0);
+ gpr_atm_rel_store(&num_threads_, 1);
gpr_tls_init(&g_this_thread_state);
- g_thread_state = static_cast<thread_state*>(
- gpr_zalloc(sizeof(thread_state) * g_max_threads));
- for (size_t i = 0; i < g_max_threads; i++) {
- gpr_mu_init(&g_thread_state[i].mu);
- gpr_cv_init(&g_thread_state[i].cv);
- g_thread_state[i].thd = grpc_core::Thread();
- g_thread_state[i].elems = GRPC_CLOSURE_LIST_INIT;
+ thd_state_ = static_cast<ThreadState*>(
+ gpr_zalloc(sizeof(ThreadState) * max_threads_));
+
+ for (size_t i = 0; i < max_threads_; i++) {
+ gpr_mu_init(&thd_state_[i].mu);
+ gpr_cv_init(&thd_state_[i].cv);
+ thd_state_[i].id = i;
+ thd_state_[i].name = name_;
+ thd_state_[i].thd = grpc_core::Thread();
+ thd_state_[i].elems = GRPC_CLOSURE_LIST_INIT;
}
- g_thread_state[0].thd =
- grpc_core::Thread("grpc_executor", executor_thread, &g_thread_state[0]);
- g_thread_state[0].thd.Start();
- } else {
- if (cur_threads == 0) return;
- for (size_t i = 0; i < g_max_threads; i++) {
- gpr_mu_lock(&g_thread_state[i].mu);
- g_thread_state[i].shutdown = true;
- gpr_cv_signal(&g_thread_state[i].cv);
- gpr_mu_unlock(&g_thread_state[i].mu);
+ thd_state_[0].thd =
+ grpc_core::Thread(name_, &GrpcExecutor::ThreadMain, &thd_state_[0]);
+ thd_state_[0].thd.Start();
+ } else { // !threading
+ if (curr_num_threads == 0) {
+ EXECUTOR_TRACE("(%s) SetThreading(false). curr_num_threads == 0", name_);
+ return;
+ }
+
+ for (size_t i = 0; i < max_threads_; i++) {
+ gpr_mu_lock(&thd_state_[i].mu);
+ thd_state_[i].shutdown = true;
+ gpr_cv_signal(&thd_state_[i].cv);
+ gpr_mu_unlock(&thd_state_[i].mu);
}
- /* ensure no thread is adding a new thread... once this is past, then
- no thread will try to add a new one either (since shutdown is true) */
- gpr_spinlock_lock(&g_adding_thread_lock);
- gpr_spinlock_unlock(&g_adding_thread_lock);
- for (gpr_atm i = 0; i < g_cur_threads; i++) {
- g_thread_state[i].thd.Join();
+
+ /* Ensure no thread is adding a new thread. Once this is past, then no
+ * thread will try to add a new one either (since shutdown is true) */
+ gpr_spinlock_lock(&adding_thread_lock_);
+ gpr_spinlock_unlock(&adding_thread_lock_);
+
+ curr_num_threads = gpr_atm_no_barrier_load(&num_threads_);
+ for (gpr_atm i = 0; i < curr_num_threads; i++) {
+ thd_state_[i].thd.Join();
+ EXECUTOR_TRACE("(%s) Thread %" PRIdPTR " of %" PRIdPTR " joined", name_,
+ i + 1, curr_num_threads);
}
- gpr_atm_no_barrier_store(&g_cur_threads, 0);
- for (size_t i = 0; i < g_max_threads; i++) {
- gpr_mu_destroy(&g_thread_state[i].mu);
- gpr_cv_destroy(&g_thread_state[i].cv);
- run_closures(g_thread_state[i].elems);
+
+ gpr_atm_rel_store(&num_threads_, 0);
+ for (size_t i = 0; i < max_threads_; i++) {
+ gpr_mu_destroy(&thd_state_[i].mu);
+ gpr_cv_destroy(&thd_state_[i].cv);
+ RunClosures(thd_state_[i].name, thd_state_[i].elems);
}
- gpr_free(g_thread_state);
+
+ gpr_free(thd_state_);
gpr_tls_destroy(&g_this_thread_state);
}
-}
-void grpc_executor_init() {
- gpr_atm_no_barrier_store(&g_cur_threads, 0);
- grpc_executor_set_threading(true);
+ EXECUTOR_TRACE("(%s) SetThreading(%d) done", name_, threading);
}
-void grpc_executor_shutdown() { grpc_executor_set_threading(false); }
+void GrpcExecutor::Shutdown() { SetThreading(false); }
-static void executor_thread(void* arg) {
- thread_state* ts = static_cast<thread_state*>(arg);
- gpr_tls_set(&g_this_thread_state, (intptr_t)ts);
+void GrpcExecutor::ThreadMain(void* arg) {
+ ThreadState* ts = static_cast<ThreadState*>(arg);
+ gpr_tls_set(&g_this_thread_state, reinterpret_cast<intptr_t>(ts));
- grpc_core::ExecCtx exec_ctx(0);
+ grpc_core::ExecCtx exec_ctx(GRPC_EXEC_CTX_FLAG_IS_INTERNAL_THREAD);
size_t subtract_depth = 0;
for (;;) {
- if (executor_trace.enabled()) {
- gpr_log(GPR_INFO, "EXECUTOR[%d]: step (sub_depth=%" PRIdPTR ")",
- static_cast<int>(ts - g_thread_state), subtract_depth);
- }
+ EXECUTOR_TRACE("(%s) [%" PRIdPTR "]: step (sub_depth=%" PRIdPTR ")",
+ ts->name, ts->id, subtract_depth);
+
gpr_mu_lock(&ts->mu);
ts->depth -= subtract_depth;
+ // Wait for closures to be enqueued or for the executor to be shutdown
while (grpc_closure_list_empty(ts->elems) && !ts->shutdown) {
ts->queued_long_job = false;
gpr_cv_wait(&ts->cv, &ts->mu, gpr_inf_future(GPR_CLOCK_MONOTONIC));
}
+
if (ts->shutdown) {
- if (executor_trace.enabled()) {
- gpr_log(GPR_INFO, "EXECUTOR[%d]: shutdown",
- static_cast<int>(ts - g_thread_state));
- }
+ EXECUTOR_TRACE("(%s) [%" PRIdPTR "]: shutdown", ts->name, ts->id);
gpr_mu_unlock(&ts->mu);
break;
}
+
GRPC_STATS_INC_EXECUTOR_QUEUE_DRAINED();
- grpc_closure_list exec = ts->elems;
+ grpc_closure_list closures = ts->elems;
ts->elems = GRPC_CLOSURE_LIST_INIT;
gpr_mu_unlock(&ts->mu);
- if (executor_trace.enabled()) {
- gpr_log(GPR_INFO, "EXECUTOR[%d]: execute",
- static_cast<int>(ts - g_thread_state));
- }
+
+ EXECUTOR_TRACE("(%s) [%" PRIdPTR "]: execute", ts->name, ts->id);
grpc_core::ExecCtx::Get()->InvalidateNow();
- subtract_depth = run_closures(exec);
+ subtract_depth = RunClosures(ts->name, closures);
}
}
-static void executor_push(grpc_closure* closure, grpc_error* error,
- bool is_short) {
+void GrpcExecutor::Enqueue(grpc_closure* closure, grpc_error* error,
+ bool is_short) {
bool retry_push;
if (is_short) {
GRPC_STATS_INC_EXECUTOR_SCHEDULED_SHORT_ITEMS();
} else {
GRPC_STATS_INC_EXECUTOR_SCHEDULED_LONG_ITEMS();
}
+
do {
retry_push = false;
size_t cur_thread_count =
- static_cast<size_t>(gpr_atm_no_barrier_load(&g_cur_threads));
+ static_cast<size_t>(gpr_atm_acq_load(&num_threads_));
+
+ // If the number of threads is zero(i.e either the executor is not threaded
+ // or already shutdown), then queue the closure on the exec context itself
if (cur_thread_count == 0) {
- if (executor_trace.enabled()) {
#ifndef NDEBUG
- gpr_log(GPR_DEBUG, "EXECUTOR: schedule %p (created %s:%d) inline",
- closure, closure->file_created, closure->line_created);
+ EXECUTOR_TRACE("(%s) schedule %p (created %s:%d) inline", name_, closure,
+ closure->file_created, closure->line_created);
#else
- gpr_log(GPR_INFO, "EXECUTOR: schedule %p inline", closure);
+ EXECUTOR_TRACE("(%s) schedule %p inline", name_, closure);
#endif
- }
grpc_closure_list_append(grpc_core::ExecCtx::Get()->closure_list(),
closure, error);
return;
}
- thread_state* ts = (thread_state*)gpr_tls_get(&g_this_thread_state);
+
+ ThreadState* ts = (ThreadState*)gpr_tls_get(&g_this_thread_state);
if (ts == nullptr) {
- ts = &g_thread_state[GPR_HASH_POINTER(grpc_core::ExecCtx::Get(),
- cur_thread_count)];
+ ts = &thd_state_[GPR_HASH_POINTER(grpc_core::ExecCtx::Get(),
+ cur_thread_count)];
} else {
GRPC_STATS_INC_EXECUTOR_SCHEDULED_TO_SELF();
}
- thread_state* orig_ts = ts;
- bool try_new_thread;
+ ThreadState* orig_ts = ts;
+ bool try_new_thread = false;
+
for (;;) {
- if (executor_trace.enabled()) {
#ifndef NDEBUG
- gpr_log(
- GPR_DEBUG,
- "EXECUTOR: try to schedule %p (%s) (created %s:%d) to thread %d",
- closure, is_short ? "short" : "long", closure->file_created,
- closure->line_created, static_cast<int>(ts - g_thread_state));
+ EXECUTOR_TRACE(
+ "(%s) try to schedule %p (%s) (created %s:%d) to thread "
+ "%" PRIdPTR,
+ name_, closure, is_short ? "short" : "long", closure->file_created,
+ closure->line_created, ts->id);
#else
- gpr_log(GPR_INFO, "EXECUTOR: try to schedule %p (%s) to thread %d",
- closure, is_short ? "short" : "long",
- (int)(ts - g_thread_state));
+ EXECUTOR_TRACE("(%s) try to schedule %p (%s) to thread %" PRIdPTR, name_,
+ closure, is_short ? "short" : "long", ts->id);
#endif
- }
+
gpr_mu_lock(&ts->mu);
if (ts->queued_long_job) {
// if there's a long job queued, we never queue anything else to this
// queue (since long jobs can take 'infinite' time and we need to
- // guarantee no starvation)
- // ... spin through queues and try again
+ // guarantee no starvation). Spin through queues and try again
gpr_mu_unlock(&ts->mu);
- size_t idx = static_cast<size_t>(ts - g_thread_state);
- ts = &g_thread_state[(idx + 1) % cur_thread_count];
+ size_t idx = ts->id;
+ ts = &thd_state_[(idx + 1) % cur_thread_count];
if (ts == orig_ts) {
+ // We cycled through all the threads. Retry enqueue again by creating
+ // a new thread
+ //
+ // TODO (sreek): There is a potential issue here. We are
+ // unconditionally setting try_new_thread to true here. What if the
+ // executor is shutdown OR if cur_thread_count is already equal to
+ // max_threads ?
+ // (Fortunately, this is not an issue yet (as of july 2018) because
+ // there is only one instance of long job in gRPC and hence we will
+ // not hit this code path)
retry_push = true;
try_new_thread = true;
break;
}
- continue;
+
+ continue; // Try the next thread-state
}
+
+ // == Found the thread state (i.e thread) to enqueue this closure! ==
+
+ // Also, if this thread has been waiting for closures, wake it up.
+ // - If grpc_closure_list_empty() is true and the Executor is not
+ // shutdown, it means that the thread must be waiting in ThreadMain()
+ // - Note that gpr_cv_signal() won't immediately wakeup the thread. That
+ // happens after we release the mutex &ts->mu a few lines below
if (grpc_closure_list_empty(ts->elems) && !ts->shutdown) {
GRPC_STATS_INC_EXECUTOR_WAKEUP_INITIATED();
gpr_cv_signal(&ts->cv);
}
+
grpc_closure_list_append(&ts->elems, closure, error);
+
+ // If we already queued more than MAX_DEPTH number of closures on this
+ // thread, use this as a hint to create more threads
ts->depth++;
try_new_thread = ts->depth > MAX_DEPTH &&
- cur_thread_count < g_max_threads && !ts->shutdown;
- if (!is_short) ts->queued_long_job = true;
+ cur_thread_count < max_threads_ && !ts->shutdown;
+
+ ts->queued_long_job = !is_short;
+
gpr_mu_unlock(&ts->mu);
break;
}
- if (try_new_thread && gpr_spinlock_trylock(&g_adding_thread_lock)) {
- cur_thread_count =
- static_cast<size_t>(gpr_atm_no_barrier_load(&g_cur_threads));
- if (cur_thread_count < g_max_threads) {
- gpr_atm_no_barrier_store(&g_cur_threads, cur_thread_count + 1);
-
- g_thread_state[cur_thread_count].thd =
- grpc_core::Thread("grpc_executor", executor_thread,
- &g_thread_state[cur_thread_count]);
- g_thread_state[cur_thread_count].thd.Start();
+
+ if (try_new_thread && gpr_spinlock_trylock(&adding_thread_lock_)) {
+ cur_thread_count = static_cast<size_t>(gpr_atm_acq_load(&num_threads_));
+ if (cur_thread_count < max_threads_) {
+ // Increment num_threads (safe to do a store instead of a cas because we
+ // always increment num_threads under the 'adding_thread_lock')
+ gpr_atm_rel_store(&num_threads_, cur_thread_count + 1);
+
+ thd_state_[cur_thread_count].thd = grpc_core::Thread(
+ name_, &GrpcExecutor::ThreadMain, &thd_state_[cur_thread_count]);
+ thd_state_[cur_thread_count].thd.Start();
}
- gpr_spinlock_unlock(&g_adding_thread_lock);
+ gpr_spinlock_unlock(&adding_thread_lock_);
}
+
if (retry_push) {
GRPC_STATS_INC_EXECUTOR_PUSH_RETRIES();
}
} while (retry_push);
}
-static void executor_push_short(grpc_closure* closure, grpc_error* error) {
- executor_push(closure, error, true);
+static GrpcExecutor* executors[GRPC_NUM_EXECUTORS];
+
+void default_enqueue_short(grpc_closure* closure, grpc_error* error) {
+ executors[GRPC_DEFAULT_EXECUTOR]->Enqueue(closure, error,
+ true /* is_short */);
}
-static void executor_push_long(grpc_closure* closure, grpc_error* error) {
- executor_push(closure, error, false);
+void default_enqueue_long(grpc_closure* closure, grpc_error* error) {
+ executors[GRPC_DEFAULT_EXECUTOR]->Enqueue(closure, error,
+ false /* is_short */);
}
-static const grpc_closure_scheduler_vtable executor_vtable_short = {
- executor_push_short, executor_push_short, "executor"};
-static grpc_closure_scheduler executor_scheduler_short = {
- &executor_vtable_short};
+void resolver_enqueue_short(grpc_closure* closure, grpc_error* error) {
+ executors[GRPC_RESOLVER_EXECUTOR]->Enqueue(closure, error,
+ true /* is_short */);
+}
+
+void resolver_enqueue_long(grpc_closure* closure, grpc_error* error) {
+ executors[GRPC_RESOLVER_EXECUTOR]->Enqueue(closure, error,
+ false /* is_short */);
+}
+
+static const grpc_closure_scheduler_vtable
+ vtables_[GRPC_NUM_EXECUTORS][GRPC_NUM_EXECUTOR_JOB_TYPES] = {
+ {{&default_enqueue_short, &default_enqueue_short, "def-ex-short"},
+ {&default_enqueue_long, &default_enqueue_long, "def-ex-long"}},
+ {{&resolver_enqueue_short, &resolver_enqueue_short, "res-ex-short"},
+ {&resolver_enqueue_long, &resolver_enqueue_long, "res-ex-long"}}};
+
+static grpc_closure_scheduler
+ schedulers_[GRPC_NUM_EXECUTORS][GRPC_NUM_EXECUTOR_JOB_TYPES] = {
+ {{&vtables_[GRPC_DEFAULT_EXECUTOR][GRPC_EXECUTOR_SHORT]},
+ {&vtables_[GRPC_DEFAULT_EXECUTOR][GRPC_EXECUTOR_LONG]}},
+ {{&vtables_[GRPC_RESOLVER_EXECUTOR][GRPC_EXECUTOR_SHORT]},
+ {&vtables_[GRPC_RESOLVER_EXECUTOR][GRPC_EXECUTOR_LONG]}}};
+
+// grpc_executor_init() and grpc_executor_shutdown() functions are called in the
+// the grpc_init() and grpc_shutdown() code paths which are protected by a
+// global mutex. So it is okay to assume that these functions are thread-safe
+void grpc_executor_init() {
+ EXECUTOR_TRACE0("grpc_executor_init() enter");
+
+ // Return if grpc_executor_init() is already called earlier
+ if (executors[GRPC_DEFAULT_EXECUTOR] != nullptr) {
+ GPR_ASSERT(executors[GRPC_RESOLVER_EXECUTOR] != nullptr);
+ return;
+ }
+
+ executors[GRPC_DEFAULT_EXECUTOR] =
+ grpc_core::New<GrpcExecutor>("default-executor");
+ executors[GRPC_RESOLVER_EXECUTOR] =
+ grpc_core::New<GrpcExecutor>("resolver-executor");
-static const grpc_closure_scheduler_vtable executor_vtable_long = {
- executor_push_long, executor_push_long, "executor"};
-static grpc_closure_scheduler executor_scheduler_long = {&executor_vtable_long};
+ executors[GRPC_DEFAULT_EXECUTOR]->Init();
+ executors[GRPC_RESOLVER_EXECUTOR]->Init();
-grpc_closure_scheduler* grpc_executor_scheduler(
- grpc_executor_job_length length) {
- return length == GRPC_EXECUTOR_SHORT ? &executor_scheduler_short
- : &executor_scheduler_long;
+ EXECUTOR_TRACE0("grpc_executor_init() done");
+}
+
+grpc_closure_scheduler* grpc_executor_scheduler(GrpcExecutorType executor_type,
+ GrpcExecutorJobType job_type) {
+ return &schedulers_[executor_type][job_type];
+}
+
+grpc_closure_scheduler* grpc_executor_scheduler(GrpcExecutorJobType job_type) {
+ return grpc_executor_scheduler(GRPC_DEFAULT_EXECUTOR, job_type);
+}
+
+void grpc_executor_shutdown() {
+ EXECUTOR_TRACE0("grpc_executor_shutdown() enter");
+
+ // Return if grpc_executor_shutdown() is already called earlier
+ if (executors[GRPC_DEFAULT_EXECUTOR] == nullptr) {
+ GPR_ASSERT(executors[GRPC_RESOLVER_EXECUTOR] == nullptr);
+ return;
+ }
+
+ executors[GRPC_DEFAULT_EXECUTOR]->Shutdown();
+ executors[GRPC_RESOLVER_EXECUTOR]->Shutdown();
+
+ // Delete the executor objects.
+ //
+ // NOTE: It is important to call Shutdown() on all executors first before
+ // calling Delete() because it is possible for one executor (that is not
+ // shutdown yet) to call Enqueue() on a different executor which is already
+ // shutdown. This is legal and in such cases, the Enqueue() operation
+ // effectively "fails" and enqueues that closure on the calling thread's
+ // exec_ctx.
+ //
+ // By ensuring that all executors are shutdown first, we are also ensuring
+ // that no thread is active across all executors.
+
+ grpc_core::Delete<GrpcExecutor>(executors[GRPC_DEFAULT_EXECUTOR]);
+ grpc_core::Delete<GrpcExecutor>(executors[GRPC_RESOLVER_EXECUTOR]);
+ executors[GRPC_DEFAULT_EXECUTOR] = nullptr;
+ executors[GRPC_RESOLVER_EXECUTOR] = nullptr;
+
+ EXECUTOR_TRACE0("grpc_executor_shutdown() done");
+}
+
+bool grpc_executor_is_threaded(GrpcExecutorType executor_type) {
+ GPR_ASSERT(executor_type < GRPC_NUM_EXECUTORS);
+ return executors[executor_type]->IsThreaded();
+}
+
+bool grpc_executor_is_threaded() {
+ return grpc_executor_is_threaded(GRPC_DEFAULT_EXECUTOR);
+}
+
+void grpc_executor_set_threading(bool enable) {
+ EXECUTOR_TRACE("grpc_executor_set_threading(%d) called", enable);
+ for (int i = 0; i < GRPC_NUM_EXECUTORS; i++) {
+ executors[i]->SetThreading(enable);
+ }
}
diff --git a/src/core/lib/iomgr/executor.h b/src/core/lib/iomgr/executor.h
index 68d540af55..8829138c5f 100644
--- a/src/core/lib/iomgr/executor.h
+++ b/src/core/lib/iomgr/executor.h
@@ -21,30 +21,96 @@
#include <grpc/support/port_platform.h>
+#include "src/core/lib/gpr/spinlock.h"
+#include "src/core/lib/gprpp/thd.h"
#include "src/core/lib/iomgr/closure.h"
+typedef struct {
+ gpr_mu mu;
+ size_t id; // For debugging purposes
+ const char* name; // Thread state name
+ gpr_cv cv;
+ grpc_closure_list elems;
+ size_t depth; // Number of closures in the closure list
+ bool shutdown;
+ bool queued_long_job;
+ grpc_core::Thread thd;
+} ThreadState;
+
typedef enum {
- GRPC_EXECUTOR_SHORT,
- GRPC_EXECUTOR_LONG
-} grpc_executor_job_length;
+ GRPC_EXECUTOR_SHORT = 0,
+ GRPC_EXECUTOR_LONG,
+ GRPC_NUM_EXECUTOR_JOB_TYPES // Add new values above this
+} GrpcExecutorJobType;
-/** Initialize the global executor.
- *
- * This mechanism is meant to outsource work (grpc_closure instances) to a
- * thread, for those cases where blocking isn't an option but there isn't a
- * non-blocking solution available. */
-void grpc_executor_init();
+class GrpcExecutor {
+ public:
+ GrpcExecutor(const char* executor_name);
-grpc_closure_scheduler* grpc_executor_scheduler(grpc_executor_job_length);
+ void Init();
-/** Shutdown the executor, running all pending work as part of the call */
-void grpc_executor_shutdown();
+ /** Is the executor multi-threaded? */
+ bool IsThreaded() const;
-/** Is the executor multi-threaded? */
-bool grpc_executor_is_threaded();
+ /* Enable/disable threading - must be called after Init and Shutdown() */
+ void SetThreading(bool threading);
+
+ /** Shutdown the executor, running all pending work as part of the call */
+ void Shutdown();
+
+ /** Enqueue the closure onto the executor. is_short is true if the closure is
+ * a short job (i.e expected to not block and complete quickly) */
+ void Enqueue(grpc_closure* closure, grpc_error* error, bool is_short);
+
+ private:
+ static size_t RunClosures(const char* executor_name, grpc_closure_list list);
+ static void ThreadMain(void* arg);
+
+ const char* name_;
+ ThreadState* thd_state_;
+ size_t max_threads_;
+ gpr_atm num_threads_;
+ gpr_spinlock adding_thread_lock_;
+};
+
+// == Global executor functions ==
+
+typedef enum {
+ GRPC_DEFAULT_EXECUTOR = 0,
+ GRPC_RESOLVER_EXECUTOR,
-/* enable/disable threading - must be called after grpc_executor_init and before
- grpc_executor_shutdown */
+ GRPC_NUM_EXECUTORS // Add new values above this
+} GrpcExecutorType;
+
+// TODO(sreek): Currently we have two executors (available globally): The
+// default executor and the resolver executor.
+//
+// Some of the functions below operate on the DEFAULT executor only while some
+// operate of ALL the executors. This is a bit confusing and should be cleaned
+// up in future (where we make all the following functions take executor_type
+// and/or job_type)
+
+// Initialize ALL the executors
+void grpc_executor_init();
+
+// Shutdown ALL the executors
+void grpc_executor_shutdown();
+
+// Set the threading mode for ALL the executors
void grpc_executor_set_threading(bool enable);
+// Get the DEFAULT executor scheduler for the given job_type
+grpc_closure_scheduler* grpc_executor_scheduler(GrpcExecutorJobType job_type);
+
+// Get the executor scheduler for a given executor_type and a job_type
+grpc_closure_scheduler* grpc_executor_scheduler(GrpcExecutorType executor_type,
+ GrpcExecutorJobType job_type);
+
+// Return if a given executor is running in threaded mode (i.e if
+// grpc_executor_set_threading(true) was called previously on that executor)
+bool grpc_executor_is_threaded(GrpcExecutorType executor_type);
+
+// Return if the DEFAULT executor is threaded
+bool grpc_executor_is_threaded();
+
#endif /* GRPC_CORE_LIB_IOMGR_EXECUTOR_H */
diff --git a/src/core/lib/iomgr/fork_posix.cc b/src/core/lib/iomgr/fork_posix.cc
index 6c506eb5c9..e957bad73d 100644
--- a/src/core/lib/iomgr/fork_posix.cc
+++ b/src/core/lib/iomgr/fork_posix.cc
@@ -25,62 +25,86 @@
#include <string.h>
#include <grpc/fork.h>
+#include <grpc/grpc.h>
#include <grpc/support/log.h>
#include "src/core/lib/gpr/env.h"
-#include "src/core/lib/gpr/fork.h"
+#include "src/core/lib/gprpp/fork.h"
#include "src/core/lib/gprpp/thd.h"
#include "src/core/lib/iomgr/ev_posix.h"
#include "src/core/lib/iomgr/executor.h"
#include "src/core/lib/iomgr/timer_manager.h"
#include "src/core/lib/iomgr/wakeup_fd_posix.h"
-#include "src/core/lib/surface/init.h"
/*
* NOTE: FORKING IS NOT GENERALLY SUPPORTED, THIS IS ONLY INTENDED TO WORK
* AROUND VERY SPECIFIC USE CASES.
*/
+namespace {
+bool skipped_handler = true;
+bool registered_handlers = false;
+} // namespace
+
void grpc_prefork() {
- if (!grpc_fork_support_enabled()) {
+ grpc_core::ExecCtx exec_ctx;
+ skipped_handler = true;
+ if (!grpc_is_initialized()) {
+ return;
+ }
+ if (!grpc_core::Fork::Enabled()) {
gpr_log(GPR_ERROR,
"Fork support not enabled; try running with the "
"environment variable GRPC_ENABLE_FORK_SUPPORT=1");
return;
}
- if (grpc_is_initialized()) {
- grpc_core::ExecCtx exec_ctx;
- grpc_timer_manager_set_threading(false);
- grpc_executor_set_threading(false);
- grpc_core::ExecCtx::Get()->Flush();
- if (!grpc_core::Thread::AwaitAll(
- gpr_time_add(gpr_now(GPR_CLOCK_REALTIME),
- gpr_time_from_seconds(3, GPR_TIMESPAN)))) {
- gpr_log(GPR_ERROR, "gRPC thread still active! Cannot fork!");
- }
+ if (strcmp(grpc_get_poll_strategy_name(), "epoll1") != 0 &&
+ strcmp(grpc_get_poll_strategy_name(), "poll") != 0) {
+ gpr_log(GPR_ERROR,
+ "Fork support is only compatible with the epoll1 and poll polling "
+ "strategies");
}
+ if (!grpc_core::Fork::BlockExecCtx()) {
+ gpr_log(GPR_INFO,
+ "Other threads are currently calling into gRPC, skipping fork() "
+ "handlers");
+ return;
+ }
+ grpc_timer_manager_set_threading(false);
+ grpc_executor_set_threading(false);
+ grpc_core::ExecCtx::Get()->Flush();
+ grpc_core::Fork::AwaitThreads();
+ skipped_handler = false;
}
void grpc_postfork_parent() {
- if (grpc_is_initialized()) {
- grpc_timer_manager_set_threading(true);
+ if (!skipped_handler) {
+ grpc_core::Fork::AllowExecCtx();
grpc_core::ExecCtx exec_ctx;
+ grpc_timer_manager_set_threading(true);
grpc_executor_set_threading(true);
}
}
void grpc_postfork_child() {
- if (grpc_is_initialized()) {
- grpc_timer_manager_set_threading(true);
+ if (!skipped_handler) {
+ grpc_core::Fork::AllowExecCtx();
grpc_core::ExecCtx exec_ctx;
+ grpc_core::Fork::child_postfork_func reset_polling_engine =
+ grpc_core::Fork::GetResetChildPollingEngineFunc();
+ if (reset_polling_engine != nullptr) {
+ reset_polling_engine();
+ }
+ grpc_timer_manager_set_threading(true);
grpc_executor_set_threading(true);
}
}
void grpc_fork_handlers_auto_register() {
- if (grpc_fork_support_enabled()) {
+ if (grpc_core::Fork::Enabled() & !registered_handlers) {
#ifdef GRPC_POSIX_FORK_ALLOW_PTHREAD_ATFORK
pthread_atfork(grpc_prefork, grpc_postfork_parent, grpc_postfork_child);
+ registered_handlers = true;
#endif // GRPC_POSIX_FORK_ALLOW_PTHREAD_ATFORK
}
}
diff --git a/src/core/lib/gpr/fork.h b/src/core/lib/iomgr/internal_errqueue.cc
index 94c61bb836..99c22e9055 100644
--- a/src/core/lib/gpr/fork.h
+++ b/src/core/lib/iomgr/internal_errqueue.cc
@@ -1,6 +1,6 @@
/*
*
- * Copyright 2017 gRPC authors.
+ * Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,20 +16,21 @@
*
*/
-#ifndef GRPC_CORE_LIB_GPR_FORK_H
-#define GRPC_CORE_LIB_GPR_FORK_H
+#include <grpc/support/port_platform.h>
-/*
- * NOTE: FORKING IS NOT GENERALLY SUPPORTED, THIS IS ONLY INTENDED TO WORK
- * AROUND VERY SPECIFIC USE CASES.
- */
+#include "src/core/lib/iomgr/port.h"
-void grpc_fork_support_init(void);
+#include "src/core/lib/iomgr/internal_errqueue.h"
-int grpc_fork_support_enabled(void);
+#ifdef GRPC_POSIX_SOCKET_TCP
-// Test only: Must be called before grpc_init(), and overrides
-// environment variables/compile flags
-void grpc_enable_fork_support(int enable);
+bool kernel_supports_errqueue() {
+#ifdef LINUX_VERSION_CODE
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0)
+ return true;
+#endif /* LINUX_VERSION_CODE <= KERNEL_VERSION(4, 0, 0) */
+#endif /* LINUX_VERSION_CODE */
+ return false;
+}
-#endif /* GRPC_CORE_LIB_GPR_FORK_H */
+#endif /* GRPC_POSIX_SOCKET_TCP */
diff --git a/src/core/lib/iomgr/internal_errqueue.h b/src/core/lib/iomgr/internal_errqueue.h
new file mode 100644
index 0000000000..9d122808f9
--- /dev/null
+++ b/src/core/lib/iomgr/internal_errqueue.h
@@ -0,0 +1,83 @@
+/*
+ *
+ * Copyright 2018 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.
+ *
+ */
+
+/* This file contains constants defined in <linux/errqueue.h> and
+ * <linux/net_tstamp.h> so as to allow collecting network timestamps in the
+ * kernel. This file allows tcp_posix.cc to compile on platforms that do not
+ * have <linux/errqueue.h> and <linux/net_tstamp.h>.
+ */
+
+#ifndef GRPC_CORE_LIB_IOMGR_INTERNAL_ERRQUEUE_H
+#define GRPC_CORE_LIB_IOMGR_INTERNAL_ERRQUEUE_H
+
+#include <grpc/support/port_platform.h>
+
+#include "src/core/lib/iomgr/port.h"
+
+#ifdef GRPC_POSIX_SOCKET_TCP
+
+#include <sys/types.h>
+#include <time.h>
+
+#ifdef GRPC_LINUX_ERRQUEUE
+#include <linux/errqueue.h>
+#include <linux/net_tstamp.h>
+#include <sys/socket.h>
+#endif /* GRPC_LINUX_ERRQUEUE */
+
+namespace grpc_core {
+
+#ifdef GRPC_LINUX_ERRQUEUE
+
+/* Redefining scm_timestamping in the same way that <linux/errqueue.h> defines
+ * it, so that code compiles on systems that don't have it. */
+struct scm_timestamping {
+ struct timespec ts[3];
+};
+/* Also redefine timestamp types */
+/* The timestamp type for when the driver passed skb to NIC, or HW. */
+constexpr int SCM_TSTAMP_SND = 0;
+/* The timestamp type for when data entered the packet scheduler. */
+constexpr int SCM_TSTAMP_SCHED = 1;
+/* The timestamp type for when data acknowledged by peer. */
+constexpr int SCM_TSTAMP_ACK = 2;
+/* Redefine required constants from <linux/net_tstamp.h> */
+constexpr uint32_t SOF_TIMESTAMPING_TX_SOFTWARE = 1u << 1;
+constexpr uint32_t SOF_TIMESTAMPING_SOFTWARE = 1u << 4;
+constexpr uint32_t SOF_TIMESTAMPING_OPT_ID = 1u << 7;
+constexpr uint32_t SOF_TIMESTAMPING_TX_SCHED = 1u << 8;
+constexpr uint32_t SOF_TIMESTAMPING_TX_ACK = 1u << 9;
+constexpr uint32_t SOF_TIMESTAMPING_OPT_TSONLY = 1u << 11;
+
+constexpr uint32_t kTimestampingSocketOptions = SOF_TIMESTAMPING_SOFTWARE |
+ SOF_TIMESTAMPING_OPT_ID |
+ SOF_TIMESTAMPING_OPT_TSONLY;
+constexpr uint32_t kTimestampingRecordingOptions =
+ SOF_TIMESTAMPING_TX_SCHED | SOF_TIMESTAMPING_TX_SOFTWARE |
+ SOF_TIMESTAMPING_TX_ACK;
+#endif /* GRPC_LINUX_ERRQUEUE */
+
+/* Returns true if kernel is capable of supporting errqueue and timestamping.
+ * Currently allowing only linux kernels above 4.0.0
+ */
+bool kernel_supports_errqueue();
+} // namespace grpc_core
+
+#endif /* GRPC_POSIX_SOCKET_TCP */
+
+#endif /* GRPC_CORE_LIB_IOMGR_INTERNAL_ERRQUEUE_H */
diff --git a/src/core/lib/iomgr/iocp_windows.cc b/src/core/lib/iomgr/iocp_windows.cc
index ce77231036..ad325fe215 100644
--- a/src/core/lib/iomgr/iocp_windows.cc
+++ b/src/core/lib/iomgr/iocp_windows.cc
@@ -89,10 +89,15 @@ grpc_iocp_work_status grpc_iocp_work(grpc_millis deadline) {
} else {
abort();
}
- success = WSAGetOverlappedResult(socket->socket, &info->overlapped, &bytes,
- FALSE, &flags);
- info->bytes_transfered = bytes;
- info->wsa_error = success ? 0 : WSAGetLastError();
+ if (socket->shutdown_called) {
+ info->bytes_transfered = 0;
+ info->wsa_error = WSA_OPERATION_ABORTED;
+ } else {
+ success = WSAGetOverlappedResult(socket->socket, &info->overlapped, &bytes,
+ FALSE, &flags);
+ info->bytes_transfered = bytes;
+ info->wsa_error = success ? 0 : WSAGetLastError();
+ }
GPR_ASSERT(overlapped == &info->overlapped);
grpc_socket_become_ready(socket, info);
return GRPC_IOCP_WORK_WORK;
diff --git a/src/core/lib/iomgr/iomgr.cc b/src/core/lib/iomgr/iomgr.cc
index 468814eaee..46afda1774 100644
--- a/src/core/lib/iomgr/iomgr.cc
+++ b/src/core/lib/iomgr/iomgr.cc
@@ -70,6 +70,8 @@ static size_t count_objects(void) {
return n;
}
+size_t grpc_iomgr_count_objects_for_testing(void) { return count_objects(); }
+
static void dump_objects(const char* kind) {
grpc_iomgr_object* obj;
for (obj = g_root_object.next; obj != &g_root_object; obj = obj->next) {
diff --git a/src/core/lib/iomgr/iomgr.h b/src/core/lib/iomgr/iomgr.h
index e6d66e545c..537ef8a6ff 100644
--- a/src/core/lib/iomgr/iomgr.h
+++ b/src/core/lib/iomgr/iomgr.h
@@ -23,6 +23,8 @@
#include "src/core/lib/iomgr/port.h"
+#include <stdlib.h>
+
/** Initializes the iomgr. */
void grpc_iomgr_init();
@@ -33,4 +35,7 @@ void grpc_iomgr_start();
* exec_ctx. */
void grpc_iomgr_shutdown();
+/* Exposed only for testing */
+size_t grpc_iomgr_count_objects_for_testing();
+
#endif /* GRPC_CORE_LIB_IOMGR_IOMGR_H */
diff --git a/src/core/lib/iomgr/iomgr_posix.cc b/src/core/lib/iomgr/iomgr_posix.cc
index 66c9cb7ff7..ca7334c9a4 100644
--- a/src/core/lib/iomgr/iomgr_posix.cc
+++ b/src/core/lib/iomgr/iomgr_posix.cc
@@ -20,7 +20,7 @@
#include "src/core/lib/iomgr/port.h"
-#ifdef GRPC_POSIX_SOCKET
+#ifdef GRPC_POSIX_SOCKET_IOMGR
#include "src/core/lib/debug/trace.h"
#include "src/core/lib/iomgr/ev_posix.h"
@@ -64,4 +64,4 @@ void grpc_set_default_iomgr_platform() {
grpc_set_iomgr_platform_vtable(&vtable);
}
-#endif /* GRPC_POSIX_SOCKET */
+#endif /* GRPC_POSIX_SOCKET_IOMGR */
diff --git a/src/core/lib/iomgr/iomgr_posix_cfstream.cc b/src/core/lib/iomgr/iomgr_posix_cfstream.cc
new file mode 100644
index 0000000000..235a9e0712
--- /dev/null
+++ b/src/core/lib/iomgr/iomgr_posix_cfstream.cc
@@ -0,0 +1,75 @@
+/*
+ *
+ * Copyright 2015 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/iomgr/port.h"
+
+#ifdef GRPC_CFSTREAM_IOMGR
+
+#include "src/core/lib/debug/trace.h"
+#include "src/core/lib/iomgr/ev_posix.h"
+#include "src/core/lib/iomgr/iomgr_internal.h"
+#include "src/core/lib/iomgr/iomgr_posix.h"
+#include "src/core/lib/iomgr/resolve_address.h"
+#include "src/core/lib/iomgr/tcp_client.h"
+#include "src/core/lib/iomgr/tcp_posix.h"
+#include "src/core/lib/iomgr/tcp_server.h"
+#include "src/core/lib/iomgr/timer.h"
+
+static const char* grpc_cfstream_env_var = "grpc_cfstream";
+
+extern grpc_tcp_server_vtable grpc_posix_tcp_server_vtable;
+extern grpc_tcp_client_vtable grpc_posix_tcp_client_vtable;
+extern grpc_tcp_client_vtable grpc_cfstream_client_vtable;
+extern grpc_timer_vtable grpc_generic_timer_vtable;
+extern grpc_pollset_vtable grpc_posix_pollset_vtable;
+extern grpc_pollset_set_vtable grpc_posix_pollset_set_vtable;
+extern grpc_address_resolver_vtable grpc_posix_resolver_vtable;
+
+static void iomgr_platform_init(void) {
+ grpc_wakeup_fd_global_init();
+ grpc_event_engine_init();
+}
+
+static void iomgr_platform_flush(void) {}
+
+static void iomgr_platform_shutdown(void) {
+ grpc_event_engine_shutdown();
+ grpc_wakeup_fd_global_destroy();
+}
+
+static grpc_iomgr_platform_vtable vtable = {
+ iomgr_platform_init, iomgr_platform_flush, iomgr_platform_shutdown};
+
+void grpc_set_default_iomgr_platform() {
+ char* enable_cfstream = getenv(grpc_cfstream_env_var);
+ grpc_tcp_client_vtable* client_vtable = &grpc_posix_tcp_client_vtable;
+ if (enable_cfstream != nullptr && enable_cfstream[0] == '1') {
+ client_vtable = &grpc_cfstream_client_vtable;
+ }
+ grpc_set_tcp_client_impl(client_vtable);
+ grpc_set_tcp_server_impl(&grpc_posix_tcp_server_vtable);
+ grpc_set_timer_impl(&grpc_generic_timer_vtable);
+ grpc_set_pollset_vtable(&grpc_posix_pollset_vtable);
+ grpc_set_pollset_set_vtable(&grpc_posix_pollset_set_vtable);
+ grpc_set_resolver_impl(&grpc_posix_resolver_vtable);
+ grpc_set_iomgr_platform_vtable(&vtable);
+}
+
+#endif /* GRPC_CFSTREAM_IOMGR */
diff --git a/src/core/lib/iomgr/is_epollexclusive_available.cc b/src/core/lib/iomgr/is_epollexclusive_available.cc
index 036b77866f..8df6a66e6b 100644
--- a/src/core/lib/iomgr/is_epollexclusive_available.cc
+++ b/src/core/lib/iomgr/is_epollexclusive_available.cc
@@ -27,6 +27,7 @@
#include <grpc/support/log.h>
#include <errno.h>
+#include <sys/epoll.h>
#include <sys/eventfd.h>
#include <unistd.h>
diff --git a/src/core/lib/iomgr/lockfree_event.cc b/src/core/lib/iomgr/lockfree_event.cc
index 5b6b79fa91..085fea40a4 100644
--- a/src/core/lib/iomgr/lockfree_event.cc
+++ b/src/core/lib/iomgr/lockfree_event.cc
@@ -89,7 +89,11 @@ void LockfreeEvent::DestroyEvent() {
void LockfreeEvent::NotifyOn(grpc_closure* closure) {
while (true) {
- gpr_atm curr = gpr_atm_no_barrier_load(&state_);
+ /* This load needs to be an acquire load because this can be a shutdown
+ * error that we might need to reference. Adding acquire semantics makes
+ * sure that the shutdown error has been initialized properly before us
+ * referencing it. */
+ gpr_atm curr = gpr_atm_acq_load(&state_);
if (grpc_polling_trace.enabled()) {
gpr_log(GPR_ERROR, "LockfreeEvent::NotifyOn: %p curr=%p closure=%p", this,
(void*)curr, closure);
diff --git a/src/core/lib/iomgr/polling_entity.cc b/src/core/lib/iomgr/polling_entity.cc
index 9f164f65b0..dea07cae53 100644
--- a/src/core/lib/iomgr/polling_entity.cc
+++ b/src/core/lib/iomgr/polling_entity.cc
@@ -61,8 +61,11 @@ bool grpc_polling_entity_is_empty(const grpc_polling_entity* pollent) {
void grpc_polling_entity_add_to_pollset_set(grpc_polling_entity* pollent,
grpc_pollset_set* pss_dst) {
if (pollent->tag == GRPC_POLLS_POLLSET) {
- GPR_ASSERT(pollent->pollent.pollset != nullptr);
- grpc_pollset_set_add_pollset(pss_dst, pollent->pollent.pollset);
+ // CFStream does not use file destriptors. When CFStream is used, the fd
+ // pollset is possible to be null.
+ if (pollent->pollent.pollset != nullptr) {
+ grpc_pollset_set_add_pollset(pss_dst, pollent->pollent.pollset);
+ }
} else if (pollent->tag == GRPC_POLLS_POLLSET_SET) {
GPR_ASSERT(pollent->pollent.pollset_set != nullptr);
grpc_pollset_set_add_pollset_set(pss_dst, pollent->pollent.pollset_set);
@@ -75,8 +78,14 @@ void grpc_polling_entity_add_to_pollset_set(grpc_polling_entity* pollent,
void grpc_polling_entity_del_from_pollset_set(grpc_polling_entity* pollent,
grpc_pollset_set* pss_dst) {
if (pollent->tag == GRPC_POLLS_POLLSET) {
+#ifdef GRPC_CFSTREAM
+ if (pollent->pollent.pollset != nullptr) {
+ grpc_pollset_set_del_pollset(pss_dst, pollent->pollent.pollset);
+ }
+#else
GPR_ASSERT(pollent->pollent.pollset != nullptr);
grpc_pollset_set_del_pollset(pss_dst, pollent->pollent.pollset);
+#endif
} else if (pollent->tag == GRPC_POLLS_POLLSET_SET) {
GPR_ASSERT(pollent->pollent.pollset_set != nullptr);
grpc_pollset_set_del_pollset_set(pss_dst, pollent->pollent.pollset_set);
diff --git a/src/core/lib/iomgr/pollset_custom.cc b/src/core/lib/iomgr/pollset_custom.cc
index 04bd104055..70e8a4596f 100644
--- a/src/core/lib/iomgr/pollset_custom.cc
+++ b/src/core/lib/iomgr/pollset_custom.cc
@@ -69,7 +69,7 @@ static grpc_error* pollset_work(grpc_pollset* pollset,
GRPC_CUSTOM_IOMGR_ASSERT_SAME_THREAD();
gpr_mu_unlock(&pollset->mu);
grpc_millis now = grpc_core::ExecCtx::Get()->Now();
- size_t timeout = 0;
+ grpc_millis timeout = 0;
if (deadline > now) {
timeout = deadline - now;
}
@@ -77,7 +77,7 @@ static grpc_error* pollset_work(grpc_pollset* pollset,
// control back to the application
grpc_core::ExecCtx* curr = grpc_core::ExecCtx::Get();
grpc_core::ExecCtx::Set(nullptr);
- poller_vtable->poll(timeout);
+ poller_vtable->poll(static_cast<size_t>(timeout));
grpc_core::ExecCtx::Set(curr);
grpc_core::ExecCtx::Get()->InvalidateNow();
if (grpc_core::ExecCtx::Get()->HasWork()) {
diff --git a/src/core/lib/iomgr/port.h b/src/core/lib/iomgr/port.h
index a397012003..c8046b21dc 100644
--- a/src/core/lib/iomgr/port.h
+++ b/src/core/lib/iomgr/port.h
@@ -60,6 +60,11 @@
#define GRPC_HAVE_IP_PKTINFO 1
#define GRPC_HAVE_MSG_NOSIGNAL 1
#define GRPC_HAVE_UNIX_SOCKET 1
+#ifdef LINUX_VERSION_CODE
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0)
+#define GRPC_LINUX_ERRQUEUE 1
+#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0) */
+#endif /* LINUX_VERSION_CODE */
#define GRPC_LINUX_MULTIPOLL_WITH_EPOLL 1
#define GRPC_POSIX_FORK 1
#define GRPC_POSIX_HOST_NAME_MAX 1
@@ -77,6 +82,11 @@
#define GRPC_LINUX_SOCKETUTILS 1
#endif
#endif
+#ifdef LINUX_VERSION_CODE
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 37)
+#define GRPC_HAVE_TCP_USER_TIMEOUT
+#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 37) */
+#endif /* LINUX_VERSION_CODE */
#ifndef __GLIBC__
#define GRPC_LINUX_EPOLL 1
#define GRPC_LINUX_EPOLL_CREATE1 1
@@ -97,7 +107,26 @@
#define GRPC_MSG_IOVLEN_TYPE int
#define GRPC_POSIX_FORK 1
#define GRPC_POSIX_NO_SPECIAL_WAKEUP_FD 1
+#ifdef GRPC_CFSTREAM
+#define GRPC_CFSTREAM_IOMGR 1
+#define GRPC_CFSTREAM_CLIENT 1
+#define GRPC_CFSTREAM_ENDPOINT 1
+#define GRPC_POSIX_SOCKET_ARES_EV_DRIVER 1
+#define GRPC_POSIX_SOCKET_EV 1
+#define GRPC_POSIX_SOCKET_EV_EPOLL1 1
+#define GRPC_POSIX_SOCKET_EV_EPOLLEX 1
+#define GRPC_POSIX_SOCKET_EV_POLL 1
+#define GRPC_POSIX_SOCKET_RESOLVE_ADDRESS 1
+#define GRPC_POSIX_SOCKET_SOCKADDR 1
+#define GRPC_POSIX_SOCKET_SOCKET_FACTORY 1
+#define GRPC_POSIX_SOCKET_TCP 1
+#define GRPC_POSIX_SOCKET_TCP_CLIENT 1
+#define GRPC_POSIX_SOCKET_TCP_SERVER 1
+#define GRPC_POSIX_SOCKET_TCP_SERVER_UTILS_COMMON 1
+#define GRPC_POSIX_SOCKET_UTILS_COMMON 1
+#else
#define GRPC_POSIX_SOCKET 1
+#endif
#define GRPC_POSIX_SOCKETUTILS 1
#define GRPC_POSIX_SYSCONF 1
#define GRPC_POSIX_WAKEUP_FD 1
@@ -120,6 +149,18 @@
#define GRPC_POSIX_SOCKET 1
#define GRPC_POSIX_SOCKETUTILS 1
#define GRPC_POSIX_WAKEUP_FD 1
+#elif defined(GPR_SOLARIS)
+#define GRPC_HAVE_UNIX_SOCKET 1
+#define GRPC_POSIX_NO_SPECIAL_WAKEUP_FD 1
+#define GRPC_POSIX_SOCKET 1
+#define GRPC_POSIX_SOCKETUTILS 1
+#define GRPC_POSIX_WAKEUP_FD 1
+#elif defined(GPR_AIX)
+#define GRPC_HAVE_UNIX_SOCKET 1
+#define GRPC_POSIX_NO_SPECIAL_WAKEUP_FD 1
+#define GRPC_POSIX_SOCKET 1
+#define GRPC_POSIX_SOCKETUTILS 1
+#define GRPC_POSIX_WAKEUP_FD 1
#elif defined(GPR_NACL)
#define GRPC_HAVE_ARPA_NAMESER 1
#define GRPC_POSIX_NO_SPECIAL_WAKEUP_FD 1
@@ -131,12 +172,29 @@
#endif
#if defined(GRPC_POSIX_SOCKET) + defined(GRPC_WINSOCK_SOCKET) + \
- defined(GRPC_CUSTOM_SOCKET) != \
+ defined(GRPC_CUSTOM_SOCKET) + defined(GRPC_CFSTREAM) != \
1
#error \
"Must define exactly one of GRPC_POSIX_SOCKET, GRPC_WINSOCK_SOCKET, GRPC_CUSTOM_SOCKET"
#endif
+#ifdef GRPC_POSIX_SOCKET
+#define GRPC_POSIX_SOCKET_ARES_EV_DRIVER 1
+#define GRPC_POSIX_SOCKET_EV 1
+#define GRPC_POSIX_SOCKET_EV_EPOLLEX 1
+#define GRPC_POSIX_SOCKET_EV_POLL 1
+#define GRPC_POSIX_SOCKET_EV_EPOLL1 1
+#define GRPC_POSIX_SOCKET_IOMGR 1
+#define GRPC_POSIX_SOCKET_RESOLVE_ADDRESS 1
+#define GRPC_POSIX_SOCKET_SOCKADDR 1
+#define GRPC_POSIX_SOCKET_SOCKET_FACTORY 1
+#define GRPC_POSIX_SOCKET_TCP 1
+#define GRPC_POSIX_SOCKET_TCP_CLIENT 1
+#define GRPC_POSIX_SOCKET_TCP_SERVER 1
+#define GRPC_POSIX_SOCKET_TCP_SERVER_UTILS_COMMON 1
+#define GRPC_POSIX_SOCKET_UTILS_COMMON 1
+#endif
+
#if defined(GRPC_POSIX_HOST_NAME_MAX) && defined(GRPC_POSIX_SYSCONF)
#error "Cannot define both GRPC_POSIX_HOST_NAME_MAX and GRPC_POSIX_SYSCONF"
#endif
diff --git a/src/core/lib/iomgr/resolve_address.h b/src/core/lib/iomgr/resolve_address.h
index fe0d834582..6afe94a7a9 100644
--- a/src/core/lib/iomgr/resolve_address.h
+++ b/src/core/lib/iomgr/resolve_address.h
@@ -33,7 +33,7 @@
#include <ws2tcpip.h>
#endif
-#ifdef GRPC_POSIX_SOCKET
+#if defined(GRPC_POSIX_SOCKET) || defined(GRPC_CFSTREAM)
#include <sys/socket.h>
#endif
diff --git a/src/core/lib/iomgr/resolve_address_posix.cc b/src/core/lib/iomgr/resolve_address_posix.cc
index a82075542f..c285d7eca6 100644
--- a/src/core/lib/iomgr/resolve_address_posix.cc
+++ b/src/core/lib/iomgr/resolve_address_posix.cc
@@ -19,7 +19,7 @@
#include <grpc/support/port_platform.h>
#include "src/core/lib/iomgr/port.h"
-#ifdef GRPC_POSIX_SOCKET
+#ifdef GRPC_POSIX_SOCKET_RESOLVE_ADDRESS
#include "src/core/lib/iomgr/sockaddr.h"
@@ -166,8 +166,9 @@ static void posix_resolve_address(const char* name, const char* default_port,
grpc_closure* on_done,
grpc_resolved_addresses** addrs) {
request* r = static_cast<request*>(gpr_malloc(sizeof(request)));
- GRPC_CLOSURE_INIT(&r->request_closure, do_request_thread, r,
- grpc_executor_scheduler(GRPC_EXECUTOR_SHORT));
+ GRPC_CLOSURE_INIT(
+ &r->request_closure, do_request_thread, r,
+ grpc_executor_scheduler(GRPC_RESOLVER_EXECUTOR, GRPC_EXECUTOR_SHORT));
r->name = gpr_strdup(name);
r->default_port = gpr_strdup(default_port);
r->on_done = on_done;
diff --git a/src/core/lib/iomgr/resolve_address_windows.cc b/src/core/lib/iomgr/resolve_address_windows.cc
index 71c92615ad..3e977dca2d 100644
--- a/src/core/lib/iomgr/resolve_address_windows.cc
+++ b/src/core/lib/iomgr/resolve_address_windows.cc
@@ -151,8 +151,9 @@ static void windows_resolve_address(const char* name, const char* default_port,
grpc_closure* on_done,
grpc_resolved_addresses** addresses) {
request* r = (request*)gpr_malloc(sizeof(request));
- GRPC_CLOSURE_INIT(&r->request_closure, do_request_thread, r,
- grpc_executor_scheduler(GRPC_EXECUTOR_SHORT));
+ GRPC_CLOSURE_INIT(
+ &r->request_closure, do_request_thread, r,
+ grpc_executor_scheduler(GRPC_RESOLVER_EXECUTOR, GRPC_EXECUTOR_SHORT));
r->name = gpr_strdup(name);
r->default_port = gpr_strdup(default_port);
r->on_done = on_done;
diff --git a/src/core/lib/iomgr/resource_quota.cc b/src/core/lib/iomgr/resource_quota.cc
index 8cf4fe9928..b6fc7579f7 100644
--- a/src/core/lib/iomgr/resource_quota.cc
+++ b/src/core/lib/iomgr/resource_quota.cc
@@ -96,6 +96,9 @@ struct grpc_resource_user {
list, false otherwise */
bool added_to_free_pool;
+ /* The number of threads currently allocated to this resource user */
+ gpr_atm num_threads_allocated;
+
/* Reclaimers: index 0 is the benign reclaimer, 1 is the destructive reclaimer
*/
grpc_closure* reclaimers[2];
@@ -135,12 +138,33 @@ struct grpc_resource_quota {
gpr_atm last_size;
+ /* Mutex to protect max_threads and num_threads_allocated */
+ /* Note: We could have used gpr_atm for max_threads and num_threads_allocated
+ * and avoid having this mutex; but in that case, each invocation of the
+ * function grpc_resource_user_allocate_threads() would have had to do at
+ * least two atomic loads (for max_threads and num_threads_allocated) followed
+ * by a CAS (on num_threads_allocated).
+ * Moreover, we expect grpc_resource_user_allocate_threads() to be often
+ * called concurrently thereby increasing the chances of failing the CAS
+ * operation. This additional complexity is not worth the tiny perf gain we
+ * may (or may not) have by using atomics */
+ gpr_mu thread_count_mu;
+
+ /* Max number of threads allowed */
+ int max_threads;
+
+ /* Number of threads currently allocated via this resource_quota object */
+ int num_threads_allocated;
+
/* Has rq_step been scheduled to occur? */
bool step_scheduled;
+
/* Are we currently reclaiming memory */
bool reclaiming;
+
/* Closure around rq_step */
grpc_closure rq_step_closure;
+
/* Closure around rq_reclamation_done */
grpc_closure rq_reclamation_done_closure;
@@ -386,7 +410,7 @@ static bool rq_reclaim(grpc_resource_quota* resource_quota, bool destructive) {
resource_quota->debug_only_last_reclaimer_resource_user = resource_user;
resource_quota->debug_only_last_initiated_reclaimer = c;
resource_user->reclaimers[destructive] = nullptr;
- GRPC_CLOSURE_RUN(c, GRPC_ERROR_NONE);
+ GRPC_CLOSURE_SCHED(c, GRPC_ERROR_NONE);
return true;
}
@@ -524,6 +548,11 @@ static void ru_shutdown(void* ru, grpc_error* error) {
static void ru_destroy(void* ru, grpc_error* error) {
grpc_resource_user* resource_user = static_cast<grpc_resource_user*>(ru);
GPR_ASSERT(gpr_atm_no_barrier_load(&resource_user->refs) == 0);
+ // Free all the remaining thread quota
+ grpc_resource_user_free_threads(resource_user,
+ static_cast<int>(gpr_atm_no_barrier_load(
+ &resource_user->num_threads_allocated)));
+
for (int i = 0; i < GRPC_RULIST_COUNT; i++) {
rulist_remove(resource_user, static_cast<grpc_rulist>(i));
}
@@ -594,6 +623,9 @@ grpc_resource_quota* grpc_resource_quota_create(const char* name) {
resource_quota->free_pool = INT64_MAX;
resource_quota->size = INT64_MAX;
gpr_atm_no_barrier_store(&resource_quota->last_size, GPR_ATM_MAX);
+ gpr_mu_init(&resource_quota->thread_count_mu);
+ resource_quota->max_threads = INT_MAX;
+ resource_quota->num_threads_allocated = 0;
resource_quota->step_scheduled = false;
resource_quota->reclaiming = false;
gpr_atm_no_barrier_store(&resource_quota->memory_usage_estimation, 0);
@@ -616,6 +648,8 @@ grpc_resource_quota* grpc_resource_quota_create(const char* name) {
void grpc_resource_quota_unref_internal(grpc_resource_quota* resource_quota) {
if (gpr_unref(&resource_quota->refs)) {
+ // No outstanding thread quota
+ GPR_ASSERT(resource_quota->num_threads_allocated == 0);
GRPC_COMBINER_UNREF(resource_quota->combiner, "resource_quota");
gpr_free(resource_quota->name);
gpr_free(resource_quota);
@@ -647,6 +681,15 @@ double grpc_resource_quota_get_memory_pressure(
}
/* Public API */
+void grpc_resource_quota_set_max_threads(grpc_resource_quota* resource_quota,
+ int new_max_threads) {
+ GPR_ASSERT(new_max_threads >= 0);
+ gpr_mu_lock(&resource_quota->thread_count_mu);
+ resource_quota->max_threads = new_max_threads;
+ gpr_mu_unlock(&resource_quota->thread_count_mu);
+}
+
+/* Public API */
void grpc_resource_quota_resize(grpc_resource_quota* resource_quota,
size_t size) {
grpc_core::ExecCtx exec_ctx;
@@ -731,6 +774,7 @@ grpc_resource_user* grpc_resource_user_create(
grpc_closure_list_init(&resource_user->on_allocated);
resource_user->allocating = false;
resource_user->added_to_free_pool = false;
+ gpr_atm_no_barrier_store(&resource_user->num_threads_allocated, 0);
resource_user->reclaimers[0] = nullptr;
resource_user->reclaimers[1] = nullptr;
resource_user->new_reclaimers[0] = nullptr;
@@ -785,6 +829,40 @@ void grpc_resource_user_shutdown(grpc_resource_user* resource_user) {
}
}
+bool grpc_resource_user_allocate_threads(grpc_resource_user* resource_user,
+ int thread_count) {
+ GPR_ASSERT(thread_count >= 0);
+ bool is_success = false;
+ gpr_mu_lock(&resource_user->resource_quota->thread_count_mu);
+ grpc_resource_quota* rq = resource_user->resource_quota;
+ if (rq->num_threads_allocated + thread_count <= rq->max_threads) {
+ rq->num_threads_allocated += thread_count;
+ gpr_atm_no_barrier_fetch_add(&resource_user->num_threads_allocated,
+ thread_count);
+ is_success = true;
+ }
+ gpr_mu_unlock(&resource_user->resource_quota->thread_count_mu);
+ return is_success;
+}
+
+void grpc_resource_user_free_threads(grpc_resource_user* resource_user,
+ int thread_count) {
+ GPR_ASSERT(thread_count >= 0);
+ gpr_mu_lock(&resource_user->resource_quota->thread_count_mu);
+ grpc_resource_quota* rq = resource_user->resource_quota;
+ rq->num_threads_allocated -= thread_count;
+ int old_count = static_cast<int>(gpr_atm_no_barrier_fetch_add(
+ &resource_user->num_threads_allocated, -thread_count));
+ if (old_count < thread_count || rq->num_threads_allocated < 0) {
+ gpr_log(GPR_ERROR,
+ "Releasing more threads (%d) than currently allocated (rq threads: "
+ "%d, ru threads: %d)",
+ thread_count, rq->num_threads_allocated + thread_count, old_count);
+ abort();
+ }
+ gpr_mu_unlock(&resource_user->resource_quota->thread_count_mu);
+}
+
void grpc_resource_user_alloc(grpc_resource_user* resource_user, size_t size,
grpc_closure* optional_on_done) {
gpr_mu_lock(&resource_user->mu);
diff --git a/src/core/lib/iomgr/resource_quota.h b/src/core/lib/iomgr/resource_quota.h
index 937daf8728..7b0ed7417a 100644
--- a/src/core/lib/iomgr/resource_quota.h
+++ b/src/core/lib/iomgr/resource_quota.h
@@ -93,6 +93,22 @@ void grpc_resource_user_ref(grpc_resource_user* resource_user);
void grpc_resource_user_unref(grpc_resource_user* resource_user);
void grpc_resource_user_shutdown(grpc_resource_user* resource_user);
+/* Attempts to get quota from the resource_user to create 'thread_count' number
+ * of threads. Returns true if successful (i.e the caller is now free to create
+ * 'thread_count' number of threads) or false if quota is not available */
+bool grpc_resource_user_allocate_threads(grpc_resource_user* resource_user,
+ int thread_count);
+/* Releases 'thread_count' worth of quota back to the resource user. The quota
+ * should have been previously obtained successfully by calling
+ * grpc_resource_user_allocate_threads().
+ *
+ * Note: There need not be an exact one-to-one correspondence between
+ * grpc_resource_user_allocate_threads() and grpc_resource_user_free_threads()
+ * calls. The only requirement is that the number of threads allocated should
+ * all be eventually released */
+void grpc_resource_user_free_threads(grpc_resource_user* resource_user,
+ int thread_count);
+
/* Allocate from the resource user (and its quota).
If optional_on_done is NULL, then allocate immediately. This may push the
quota over-limit, at which point reclamation will kick in.
diff --git a/src/core/lib/iomgr/sockaddr_posix.h b/src/core/lib/iomgr/sockaddr_posix.h
index 5b18bbc465..3cedd9082d 100644
--- a/src/core/lib/iomgr/sockaddr_posix.h
+++ b/src/core/lib/iomgr/sockaddr_posix.h
@@ -23,7 +23,7 @@
#include "src/core/lib/iomgr/port.h"
-#ifdef GRPC_POSIX_SOCKET
+#ifdef GRPC_POSIX_SOCKET_SOCKADDR
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
diff --git a/src/core/lib/iomgr/socket_factory_posix.cc b/src/core/lib/iomgr/socket_factory_posix.cc
index 1d1e36c0e3..57137769c8 100644
--- a/src/core/lib/iomgr/socket_factory_posix.cc
+++ b/src/core/lib/iomgr/socket_factory_posix.cc
@@ -20,7 +20,7 @@
#include "src/core/lib/iomgr/port.h"
-#ifdef GRPC_POSIX_SOCKET
+#ifdef GRPC_POSIX_SOCKET_SOCKET_FACTORY
#include "src/core/lib/channel/channel_args.h"
#include "src/core/lib/gpr/useful.h"
diff --git a/src/core/lib/iomgr/socket_mutator.cc b/src/core/lib/iomgr/socket_mutator.cc
index b9b8eaf4ad..a448c9f61c 100644
--- a/src/core/lib/iomgr/socket_mutator.cc
+++ b/src/core/lib/iomgr/socket_mutator.cc
@@ -57,7 +57,7 @@ int grpc_socket_mutator_compare(grpc_socket_mutator* a,
void grpc_socket_mutator_unref(grpc_socket_mutator* mutator) {
if (gpr_unref(&mutator->refcount)) {
- mutator->vtable->destory(mutator);
+ mutator->vtable->destroy(mutator);
}
}
diff --git a/src/core/lib/iomgr/socket_mutator.h b/src/core/lib/iomgr/socket_mutator.h
index 6c7781c51d..8742a3ba61 100644
--- a/src/core/lib/iomgr/socket_mutator.h
+++ b/src/core/lib/iomgr/socket_mutator.h
@@ -33,7 +33,7 @@ typedef struct {
/** Compare socket mutator \a a and \a b */
int (*compare)(grpc_socket_mutator* a, grpc_socket_mutator* b);
/** Destroys the socket mutator instance */
- void (*destory)(grpc_socket_mutator* mutator);
+ void (*destroy)(grpc_socket_mutator* mutator);
} grpc_socket_mutator_vtable;
/** The Socket Mutator interface allows changes on socket options */
diff --git a/src/core/lib/iomgr/socket_utils.h b/src/core/lib/iomgr/socket_utils.h
index cf1a7be648..14bb081e93 100644
--- a/src/core/lib/iomgr/socket_utils.h
+++ b/src/core/lib/iomgr/socket_utils.h
@@ -23,12 +23,21 @@
#include <stddef.h>
+// TODO(juanlishen): The following functions might be simple enough to implement
+// ourselves, so that they don't cause any portability hassle.
+
/* A wrapper for htons on POSIX and Windows */
uint16_t grpc_htons(uint16_t hostshort);
/* A wrapper for ntohs on POSIX and WINDOWS */
uint16_t grpc_ntohs(uint16_t netshort);
+/* A wrapper for htonl on POSIX and Windows */
+uint32_t grpc_htonl(uint32_t hostlong);
+
+/* A wrapper for ntohl on POSIX and WINDOWS */
+uint32_t grpc_ntohl(uint32_t netlong);
+
/* A wrapper for inet_pton on POSIX and WINDOWS */
int grpc_inet_pton(int af, const char* src, void* dst);
diff --git a/src/core/lib/iomgr/socket_utils_common_posix.cc b/src/core/lib/iomgr/socket_utils_common_posix.cc
index 04a1767731..50674b0845 100644
--- a/src/core/lib/iomgr/socket_utils_common_posix.cc
+++ b/src/core/lib/iomgr/socket_utils_common_posix.cc
@@ -20,7 +20,7 @@
#include "src/core/lib/iomgr/port.h"
-#ifdef GRPC_POSIX_SOCKET
+#ifdef GRPC_POSIX_SOCKET_UTILS_COMMON
#include "src/core/lib/iomgr/socket_utils.h"
#include "src/core/lib/iomgr/socket_utils_posix.h"
@@ -41,6 +41,7 @@
#include <grpc/support/log.h>
#include <grpc/support/sync.h>
+#include "src/core/lib/channel/channel_args.h"
#include "src/core/lib/gpr/host_port.h"
#include "src/core/lib/gpr/string.h"
#include "src/core/lib/iomgr/sockaddr.h"
@@ -222,6 +223,95 @@ grpc_error* grpc_set_socket_low_latency(int fd, int low_latency) {
return GRPC_ERROR_NONE;
}
+/* The default values for TCP_USER_TIMEOUT are currently configured to be in
+ * line with the default values of KEEPALIVE_TIMEOUT as proposed in
+ * https://github.com/grpc/proposal/blob/master/A18-tcp-user-timeout.md */
+#define DEFAULT_CLIENT_TCP_USER_TIMEOUT_MS 20000 /* 20 seconds */
+#define DEFAULT_SERVER_TCP_USER_TIMEOUT_MS 20000 /* 20 seconds */
+
+static int g_default_client_tcp_user_timeout_ms =
+ DEFAULT_CLIENT_TCP_USER_TIMEOUT_MS;
+static int g_default_server_tcp_user_timeout_ms =
+ DEFAULT_SERVER_TCP_USER_TIMEOUT_MS;
+static bool g_default_client_tcp_user_timeout_enabled = false;
+static bool g_default_server_tcp_user_timeout_enabled = true;
+
+void config_default_tcp_user_timeout(bool enable, int timeout, bool is_client) {
+ if (is_client) {
+ g_default_client_tcp_user_timeout_enabled = enable;
+ if (timeout > 0) {
+ g_default_client_tcp_user_timeout_ms = timeout;
+ }
+ } else {
+ g_default_server_tcp_user_timeout_enabled = enable;
+ if (timeout > 0) {
+ g_default_server_tcp_user_timeout_ms = timeout;
+ }
+ }
+}
+
+/* Set TCP_USER_TIMEOUT */
+grpc_error* grpc_set_socket_tcp_user_timeout(
+ int fd, const grpc_channel_args* channel_args, bool is_client) {
+#ifdef GRPC_HAVE_TCP_USER_TIMEOUT
+ bool enable;
+ int timeout;
+ if (is_client) {
+ enable = g_default_client_tcp_user_timeout_enabled;
+ timeout = g_default_client_tcp_user_timeout_ms;
+ } else {
+ enable = g_default_server_tcp_user_timeout_enabled;
+ timeout = g_default_server_tcp_user_timeout_ms;
+ }
+ if (channel_args) {
+ for (unsigned int i = 0; i < channel_args->num_args; i++) {
+ if (0 == strcmp(channel_args->args[i].key, GRPC_ARG_KEEPALIVE_TIME_MS)) {
+ const int value = grpc_channel_arg_get_integer(
+ &channel_args->args[i], grpc_integer_options{0, 1, INT_MAX});
+ /* Continue using default if value is 0 */
+ if (value == 0) {
+ continue;
+ }
+ /* Disable if value is INT_MAX */
+ enable = value != INT_MAX;
+ } else if (0 == strcmp(channel_args->args[i].key,
+ GRPC_ARG_KEEPALIVE_TIMEOUT_MS)) {
+ const int value = grpc_channel_arg_get_integer(
+ &channel_args->args[i], grpc_integer_options{0, 1, INT_MAX});
+ /* Continue using default if value is 0 */
+ if (value == 0) {
+ continue;
+ }
+ timeout = value;
+ }
+ }
+ }
+ if (enable) {
+ extern grpc_core::TraceFlag grpc_tcp_trace;
+ if (grpc_tcp_trace.enabled()) {
+ gpr_log(GPR_INFO, "Enabling TCP_USER_TIMEOUT with a timeout of %d ms",
+ timeout);
+ }
+ int newval;
+ socklen_t len = sizeof(newval);
+ if (0 != setsockopt(fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &timeout,
+ sizeof(timeout))) {
+ return GRPC_OS_ERROR(errno, "setsockopt(TCP_USER_TIMEOUT)");
+ }
+ if (0 != getsockopt(fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &newval, &len)) {
+ return GRPC_OS_ERROR(errno, "getsockopt(TCP_USER_TIMEOUT)");
+ }
+ if (newval != timeout) {
+ return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+ "Failed to set TCP_USER_TIMEOUT");
+ }
+ }
+#else
+ gpr_log(GPR_INFO, "TCP_USER_TIMEOUT not supported for this platform");
+#endif /* GRPC_HAVE_TCP_USER_TIMEOUT */
+ return GRPC_ERROR_NONE;
+}
+
/* set a socket using a grpc_socket_mutator */
grpc_error* grpc_set_socket_with_mutator(int fd, grpc_socket_mutator* mutator) {
GPR_ASSERT(mutator);
@@ -339,6 +429,10 @@ uint16_t grpc_htons(uint16_t hostshort) { return htons(hostshort); }
uint16_t grpc_ntohs(uint16_t netshort) { return ntohs(netshort); }
+uint32_t grpc_htonl(uint32_t hostlong) { return htonl(hostlong); }
+
+uint32_t grpc_ntohl(uint32_t netlong) { return ntohl(netlong); }
+
int grpc_inet_pton(int af, const char* src, void* dst) {
return inet_pton(af, src, dst);
}
diff --git a/src/core/lib/iomgr/socket_utils_posix.h b/src/core/lib/iomgr/socket_utils_posix.h
index b3fd58a530..71a304dc4e 100644
--- a/src/core/lib/iomgr/socket_utils_posix.h
+++ b/src/core/lib/iomgr/socket_utils_posix.h
@@ -53,6 +53,13 @@ grpc_error* grpc_set_socket_low_latency(int fd, int low_latency);
/* set SO_REUSEPORT */
grpc_error* grpc_set_socket_reuse_port(int fd, int reuse);
+/* Configure the default values for TCP_USER_TIMEOUT */
+void config_default_tcp_user_timeout(bool enable, int timeout, bool is_client);
+
+/* Set TCP_USER_TIMEOUT */
+grpc_error* grpc_set_socket_tcp_user_timeout(
+ int fd, const grpc_channel_args* channel_args, bool is_client);
+
/* Returns true if this system can create AF_INET6 sockets bound to ::1.
The value is probed once, and cached for the life of the process.
diff --git a/src/core/lib/iomgr/socket_utils_uv.cc b/src/core/lib/iomgr/socket_utils_uv.cc
index 7eba40c46b..b5f96b52df 100644
--- a/src/core/lib/iomgr/socket_utils_uv.cc
+++ b/src/core/lib/iomgr/socket_utils_uv.cc
@@ -33,6 +33,10 @@ uint16_t grpc_htons(uint16_t hostshort) { return htons(hostshort); }
uint16_t grpc_ntohs(uint16_t netshort) { return ntohs(netshort); }
+uint32_t grpc_htonl(uint32_t hostlong) { return htonl(hostlong); }
+
+uint32_t grpc_ntohl(uint32_t netlong) { return ntohl(netlong); }
+
int grpc_inet_pton(int af, const char* src, void* dst) {
return inet_pton(af, src, dst);
}
diff --git a/src/core/lib/iomgr/socket_utils_windows.cc b/src/core/lib/iomgr/socket_utils_windows.cc
index 3e7b5b812d..9137ab98e6 100644
--- a/src/core/lib/iomgr/socket_utils_windows.cc
+++ b/src/core/lib/iomgr/socket_utils_windows.cc
@@ -31,6 +31,10 @@ uint16_t grpc_htons(uint16_t hostshort) { return htons(hostshort); }
uint16_t grpc_ntohs(uint16_t netshort) { return ntohs(netshort); }
+uint32_t grpc_htonl(uint32_t hostlong) { return htonl(hostlong); }
+
+uint32_t grpc_ntohl(uint32_t netlong) { return ntohl(netlong); }
+
int grpc_inet_pton(int af, const char* src, void* dst) {
return inet_pton(af, src, dst);
}
diff --git a/src/core/lib/iomgr/socket_windows.cc b/src/core/lib/iomgr/socket_windows.cc
index 2e23409582..999c6646ad 100644
--- a/src/core/lib/iomgr/socket_windows.cc
+++ b/src/core/lib/iomgr/socket_windows.cc
@@ -36,6 +36,7 @@
#include "src/core/lib/iomgr/iomgr_internal.h"
#include "src/core/lib/iomgr/pollset.h"
#include "src/core/lib/iomgr/pollset_windows.h"
+#include "src/core/lib/iomgr/sockaddr_windows.h"
#include "src/core/lib/iomgr/socket_windows.h"
grpc_winsocket* grpc_winsocket_create(SOCKET socket, const char* name) {
@@ -51,6 +52,10 @@ grpc_winsocket* grpc_winsocket_create(SOCKET socket, const char* name) {
return r;
}
+SOCKET grpc_winsocket_wrapped_socket(grpc_winsocket* socket) {
+ return socket->socket;
+}
+
/* Schedule a shutdown of the socket operations. Will call the pending
operations to abort them. We need to do that this way because of the
various callsites of that function, which happens to be in various
@@ -148,4 +153,32 @@ void grpc_socket_become_ready(grpc_winsocket* socket,
if (should_destroy) destroy(socket);
}
+static gpr_once g_probe_ipv6_once = GPR_ONCE_INIT;
+static bool g_ipv6_loopback_available = false;
+
+static void probe_ipv6_once(void) {
+ SOCKET s = socket(AF_INET6, SOCK_STREAM, 0);
+ g_ipv6_loopback_available = 0;
+ if (s == INVALID_SOCKET) {
+ gpr_log(GPR_INFO, "Disabling AF_INET6 sockets because socket() failed.");
+ } else {
+ grpc_sockaddr_in6 addr;
+ memset(&addr, 0, sizeof(addr));
+ addr.sin6_family = AF_INET6;
+ addr.sin6_addr.s6_addr[15] = 1; /* [::1]:0 */
+ if (bind(s, reinterpret_cast<grpc_sockaddr*>(&addr), sizeof(addr)) == 0) {
+ g_ipv6_loopback_available = 1;
+ } else {
+ gpr_log(GPR_INFO,
+ "Disabling AF_INET6 sockets because ::1 is not available.");
+ }
+ closesocket(s);
+ }
+}
+
+int grpc_ipv6_loopback_available(void) {
+ gpr_once_init(&g_probe_ipv6_once, probe_ipv6_once);
+ return g_ipv6_loopback_available;
+}
+
#endif /* GRPC_WINSOCK_SOCKET */
diff --git a/src/core/lib/iomgr/socket_windows.h b/src/core/lib/iomgr/socket_windows.h
index 7bd01eded5..46d7d58356 100644
--- a/src/core/lib/iomgr/socket_windows.h
+++ b/src/core/lib/iomgr/socket_windows.h
@@ -92,6 +92,8 @@ typedef struct grpc_winsocket {
it will be responsible for closing it. */
grpc_winsocket* grpc_winsocket_create(SOCKET socket, const char* name);
+SOCKET grpc_winsocket_wrapped_socket(grpc_winsocket* socket);
+
/* Initiate an asynchronous shutdown of the socket. Will call off any pending
operation to cancel them. */
void grpc_winsocket_shutdown(grpc_winsocket* socket);
@@ -108,6 +110,10 @@ void grpc_socket_notify_on_read(grpc_winsocket* winsocket,
void grpc_socket_become_ready(grpc_winsocket* winsocket,
grpc_winsocket_callback_info* ci);
+/* Returns true if this system can create AF_INET6 sockets bound to ::1.
+ The value is probed once, and cached for the life of the process. */
+int grpc_ipv6_loopback_available(void);
+
#endif
#endif /* GRPC_CORE_LIB_IOMGR_SOCKET_WINDOWS_H */
diff --git a/src/core/lib/iomgr/tcp_client_cfstream.cc b/src/core/lib/iomgr/tcp_client_cfstream.cc
new file mode 100644
index 0000000000..4b21322d74
--- /dev/null
+++ b/src/core/lib/iomgr/tcp_client_cfstream.cc
@@ -0,0 +1,216 @@
+
+/*
+ *
+ * Copyright 2018 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/iomgr/port.h"
+
+#ifdef GRPC_CFSTREAM_CLIENT
+
+#include <CoreFoundation/CoreFoundation.h>
+
+#include <string.h>
+
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+#include <grpc/support/sync.h>
+
+#include <netinet/in.h>
+
+#include "src/core/lib/channel/channel_args.h"
+#include "src/core/lib/gpr/host_port.h"
+#include "src/core/lib/iomgr/cfstream_handle.h"
+#include "src/core/lib/iomgr/closure.h"
+#include "src/core/lib/iomgr/endpoint_cfstream.h"
+#include "src/core/lib/iomgr/error.h"
+#include "src/core/lib/iomgr/error_cfstream.h"
+#include "src/core/lib/iomgr/sockaddr_utils.h"
+#include "src/core/lib/iomgr/tcp_client.h"
+#include "src/core/lib/iomgr/timer.h"
+
+extern grpc_core::TraceFlag grpc_tcp_trace;
+
+typedef struct CFStreamConnect {
+ gpr_mu mu;
+ gpr_refcount refcount;
+
+ CFReadStreamRef read_stream;
+ CFWriteStreamRef write_stream;
+ CFStreamHandle* stream_handle;
+
+ grpc_timer alarm;
+ grpc_closure on_alarm;
+ grpc_closure on_open;
+
+ bool read_stream_open;
+ bool write_stream_open;
+ bool failed;
+
+ grpc_closure* closure;
+ grpc_endpoint** endpoint;
+ int refs;
+ char* addr_name;
+ grpc_resource_quota* resource_quota;
+} CFStreamConnect;
+
+static void CFStreamConnectCleanup(CFStreamConnect* connect) {
+ grpc_resource_quota_unref_internal(connect->resource_quota);
+ CFSTREAM_HANDLE_UNREF(connect->stream_handle, "async connect clean up");
+ CFRelease(connect->read_stream);
+ CFRelease(connect->write_stream);
+ gpr_mu_destroy(&connect->mu);
+ gpr_free(connect->addr_name);
+ gpr_free(connect);
+}
+
+static void OnAlarm(void* arg, grpc_error* error) {
+ CFStreamConnect* connect = static_cast<CFStreamConnect*>(arg);
+ if (grpc_tcp_trace.enabled()) {
+ gpr_log(GPR_DEBUG, "CLIENT_CONNECT :%p OnAlarm, error:%p", connect, error);
+ }
+ gpr_mu_lock(&connect->mu);
+ grpc_closure* closure = connect->closure;
+ connect->closure = nil;
+ const bool done = (--connect->refs == 0);
+ gpr_mu_unlock(&connect->mu);
+ // Only schedule a callback once, by either OnAlarm or OnOpen. The
+ // first one issues callback while the second one does cleanup.
+ if (done) {
+ CFStreamConnectCleanup(connect);
+ } else {
+ grpc_error* error =
+ GRPC_ERROR_CREATE_FROM_STATIC_STRING("connect() timed out");
+ GRPC_CLOSURE_SCHED(closure, error);
+ }
+}
+
+static void OnOpen(void* arg, grpc_error* error) {
+ CFStreamConnect* connect = static_cast<CFStreamConnect*>(arg);
+ if (grpc_tcp_trace.enabled()) {
+ gpr_log(GPR_DEBUG, "CLIENT_CONNECT :%p OnOpen, error:%p", connect, error);
+ }
+ gpr_mu_lock(&connect->mu);
+ grpc_timer_cancel(&connect->alarm);
+ grpc_closure* closure = connect->closure;
+ connect->closure = nil;
+
+ bool done = (--connect->refs == 0);
+ grpc_endpoint** endpoint = connect->endpoint;
+
+ // Only schedule a callback once, by either OnAlarm or OnOpen. The
+ // first one issues callback while the second one does cleanup.
+ if (done) {
+ gpr_mu_unlock(&connect->mu);
+ CFStreamConnectCleanup(connect);
+ } else {
+ if (error == GRPC_ERROR_NONE) {
+ CFErrorRef stream_error = CFReadStreamCopyError(connect->read_stream);
+ if (stream_error == NULL) {
+ stream_error = CFWriteStreamCopyError(connect->write_stream);
+ }
+ if (stream_error) {
+ error = GRPC_ERROR_CREATE_FROM_CFERROR(stream_error, "connect() error");
+ CFRelease(stream_error);
+ }
+ if (error == GRPC_ERROR_NONE) {
+ *endpoint = grpc_cfstream_endpoint_create(
+ connect->read_stream, connect->write_stream, connect->addr_name,
+ connect->resource_quota, connect->stream_handle);
+ }
+ } else {
+ GRPC_ERROR_REF(error);
+ }
+ gpr_mu_unlock(&connect->mu);
+ GRPC_CLOSURE_SCHED(closure, error);
+ }
+}
+
+static void ParseResolvedAddress(const grpc_resolved_address* addr,
+ CFStringRef* host, int* port) {
+ char *host_port, *host_string, *port_string;
+ grpc_sockaddr_to_string(&host_port, addr, 1);
+ gpr_split_host_port(host_port, &host_string, &port_string);
+ *host = CFStringCreateWithCString(NULL, host_string, kCFStringEncodingUTF8);
+ gpr_free(host_string);
+ gpr_free(port_string);
+ gpr_free(host_port);
+ *port = grpc_sockaddr_get_port(addr);
+}
+
+static void CFStreamClientConnect(grpc_closure* closure, grpc_endpoint** ep,
+ grpc_pollset_set* interested_parties,
+ const grpc_channel_args* channel_args,
+ const grpc_resolved_address* resolved_addr,
+ grpc_millis deadline) {
+ CFStreamConnect* connect;
+
+ connect = (CFStreamConnect*)gpr_zalloc(sizeof(CFStreamConnect));
+ connect->closure = closure;
+ connect->endpoint = ep;
+ connect->addr_name = grpc_sockaddr_to_uri(resolved_addr);
+ // connect->resource_quota = resource_quota;
+ connect->refs = 2; // One for the connect operation, one for the timer.
+ gpr_ref_init(&connect->refcount, 1);
+ gpr_mu_init(&connect->mu);
+
+ if (grpc_tcp_trace.enabled()) {
+ gpr_log(GPR_DEBUG, "CLIENT_CONNECT: %p, %s: asynchronously connecting",
+ connect, connect->addr_name);
+ }
+
+ grpc_resource_quota* resource_quota = grpc_resource_quota_create(NULL);
+ if (channel_args != NULL) {
+ for (size_t i = 0; i < channel_args->num_args; i++) {
+ if (0 == strcmp(channel_args->args[i].key, GRPC_ARG_RESOURCE_QUOTA)) {
+ grpc_resource_quota_unref_internal(resource_quota);
+ resource_quota = grpc_resource_quota_ref_internal(
+ (grpc_resource_quota*)channel_args->args[i].value.pointer.p);
+ }
+ }
+ }
+ connect->resource_quota = resource_quota;
+
+ CFReadStreamRef read_stream;
+ CFWriteStreamRef write_stream;
+
+ CFStringRef host;
+ int port;
+ ParseResolvedAddress(resolved_addr, &host, &port);
+ CFStreamCreatePairWithSocketToHost(NULL, host, port, &read_stream,
+ &write_stream);
+ CFRelease(host);
+ connect->read_stream = read_stream;
+ connect->write_stream = write_stream;
+ connect->stream_handle =
+ CFStreamHandle::CreateStreamHandle(read_stream, write_stream);
+ GRPC_CLOSURE_INIT(&connect->on_open, OnOpen, static_cast<void*>(connect),
+ grpc_schedule_on_exec_ctx);
+ connect->stream_handle->NotifyOnOpen(&connect->on_open);
+ GRPC_CLOSURE_INIT(&connect->on_alarm, OnAlarm, connect,
+ grpc_schedule_on_exec_ctx);
+ gpr_mu_lock(&connect->mu);
+ CFReadStreamOpen(read_stream);
+ CFWriteStreamOpen(write_stream);
+ grpc_timer_init(&connect->alarm, deadline, &connect->on_alarm);
+ gpr_mu_unlock(&connect->mu);
+}
+
+grpc_tcp_client_vtable grpc_cfstream_client_vtable = {CFStreamClientConnect};
+
+#endif /* GRPC_CFSTREAM_CLIENT */
diff --git a/src/core/lib/iomgr/tcp_client_custom.cc b/src/core/lib/iomgr/tcp_client_custom.cc
index 932c79ea0b..9389861d07 100644
--- a/src/core/lib/iomgr/tcp_client_custom.cc
+++ b/src/core/lib/iomgr/tcp_client_custom.cc
@@ -140,12 +140,12 @@ static void tcp_connect(grpc_closure* closure, grpc_endpoint** ep,
socket, connect->addr_name);
}
- grpc_custom_socket_vtable->connect(
- socket, (const grpc_sockaddr*)resolved_addr->addr, resolved_addr->len,
- custom_connect_callback);
GRPC_CLOSURE_INIT(&connect->on_alarm, on_alarm, socket,
grpc_schedule_on_exec_ctx);
grpc_timer_init(&connect->alarm, deadline, &connect->on_alarm);
+ grpc_custom_socket_vtable->connect(
+ socket, (const grpc_sockaddr*)resolved_addr->addr, resolved_addr->len,
+ custom_connect_callback);
}
grpc_tcp_client_vtable custom_tcp_client_vtable = {tcp_connect};
diff --git a/src/core/lib/iomgr/tcp_client_posix.cc b/src/core/lib/iomgr/tcp_client_posix.cc
index 6144d389f7..8553ed0db4 100644
--- a/src/core/lib/iomgr/tcp_client_posix.cc
+++ b/src/core/lib/iomgr/tcp_client_posix.cc
@@ -20,7 +20,7 @@
#include "src/core/lib/iomgr/port.h"
-#ifdef GRPC_POSIX_SOCKET
+#ifdef GRPC_POSIX_SOCKET_TCP_CLIENT
#include "src/core/lib/iomgr/tcp_client_posix.h"
@@ -45,6 +45,7 @@
#include "src/core/lib/iomgr/tcp_posix.h"
#include "src/core/lib/iomgr/timer.h"
#include "src/core/lib/iomgr/unix_sockets_posix.h"
+#include "src/core/lib/slice/slice_internal.h"
extern grpc_core::TraceFlag grpc_tcp_trace;
@@ -75,6 +76,9 @@ static grpc_error* prepare_socket(const grpc_resolved_address* addr, int fd,
if (!grpc_is_unix_socket(addr)) {
err = grpc_set_socket_low_latency(fd, 1);
if (err != GRPC_ERROR_NONE) goto error;
+ err = grpc_set_socket_tcp_user_timeout(fd, channel_args,
+ true /* is_client */);
+ if (err != GRPC_ERROR_NONE) goto error;
}
err = grpc_set_socket_no_sigpipe_if_possible(fd);
if (err != GRPC_ERROR_NONE) goto error;
@@ -210,8 +214,7 @@ static void on_writable(void* acp, grpc_error* error) {
finish:
if (fd != nullptr) {
grpc_pollset_set_del_fd(ac->interested_parties, fd);
- grpc_fd_orphan(fd, nullptr, nullptr, false /* already_closed */,
- "tcp_client_orphan");
+ grpc_fd_orphan(fd, nullptr, nullptr, "tcp_client_orphan");
fd = nullptr;
}
done = (--ac->refs == 0);
@@ -233,7 +236,7 @@ finish:
error = grpc_error_set_str(error, GRPC_ERROR_STR_TARGET_ADDRESS,
addr_str_slice /* takes ownership */);
} else {
- grpc_slice_unref(addr_str_slice);
+ grpc_slice_unref_internal(addr_str_slice);
}
if (done) {
// This is safe even outside the lock, because "done", the sentinel, is
@@ -279,7 +282,7 @@ grpc_error* grpc_tcp_client_prepare_fd(const grpc_channel_args* channel_args,
}
addr_str = grpc_sockaddr_to_uri(mapped_addr);
gpr_asprintf(&name, "tcp-client:%s", addr_str);
- *fdobj = grpc_fd_create(fd, name);
+ *fdobj = grpc_fd_create(fd, name, true);
gpr_free(name);
gpr_free(addr_str);
return GRPC_ERROR_NONE;
@@ -304,8 +307,7 @@ void grpc_tcp_client_create_from_prepared_fd(
return;
}
if (errno != EWOULDBLOCK && errno != EINPROGRESS) {
- grpc_fd_orphan(fdobj, nullptr, nullptr, false /* already_closed */,
- "tcp_client_connect_error");
+ grpc_fd_orphan(fdobj, nullptr, nullptr, "tcp_client_connect_error");
GRPC_CLOSURE_SCHED(closure, GRPC_OS_ERROR(errno, "connect"));
return;
}
diff --git a/src/core/lib/iomgr/tcp_custom.cc b/src/core/lib/iomgr/tcp_custom.cc
index b3b2934014..e02a1898f2 100644
--- a/src/core/lib/iomgr/tcp_custom.cc
+++ b/src/core/lib/iomgr/tcp_custom.cc
@@ -141,7 +141,7 @@ static void call_read_cb(custom_tcp_endpoint* tcp, grpc_error* error) {
TCP_UNREF(tcp, "read");
tcp->read_slices = nullptr;
tcp->read_cb = nullptr;
- GRPC_CLOSURE_RUN(cb, error);
+ GRPC_CLOSURE_SCHED(cb, error);
}
static void custom_read_callback(grpc_custom_socket* socket, size_t nread,
@@ -221,7 +221,7 @@ static void custom_write_callback(grpc_custom_socket* socket,
}
static void endpoint_write(grpc_endpoint* ep, grpc_slice_buffer* write_slices,
- grpc_closure* cb) {
+ grpc_closure* cb, void* arg) {
custom_tcp_endpoint* tcp = (custom_tcp_endpoint*)ep;
GRPC_CUSTOM_IOMGR_ASSERT_SAME_THREAD();
diff --git a/src/core/lib/iomgr/tcp_posix.cc b/src/core/lib/iomgr/tcp_posix.cc
index 153be05e83..ac1e919acb 100644
--- a/src/core/lib/iomgr/tcp_posix.cc
+++ b/src/core/lib/iomgr/tcp_posix.cc
@@ -20,13 +20,16 @@
#include "src/core/lib/iomgr/port.h"
-#ifdef GRPC_POSIX_SOCKET
+#ifdef GRPC_POSIX_SOCKET_TCP
#include "src/core/lib/iomgr/network_status_tracker.h"
#include "src/core/lib/iomgr/tcp_posix.h"
#include <errno.h>
+#include <limits.h>
+#include <netinet/in.h>
#include <stdbool.h>
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
@@ -45,6 +48,7 @@
#include "src/core/lib/debug/trace.h"
#include "src/core/lib/gpr/string.h"
#include "src/core/lib/gpr/useful.h"
+#include "src/core/lib/iomgr/buffer_list.h"
#include "src/core/lib/iomgr/ev_posix.h"
#include "src/core/lib/iomgr/executor.h"
#include "src/core/lib/profiling/timers.h"
@@ -70,7 +74,9 @@ struct grpc_tcp {
grpc_endpoint base;
grpc_fd* em_fd;
int fd;
- bool finished_edge;
+ /* Used by the endpoint read function to distinguish the very first read call
+ * from the rest */
+ bool is_first_read;
double target_length;
double bytes_read_this_round;
gpr_refcount refcount;
@@ -94,17 +100,42 @@ struct grpc_tcp {
grpc_closure read_done_closure;
grpc_closure write_done_closure;
+ grpc_closure error_closure;
char* peer_string;
grpc_resource_user* resource_user;
grpc_resource_user_slice_allocator slice_allocator;
+
+ grpc_core::TracedBuffer* tb_head; /* List of traced buffers */
+ gpr_mu tb_mu; /* Lock for access to list of traced buffers */
+
+ /* grpc_endpoint_write takes an argument which if non-null means that the
+ * transport layer wants the TCP layer to collect timestamps for this write.
+ * This arg is forwarded to the timestamps callback function when the ACK
+ * timestamp is received from the kernel. This arg is a (void *) which allows
+ * users of this API to pass in a pointer to any kind of structure. This
+ * structure could actually be a tag or any book-keeping object that the user
+ * can use to distinguish between different traced writes. The only
+ * requirement from the TCP endpoint layer is that this arg should be non-null
+ * if the user wants timestamps for the write. */
+ void* outgoing_buffer_arg;
+ /* A counter which starts at 0. It is initialized the first time the socket
+ * options for collecting timestamps are set, and is incremented with each
+ * byte sent. */
+ int bytes_counter;
+ bool socket_ts_enabled; /* True if timestamping options are set on the socket
+ */
+ gpr_atm
+ stop_error_notification; /* Set to 1 if we do not want to be notified on
+ errors anymore */
};
struct backup_poller {
gpr_mu* pollset_mu;
grpc_closure run_poller;
};
+
} // namespace
#define BACKUP_POLLER_POLLSET(b) ((grpc_pollset*)((b) + 1))
@@ -295,10 +326,11 @@ static void tcp_shutdown(grpc_endpoint* ep, grpc_error* why) {
static void tcp_free(grpc_tcp* tcp) {
grpc_fd_orphan(tcp->em_fd, tcp->release_fd_cb, tcp->release_fd,
- false /* already_closed */, "tcp_unref_orphan");
+ "tcp_unref_orphan");
grpc_slice_buffer_destroy_internal(&tcp->last_read_buffer);
grpc_resource_user_unref(tcp->resource_user);
gpr_free(tcp->peer_string);
+ gpr_mu_destroy(&tcp->tb_mu);
gpr_free(tcp);
}
@@ -344,6 +376,10 @@ static void tcp_destroy(grpc_endpoint* ep) {
grpc_network_status_unregister_endpoint(ep);
grpc_tcp* tcp = reinterpret_cast<grpc_tcp*>(ep);
grpc_slice_buffer_reset_and_unref_internal(&tcp->last_read_buffer);
+ if (grpc_event_engine_can_track_errors()) {
+ gpr_atm_no_barrier_store(&tcp->stop_error_notification, true);
+ grpc_fd_set_error(tcp->em_fd);
+ }
TCP_UNREF(tcp, "destroy");
}
@@ -366,7 +402,7 @@ static void call_read_cb(grpc_tcp* tcp, grpc_error* error) {
tcp->read_cb = nullptr;
tcp->incoming_buffer = nullptr;
- GRPC_CLOSURE_RUN(cb, error);
+ GRPC_CLOSURE_SCHED(cb, error);
}
#define MAX_READ_IOVEC 4
@@ -377,7 +413,6 @@ static void tcp_do_read(grpc_tcp* tcp) {
ssize_t read_bytes;
size_t i;
- GPR_ASSERT(!tcp->finished_edge);
GPR_ASSERT(tcp->incoming_buffer->count <= MAX_READ_IOVEC);
for (i = 0; i < tcp->incoming_buffer->count; i++) {
@@ -473,7 +508,6 @@ static void tcp_continue_read(grpc_tcp* tcp) {
static void tcp_handle_read(void* arg /* grpc_tcp */, grpc_error* error) {
grpc_tcp* tcp = static_cast<grpc_tcp*>(arg);
- GPR_ASSERT(!tcp->finished_edge);
if (grpc_tcp_trace.enabled()) {
gpr_log(GPR_INFO, "TCP:%p got_read: %s", tcp, grpc_error_string(error));
}
@@ -497,16 +531,256 @@ static void tcp_read(grpc_endpoint* ep, grpc_slice_buffer* incoming_buffer,
grpc_slice_buffer_reset_and_unref_internal(incoming_buffer);
grpc_slice_buffer_swap(incoming_buffer, &tcp->last_read_buffer);
TCP_REF(tcp, "read");
- if (tcp->finished_edge) {
- tcp->finished_edge = false;
+ if (tcp->is_first_read) {
+ /* Endpoint read called for the very first time. Register read callback with
+ * the polling engine */
+ tcp->is_first_read = false;
notify_on_read(tcp);
} else {
+ /* Not the first time. We may or may not have more bytes available. In any
+ * case call tcp->read_done_closure (i.e tcp_handle_read()) which does the
+ * right thing (i.e calls tcp_do_read() which either reads the available
+ * bytes or calls notify_on_read() to be notified when new bytes become
+ * available */
GRPC_CLOSURE_SCHED(&tcp->read_done_closure, GRPC_ERROR_NONE);
}
}
+/* A wrapper around sendmsg. It sends \a msg over \a fd and returns the number
+ * of bytes sent. */
+ssize_t tcp_send(int fd, const struct msghdr* msg) {
+ GPR_TIMER_SCOPE("sendmsg", 1);
+ ssize_t sent_length;
+ do {
+ /* TODO(klempner): Cork if this is a partial write */
+ GRPC_STATS_INC_SYSCALL_WRITE();
+ sent_length = sendmsg(fd, msg, SENDMSG_FLAGS);
+ } while (sent_length < 0 && errno == EINTR);
+ return sent_length;
+}
+
+/** This is to be called if outgoing_buffer_arg is not null. On linux platforms,
+ * this will call sendmsg with socket options set to collect timestamps inside
+ * the kernel. On return, sent_length is set to the return value of the sendmsg
+ * call. Returns false if setting the socket options failed. This is not
+ * implemented for non-linux platforms currently, and crashes out.
+ */
+static bool tcp_write_with_timestamps(grpc_tcp* tcp, struct msghdr* msg,
+ size_t sending_length,
+ ssize_t* sent_length, grpc_error** error);
+
+/** The callback function to be invoked when we get an error on the socket. */
+static void tcp_handle_error(void* arg /* grpc_tcp */, grpc_error* error);
+
+#ifdef GRPC_LINUX_ERRQUEUE
+static bool tcp_write_with_timestamps(grpc_tcp* tcp, struct msghdr* msg,
+ size_t sending_length,
+ ssize_t* sent_length,
+ grpc_error** error) {
+ if (!tcp->socket_ts_enabled) {
+ uint32_t opt = grpc_core::kTimestampingSocketOptions;
+ if (setsockopt(tcp->fd, SOL_SOCKET, SO_TIMESTAMPING,
+ static_cast<void*>(&opt), sizeof(opt)) != 0) {
+ *error = tcp_annotate_error(GRPC_OS_ERROR(errno, "setsockopt"), tcp);
+ grpc_slice_buffer_reset_and_unref_internal(tcp->outgoing_buffer);
+ if (grpc_tcp_trace.enabled()) {
+ gpr_log(GPR_ERROR, "Failed to set timestamping options on the socket.");
+ }
+ return false;
+ }
+ tcp->bytes_counter = -1;
+ tcp->socket_ts_enabled = true;
+ }
+ /* Set control message to indicate that you want timestamps. */
+ union {
+ char cmsg_buf[CMSG_SPACE(sizeof(uint32_t))];
+ struct cmsghdr align;
+ } u;
+ cmsghdr* cmsg = reinterpret_cast<cmsghdr*>(u.cmsg_buf);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SO_TIMESTAMPING;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(uint32_t));
+ *reinterpret_cast<int*>(CMSG_DATA(cmsg)) =
+ grpc_core::kTimestampingRecordingOptions;
+ msg->msg_control = u.cmsg_buf;
+ msg->msg_controllen = CMSG_SPACE(sizeof(uint32_t));
+
+ /* If there was an error on sendmsg the logic in tcp_flush will handle it. */
+ ssize_t length = tcp_send(tcp->fd, msg);
+ *sent_length = length;
+ /* Only save timestamps if all the bytes were taken by sendmsg. */
+ if (sending_length == static_cast<size_t>(length)) {
+ gpr_mu_lock(&tcp->tb_mu);
+ grpc_core::TracedBuffer::AddNewEntry(
+ &tcp->tb_head, static_cast<int>(tcp->bytes_counter + length),
+ tcp->outgoing_buffer_arg);
+ gpr_mu_unlock(&tcp->tb_mu);
+ tcp->outgoing_buffer_arg = nullptr;
+ }
+ return true;
+}
+
+/** Reads \a cmsg to derive timestamps from the control messages. If a valid
+ * timestamp is found, the traced buffer list is updated with this timestamp.
+ * The caller of this function should be looping on the control messages found
+ * in \a msg. \a cmsg should point to the control message that the caller wants
+ * processed.
+ * On return, a pointer to a control message is returned. On the next iteration,
+ * CMSG_NXTHDR(msg, ret_val) should be passed as \a cmsg. */
+struct cmsghdr* process_timestamp(grpc_tcp* tcp, msghdr* msg,
+ struct cmsghdr* cmsg) {
+ auto next_cmsg = CMSG_NXTHDR(msg, cmsg);
+ if (next_cmsg == nullptr) {
+ if (grpc_tcp_trace.enabled()) {
+ gpr_log(GPR_ERROR, "Received timestamp without extended error");
+ }
+ return cmsg;
+ }
+
+ if (!(next_cmsg->cmsg_level == SOL_IP || next_cmsg->cmsg_level == SOL_IPV6) ||
+ !(next_cmsg->cmsg_type == IP_RECVERR ||
+ next_cmsg->cmsg_type == IPV6_RECVERR)) {
+ if (grpc_tcp_trace.enabled()) {
+ gpr_log(GPR_ERROR, "Unexpected control message");
+ }
+ return cmsg;
+ }
+
+ auto tss =
+ reinterpret_cast<struct grpc_core::scm_timestamping*>(CMSG_DATA(cmsg));
+ auto serr = reinterpret_cast<struct sock_extended_err*>(CMSG_DATA(next_cmsg));
+ if (serr->ee_errno != ENOMSG ||
+ serr->ee_origin != SO_EE_ORIGIN_TIMESTAMPING) {
+ gpr_log(GPR_ERROR, "Unexpected control message");
+ return cmsg;
+ }
+ /* The error handling can potentially be done on another thread so we need
+ * to protect the traced buffer list. A lock free list might be better. Using
+ * a simple mutex for now. */
+ gpr_mu_lock(&tcp->tb_mu);
+ grpc_core::TracedBuffer::ProcessTimestamp(&tcp->tb_head, serr, tss);
+ gpr_mu_unlock(&tcp->tb_mu);
+ return next_cmsg;
+}
+
+/** For linux platforms, reads the socket's error queue and processes error
+ * messages from the queue. Returns true if all the errors processed were
+ * timestamps. Returns false if any of the errors were not timestamps. For
+ * non-linux platforms, error processing is not used/enabled currently.
+ */
+static bool process_errors(grpc_tcp* tcp) {
+ while (true) {
+ struct iovec iov;
+ iov.iov_base = nullptr;
+ iov.iov_len = 0;
+ struct msghdr msg;
+ msg.msg_name = nullptr;
+ msg.msg_namelen = 0;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 0;
+ msg.msg_flags = 0;
+
+ union {
+ char rbuf[1024 /*CMSG_SPACE(sizeof(scm_timestamping)) +
+ CMSG_SPACE(sizeof(sock_extended_err) + sizeof(sockaddr_in))*/];
+ struct cmsghdr align;
+ } aligned_buf;
+ memset(&aligned_buf, 0, sizeof(aligned_buf));
+
+ msg.msg_control = aligned_buf.rbuf;
+ msg.msg_controllen = sizeof(aligned_buf.rbuf);
+
+ int r, saved_errno;
+ do {
+ r = recvmsg(tcp->fd, &msg, MSG_ERRQUEUE);
+ saved_errno = errno;
+ } while (r < 0 && saved_errno == EINTR);
+
+ if (r == -1 && saved_errno == EAGAIN) {
+ return true; /* No more errors to process */
+ }
+ if (r == -1) {
+ return false;
+ }
+ if (grpc_tcp_trace.enabled()) {
+ if ((msg.msg_flags & MSG_CTRUNC) == 1) {
+ gpr_log(GPR_INFO, "Error message was truncated.");
+ }
+ }
+
+ if (msg.msg_controllen == 0) {
+ /* There was no control message found. It was probably spurious. */
+ return true;
+ }
+ for (auto cmsg = CMSG_FIRSTHDR(&msg); cmsg && cmsg->cmsg_len;
+ cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+ if (cmsg->cmsg_level != SOL_SOCKET ||
+ cmsg->cmsg_type != SCM_TIMESTAMPING) {
+ /* Got a control message that is not a timestamp. Don't know how to
+ * handle this. */
+ if (grpc_tcp_trace.enabled()) {
+ gpr_log(GPR_INFO,
+ "unknown control message cmsg_level:%d cmsg_type:%d",
+ cmsg->cmsg_level, cmsg->cmsg_type);
+ }
+ return false;
+ }
+ process_timestamp(tcp, &msg, cmsg);
+ }
+ }
+}
+
+static void tcp_handle_error(void* arg /* grpc_tcp */, grpc_error* error) {
+ grpc_tcp* tcp = static_cast<grpc_tcp*>(arg);
+ if (grpc_tcp_trace.enabled()) {
+ gpr_log(GPR_INFO, "TCP:%p got_error: %s", tcp, grpc_error_string(error));
+ }
+
+ if (error != GRPC_ERROR_NONE ||
+ static_cast<bool>(gpr_atm_acq_load(&tcp->stop_error_notification))) {
+ /* We aren't going to register to hear on error anymore, so it is safe to
+ * unref. */
+ grpc_core::TracedBuffer::Shutdown(&tcp->tb_head, GRPC_ERROR_REF(error));
+ TCP_UNREF(tcp, "error-tracking");
+ return;
+ }
+
+ /* We are still interested in collecting timestamps, so let's try reading
+ * them. */
+ if (!process_errors(tcp)) {
+ /* This was not a timestamps error. This was an actual error. Set the
+ * read and write closures to be ready.
+ */
+ grpc_fd_set_readable(tcp->em_fd);
+ grpc_fd_set_writable(tcp->em_fd);
+ }
+ GRPC_CLOSURE_INIT(&tcp->error_closure, tcp_handle_error, tcp,
+ grpc_schedule_on_exec_ctx);
+ grpc_fd_notify_on_error(tcp->em_fd, &tcp->error_closure);
+}
+
+#else /* GRPC_LINUX_ERRQUEUE */
+static bool tcp_write_with_timestamps(grpc_tcp* tcp, struct msghdr* msg,
+ size_t sending_length,
+ ssize_t* sent_length,
+ grpc_error** error) {
+ gpr_log(GPR_ERROR, "Write with timestamps not supported for this platform");
+ GPR_ASSERT(0);
+ return false;
+}
+
+static void tcp_handle_error(void* arg /* grpc_tcp */, grpc_error* error) {
+ gpr_log(GPR_ERROR, "Error handling is not supported for this platform");
+ GPR_ASSERT(0);
+}
+#endif /* GRPC_LINUX_ERRQUEUE */
+
/* returns true if done, false if pending; if returning true, *error is set */
+#if defined(IOV_MAX) && IOV_MAX < 1000
+#define MAX_WRITE_IOVEC IOV_MAX
+#else
#define MAX_WRITE_IOVEC 1000
+#endif
static bool tcp_flush(grpc_tcp* tcp, grpc_error** error) {
struct msghdr msg;
struct iovec iov[MAX_WRITE_IOVEC];
@@ -545,19 +819,20 @@ static bool tcp_flush(grpc_tcp* tcp, grpc_error** error) {
msg.msg_namelen = 0;
msg.msg_iov = iov;
msg.msg_iovlen = iov_size;
- msg.msg_control = nullptr;
- msg.msg_controllen = 0;
msg.msg_flags = 0;
+ if (tcp->outgoing_buffer_arg != nullptr) {
+ if (!tcp_write_with_timestamps(tcp, &msg, sending_length, &sent_length,
+ error))
+ return true; /* something went wrong with timestamps */
+ } else {
+ msg.msg_control = nullptr;
+ msg.msg_controllen = 0;
- GRPC_STATS_INC_TCP_WRITE_SIZE(sending_length);
- GRPC_STATS_INC_TCP_WRITE_IOV_SIZE(iov_size);
+ GRPC_STATS_INC_TCP_WRITE_SIZE(sending_length);
+ GRPC_STATS_INC_TCP_WRITE_IOV_SIZE(iov_size);
- GPR_TIMER_SCOPE("sendmsg", 1);
- do {
- /* TODO(klempner): Cork if this is a partial write */
- GRPC_STATS_INC_SYSCALL_WRITE();
- sent_length = sendmsg(tcp->fd, &msg, SENDMSG_FLAGS);
- } while (sent_length < 0 && errno == EINTR);
+ sent_length = tcp_send(tcp->fd, &msg);
+ }
if (sent_length < 0) {
if (errno == EAGAIN) {
@@ -581,6 +856,7 @@ static bool tcp_flush(grpc_tcp* tcp, grpc_error** error) {
}
GPR_ASSERT(tcp->outgoing_byte_idx == 0);
+ tcp->bytes_counter += sent_length;
trailing = sending_length - static_cast<size_t>(sent_length);
while (trailing > 0) {
size_t slice_length;
@@ -595,7 +871,6 @@ static bool tcp_flush(grpc_tcp* tcp, grpc_error** error) {
trailing -= slice_length;
}
}
-
if (outgoing_slice_idx == tcp->outgoing_buffer->count) {
*error = GRPC_ERROR_NONE;
grpc_slice_buffer_reset_and_unref_internal(tcp->outgoing_buffer);
@@ -628,14 +903,13 @@ static void tcp_handle_write(void* arg /* grpc_tcp */, grpc_error* error) {
const char* str = grpc_error_string(error);
gpr_log(GPR_INFO, "write: %s", str);
}
-
- GRPC_CLOSURE_RUN(cb, error);
+ GRPC_CLOSURE_SCHED(cb, error);
TCP_UNREF(tcp, "write");
}
}
static void tcp_write(grpc_endpoint* ep, grpc_slice_buffer* buf,
- grpc_closure* cb) {
+ grpc_closure* cb, void* arg) {
GPR_TIMER_SCOPE("tcp_write", 0);
grpc_tcp* tcp = reinterpret_cast<grpc_tcp*>(ep);
grpc_error* error = GRPC_ERROR_NONE;
@@ -663,6 +937,10 @@ static void tcp_write(grpc_endpoint* ep, grpc_slice_buffer* buf,
}
tcp->outgoing_buffer = buf;
tcp->outgoing_byte_idx = 0;
+ tcp->outgoing_buffer_arg = arg;
+ if (arg) {
+ GPR_ASSERT(grpc_event_engine_can_track_errors());
+ }
if (!tcp_flush(tcp, &error)) {
TCP_REF(tcp, "write");
@@ -778,7 +1056,10 @@ grpc_endpoint* grpc_tcp_create(grpc_fd* em_fd,
tcp->min_read_chunk_size = tcp_min_read_chunk_size;
tcp->max_read_chunk_size = tcp_max_read_chunk_size;
tcp->bytes_read_this_round = 0;
- tcp->finished_edge = true;
+ /* Will be set to false by the very first endpoint read function */
+ tcp->is_first_read = true;
+ tcp->bytes_counter = -1;
+ tcp->socket_ts_enabled = false;
/* paired with unref in grpc_tcp_destroy */
gpr_ref_init(&tcp->refcount, 1);
gpr_atm_no_barrier_store(&tcp->shutdown_count, 0);
@@ -790,6 +1071,19 @@ grpc_endpoint* grpc_tcp_create(grpc_fd* em_fd,
/* Tell network status tracker about new endpoint */
grpc_network_status_register_endpoint(&tcp->base);
grpc_resource_quota_unref_internal(resource_quota);
+ gpr_mu_init(&tcp->tb_mu);
+ tcp->tb_head = nullptr;
+ /* Start being notified on errors if event engine can track errors. */
+ if (grpc_event_engine_can_track_errors()) {
+ /* Grab a ref to tcp so that we can safely access the tcp struct when
+ * processing errors. We unref when we no longer want to track errors
+ * separately. */
+ TCP_REF(tcp, "error-tracking");
+ gpr_atm_rel_store(&tcp->stop_error_notification, 0);
+ GRPC_CLOSURE_INIT(&tcp->error_closure, tcp_handle_error, tcp,
+ grpc_schedule_on_exec_ctx);
+ grpc_fd_notify_on_error(tcp->em_fd, &tcp->error_closure);
+ }
return &tcp->base;
}
@@ -808,7 +1102,12 @@ void grpc_tcp_destroy_and_release_fd(grpc_endpoint* ep, int* fd,
tcp->release_fd = fd;
tcp->release_fd_cb = done;
grpc_slice_buffer_reset_and_unref_internal(&tcp->last_read_buffer);
+ if (grpc_event_engine_can_track_errors()) {
+ /* Stop errors notification. */
+ gpr_atm_no_barrier_store(&tcp->stop_error_notification, true);
+ grpc_fd_set_error(tcp->em_fd);
+ }
TCP_UNREF(tcp, "destroy");
}
-#endif
+#endif /* GRPC_POSIX_SOCKET_TCP */
diff --git a/src/core/lib/iomgr/tcp_posix.h b/src/core/lib/iomgr/tcp_posix.h
index af89bd24db..eff825cb92 100644
--- a/src/core/lib/iomgr/tcp_posix.h
+++ b/src/core/lib/iomgr/tcp_posix.h
@@ -31,7 +31,10 @@
#include <grpc/support/port_platform.h>
+#include "src/core/lib/iomgr/port.h"
+
#include "src/core/lib/debug/trace.h"
+#include "src/core/lib/iomgr/buffer_list.h"
#include "src/core/lib/iomgr/endpoint.h"
#include "src/core/lib/iomgr/ev_posix.h"
diff --git a/src/core/lib/iomgr/tcp_server_posix.cc b/src/core/lib/iomgr/tcp_server_posix.cc
index 153ac63424..824db07fbf 100644
--- a/src/core/lib/iomgr/tcp_server_posix.cc
+++ b/src/core/lib/iomgr/tcp_server_posix.cc
@@ -25,7 +25,7 @@
#include "src/core/lib/iomgr/port.h"
-#ifdef GRPC_POSIX_SOCKET
+#ifdef GRPC_POSIX_SOCKET_TCP_SERVER
#include "src/core/lib/iomgr/tcp_server.h"
@@ -150,7 +150,7 @@ static void deactivated_all_ports(grpc_tcp_server* s) {
GRPC_CLOSURE_INIT(&sp->destroyed_closure, destroyed_port, s,
grpc_schedule_on_exec_ctx);
grpc_fd_orphan(sp->emfd, &sp->destroyed_closure, nullptr,
- false /* already_closed */, "tcp_listener_shutdown");
+ "tcp_listener_shutdown");
}
gpr_mu_unlock(&s->mu);
} else {
@@ -187,11 +187,6 @@ static void on_read(void* arg, grpc_error* err) {
goto error;
}
- read_notifier_pollset =
- sp->server->pollsets[static_cast<size_t>(gpr_atm_no_barrier_fetch_add(
- &sp->server->next_pollset_to_assign, 1)) %
- sp->server->pollset_count];
-
/* loop until accept4 returns EAGAIN, and then re-arm notification */
for (;;) {
grpc_resolved_address addr;
@@ -231,7 +226,12 @@ static void on_read(void* arg, grpc_error* err) {
gpr_log(GPR_INFO, "SERVER_CONNECT: incoming connection: %s", addr_str);
}
- grpc_fd* fdobj = grpc_fd_create(fd, name);
+ grpc_fd* fdobj = grpc_fd_create(fd, name, true);
+
+ read_notifier_pollset =
+ sp->server->pollsets[static_cast<size_t>(gpr_atm_no_barrier_fetch_add(
+ &sp->server->next_pollset_to_assign, 1)) %
+ sp->server->pollset_count];
grpc_pollset_add_fd(read_notifier_pollset, fdobj);
@@ -362,7 +362,7 @@ static grpc_error* clone_port(grpc_tcp_listener* listener, unsigned count) {
listener->sibling = sp;
sp->server = listener->server;
sp->fd = fd;
- sp->emfd = grpc_fd_create(fd, name);
+ sp->emfd = grpc_fd_create(fd, name, true);
memcpy(&sp->addr, &listener->addr, sizeof(grpc_resolved_address));
sp->port = port;
sp->port_index = listener->port_index;
@@ -559,4 +559,4 @@ grpc_tcp_server_vtable grpc_posix_tcp_server_vtable = {
tcp_server_shutdown_starting_add,
tcp_server_unref,
tcp_server_shutdown_listeners};
-#endif
+#endif /* GRPC_POSIX_SOCKET_TCP_SERVER */
diff --git a/src/core/lib/iomgr/tcp_server_utils_posix_common.cc b/src/core/lib/iomgr/tcp_server_utils_posix_common.cc
index 2d95aa66d6..8d8d3f4273 100644
--- a/src/core/lib/iomgr/tcp_server_utils_posix_common.cc
+++ b/src/core/lib/iomgr/tcp_server_utils_posix_common.cc
@@ -20,7 +20,7 @@
#include "src/core/lib/iomgr/port.h"
-#ifdef GRPC_POSIX_SOCKET
+#ifdef GRPC_POSIX_SOCKET_TCP_SERVER_UTILS_COMMON
#include "src/core/lib/iomgr/tcp_server_utils_posix.h"
@@ -105,7 +105,7 @@ static grpc_error* add_socket_to_server(grpc_tcp_server* s, int fd,
s->tail = sp;
sp->server = s;
sp->fd = fd;
- sp->emfd = grpc_fd_create(fd, name);
+ sp->emfd = grpc_fd_create(fd, name, true);
memcpy(&sp->addr, addr, sizeof(grpc_resolved_address));
sp->port = port;
sp->port_index = port_index;
@@ -166,6 +166,9 @@ grpc_error* grpc_tcp_server_prepare_socket(grpc_tcp_server* s, int fd,
if (err != GRPC_ERROR_NONE) goto error;
err = grpc_set_socket_reuse_addr(fd, 1);
if (err != GRPC_ERROR_NONE) goto error;
+ err = grpc_set_socket_tcp_user_timeout(fd, s->channel_args,
+ false /* is_client */);
+ if (err != GRPC_ERROR_NONE) goto error;
}
err = grpc_set_socket_no_sigpipe_if_possible(fd);
if (err != GRPC_ERROR_NONE) goto error;
@@ -217,4 +220,4 @@ error:
return ret;
}
-#endif /* GRPC_POSIX_SOCKET */
+#endif /* GRPC_POSIX_SOCKET_TCP_SERVER_UTILS_COMMON */
diff --git a/src/core/lib/iomgr/tcp_windows.cc b/src/core/lib/iomgr/tcp_windows.cc
index 5d316d477b..64c4a56ae9 100644
--- a/src/core/lib/iomgr/tcp_windows.cc
+++ b/src/core/lib/iomgr/tcp_windows.cc
@@ -53,7 +53,7 @@
extern grpc_core::TraceFlag grpc_tcp_trace;
-static grpc_error* set_non_block(SOCKET sock) {
+grpc_error* grpc_tcp_set_non_block(SOCKET sock) {
int status;
uint32_t param = 1;
DWORD ret;
@@ -90,7 +90,7 @@ static grpc_error* enable_loopback_fast_path(SOCKET sock) {
grpc_error* grpc_tcp_prepare_socket(SOCKET sock) {
grpc_error* err;
- err = set_non_block(sock);
+ err = grpc_tcp_set_non_block(sock);
if (err != GRPC_ERROR_NONE) return err;
err = set_dualstack(sock);
if (err != GRPC_ERROR_NONE) return err;
@@ -296,7 +296,7 @@ static void on_write(void* tcpp, grpc_error* error) {
/* Initiates a write. */
static void win_write(grpc_endpoint* ep, grpc_slice_buffer* slices,
- grpc_closure* cb) {
+ grpc_closure* cb, void* arg) {
grpc_tcp* tcp = (grpc_tcp*)ep;
grpc_winsocket* socket = tcp->socket;
grpc_winsocket_callback_info* info = &socket->write_info;
diff --git a/src/core/lib/iomgr/tcp_windows.h b/src/core/lib/iomgr/tcp_windows.h
index 161a545a2a..04ef8102b6 100644
--- a/src/core/lib/iomgr/tcp_windows.h
+++ b/src/core/lib/iomgr/tcp_windows.h
@@ -46,6 +46,8 @@ grpc_endpoint* grpc_tcp_create(grpc_winsocket* socket,
grpc_error* grpc_tcp_prepare_socket(SOCKET sock);
+grpc_error* grpc_tcp_set_non_block(SOCKET sock);
+
#endif
#endif /* GRPC_CORE_LIB_IOMGR_TCP_WINDOWS_H */
diff --git a/src/core/lib/iomgr/timer.h b/src/core/lib/iomgr/timer.h
index 5ff10d3aee..17e933b865 100644
--- a/src/core/lib/iomgr/timer.h
+++ b/src/core/lib/iomgr/timer.h
@@ -28,7 +28,7 @@
#include "src/core/lib/iomgr/iomgr.h"
typedef struct grpc_timer {
- gpr_atm deadline;
+ grpc_millis deadline;
uint32_t heap_index; /* INVALID_HEAP_INDEX if not in heap */
bool pending;
struct grpc_timer* next;
@@ -61,10 +61,11 @@ typedef struct grpc_timer_vtable {
/* Initialize *timer. When expired or canceled, closure will be called with
error set to indicate if it expired (GRPC_ERROR_NONE) or was canceled
- (GRPC_ERROR_CANCELLED). timer_cb is guaranteed to be called exactly once, and
+ (GRPC_ERROR_CANCELLED). *closure is guaranteed to be called exactly once, and
application code should check the error to determine how it was invoked. The
application callback is also responsible for maintaining information about
- when to free up any user-level state. */
+ when to free up any user-level state. Behavior is undefined for a deadline of
+ GRPC_MILLIS_INF_FUTURE. */
void grpc_timer_init(grpc_timer* timer, grpc_millis deadline,
grpc_closure* closure);
diff --git a/src/core/lib/iomgr/timer_generic.cc b/src/core/lib/iomgr/timer_generic.cc
index de2256f7cb..008d37119a 100644
--- a/src/core/lib/iomgr/timer_generic.cc
+++ b/src/core/lib/iomgr/timer_generic.cc
@@ -34,6 +34,7 @@
#include "src/core/lib/gpr/spinlock.h"
#include "src/core/lib/gpr/tls.h"
#include "src/core/lib/gpr/useful.h"
+#include "src/core/lib/iomgr/exec_ctx.h"
#include "src/core/lib/iomgr/time_averaged_stats.h"
#include "src/core/lib/iomgr/timer_heap.h"
@@ -59,9 +60,9 @@ typedef struct {
gpr_mu mu;
grpc_time_averaged_stats stats;
/* All and only timers with deadlines <= this will be in the heap. */
- gpr_atm queue_deadline_cap;
+ grpc_millis queue_deadline_cap;
/* The deadline of the next timer due in this shard */
- gpr_atm min_deadline;
+ grpc_millis min_deadline;
/* Index of this timer_shard in the g_shard_queue */
uint32_t shard_queue_index;
/* This holds all timers with deadlines < queue_deadline_cap. Timers in this
@@ -209,15 +210,23 @@ static void validate_non_pending_timer(grpc_timer* t) {
#endif
+#if GPR_ARCH_64
+/* NOTE: TODO(sreek) - Currently the thread local storage support in grpc is
+ for intptr_t which means on 32-bit machines it is not wide enough to hold
+ grpc_millis which is 64-bit. Adding thread local support for 64 bit values
+ is a lot of work for very little gain. So we are currently restricting this
+ optimization to only 64 bit machines */
+
/* Thread local variable that stores the deadline of the next timer the thread
* has last-seen. This is an optimization to prevent the thread from checking
* shared_mutables.min_timer (which requires acquiring shared_mutables.mu lock,
* an expensive operation) */
GPR_TLS_DECL(g_last_seen_min_timer);
+#endif
struct shared_mutables {
/* The deadline of the next timer due across all timer shards */
- gpr_atm min_timer;
+ grpc_millis min_timer;
/* Allow only one run_some_expired_timers at once */
gpr_spinlock checker_mu;
bool initialized;
@@ -227,18 +236,18 @@ struct shared_mutables {
static struct shared_mutables g_shared_mutables;
-static gpr_atm saturating_add(gpr_atm a, gpr_atm b) {
- if (a > GPR_ATM_MAX - b) {
- return GPR_ATM_MAX;
+static grpc_millis saturating_add(grpc_millis a, grpc_millis b) {
+ if (a > GRPC_MILLIS_INF_FUTURE - b) {
+ return GRPC_MILLIS_INF_FUTURE;
}
return a + b;
}
-static grpc_timer_check_result run_some_expired_timers(gpr_atm now,
- gpr_atm* next,
+static grpc_timer_check_result run_some_expired_timers(grpc_millis now,
+ grpc_millis* next,
grpc_error* error);
-static gpr_atm compute_min_deadline(timer_shard* shard) {
+static grpc_millis compute_min_deadline(timer_shard* shard) {
return grpc_timer_heap_is_empty(&shard->heap)
? saturating_add(shard->queue_deadline_cap, 1)
: grpc_timer_heap_top(&shard->heap)->deadline;
@@ -257,8 +266,11 @@ static void timer_list_init() {
g_shared_mutables.checker_mu = GPR_SPINLOCK_INITIALIZER;
gpr_mu_init(&g_shared_mutables.mu);
g_shared_mutables.min_timer = grpc_core::ExecCtx::Get()->Now();
+
+#if GPR_ARCH_64
gpr_tls_init(&g_last_seen_min_timer);
gpr_tls_set(&g_last_seen_min_timer, 0);
+#endif
for (i = 0; i < g_num_shards; i++) {
timer_shard* shard = &g_shards[i];
@@ -279,7 +291,7 @@ static void timer_list_init() {
static void timer_list_shutdown() {
size_t i;
run_some_expired_timers(
- GPR_ATM_MAX, nullptr,
+ GRPC_MILLIS_INF_FUTURE, nullptr,
GRPC_ERROR_CREATE_FROM_STATIC_STRING("Timer list shutdown"));
for (i = 0; i < g_num_shards; i++) {
timer_shard* shard = &g_shards[i];
@@ -287,7 +299,11 @@ static void timer_list_shutdown() {
grpc_timer_heap_destroy(&shard->heap);
}
gpr_mu_destroy(&g_shared_mutables.mu);
+
+#if GPR_ARCH_64
gpr_tls_destroy(&g_last_seen_min_timer);
+#endif
+
gpr_free(g_shards);
gpr_free(g_shard_queue);
g_shared_mutables.initialized = false;
@@ -346,7 +362,7 @@ static void timer_init(grpc_timer* timer, grpc_millis deadline,
#endif
if (grpc_timer_trace.enabled()) {
- gpr_log(GPR_INFO, "TIMER %p: SET %" PRIdPTR " now %" PRIdPTR " call %p[%p]",
+ gpr_log(GPR_INFO, "TIMER %p: SET %" PRId64 " now %" PRId64 " call %p[%p]",
timer, deadline, grpc_core::ExecCtx::Get()->Now(), closure,
closure->cb);
}
@@ -383,7 +399,7 @@ static void timer_init(grpc_timer* timer, grpc_millis deadline,
}
if (grpc_timer_trace.enabled()) {
gpr_log(GPR_INFO,
- " .. add to shard %d with queue_deadline_cap=%" PRIdPTR
+ " .. add to shard %d with queue_deadline_cap=%" PRId64
" => is_first_timer=%s",
static_cast<int>(shard - g_shards), shard->queue_deadline_cap,
is_first_timer ? "true" : "false");
@@ -404,15 +420,27 @@ static void timer_init(grpc_timer* timer, grpc_millis deadline,
if (is_first_timer) {
gpr_mu_lock(&g_shared_mutables.mu);
if (grpc_timer_trace.enabled()) {
- gpr_log(GPR_INFO, " .. old shard min_deadline=%" PRIdPTR,
+ gpr_log(GPR_INFO, " .. old shard min_deadline=%" PRId64,
shard->min_deadline);
}
if (deadline < shard->min_deadline) {
- gpr_atm old_min_deadline = g_shard_queue[0]->min_deadline;
+ grpc_millis old_min_deadline = g_shard_queue[0]->min_deadline;
shard->min_deadline = deadline;
note_deadline_change(shard);
if (shard->shard_queue_index == 0 && deadline < old_min_deadline) {
- gpr_atm_no_barrier_store(&g_shared_mutables.min_timer, deadline);
+#if GPR_ARCH_64
+ // TODO: sreek - Using c-style cast here. static_cast<> gives an error
+ // (on mac platforms complaining that gpr_atm* is (long *) while
+ // (&g_shared_mutables.min_timer) is a (long long *). The cast should be
+ // safe since we know that both are pointer types and 64-bit wide.
+ gpr_atm_no_barrier_store((gpr_atm*)(&g_shared_mutables.min_timer),
+ deadline);
+#else
+ // On 32-bit systems, gpr_atm_no_barrier_store does not work on 64-bit
+ // types (like grpc_millis). So all reads and writes to
+ // g_shared_mutables.min_timer varialbe under g_shared_mutables.mu
+ g_shared_mutables.min_timer = deadline;
+#endif
grpc_kick_poller();
}
}
@@ -421,8 +449,10 @@ static void timer_init(grpc_timer* timer, grpc_millis deadline,
}
static void timer_consume_kick(void) {
- /* force re-evaluation of last seeen min */
+#if GPR_ARCH_64
+ /* Force re-evaluation of last seen min */
gpr_tls_set(&g_last_seen_min_timer, 0);
+#endif
}
static void timer_cancel(grpc_timer* timer) {
@@ -459,7 +489,7 @@ static void timer_cancel(grpc_timer* timer) {
'queue_deadline_cap') into into shard->heap.
Returns 'true' if shard->heap has atleast ONE element
REQUIRES: shard->mu locked */
-static int refill_heap(timer_shard* shard, gpr_atm now) {
+static int refill_heap(timer_shard* shard, grpc_millis now) {
/* Compute the new queue window width and bound by the limits: */
double computed_deadline_delta =
grpc_time_averaged_stats_update_average(&shard->stats) *
@@ -472,10 +502,10 @@ static int refill_heap(timer_shard* shard, gpr_atm now) {
/* Compute the new cap and put all timers under it into the queue: */
shard->queue_deadline_cap =
saturating_add(GPR_MAX(now, shard->queue_deadline_cap),
- static_cast<gpr_atm>(deadline_delta * 1000.0));
+ static_cast<grpc_millis>(deadline_delta * 1000.0));
if (grpc_timer_check_trace.enabled()) {
- gpr_log(GPR_INFO, " .. shard[%d]->queue_deadline_cap --> %" PRIdPTR,
+ gpr_log(GPR_INFO, " .. shard[%d]->queue_deadline_cap --> %" PRId64,
static_cast<int>(shard - g_shards), shard->queue_deadline_cap);
}
for (timer = shard->list.next; timer != &shard->list; timer = next) {
@@ -483,7 +513,7 @@ static int refill_heap(timer_shard* shard, gpr_atm now) {
if (timer->deadline < shard->queue_deadline_cap) {
if (grpc_timer_check_trace.enabled()) {
- gpr_log(GPR_INFO, " .. add timer with deadline %" PRIdPTR " to heap",
+ gpr_log(GPR_INFO, " .. add timer with deadline %" PRId64 " to heap",
timer->deadline);
}
list_remove(timer);
@@ -496,7 +526,7 @@ static int refill_heap(timer_shard* shard, gpr_atm now) {
/* This pops the next non-cancelled timer with deadline <= now from the
queue, or returns NULL if there isn't one.
REQUIRES: shard->mu locked */
-static grpc_timer* pop_one(timer_shard* shard, gpr_atm now) {
+static grpc_timer* pop_one(timer_shard* shard, grpc_millis now) {
grpc_timer* timer;
for (;;) {
if (grpc_timer_check_trace.enabled()) {
@@ -511,12 +541,12 @@ static grpc_timer* pop_one(timer_shard* shard, gpr_atm now) {
timer = grpc_timer_heap_top(&shard->heap);
if (grpc_timer_check_trace.enabled()) {
gpr_log(GPR_INFO,
- " .. check top timer deadline=%" PRIdPTR " now=%" PRIdPTR,
+ " .. check top timer deadline=%" PRId64 " now=%" PRId64,
timer->deadline, now);
}
if (timer->deadline > now) return nullptr;
if (grpc_timer_trace.enabled()) {
- gpr_log(GPR_INFO, "TIMER %p: FIRE %" PRIdPTR "ms late via %s scheduler",
+ gpr_log(GPR_INFO, "TIMER %p: FIRE %" PRId64 "ms late via %s scheduler",
timer, now - timer->deadline,
timer->closure->scheduler->vtable->name);
}
@@ -527,8 +557,8 @@ static grpc_timer* pop_one(timer_shard* shard, gpr_atm now) {
}
/* REQUIRES: shard->mu unlocked */
-static size_t pop_timers(timer_shard* shard, gpr_atm now,
- gpr_atm* new_min_deadline, grpc_error* error) {
+static size_t pop_timers(timer_shard* shard, grpc_millis now,
+ grpc_millis* new_min_deadline, grpc_error* error) {
size_t n = 0;
grpc_timer* timer;
gpr_mu_lock(&shard->mu);
@@ -546,13 +576,27 @@ static size_t pop_timers(timer_shard* shard, gpr_atm now,
return n;
}
-static grpc_timer_check_result run_some_expired_timers(gpr_atm now,
- gpr_atm* next,
+static grpc_timer_check_result run_some_expired_timers(grpc_millis now,
+ grpc_millis* next,
grpc_error* error) {
grpc_timer_check_result result = GRPC_TIMERS_NOT_CHECKED;
- gpr_atm min_timer = gpr_atm_no_barrier_load(&g_shared_mutables.min_timer);
+#if GPR_ARCH_64
+ // TODO: sreek - Using c-style cast here. static_cast<> gives an error (on
+ // mac platforms complaining that gpr_atm* is (long *) while
+ // (&g_shared_mutables.min_timer) is a (long long *). The cast should be
+ // safe since we know that both are pointer types and 64-bit wide
+ grpc_millis min_timer = static_cast<grpc_millis>(
+ gpr_atm_no_barrier_load((gpr_atm*)(&g_shared_mutables.min_timer)));
gpr_tls_set(&g_last_seen_min_timer, min_timer);
+#else
+ // On 32-bit systems, gpr_atm_no_barrier_load does not work on 64-bit types
+ // (like grpc_millis). So all reads and writes to g_shared_mutables.min_timer
+ // are done under g_shared_mutables.mu
+ gpr_mu_lock(&g_shared_mutables.mu);
+ grpc_millis min_timer = g_shared_mutables.min_timer;
+ gpr_mu_unlock(&g_shared_mutables.mu);
+#endif
if (now < min_timer) {
if (next != nullptr) *next = GPR_MIN(*next, min_timer);
return GRPC_TIMERS_CHECKED_AND_EMPTY;
@@ -563,14 +607,15 @@ static grpc_timer_check_result run_some_expired_timers(gpr_atm now,
result = GRPC_TIMERS_CHECKED_AND_EMPTY;
if (grpc_timer_check_trace.enabled()) {
- gpr_log(GPR_INFO, " .. shard[%d]->min_deadline = %" PRIdPTR,
+ gpr_log(GPR_INFO, " .. shard[%d]->min_deadline = %" PRId64,
static_cast<int>(g_shard_queue[0] - g_shards),
g_shard_queue[0]->min_deadline);
}
while (g_shard_queue[0]->min_deadline < now ||
- (now != GPR_ATM_MAX && g_shard_queue[0]->min_deadline == now)) {
- gpr_atm new_min_deadline;
+ (now != GRPC_MILLIS_INF_FUTURE &&
+ g_shard_queue[0]->min_deadline == now)) {
+ grpc_millis new_min_deadline;
/* For efficiency, we pop as many available timers as we can from the
shard. This may violate perfect timer deadline ordering, but that
@@ -582,8 +627,8 @@ static grpc_timer_check_result run_some_expired_timers(gpr_atm now,
if (grpc_timer_check_trace.enabled()) {
gpr_log(GPR_INFO,
" .. result --> %d"
- ", shard[%d]->min_deadline %" PRIdPTR " --> %" PRIdPTR
- ", now=%" PRIdPTR,
+ ", shard[%d]->min_deadline %" PRId64 " --> %" PRId64
+ ", now=%" PRId64,
result, static_cast<int>(g_shard_queue[0] - g_shards),
g_shard_queue[0]->min_deadline, new_min_deadline, now);
}
@@ -601,8 +646,19 @@ static grpc_timer_check_result run_some_expired_timers(gpr_atm now,
*next = GPR_MIN(*next, g_shard_queue[0]->min_deadline);
}
- gpr_atm_no_barrier_store(&g_shared_mutables.min_timer,
+#if GPR_ARCH_64
+ // TODO: sreek - Using c-style cast here. static_cast<> gives an error (on
+ // mac platforms complaining that gpr_atm* is (long *) while
+ // (&g_shared_mutables.min_timer) is a (long long *). The cast should be
+ // safe since we know that both are pointer types and 64-bit wide
+ gpr_atm_no_barrier_store((gpr_atm*)(&g_shared_mutables.min_timer),
g_shard_queue[0]->min_deadline);
+#else
+ // On 32-bit systems, gpr_atm_no_barrier_store does not work on 64-bit
+ // types (like grpc_millis). So all reads and writes to
+ // g_shared_mutables.min_timer are done under g_shared_mutables.mu
+ g_shared_mutables.min_timer = g_shard_queue[0]->min_deadline;
+#endif
gpr_mu_unlock(&g_shared_mutables.mu);
gpr_spinlock_unlock(&g_shared_mutables.checker_mu);
}
@@ -616,17 +672,28 @@ static grpc_timer_check_result timer_check(grpc_millis* next) {
// prelude
grpc_millis now = grpc_core::ExecCtx::Get()->Now();
+#if GPR_ARCH_64
/* fetch from a thread-local first: this avoids contention on a globally
mutable cacheline in the common case */
grpc_millis min_timer = gpr_tls_get(&g_last_seen_min_timer);
+#else
+ // On 32-bit systems, we currently do not have thread local support for 64-bit
+ // types. In this case, directly read from g_shared_mutables.min_timer.
+ // Also, note that on 32-bit systems, gpr_atm_no_barrier_store does not work
+ // on 64-bit types (like grpc_millis). So all reads and writes to
+ // g_shared_mutables.min_timer are done under g_shared_mutables.mu
+ gpr_mu_lock(&g_shared_mutables.mu);
+ grpc_millis min_timer = g_shared_mutables.min_timer;
+ gpr_mu_unlock(&g_shared_mutables.mu);
+#endif
+
if (now < min_timer) {
if (next != nullptr) {
*next = GPR_MIN(*next, min_timer);
}
if (grpc_timer_check_trace.enabled()) {
- gpr_log(GPR_INFO,
- "TIMER CHECK SKIP: now=%" PRIdPTR " min_timer=%" PRIdPTR, now,
- min_timer);
+ gpr_log(GPR_INFO, "TIMER CHECK SKIP: now=%" PRId64 " min_timer=%" PRId64,
+ now, min_timer);
}
return GRPC_TIMERS_CHECKED_AND_EMPTY;
}
@@ -642,13 +709,19 @@ static grpc_timer_check_result timer_check(grpc_millis* next) {
if (next == nullptr) {
next_str = gpr_strdup("NULL");
} else {
- gpr_asprintf(&next_str, "%" PRIdPTR, *next);
+ gpr_asprintf(&next_str, "%" PRId64, *next);
}
+#if GPR_ARCH_64
gpr_log(GPR_INFO,
- "TIMER CHECK BEGIN: now=%" PRIdPTR " next=%s tls_min=%" PRIdPTR
- " glob_min=%" PRIdPTR,
- now, next_str, gpr_tls_get(&g_last_seen_min_timer),
- gpr_atm_no_barrier_load(&g_shared_mutables.min_timer));
+ "TIMER CHECK BEGIN: now=%" PRId64 " next=%s tls_min=%" PRId64
+ " glob_min=%" PRId64,
+ now, next_str, min_timer,
+ static_cast<grpc_millis>(gpr_atm_no_barrier_load(
+ (gpr_atm*)(&g_shared_mutables.min_timer))));
+#else
+ gpr_log(GPR_INFO, "TIMER CHECK BEGIN: now=%" PRId64 " next=%s min=%" PRId64,
+ now, next_str, min_timer);
+#endif
gpr_free(next_str);
}
// actual code
@@ -660,7 +733,7 @@ static grpc_timer_check_result timer_check(grpc_millis* next) {
if (next == nullptr) {
next_str = gpr_strdup("NULL");
} else {
- gpr_asprintf(&next_str, "%" PRIdPTR, *next);
+ gpr_asprintf(&next_str, "%" PRId64, *next);
}
gpr_log(GPR_INFO, "TIMER CHECK END: r=%d; next=%s", r, next_str);
gpr_free(next_str);
diff --git a/src/core/lib/iomgr/timer_manager.cc b/src/core/lib/iomgr/timer_manager.cc
index 35e7914568..26de216671 100644
--- a/src/core/lib/iomgr/timer_manager.cc
+++ b/src/core/lib/iomgr/timer_manager.cc
@@ -172,7 +172,7 @@ static bool wait_until(grpc_millis next) {
if (grpc_timer_check_trace.enabled()) {
grpc_millis wait_time = next - grpc_core::ExecCtx::Get()->Now();
- gpr_log(GPR_INFO, "sleep for a %" PRIdPTR " milliseconds", wait_time);
+ gpr_log(GPR_INFO, "sleep for a %" PRId64 " milliseconds", wait_time);
}
} else { // g_timed_waiter == true && next >= g_timed_waiter_deadline
next = GRPC_MILLIS_INF_FUTURE;
@@ -265,7 +265,7 @@ static void timer_thread_cleanup(completed_thread* ct) {
static void timer_thread(void* completed_thread_ptr) {
// this threads exec_ctx: we try to run things through to completion here
// since it's easy to spin up new threads
- grpc_core::ExecCtx exec_ctx(0);
+ grpc_core::ExecCtx exec_ctx(GRPC_EXEC_CTX_FLAG_IS_INTERNAL_THREAD);
timer_main_loop();
timer_thread_cleanup(static_cast<completed_thread*>(completed_thread_ptr));
@@ -277,7 +277,6 @@ static void start_threads(void) {
g_threaded = true;
start_timer_thread_and_unlock();
} else {
- g_threaded = false;
gpr_mu_unlock(&g_mu);
}
}
diff --git a/src/core/lib/iomgr/udp_server.cc b/src/core/lib/iomgr/udp_server.cc
index 51d17eb174..3dd7cab855 100644
--- a/src/core/lib/iomgr/udp_server.cc
+++ b/src/core/lib/iomgr/udp_server.cc
@@ -152,7 +152,7 @@ GrpcUdpListener::GrpcUdpListener(grpc_udp_server* server, int fd,
grpc_sockaddr_to_string(&addr_str, addr, 1);
gpr_asprintf(&name, "udp-server-listener:%s", addr_str);
gpr_free(addr_str);
- emfd_ = grpc_fd_create(fd, name);
+ emfd_ = grpc_fd_create(fd, name, true);
memcpy(&addr_, addr, sizeof(grpc_resolved_address));
GPR_ASSERT(emfd_);
gpr_free(name);
@@ -300,8 +300,7 @@ void GrpcUdpListener::OrphanFd() {
grpc_schedule_on_exec_ctx);
/* Because at this point, all listening sockets have been shutdown already, no
* need to call OnFdAboutToOrphan() to notify the handler again. */
- grpc_fd_orphan(emfd_, &destroyed_closure_, nullptr,
- false /* already_closed */, "udp_listener_shutdown");
+ grpc_fd_orphan(emfd_, &destroyed_closure_, nullptr, "udp_listener_shutdown");
}
void grpc_udp_server_destroy(grpc_udp_server* s, grpc_closure* on_done) {
diff --git a/src/core/lib/json/json.cc b/src/core/lib/json/json.cc
index 816241bbf0..e78b73cefd 100644
--- a/src/core/lib/json/json.cc
+++ b/src/core/lib/json/json.cc
@@ -18,10 +18,12 @@
#include <grpc/support/port_platform.h>
+#include <inttypes.h>
#include <string.h>
#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
+#include <grpc/support/string_util.h>
#include "src/core/lib/json/json.h"
@@ -56,6 +58,8 @@ void grpc_json_destroy(grpc_json* json) {
grpc_json* grpc_json_link_child(grpc_json* parent, grpc_json* child,
grpc_json* sibling) {
+ // link child up to parent
+ child->parent = parent;
// first child case.
if (parent->child == nullptr) {
GPR_ASSERT(sibling == nullptr);
@@ -79,8 +83,15 @@ grpc_json* grpc_json_create_child(grpc_json* sibling, grpc_json* parent,
grpc_json* child = grpc_json_create(type);
grpc_json_link_child(parent, child, sibling);
child->owns_value = owns_value;
- child->parent = parent;
child->value = value;
child->key = key;
return child;
}
+
+grpc_json* grpc_json_add_number_string_child(grpc_json* parent, grpc_json* it,
+ const char* name, int64_t num) {
+ char* num_str;
+ gpr_asprintf(&num_str, "%" PRId64, num);
+ return grpc_json_create_child(it, parent, name, num_str, GRPC_JSON_STRING,
+ true);
+}
diff --git a/src/core/lib/json/json.h b/src/core/lib/json/json.h
index f93b43048b..8173845c72 100644
--- a/src/core/lib/json/json.h
+++ b/src/core/lib/json/json.h
@@ -91,4 +91,9 @@ grpc_json* grpc_json_create_child(grpc_json* sibling, grpc_json* parent,
const char* key, const char* value,
grpc_json_type type, bool owns_value);
+/* Creates a child json string object from the integer num, then links the
+ json object into the parent's json tree */
+grpc_json* grpc_json_add_number_string_child(grpc_json* parent, grpc_json* it,
+ const char* name, int64_t num);
+
#endif /* GRPC_CORE_LIB_JSON_JSON_H */
diff --git a/src/core/lib/security/context/security_context.cc b/src/core/lib/security/context/security_context.cc
index 14051a3f00..94c9c69fcd 100644
--- a/src/core/lib/security/context/security_context.cc
+++ b/src/core/lib/security/context/security_context.cc
@@ -21,6 +21,7 @@
#include <string.h>
#include "src/core/lib/channel/channel_args.h"
+#include "src/core/lib/gpr/arena.h"
#include "src/core/lib/gpr/string.h"
#include "src/core/lib/security/context/security_context.h"
#include "src/core/lib/surface/api_trace.h"
@@ -49,7 +50,7 @@ grpc_call_error grpc_call_set_credentials(grpc_call* call,
ctx = static_cast<grpc_client_security_context*>(
grpc_call_context_get(call, GRPC_CONTEXT_SECURITY));
if (ctx == nullptr) {
- ctx = grpc_client_security_context_create();
+ ctx = grpc_client_security_context_create(grpc_call_get_arena(call));
ctx->creds = grpc_call_credentials_ref(creds);
grpc_call_context_set(call, GRPC_CONTEXT_SECURITY, ctx,
grpc_client_security_context_destroy);
@@ -81,9 +82,10 @@ void grpc_auth_context_release(grpc_auth_context* context) {
/* --- grpc_client_security_context --- */
-grpc_client_security_context* grpc_client_security_context_create(void) {
+grpc_client_security_context* grpc_client_security_context_create(
+ gpr_arena* arena) {
return static_cast<grpc_client_security_context*>(
- gpr_zalloc(sizeof(grpc_client_security_context)));
+ gpr_arena_alloc(arena, sizeof(grpc_client_security_context)));
}
void grpc_client_security_context_destroy(void* ctx) {
@@ -95,14 +97,13 @@ void grpc_client_security_context_destroy(void* ctx) {
if (c->extension.instance != nullptr && c->extension.destroy != nullptr) {
c->extension.destroy(c->extension.instance);
}
- gpr_free(ctx);
}
/* --- grpc_server_security_context --- */
-
-grpc_server_security_context* grpc_server_security_context_create(void) {
+grpc_server_security_context* grpc_server_security_context_create(
+ gpr_arena* arena) {
return static_cast<grpc_server_security_context*>(
- gpr_zalloc(sizeof(grpc_server_security_context)));
+ gpr_arena_alloc(arena, sizeof(grpc_server_security_context)));
}
void grpc_server_security_context_destroy(void* ctx) {
@@ -112,7 +113,6 @@ void grpc_server_security_context_destroy(void* ctx) {
if (c->extension.instance != nullptr && c->extension.destroy != nullptr) {
c->extension.destroy(c->extension.instance);
}
- gpr_free(ctx);
}
/* --- grpc_auth_context --- */
diff --git a/src/core/lib/security/context/security_context.h b/src/core/lib/security/context/security_context.h
index e782e4f28f..a8e1c3fd64 100644
--- a/src/core/lib/security/context/security_context.h
+++ b/src/core/lib/security/context/security_context.h
@@ -26,6 +26,8 @@
extern grpc_core::DebugOnlyTraceFlag grpc_trace_auth_context_refcount;
+struct gpr_arena;
+
/* --- grpc_auth_context ---
High level authentication context object. Can optionally be chained. */
@@ -89,7 +91,8 @@ typedef struct {
grpc_security_context_extension extension;
} grpc_client_security_context;
-grpc_client_security_context* grpc_client_security_context_create(void);
+grpc_client_security_context* grpc_client_security_context_create(
+ gpr_arena* arena);
void grpc_client_security_context_destroy(void* ctx);
/* --- grpc_server_security_context ---
@@ -101,7 +104,8 @@ typedef struct {
grpc_security_context_extension extension;
} grpc_server_security_context;
-grpc_server_security_context* grpc_server_security_context_create(void);
+grpc_server_security_context* grpc_server_security_context_create(
+ gpr_arena* arena);
void grpc_server_security_context_destroy(void* ctx);
/* --- Channel args for auth context --- */
diff --git a/src/core/lib/security/credentials/alts/check_gcp_environment_linux.cc b/src/core/lib/security/credentials/alts/check_gcp_environment_linux.cc
index 7c4d7a71cd..8454fd7558 100644
--- a/src/core/lib/security/credentials/alts/check_gcp_environment_linux.cc
+++ b/src/core/lib/security/credentials/alts/check_gcp_environment_linux.cc
@@ -41,8 +41,9 @@ namespace internal {
bool check_bios_data(const char* bios_data_file) {
char* bios_data = read_bios_file(bios_data_file);
- bool result = (!strcmp(bios_data, GRPC_ALTS_EXPECT_NAME_GOOGLE)) ||
- (!strcmp(bios_data, GRPC_ALTS_EXPECT_NAME_GCE));
+ bool result =
+ bios_data && ((!strcmp(bios_data, GRPC_ALTS_EXPECT_NAME_GOOGLE)) ||
+ (!strcmp(bios_data, GRPC_ALTS_EXPECT_NAME_GCE)));
gpr_free(bios_data);
return result;
}
diff --git a/src/core/lib/security/credentials/alts/grpc_alts_credentials_client_options.cc b/src/core/lib/security/credentials/alts/grpc_alts_credentials_client_options.cc
index 0a39c6c485..118d18d119 100644
--- a/src/core/lib/security/credentials/alts/grpc_alts_credentials_client_options.cc
+++ b/src/core/lib/security/credentials/alts/grpc_alts_credentials_client_options.cc
@@ -72,7 +72,8 @@ static void target_service_account_destroy(
static const grpc_alts_credentials_options_vtable vtable = {
alts_client_options_copy, alts_client_options_destroy};
-grpc_alts_credentials_options* grpc_alts_credentials_client_options_create() {
+grpc_alts_credentials_options* grpc_alts_credentials_client_options_create(
+ void) {
auto client_options = static_cast<grpc_alts_credentials_client_options*>(
gpr_zalloc(sizeof(grpc_alts_credentials_client_options)));
client_options->base.vtable = &vtable;
diff --git a/src/core/lib/security/credentials/alts/grpc_alts_credentials_server_options.cc b/src/core/lib/security/credentials/alts/grpc_alts_credentials_server_options.cc
index 62aa7a620a..1a59c45675 100644
--- a/src/core/lib/security/credentials/alts/grpc_alts_credentials_server_options.cc
+++ b/src/core/lib/security/credentials/alts/grpc_alts_credentials_server_options.cc
@@ -36,7 +36,8 @@ static void alts_server_options_destroy(
static const grpc_alts_credentials_options_vtable vtable = {
alts_server_options_copy, alts_server_options_destroy};
-grpc_alts_credentials_options* grpc_alts_credentials_server_options_create() {
+grpc_alts_credentials_options* grpc_alts_credentials_server_options_create(
+ void) {
grpc_alts_credentials_server_options* server_options =
static_cast<grpc_alts_credentials_server_options*>(
gpr_zalloc(sizeof(*server_options)));
diff --git a/src/core/lib/security/credentials/google_default/google_default_credentials.cc b/src/core/lib/security/credentials/google_default/google_default_credentials.cc
index 03e8cb3b40..c456ffaf5d 100644
--- a/src/core/lib/security/credentials/google_default/google_default_credentials.cc
+++ b/src/core/lib/security/credentials/google_default/google_default_credentials.cc
@@ -35,6 +35,7 @@
#include "src/core/lib/iomgr/load_file.h"
#include "src/core/lib/iomgr/polling_entity.h"
#include "src/core/lib/security/credentials/alts/alts_credentials.h"
+#include "src/core/lib/security/credentials/alts/check_gcp_environment.h"
#include "src/core/lib/security/credentials/google_default/google_default_credentials.h"
#include "src/core/lib/security/credentials/jwt/jwt_credentials.h"
#include "src/core/lib/security/credentials/oauth2/oauth2_credentials.h"
@@ -51,8 +52,9 @@
static grpc_channel_credentials* g_default_credentials = nullptr;
static int g_compute_engine_detection_done = 0;
static gpr_mu g_state_mu;
-static gpr_mu* g_polling_mu;
static gpr_once g_once = GPR_ONCE_INIT;
+static grpc_core::internal::grpc_gce_tenancy_checker g_gce_tenancy_checker =
+ grpc_alts_is_running_on_gcp;
static void init_default_credentials(void) { gpr_mu_init(&g_state_mu); }
@@ -111,103 +113,6 @@ static grpc_channel_credentials_vtable google_default_credentials_vtable = {
google_default_credentials_destruct,
google_default_create_security_connector, nullptr};
-static void on_compute_engine_detection_http_response(void* user_data,
- grpc_error* error) {
- compute_engine_detector* detector =
- static_cast<compute_engine_detector*>(user_data);
- if (error == GRPC_ERROR_NONE && detector->response.status == 200 &&
- detector->response.hdr_count > 0) {
- /* Internet providers can return a generic response to all requests, so
- it is necessary to check that metadata header is present also. */
- size_t i;
- for (i = 0; i < detector->response.hdr_count; i++) {
- grpc_http_header* header = &detector->response.hdrs[i];
- if (strcmp(header->key, "Metadata-Flavor") == 0 &&
- strcmp(header->value, "Google") == 0) {
- detector->success = 1;
- break;
- }
- }
- }
- gpr_mu_lock(g_polling_mu);
- detector->is_done = 1;
- GRPC_LOG_IF_ERROR(
- "Pollset kick",
- grpc_pollset_kick(grpc_polling_entity_pollset(&detector->pollent),
- nullptr));
- gpr_mu_unlock(g_polling_mu);
-}
-
-static void destroy_pollset(void* p, grpc_error* e) {
- grpc_pollset_destroy(static_cast<grpc_pollset*>(p));
-}
-
-static int is_stack_running_on_compute_engine() {
- compute_engine_detector detector;
- grpc_httpcli_request request;
- grpc_httpcli_context context;
- grpc_closure destroy_closure;
-
- /* The http call is local. If it takes more than one sec, it is for sure not
- on compute engine. */
- grpc_millis max_detection_delay = GPR_MS_PER_SEC;
-
- grpc_pollset* pollset =
- static_cast<grpc_pollset*>(gpr_zalloc(grpc_pollset_size()));
- grpc_pollset_init(pollset, &g_polling_mu);
- detector.pollent = grpc_polling_entity_create_from_pollset(pollset);
- detector.is_done = 0;
- detector.success = 0;
-
- memset(&detector.response, 0, sizeof(detector.response));
- memset(&request, 0, sizeof(grpc_httpcli_request));
- request.host = (char*)GRPC_COMPUTE_ENGINE_DETECTION_HOST;
- request.http.path = (char*)"/";
-
- grpc_httpcli_context_init(&context);
-
- grpc_resource_quota* resource_quota =
- grpc_resource_quota_create("google_default_credentials");
- grpc_httpcli_get(
- &context, &detector.pollent, resource_quota, &request,
- grpc_core::ExecCtx::Get()->Now() + max_detection_delay,
- GRPC_CLOSURE_CREATE(on_compute_engine_detection_http_response, &detector,
- grpc_schedule_on_exec_ctx),
- &detector.response);
- grpc_resource_quota_unref_internal(resource_quota);
-
- grpc_core::ExecCtx::Get()->Flush();
-
- /* Block until we get the response. This is not ideal but this should only be
- called once for the lifetime of the process by the default credentials. */
- gpr_mu_lock(g_polling_mu);
- while (!detector.is_done) {
- grpc_pollset_worker* worker = nullptr;
- if (!GRPC_LOG_IF_ERROR(
- "pollset_work",
- grpc_pollset_work(grpc_polling_entity_pollset(&detector.pollent),
- &worker, GRPC_MILLIS_INF_FUTURE))) {
- detector.is_done = 1;
- detector.success = 0;
- }
- }
- gpr_mu_unlock(g_polling_mu);
-
- grpc_httpcli_context_destroy(&context);
- GRPC_CLOSURE_INIT(&destroy_closure, destroy_pollset,
- grpc_polling_entity_pollset(&detector.pollent),
- grpc_schedule_on_exec_ctx);
- grpc_pollset_shutdown(grpc_polling_entity_pollset(&detector.pollent),
- &destroy_closure);
- g_polling_mu = nullptr;
- grpc_core::ExecCtx::Get()->Flush();
-
- gpr_free(grpc_polling_entity_pollset(&detector.pollent));
- grpc_http_response_destroy(&detector.response);
-
- return detector.success;
-}
-
/* Takes ownership of creds_path if not NULL. */
static grpc_error* create_default_creds_from_path(
char* creds_path, grpc_call_credentials** creds) {
@@ -305,7 +210,7 @@ grpc_channel_credentials* grpc_google_default_credentials_create(void) {
/* At last try to see if we're on compute engine (do the detection only once
since it requires a network test). */
if (!g_compute_engine_detection_done) {
- int need_compute_engine_creds = is_stack_running_on_compute_engine();
+ int need_compute_engine_creds = g_gce_tenancy_checker();
g_compute_engine_detection_done = 1;
if (need_compute_engine_creds) {
call_creds = grpc_google_compute_engine_credentials_create(nullptr);
@@ -326,7 +231,8 @@ end:
creds->base.vtable = &google_default_credentials_vtable;
creds->base.type = GRPC_CHANNEL_CREDENTIALS_TYPE_GOOGLE_DEFAULT;
gpr_ref_init(&creds->base.refcount, 1);
- creds->ssl_creds = grpc_ssl_credentials_create(nullptr, nullptr, nullptr);
+ creds->ssl_creds =
+ grpc_ssl_credentials_create(nullptr, nullptr, nullptr, nullptr);
GPR_ASSERT(creds->ssl_creds != nullptr);
grpc_alts_credentials_options* options =
grpc_alts_credentials_client_options_create();
@@ -353,6 +259,16 @@ end:
return result;
}
+namespace grpc_core {
+namespace internal {
+
+void set_gce_tenancy_checker_for_testing(grpc_gce_tenancy_checker checker) {
+ g_gce_tenancy_checker = checker;
+}
+
+} // namespace internal
+} // namespace grpc_core
+
void grpc_flush_cached_google_default_credentials(void) {
grpc_core::ExecCtx exec_ctx;
gpr_once_init(&g_once, init_default_credentials);
diff --git a/src/core/lib/security/credentials/google_default/google_default_credentials.h b/src/core/lib/security/credentials/google_default/google_default_credentials.h
index 9b4063c775..a7dd0ea8ae 100644
--- a/src/core/lib/security/credentials/google_default/google_default_credentials.h
+++ b/src/core/lib/security/credentials/google_default/google_default_credentials.h
@@ -47,5 +47,15 @@ typedef struct {
void grpc_flush_cached_google_default_credentials(void);
+namespace grpc_core {
+namespace internal {
+
+typedef bool (*grpc_gce_tenancy_checker)(void);
+
+void set_gce_tenancy_checker_for_testing(grpc_gce_tenancy_checker checker);
+
+} // namespace internal
+} // namespace grpc_core
+
#endif /* GRPC_CORE_LIB_SECURITY_CREDENTIALS_GOOGLE_DEFAULT_GOOGLE_DEFAULT_CREDENTIALS_H \
*/
diff --git a/src/core/lib/security/credentials/jwt/json_token.h b/src/core/lib/security/credentials/jwt/json_token.h
index d0fb4ebd0a..3ed990140d 100644
--- a/src/core/lib/security/credentials/jwt/json_token.h
+++ b/src/core/lib/security/credentials/jwt/json_token.h
@@ -21,6 +21,8 @@
#include <grpc/support/port_platform.h>
+#include "src/core/tsi/grpc_shadow_boringssl.h"
+
#include <grpc/slice.h>
#include <openssl/rsa.h>
diff --git a/src/core/lib/security/credentials/jwt/jwt_verifier.cc b/src/core/lib/security/credentials/jwt/jwt_verifier.cc
index 5c47276e32..c7d1b36ff0 100644
--- a/src/core/lib/security/credentials/jwt/jwt_verifier.cc
+++ b/src/core/lib/security/credentials/jwt/jwt_verifier.cc
@@ -18,6 +18,8 @@
#include <grpc/support/port_platform.h>
+#include "src/core/tsi/grpc_shadow_boringssl.h"
+
#include "src/core/lib/security/credentials/jwt/jwt_verifier.h"
#include <limits.h>
diff --git a/src/core/lib/security/credentials/local/local_credentials.cc b/src/core/lib/security/credentials/local/local_credentials.cc
new file mode 100644
index 0000000000..9a2f646ba5
--- /dev/null
+++ b/src/core/lib/security/credentials/local/local_credentials.cc
@@ -0,0 +1,77 @@
+/*
+ *
+ * Copyright 2018 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/security/credentials/local/local_credentials.h"
+
+#include <grpc/grpc.h>
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+
+#include "src/core/lib/channel/channel_args.h"
+#include "src/core/lib/security/security_connector/local_security_connector.h"
+
+#define GRPC_CREDENTIALS_TYPE_LOCAL "Local"
+
+static void local_credentials_destruct(grpc_channel_credentials* creds) {}
+
+static void local_server_credentials_destruct(grpc_server_credentials* creds) {}
+
+static grpc_security_status local_create_security_connector(
+ grpc_channel_credentials* creds,
+ grpc_call_credentials* request_metadata_creds, const char* target_name,
+ const grpc_channel_args* args, grpc_channel_security_connector** sc,
+ grpc_channel_args** new_args) {
+ return grpc_local_channel_security_connector_create(
+ creds, request_metadata_creds, args, target_name, sc);
+}
+
+static grpc_security_status local_server_create_security_connector(
+ grpc_server_credentials* creds, grpc_server_security_connector** sc) {
+ return grpc_local_server_security_connector_create(creds, sc);
+}
+
+static const grpc_channel_credentials_vtable local_credentials_vtable = {
+ local_credentials_destruct, local_create_security_connector,
+ /*duplicate_without_call_credentials=*/nullptr};
+
+static const grpc_server_credentials_vtable local_server_credentials_vtable = {
+ local_server_credentials_destruct, local_server_create_security_connector};
+
+grpc_channel_credentials* grpc_local_credentials_create(
+ grpc_local_connect_type connect_type) {
+ auto creds = static_cast<grpc_local_credentials*>(
+ gpr_zalloc(sizeof(grpc_local_credentials)));
+ creds->connect_type = connect_type;
+ creds->base.type = GRPC_CREDENTIALS_TYPE_LOCAL;
+ creds->base.vtable = &local_credentials_vtable;
+ gpr_ref_init(&creds->base.refcount, 1);
+ return &creds->base;
+}
+
+grpc_server_credentials* grpc_local_server_credentials_create(
+ grpc_local_connect_type connect_type) {
+ auto creds = static_cast<grpc_local_server_credentials*>(
+ gpr_zalloc(sizeof(grpc_local_server_credentials)));
+ creds->connect_type = connect_type;
+ creds->base.type = GRPC_CREDENTIALS_TYPE_LOCAL;
+ creds->base.vtable = &local_server_credentials_vtable;
+ gpr_ref_init(&creds->base.refcount, 1);
+ return &creds->base;
+}
diff --git a/src/core/lib/security/credentials/local/local_credentials.h b/src/core/lib/security/credentials/local/local_credentials.h
new file mode 100644
index 0000000000..47358b04bc
--- /dev/null
+++ b/src/core/lib/security/credentials/local/local_credentials.h
@@ -0,0 +1,40 @@
+/*
+ *
+ * Copyright 2018 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_SECURITY_CREDENTIALS_LOCAL_LOCAL_CREDENTIALS_H
+#define GRPC_CORE_LIB_SECURITY_CREDENTIALS_LOCAL_LOCAL_CREDENTIALS_H
+
+#include <grpc/support/port_platform.h>
+
+#include <grpc/grpc_security.h>
+
+#include "src/core/lib/security/credentials/credentials.h"
+
+/* Main struct for grpc local channel credential. */
+typedef struct grpc_local_credentials {
+ grpc_channel_credentials base;
+ grpc_local_connect_type connect_type;
+} grpc_local_credentials;
+
+/* Main struct for grpc local server credential. */
+typedef struct grpc_local_server_credentials {
+ grpc_server_credentials base;
+ grpc_local_connect_type connect_type;
+} grpc_local_server_credentials;
+
+#endif /* GRPC_CORE_LIB_SECURITY_CREDENTIALS_LOCAL_LOCAL_CREDENTIALS_H */
diff --git a/src/core/lib/security/credentials/oauth2/oauth2_credentials.cc b/src/core/lib/security/credentials/oauth2/oauth2_credentials.cc
index 2129029737..44b093557f 100644
--- a/src/core/lib/security/credentials/oauth2/oauth2_credentials.cc
+++ b/src/core/lib/security/credentials/oauth2/oauth2_credentials.cc
@@ -219,9 +219,11 @@ static void on_oauth2_token_fetcher_http_response(void* user_data,
gpr_mu_lock(&c->mu);
c->token_fetch_pending = false;
c->access_token_md = GRPC_MDELEM_REF(access_token_md);
- c->token_expiration = status == GRPC_CREDENTIALS_OK
- ? grpc_core::ExecCtx::Get()->Now() + token_lifetime
- : 0;
+ c->token_expiration =
+ status == GRPC_CREDENTIALS_OK
+ ? gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC),
+ gpr_time_from_millis(token_lifetime, GPR_TIMESPAN))
+ : gpr_inf_past(GPR_CLOCK_MONOTONIC);
grpc_oauth2_pending_get_request_metadata* pending_request =
c->pending_requests;
c->pending_requests = nullptr;
@@ -233,7 +235,7 @@ static void on_oauth2_token_fetcher_http_response(void* user_data,
access_token_md);
} else {
error = GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING(
- "Error occured when fetching oauth2 token.", &error, 1);
+ "Error occurred when fetching oauth2 token.", &error, 1);
}
GRPC_CLOSURE_SCHED(pending_request->on_request_metadata, error);
grpc_polling_entity_del_from_pollset_set(
@@ -259,8 +261,10 @@ static bool oauth2_token_fetcher_get_request_metadata(
grpc_mdelem cached_access_token_md = GRPC_MDNULL;
gpr_mu_lock(&c->mu);
if (!GRPC_MDISNULL(c->access_token_md) &&
- (c->token_expiration - grpc_core::ExecCtx::Get()->Now() >
- refresh_threshold)) {
+ gpr_time_cmp(
+ gpr_time_sub(c->token_expiration, gpr_now(GPR_CLOCK_MONOTONIC)),
+ gpr_time_from_seconds(GRPC_SECURE_TOKEN_REFRESH_THRESHOLD_SECS,
+ GPR_TIMESPAN)) > 0) {
cached_access_token_md = GRPC_MDELEM_REF(c->access_token_md);
}
if (!GRPC_MDISNULL(cached_access_token_md)) {
@@ -333,7 +337,7 @@ static void init_oauth2_token_fetcher(grpc_oauth2_token_fetcher_credentials* c,
c->base.type = GRPC_CALL_CREDENTIALS_TYPE_OAUTH2;
gpr_ref_init(&c->base.refcount, 1);
gpr_mu_init(&c->mu);
- c->token_expiration = 0;
+ c->token_expiration = gpr_inf_past(GPR_CLOCK_MONOTONIC);
c->fetch_func = fetch_func;
c->pollent =
grpc_polling_entity_create_from_pollset_set(grpc_pollset_set_create());
diff --git a/src/core/lib/security/credentials/oauth2/oauth2_credentials.h b/src/core/lib/security/credentials/oauth2/oauth2_credentials.h
index c0dd1546e3..12a1d4484f 100644
--- a/src/core/lib/security/credentials/oauth2/oauth2_credentials.h
+++ b/src/core/lib/security/credentials/oauth2/oauth2_credentials.h
@@ -71,7 +71,7 @@ typedef struct {
grpc_call_credentials base;
gpr_mu mu;
grpc_mdelem access_token_md;
- grpc_millis token_expiration;
+ gpr_timespec token_expiration;
bool token_fetch_pending;
grpc_oauth2_pending_get_request_metadata* pending_requests;
grpc_httpcli_context httpcli_context;
diff --git a/src/core/lib/security/credentials/ssl/ssl_credentials.cc b/src/core/lib/security/credentials/ssl/ssl_credentials.cc
index 2b6377d3ec..3d6f2f200a 100644
--- a/src/core/lib/security/credentials/ssl/ssl_credentials.cc
+++ b/src/core/lib/security/credentials/ssl/ssl_credentials.cc
@@ -48,6 +48,10 @@ static void ssl_destruct(grpc_channel_credentials* creds) {
grpc_ssl_credentials* c = reinterpret_cast<grpc_ssl_credentials*>(creds);
gpr_free(c->config.pem_root_certs);
grpc_tsi_ssl_pem_key_cert_pairs_destroy(c->config.pem_key_cert_pair, 1);
+ if (c->config.verify_options.verify_peer_destruct != nullptr) {
+ c->config.verify_options.verify_peer_destruct(
+ c->config.verify_options.verify_peer_callback_userdata);
+ }
}
static grpc_security_status ssl_create_security_connector(
@@ -87,6 +91,7 @@ static grpc_channel_credentials_vtable ssl_vtable = {
static void ssl_build_config(const char* pem_root_certs,
grpc_ssl_pem_key_cert_pair* pem_key_cert_pair,
+ const verify_peer_options* verify_options,
grpc_ssl_config* config) {
if (pem_root_certs != nullptr) {
config->pem_root_certs = gpr_strdup(pem_root_certs);
@@ -101,23 +106,32 @@ static void ssl_build_config(const char* pem_root_certs,
config->pem_key_cert_pair->private_key =
gpr_strdup(pem_key_cert_pair->private_key);
}
+ if (verify_options != nullptr) {
+ memcpy(&config->verify_options, verify_options,
+ sizeof(verify_peer_options));
+ } else {
+ // Otherwise set all options to default values
+ memset(&config->verify_options, 0, sizeof(verify_peer_options));
+ }
}
grpc_channel_credentials* grpc_ssl_credentials_create(
const char* pem_root_certs, grpc_ssl_pem_key_cert_pair* pem_key_cert_pair,
- void* reserved) {
+ const verify_peer_options* verify_options, void* reserved) {
grpc_ssl_credentials* c = static_cast<grpc_ssl_credentials*>(
gpr_zalloc(sizeof(grpc_ssl_credentials)));
GRPC_API_TRACE(
"grpc_ssl_credentials_create(pem_root_certs=%s, "
"pem_key_cert_pair=%p, "
+ "verify_options=%p, "
"reserved=%p)",
- 3, (pem_root_certs, pem_key_cert_pair, reserved));
+ 4, (pem_root_certs, pem_key_cert_pair, verify_options, reserved));
GPR_ASSERT(reserved == nullptr);
c->base.type = GRPC_CHANNEL_CREDENTIALS_TYPE_SSL;
c->base.vtable = &ssl_vtable;
gpr_ref_init(&c->base.refcount, 1);
- ssl_build_config(pem_root_certs, pem_key_cert_pair, &c->config);
+ ssl_build_config(pem_root_certs, pem_key_cert_pair, verify_options,
+ &c->config);
return &c->base;
}
diff --git a/src/core/lib/security/security_connector/alts_security_connector.cc b/src/core/lib/security/security_connector/alts_security_connector.cc
index 5ff7d7938b..35a787871a 100644
--- a/src/core/lib/security/security_connector/alts_security_connector.cc
+++ b/src/core/lib/security/security_connector/alts_security_connector.cc
@@ -30,6 +30,7 @@
#include "src/core/lib/security/credentials/alts/alts_credentials.h"
#include "src/core/lib/security/transport/security_handshaker.h"
+#include "src/core/lib/slice/slice_internal.h"
#include "src/core/lib/transport/transport.h"
#include "src/core/tsi/alts/handshaker/alts_tsi_handshaker.h"
@@ -133,7 +134,7 @@ grpc_security_status grpc_alts_auth_context_from_tsi_peer(
rpc_versions_prop->value.data, rpc_versions_prop->value.length);
bool decode_result =
grpc_gcp_rpc_protocol_versions_decode(slice, &peer_versions);
- grpc_slice_unref(slice);
+ grpc_slice_unref_internal(slice);
if (!decode_result) {
gpr_log(GPR_ERROR, "Invalid peer rpc protocol versions.");
return GRPC_SECURITY_ERROR;
diff --git a/src/core/lib/security/security_connector/load_system_roots.h b/src/core/lib/security/security_connector/load_system_roots.h
new file mode 100644
index 0000000000..5fdec15498
--- /dev/null
+++ b/src/core/lib/security/security_connector/load_system_roots.h
@@ -0,0 +1,29 @@
+/*
+ *
+ * Copyright 2018 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_SECURITY_SECURITY_CONNECTOR_LOAD_SYSTEM_ROOTS_H
+#define GRPC_CORE_LIB_SECURITY_SECURITY_CONNECTOR_LOAD_SYSTEM_ROOTS_H
+
+namespace grpc_core {
+
+// Returns a slice containing roots from the OS trust store
+grpc_slice LoadSystemRootCerts();
+
+} // namespace grpc_core
+
+#endif /* GRPC_CORE_LIB_SECURITY_SECURITY_CONNECTOR_LOAD_SYSTEM_ROOTS_H */
diff --git a/src/core/lib/security/security_connector/load_system_roots_fallback.cc b/src/core/lib/security/security_connector/load_system_roots_fallback.cc
new file mode 100644
index 0000000000..73d1245f33
--- /dev/null
+++ b/src/core/lib/security/security_connector/load_system_roots_fallback.cc
@@ -0,0 +1,32 @@
+/*
+ *
+ * Copyright 2018 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/slice_buffer.h>
+#include "src/core/lib/security/security_connector/load_system_roots.h"
+
+#ifndef GPR_LINUX
+
+namespace grpc_core {
+
+grpc_slice LoadSystemRootCerts() { return grpc_empty_slice(); }
+
+} // namespace grpc_core
+
+#endif /* GPR_LINUX */
diff --git a/src/core/lib/security/security_connector/load_system_roots_linux.cc b/src/core/lib/security/security_connector/load_system_roots_linux.cc
new file mode 100644
index 0000000000..924fa8a3e2
--- /dev/null
+++ b/src/core/lib/security/security_connector/load_system_roots_linux.cc
@@ -0,0 +1,165 @@
+/*
+ *
+ * Copyright 2018 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/slice_buffer.h>
+#include "src/core/lib/security/security_connector/load_system_roots_linux.h"
+
+#ifdef GPR_LINUX
+
+#include "src/core/lib/security/security_connector/load_system_roots.h"
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+#include <grpc/support/string_util.h>
+
+#include "src/core/lib/gpr/env.h"
+#include "src/core/lib/gpr/string.h"
+#include "src/core/lib/gpr/useful.h"
+#include "src/core/lib/gprpp/inlined_vector.h"
+#include "src/core/lib/iomgr/load_file.h"
+
+namespace grpc_core {
+namespace {
+
+const char* kLinuxCertFiles[] = {
+ "/etc/ssl/certs/ca-certificates.crt", "/etc/pki/tls/certs/ca-bundle.crt",
+ "/etc/ssl/ca-bundle.pem", "/etc/pki/tls/cacert.pem",
+ "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem"};
+const char* kLinuxCertDirectories[] = {
+ "/etc/ssl/certs", "/system/etc/security/cacerts", "/usr/local/share/certs",
+ "/etc/pki/tls/certs", "/etc/openssl/certs"};
+
+grpc_slice GetSystemRootCerts() {
+ grpc_slice valid_bundle_slice = grpc_empty_slice();
+ size_t num_cert_files_ = GPR_ARRAY_SIZE(kLinuxCertFiles);
+ for (size_t i = 0; i < num_cert_files_; i++) {
+ grpc_error* error =
+ grpc_load_file(kLinuxCertFiles[i], 1, &valid_bundle_slice);
+ if (error == GRPC_ERROR_NONE) {
+ return valid_bundle_slice;
+ }
+ }
+ return grpc_empty_slice();
+}
+
+} // namespace
+
+void GetAbsoluteFilePath(const char* valid_file_dir,
+ const char* file_entry_name, char* path_buffer) {
+ if (valid_file_dir != nullptr && file_entry_name != nullptr) {
+ int path_len = snprintf(path_buffer, MAXPATHLEN, "%s/%s", valid_file_dir,
+ file_entry_name);
+ if (path_len == 0) {
+ gpr_log(GPR_ERROR, "failed to get absolute path for file: %s",
+ file_entry_name);
+ }
+ }
+}
+
+grpc_slice CreateRootCertsBundle(const char* certs_directory) {
+ grpc_slice bundle_slice = grpc_empty_slice();
+ if (certs_directory == nullptr) {
+ return bundle_slice;
+ }
+ DIR* ca_directory = opendir(certs_directory);
+ if (ca_directory == nullptr) {
+ return bundle_slice;
+ }
+ struct FileData {
+ char path[MAXPATHLEN];
+ off_t size;
+ };
+ InlinedVector<FileData, 2> roots_filenames;
+ size_t total_bundle_size = 0;
+ struct dirent* directory_entry;
+ while ((directory_entry = readdir(ca_directory)) != nullptr) {
+ struct stat dir_entry_stat;
+ const char* file_entry_name = directory_entry->d_name;
+ FileData file_data;
+ GetAbsoluteFilePath(certs_directory, file_entry_name, file_data.path);
+ int stat_return = stat(file_data.path, &dir_entry_stat);
+ if (stat_return == -1 || !S_ISREG(dir_entry_stat.st_mode)) {
+ // no subdirectories.
+ if (stat_return == -1) {
+ gpr_log(GPR_ERROR, "failed to get status for file: %s", file_data.path);
+ }
+ continue;
+ }
+ file_data.size = dir_entry_stat.st_size;
+ total_bundle_size += file_data.size;
+ roots_filenames.push_back(file_data);
+ }
+ closedir(ca_directory);
+ char* bundle_string = static_cast<char*>(gpr_zalloc(total_bundle_size + 1));
+ size_t bytes_read = 0;
+ for (size_t i = 0; i < roots_filenames.size(); i++) {
+ int file_descriptor = open(roots_filenames[i].path, O_RDONLY);
+ if (file_descriptor != -1) {
+ // Read file into bundle.
+ size_t cert_file_size = roots_filenames[i].size;
+ int read_ret =
+ read(file_descriptor, bundle_string + bytes_read, cert_file_size);
+ if (read_ret != -1) {
+ bytes_read += read_ret;
+ } else {
+ gpr_log(GPR_ERROR, "failed to read file: %s", roots_filenames[i].path);
+ }
+ }
+ }
+ bundle_slice = grpc_slice_new(bundle_string, bytes_read, gpr_free);
+ return bundle_slice;
+}
+
+grpc_slice LoadSystemRootCerts() {
+ grpc_slice result = grpc_empty_slice();
+ // Prioritize user-specified custom directory if flag is set.
+ char* custom_dir = gpr_getenv("GRPC_SYSTEM_SSL_ROOTS_DIR");
+ if (custom_dir != nullptr) {
+ result = CreateRootCertsBundle(custom_dir);
+ gpr_free(custom_dir);
+ }
+ // If the custom directory is empty/invalid/not specified, fallback to
+ // distribution-specific directory.
+ if (GRPC_SLICE_IS_EMPTY(result)) {
+ result = GetSystemRootCerts();
+ }
+ if (GRPC_SLICE_IS_EMPTY(result)) {
+ for (size_t i = 0; i < GPR_ARRAY_SIZE(kLinuxCertDirectories); i++) {
+ result = CreateRootCertsBundle(kLinuxCertDirectories[i]);
+ if (!GRPC_SLICE_IS_EMPTY(result)) {
+ break;
+ }
+ }
+ }
+ return result;
+}
+
+} // namespace grpc_core
+
+#endif /* GPR_LINUX */
diff --git a/src/core/lib/security/security_connector/load_system_roots_linux.h b/src/core/lib/security/security_connector/load_system_roots_linux.h
new file mode 100644
index 0000000000..12617df492
--- /dev/null
+++ b/src/core/lib/security/security_connector/load_system_roots_linux.h
@@ -0,0 +1,44 @@
+/*
+ *
+ * Copyright 2018 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_SECURITY_SECURITY_CONNECTOR_LOAD_SYSTEM_ROOTS_LINUX_H
+#define GRPC_CORE_LIB_SECURITY_SECURITY_CONNECTOR_LOAD_SYSTEM_ROOTS_LINUX_H
+
+#include <grpc/support/port_platform.h>
+
+#ifdef GPR_LINUX
+
+namespace grpc_core {
+
+// Creates a bundle slice containing the contents of all certificate files in
+// a directory.
+// Returns such slice.
+// Exposed for testing purposes only.
+grpc_slice CreateRootCertsBundle(const char* certs_directory);
+
+// Gets the absolute file path needed to load a certificate file.
+// Populates path_buffer, which must be of size MAXPATHLEN.
+// Exposed for testing purposes only.
+void GetAbsoluteFilePath(const char* valid_file_dir,
+ const char* file_entry_name, char* path_buffer);
+
+} // namespace grpc_core
+
+#endif /* GPR_LINUX */
+#endif /* GRPC_CORE_LIB_SECURITY_SECURITY_CONNECTOR_LOAD_SYSTEM_ROOTS_LINUX_H \
+ */
diff --git a/src/core/lib/security/security_connector/local_security_connector.cc b/src/core/lib/security/security_connector/local_security_connector.cc
new file mode 100644
index 0000000000..c436a7906b
--- /dev/null
+++ b/src/core/lib/security/security_connector/local_security_connector.cc
@@ -0,0 +1,245 @@
+/*
+ *
+ * Copyright 2018 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/security/security_connector/local_security_connector.h"
+
+#include <stdbool.h>
+#include <string.h>
+
+#include <grpc/grpc.h>
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+#include <grpc/support/string_util.h>
+
+#include "src/core/ext/filters/client_channel/client_channel.h"
+#include "src/core/lib/channel/channel_args.h"
+#include "src/core/lib/security/credentials/local/local_credentials.h"
+#include "src/core/lib/security/transport/security_handshaker.h"
+#include "src/core/tsi/local_transport_security.h"
+
+#define GRPC_UDS_URI_PATTERN "unix:"
+#define GRPC_UDS_URL_SCHEME "unix"
+#define GRPC_LOCAL_TRANSPORT_SECURITY_TYPE "local"
+
+typedef struct {
+ grpc_channel_security_connector base;
+ char* target_name;
+} grpc_local_channel_security_connector;
+
+typedef struct {
+ grpc_server_security_connector base;
+} grpc_local_server_security_connector;
+
+static void local_channel_destroy(grpc_security_connector* sc) {
+ if (sc == nullptr) {
+ return;
+ }
+ auto c = reinterpret_cast<grpc_local_channel_security_connector*>(sc);
+ grpc_call_credentials_unref(c->base.request_metadata_creds);
+ grpc_channel_credentials_unref(c->base.channel_creds);
+ gpr_free(c->target_name);
+ gpr_free(sc);
+}
+
+static void local_server_destroy(grpc_security_connector* sc) {
+ if (sc == nullptr) {
+ return;
+ }
+ auto c = reinterpret_cast<grpc_local_server_security_connector*>(sc);
+ grpc_server_credentials_unref(c->base.server_creds);
+ gpr_free(sc);
+}
+
+static void local_channel_add_handshakers(
+ grpc_channel_security_connector* sc,
+ grpc_handshake_manager* handshake_manager) {
+ tsi_handshaker* handshaker = nullptr;
+ GPR_ASSERT(local_tsi_handshaker_create(true /* is_client */, &handshaker) ==
+ TSI_OK);
+ grpc_handshake_manager_add(handshake_manager, grpc_security_handshaker_create(
+ handshaker, &sc->base));
+}
+
+static void local_server_add_handshakers(
+ grpc_server_security_connector* sc,
+ grpc_handshake_manager* handshake_manager) {
+ tsi_handshaker* handshaker = nullptr;
+ GPR_ASSERT(local_tsi_handshaker_create(false /* is_client */, &handshaker) ==
+ TSI_OK);
+ grpc_handshake_manager_add(handshake_manager, grpc_security_handshaker_create(
+ handshaker, &sc->base));
+}
+
+static int local_channel_cmp(grpc_security_connector* sc1,
+ grpc_security_connector* sc2) {
+ grpc_local_channel_security_connector* c1 =
+ reinterpret_cast<grpc_local_channel_security_connector*>(sc1);
+ grpc_local_channel_security_connector* c2 =
+ reinterpret_cast<grpc_local_channel_security_connector*>(sc2);
+ int c = grpc_channel_security_connector_cmp(&c1->base, &c2->base);
+ if (c != 0) return c;
+ return strcmp(c1->target_name, c2->target_name);
+}
+
+static int local_server_cmp(grpc_security_connector* sc1,
+ grpc_security_connector* sc2) {
+ grpc_local_server_security_connector* c1 =
+ reinterpret_cast<grpc_local_server_security_connector*>(sc1);
+ grpc_local_server_security_connector* c2 =
+ reinterpret_cast<grpc_local_server_security_connector*>(sc2);
+ return grpc_server_security_connector_cmp(&c1->base, &c2->base);
+}
+
+static grpc_security_status local_auth_context_create(grpc_auth_context** ctx) {
+ if (ctx == nullptr) {
+ gpr_log(GPR_ERROR, "Invalid arguments to local_auth_context_create()");
+ return GRPC_SECURITY_ERROR;
+ }
+ /* Create auth context. */
+ *ctx = grpc_auth_context_create(nullptr);
+ grpc_auth_context_add_cstring_property(
+ *ctx, GRPC_TRANSPORT_SECURITY_TYPE_PROPERTY_NAME,
+ GRPC_LOCAL_TRANSPORT_SECURITY_TYPE);
+ GPR_ASSERT(grpc_auth_context_set_peer_identity_property_name(
+ *ctx, GRPC_TRANSPORT_SECURITY_TYPE_PROPERTY_NAME) == 1);
+ return GRPC_SECURITY_OK;
+}
+
+static void local_check_peer(grpc_security_connector* sc, tsi_peer peer,
+ grpc_auth_context** auth_context,
+ grpc_closure* on_peer_checked) {
+ grpc_security_status status;
+ /* Create an auth context which is necessary to pass the santiy check in
+ * {client, server}_auth_filter that verifies if the peer's auth context is
+ * obtained during handshakes. The auth context is only checked for its
+ * existence and not actually used.
+ */
+ status = local_auth_context_create(auth_context);
+ grpc_error* error = status == GRPC_SECURITY_OK
+ ? GRPC_ERROR_NONE
+ : GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+ "Could not create local auth context");
+ GRPC_CLOSURE_SCHED(on_peer_checked, error);
+}
+
+static grpc_security_connector_vtable local_channel_vtable = {
+ local_channel_destroy, local_check_peer, local_channel_cmp};
+
+static grpc_security_connector_vtable local_server_vtable = {
+ local_server_destroy, local_check_peer, local_server_cmp};
+
+static bool local_check_call_host(grpc_channel_security_connector* sc,
+ const char* host,
+ grpc_auth_context* auth_context,
+ grpc_closure* on_call_host_checked,
+ grpc_error** error) {
+ grpc_local_channel_security_connector* local_sc =
+ reinterpret_cast<grpc_local_channel_security_connector*>(sc);
+ if (host == nullptr || local_sc == nullptr ||
+ strcmp(host, local_sc->target_name) != 0) {
+ *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+ "local call host does not match target name");
+ }
+ return true;
+}
+
+static void local_cancel_check_call_host(grpc_channel_security_connector* sc,
+ grpc_closure* on_call_host_checked,
+ grpc_error* error) {
+ GRPC_ERROR_UNREF(error);
+}
+
+grpc_security_status grpc_local_channel_security_connector_create(
+ grpc_channel_credentials* channel_creds,
+ grpc_call_credentials* request_metadata_creds,
+ const grpc_channel_args* args, const char* target_name,
+ grpc_channel_security_connector** sc) {
+ if (channel_creds == nullptr || sc == nullptr || target_name == nullptr) {
+ gpr_log(
+ GPR_ERROR,
+ "Invalid arguments to grpc_local_channel_security_connector_create()");
+ return GRPC_SECURITY_ERROR;
+ }
+ // Check if local_connect_type is UDS. Only UDS is supported for now.
+ grpc_local_credentials* creds =
+ reinterpret_cast<grpc_local_credentials*>(channel_creds);
+ if (creds->connect_type != UDS) {
+ gpr_log(GPR_ERROR,
+ "Invalid local channel type to "
+ "grpc_local_channel_security_connector_create()");
+ return GRPC_SECURITY_ERROR;
+ }
+ // Check if target_name is a valid UDS address.
+ const grpc_arg* server_uri_arg =
+ grpc_channel_args_find(args, GRPC_ARG_SERVER_URI);
+ const char* server_uri_str = grpc_channel_arg_get_string(server_uri_arg);
+ if (strncmp(GRPC_UDS_URI_PATTERN, server_uri_str,
+ strlen(GRPC_UDS_URI_PATTERN)) != 0) {
+ gpr_log(GPR_ERROR,
+ "Invalid target_name to "
+ "grpc_local_channel_security_connector_create()");
+ return GRPC_SECURITY_ERROR;
+ }
+ auto c = static_cast<grpc_local_channel_security_connector*>(
+ gpr_zalloc(sizeof(grpc_local_channel_security_connector)));
+ gpr_ref_init(&c->base.base.refcount, 1);
+ c->base.base.vtable = &local_channel_vtable;
+ c->base.add_handshakers = local_channel_add_handshakers;
+ c->base.channel_creds = grpc_channel_credentials_ref(channel_creds);
+ c->base.request_metadata_creds =
+ grpc_call_credentials_ref(request_metadata_creds);
+ c->base.check_call_host = local_check_call_host;
+ c->base.cancel_check_call_host = local_cancel_check_call_host;
+ c->base.base.url_scheme =
+ creds->connect_type == UDS ? GRPC_UDS_URL_SCHEME : nullptr;
+ c->target_name = gpr_strdup(target_name);
+ *sc = &c->base;
+ return GRPC_SECURITY_OK;
+}
+
+grpc_security_status grpc_local_server_security_connector_create(
+ grpc_server_credentials* server_creds,
+ grpc_server_security_connector** sc) {
+ if (server_creds == nullptr || sc == nullptr) {
+ gpr_log(
+ GPR_ERROR,
+ "Invalid arguments to grpc_local_server_security_connector_create()");
+ return GRPC_SECURITY_ERROR;
+ }
+ // Check if local_connect_type is UDS. Only UDS is supported for now.
+ grpc_local_server_credentials* creds =
+ reinterpret_cast<grpc_local_server_credentials*>(server_creds);
+ if (creds->connect_type != UDS) {
+ gpr_log(GPR_ERROR,
+ "Invalid local server type to "
+ "grpc_local_server_security_connector_create()");
+ return GRPC_SECURITY_ERROR;
+ }
+ auto c = static_cast<grpc_local_server_security_connector*>(
+ gpr_zalloc(sizeof(grpc_local_server_security_connector)));
+ gpr_ref_init(&c->base.base.refcount, 1);
+ c->base.base.vtable = &local_server_vtable;
+ c->base.server_creds = grpc_server_credentials_ref(server_creds);
+ c->base.base.url_scheme =
+ creds->connect_type == UDS ? GRPC_UDS_URL_SCHEME : nullptr;
+ c->base.add_handshakers = local_server_add_handshakers;
+ *sc = &c->base;
+ return GRPC_SECURITY_OK;
+}
diff --git a/src/core/lib/security/security_connector/local_security_connector.h b/src/core/lib/security/security_connector/local_security_connector.h
new file mode 100644
index 0000000000..a970a74788
--- /dev/null
+++ b/src/core/lib/security/security_connector/local_security_connector.h
@@ -0,0 +1,58 @@
+/*
+ *
+ * Copyright 2018 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_SECURITY_SECURITY_CONNECTOR_LOCAL_SECURITY_CONNECTOR_H
+#define GRPC_CORE_LIB_SECURITY_SECURITY_CONNECTOR_LOCAL_SECURITY_CONNECTOR_H
+
+#include <grpc/support/port_platform.h>
+
+#include "src/core/lib/security/context/security_context.h"
+
+/**
+ * This method creates a local channel security connector.
+ *
+ * - channel_creds: channel credential instance.
+ * - request_metadata_creds: credential object which will be sent with each
+ * request. This parameter can be nullptr.
+ * - target_name: the name of the endpoint that the channel is connecting to.
+ * - args: channel args passed from the caller.
+ * - sc: address of local channel security connector instance to be returned
+ * from the method.
+ *
+ * It returns GRPC_SECURITY_OK on success, and an error stauts code on failure.
+ */
+grpc_security_status grpc_local_channel_security_connector_create(
+ grpc_channel_credentials* channel_creds,
+ grpc_call_credentials* request_metadata_creds,
+ const grpc_channel_args* args, const char* target_name,
+ grpc_channel_security_connector** sc);
+
+/**
+ * This method creates a local server security connector.
+ *
+ * - server_creds: server credential instance.
+ * - sc: address of local server security connector instance to be returned from
+ * the method.
+ *
+ * It returns GRPC_SECURITY_OK on success, and an error status code on failure.
+ */
+grpc_security_status grpc_local_server_security_connector_create(
+ grpc_server_credentials* server_creds, grpc_server_security_connector** sc);
+
+#endif /* GRPC_CORE_LIB_SECURITY_SECURITY_CONNECTOR_LOCAL_SECURITY_CONNECTOR_H \
+ */
diff --git a/src/core/lib/security/security_connector/security_connector.cc b/src/core/lib/security/security_connector/security_connector.cc
index a30696703f..6246613e7b 100644
--- a/src/core/lib/security/security_connector/security_connector.cc
+++ b/src/core/lib/security/security_connector/security_connector.cc
@@ -21,7 +21,6 @@
#include "src/core/lib/security/security_connector/security_connector.h"
#include <stdbool.h>
-#include <string.h>
#include <grpc/slice_buffer.h>
#include <grpc/support/alloc.h>
@@ -39,6 +38,7 @@
#include "src/core/lib/security/credentials/credentials.h"
#include "src/core/lib/security/credentials/fake/fake_credentials.h"
#include "src/core/lib/security/credentials/ssl/ssl_credentials.h"
+#include "src/core/lib/security/security_connector/load_system_roots.h"
#include "src/core/lib/security/transport/secure_endpoint.h"
#include "src/core/lib/security/transport/security_handshaker.h"
#include "src/core/lib/security/transport/target_authority_table.h"
@@ -57,6 +57,16 @@ static const char* installed_roots_path =
INSTALL_PREFIX "/share/grpc/roots.pem";
#endif
+/** Environment variable used as a flag to enable/disable loading system root
+ certificates from the OS trust store. */
+#ifndef GRPC_NOT_USE_SYSTEM_SSL_ROOTS_ENV_VAR
+#define GRPC_NOT_USE_SYSTEM_SSL_ROOTS_ENV_VAR "GRPC_NOT_USE_SYSTEM_SSL_ROOTS"
+#endif
+
+#ifndef TSI_OPENSSL_ALPN_SUPPORT
+#define TSI_OPENSSL_ALPN_SUPPORT 1
+#endif
+
/* -- Overridden default roots. -- */
static grpc_ssl_roots_override_callback ssl_roots_override_cb = nullptr;
@@ -69,8 +79,11 @@ void grpc_set_ssl_roots_override_callback(grpc_ssl_roots_override_callback cb) {
/* Defines the cipher suites that we accept by default. All these cipher suites
are compliant with HTTP2. */
-#define GRPC_SSL_CIPHER_SUITES \
- "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384"
+#define GRPC_SSL_CIPHER_SUITES \
+ "ECDHE-ECDSA-AES128-GCM-SHA256:" \
+ "ECDHE-ECDSA-AES256-GCM-SHA384:" \
+ "ECDHE-RSA-AES128-GCM-SHA256:" \
+ "ECDHE-RSA-AES256-GCM-SHA384"
static gpr_once cipher_suites_once = GPR_ONCE_INIT;
static const char* cipher_suites = nullptr;
@@ -617,6 +630,7 @@ typedef struct {
tsi_ssl_client_handshaker_factory* client_handshaker_factory;
char* target_name;
char* overridden_target_name;
+ const verify_peer_options* verify_options;
} grpc_ssl_channel_security_connector;
typedef struct {
@@ -846,7 +860,8 @@ grpc_auth_context* grpc_ssl_peer_to_auth_context(const tsi_peer* peer) {
static grpc_error* ssl_check_peer(grpc_security_connector* sc,
const char* peer_name, const tsi_peer* peer,
grpc_auth_context** auth_context) {
- /* Check the ALPN. */
+#if TSI_OPENSSL_ALPN_SUPPORT
+ /* Check the ALPN if ALPN is supported. */
const tsi_peer_property* p =
tsi_peer_get_property_by_name(peer, TSI_SSL_ALPN_SELECTED_PROTOCOL);
if (p == nullptr) {
@@ -857,7 +872,7 @@ static grpc_error* ssl_check_peer(grpc_security_connector* sc,
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Cannot check peer: invalid ALPN value.");
}
-
+#endif /* TSI_OPENSSL_ALPN_SUPPORT */
/* Check the peer name if specified. */
if (peer_name != nullptr && !grpc_ssl_host_matches_name(peer, peer_name)) {
char* msg;
@@ -875,11 +890,34 @@ static void ssl_channel_check_peer(grpc_security_connector* sc, tsi_peer peer,
grpc_closure* on_peer_checked) {
grpc_ssl_channel_security_connector* c =
reinterpret_cast<grpc_ssl_channel_security_connector*>(sc);
- grpc_error* error = ssl_check_peer(sc,
- c->overridden_target_name != nullptr
- ? c->overridden_target_name
- : c->target_name,
- &peer, auth_context);
+ const char* target_name = c->overridden_target_name != nullptr
+ ? c->overridden_target_name
+ : c->target_name;
+ grpc_error* error = ssl_check_peer(sc, target_name, &peer, auth_context);
+ if (error == GRPC_ERROR_NONE &&
+ c->verify_options->verify_peer_callback != nullptr) {
+ const tsi_peer_property* p =
+ tsi_peer_get_property_by_name(&peer, TSI_X509_PEM_CERT_PROPERTY);
+ if (p == nullptr) {
+ error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+ "Cannot check peer: missing pem cert property.");
+ } else {
+ char* peer_pem = static_cast<char*>(gpr_malloc(p->value.length + 1));
+ memcpy(peer_pem, p->value.data, p->value.length);
+ peer_pem[p->value.length] = '\0';
+ int callback_status = c->verify_options->verify_peer_callback(
+ target_name, peer_pem,
+ c->verify_options->verify_peer_callback_userdata);
+ gpr_free(peer_pem);
+ if (callback_status) {
+ char* msg;
+ gpr_asprintf(&msg, "Verify peer callback returned a failure (%d)",
+ callback_status);
+ error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg);
+ gpr_free(msg);
+ }
+ }
+ }
GRPC_CLOSURE_SCHED(on_peer_checked, error);
tsi_peer_destruct(&peer);
}
@@ -1044,6 +1082,7 @@ grpc_security_status grpc_ssl_channel_security_connector_create(
if (overridden_target_name != nullptr) {
c->overridden_target_name = gpr_strdup(overridden_target_name);
}
+ c->verify_options = &config->verify_options;
has_key_cert_pair = config->pem_key_cert_pair != nullptr &&
config->pem_key_cert_pair->private_key != nullptr &&
@@ -1153,6 +1192,10 @@ const char* DefaultSslRootStore::GetPemRootCerts() {
grpc_slice DefaultSslRootStore::ComputePemRootCerts() {
grpc_slice result = grpc_empty_slice();
+ char* not_use_system_roots_env_value =
+ gpr_getenv(GRPC_NOT_USE_SYSTEM_SSL_ROOTS_ENV_VAR);
+ const bool not_use_system_roots = gpr_is_true(not_use_system_roots_env_value);
+ gpr_free(not_use_system_roots_env_value);
// First try to load the roots from the environment.
char* default_root_certs_path =
gpr_getenv(GRPC_DEFAULT_SSL_ROOTS_FILE_PATH_ENV_VAR);
@@ -1174,7 +1217,11 @@ grpc_slice DefaultSslRootStore::ComputePemRootCerts() {
}
gpr_free(pem_root_certs);
}
- // Fall back to installed certs if needed.
+ // Try loading roots from OS trust store if flag is enabled.
+ if (GRPC_SLICE_IS_EMPTY(result) && !not_use_system_roots) {
+ result = LoadSystemRootCerts();
+ }
+ // Fallback to roots manually shipped with gRPC.
if (GRPC_SLICE_IS_EMPTY(result) &&
ovrd_res != GRPC_SSL_ROOTS_OVERRIDE_FAIL_PERMANENTLY) {
GRPC_LOG_IF_ERROR("load_file",
diff --git a/src/core/lib/security/security_connector/security_connector.h b/src/core/lib/security/security_connector/security_connector.h
index f9723166d0..67a506b576 100644
--- a/src/core/lib/security/security_connector/security_connector.h
+++ b/src/core/lib/security/security_connector/security_connector.h
@@ -193,6 +193,7 @@ grpc_server_security_connector* grpc_fake_server_security_connector_create(
typedef struct {
tsi_ssl_pem_key_cert_pair* pem_key_cert_pair;
char* pem_root_certs;
+ verify_peer_options verify_options;
} grpc_ssl_config;
/* Creates an SSL channel_security_connector.
diff --git a/src/core/lib/security/transport/client_auth_filter.cc b/src/core/lib/security/transport/client_auth_filter.cc
index 048e390a71..0f125e7c26 100644
--- a/src/core/lib/security/transport/client_auth_filter.cc
+++ b/src/core/lib/security/transport/client_auth_filter.cc
@@ -42,6 +42,7 @@
namespace {
/* We can have a per-call credentials. */
struct call_data {
+ gpr_arena* arena;
grpc_call_stack* owning_call;
grpc_call_combiner* call_combiner;
grpc_call_credentials* creds;
@@ -166,7 +167,6 @@ static void cancel_get_request_metadata(void* arg, grpc_error* error) {
grpc_call_credentials_cancel_get_request_metadata(
calld->creds, &calld->md_array, GRPC_ERROR_REF(error));
}
- GRPC_CALL_STACK_UNREF(calld->owning_call, "cancel_get_request_metadata");
}
static void send_security_metadata(grpc_call_element* elem,
@@ -221,7 +221,6 @@ static void send_security_metadata(grpc_call_element* elem,
GRPC_ERROR_UNREF(error);
} else {
// Async return; register cancellation closure with call combiner.
- GRPC_CALL_STACK_REF(calld->owning_call, "cancel_get_request_metadata");
grpc_call_combiner_set_notify_on_cancel(
calld->call_combiner,
GRPC_CLOSURE_INIT(&calld->get_request_metadata_cancel_closure,
@@ -264,7 +263,6 @@ static void cancel_check_call_host(void* arg, grpc_error* error) {
chand->security_connector, &calld->async_result_closure,
GRPC_ERROR_REF(error));
}
- GRPC_CALL_STACK_UNREF(calld->owning_call, "cancel_check_call_host");
}
static void auth_start_transport_stream_op_batch(
@@ -276,10 +274,12 @@ static void auth_start_transport_stream_op_batch(
channel_data* chand = static_cast<channel_data*>(elem->channel_data);
if (!batch->cancel_stream) {
+ // TODO(hcaseyal): move this to init_call_elem once issue #15927 is
+ // resolved.
GPR_ASSERT(batch->payload->context != nullptr);
if (batch->payload->context[GRPC_CONTEXT_SECURITY].value == nullptr) {
batch->payload->context[GRPC_CONTEXT_SECURITY].value =
- grpc_client_security_context_create();
+ grpc_client_security_context_create(calld->arena);
batch->payload->context[GRPC_CONTEXT_SECURITY].destroy =
grpc_client_security_context_destroy;
}
@@ -315,7 +315,6 @@ static void auth_start_transport_stream_op_batch(
GRPC_ERROR_UNREF(error);
} else {
// Async return; register cancellation closure with call combiner.
- GRPC_CALL_STACK_REF(calld->owning_call, "cancel_check_call_host");
grpc_call_combiner_set_notify_on_cancel(
calld->call_combiner,
GRPC_CLOSURE_INIT(&calld->check_call_host_cancel_closure,
@@ -335,6 +334,7 @@ static void auth_start_transport_stream_op_batch(
static grpc_error* init_call_elem(grpc_call_element* elem,
const grpc_call_element_args* args) {
call_data* calld = static_cast<call_data*>(elem->call_data);
+ calld->arena = args->arena;
calld->owning_call = args->call_stack;
calld->call_combiner = args->call_combiner;
calld->host = grpc_empty_slice();
diff --git a/src/core/lib/security/transport/secure_endpoint.cc b/src/core/lib/security/transport/secure_endpoint.cc
index 840b2e73bc..f40f969bb7 100644
--- a/src/core/lib/security/transport/secure_endpoint.cc
+++ b/src/core/lib/security/transport/secure_endpoint.cc
@@ -254,7 +254,7 @@ static void flush_write_staging_buffer(secure_endpoint* ep, uint8_t** cur,
}
static void endpoint_write(grpc_endpoint* secure_ep, grpc_slice_buffer* slices,
- grpc_closure* cb) {
+ grpc_closure* cb, void* arg) {
GPR_TIMER_SCOPE("secure_endpoint.endpoint_write", 0);
unsigned i;
@@ -342,7 +342,7 @@ static void endpoint_write(grpc_endpoint* secure_ep, grpc_slice_buffer* slices,
return;
}
- grpc_endpoint_write(ep->wrapped_ep, &ep->output_buffer, cb);
+ grpc_endpoint_write(ep->wrapped_ep, &ep->output_buffer, cb, arg);
}
static void endpoint_shutdown(grpc_endpoint* secure_ep, grpc_error* why) {
diff --git a/src/core/lib/security/transport/security_handshaker.cc b/src/core/lib/security/transport/security_handshaker.cc
index aff723ed04..d76d582638 100644
--- a/src/core/lib/security/transport/security_handshaker.cc
+++ b/src/core/lib/security/transport/security_handshaker.cc
@@ -259,7 +259,7 @@ static grpc_error* on_handshake_next_done_locked(
grpc_slice_buffer_reset_and_unref_internal(&h->outgoing);
grpc_slice_buffer_add(&h->outgoing, to_send);
grpc_endpoint_write(h->args->endpoint, &h->outgoing,
- &h->on_handshake_data_sent_to_peer);
+ &h->on_handshake_data_sent_to_peer, nullptr);
} else if (handshaker_result == nullptr) {
// There is nothing to send, but need to read from peer.
grpc_endpoint_read(h->args->endpoint, h->args->read_buffer,
diff --git a/src/core/lib/security/transport/server_auth_filter.cc b/src/core/lib/security/transport/server_auth_filter.cc
index a560a4a02e..b99fc5e178 100644
--- a/src/core/lib/security/transport/server_auth_filter.cc
+++ b/src/core/lib/security/transport/server_auth_filter.cc
@@ -41,10 +41,14 @@ struct call_data {
grpc_transport_stream_op_batch* recv_initial_metadata_batch;
grpc_closure* original_recv_initial_metadata_ready;
grpc_closure recv_initial_metadata_ready;
+ grpc_error* recv_initial_metadata_error;
+ grpc_closure recv_trailing_metadata_ready;
+ grpc_closure* original_recv_trailing_metadata_ready;
+ grpc_error* recv_trailing_metadata_error;
+ bool seen_recv_trailing_metadata_ready;
grpc_metadata_array md;
const grpc_metadata* consumed_md;
size_t num_consumed_md;
- grpc_auth_context* auth_context;
grpc_closure cancel_closure;
gpr_atm state; // async_state
};
@@ -112,7 +116,16 @@ static void on_md_processing_done_inner(grpc_call_element* elem,
batch->payload->recv_initial_metadata.recv_initial_metadata,
remove_consumed_md, elem, "Response metadata filtering error");
}
- GRPC_CLOSURE_SCHED(calld->original_recv_initial_metadata_ready, error);
+ calld->recv_initial_metadata_error = GRPC_ERROR_REF(error);
+ grpc_closure* closure = calld->original_recv_initial_metadata_ready;
+ calld->original_recv_initial_metadata_ready = nullptr;
+ if (calld->seen_recv_trailing_metadata_ready) {
+ GRPC_CALL_COMBINER_START(calld->call_combiner,
+ &calld->recv_trailing_metadata_ready,
+ calld->recv_trailing_metadata_error,
+ "continue recv_trailing_metadata_ready");
+ }
+ GRPC_CLOSURE_SCHED(closure, error);
}
// Called from application code.
@@ -157,7 +170,6 @@ static void cancel_call(void* arg, grpc_error* error) {
on_md_processing_done_inner(elem, nullptr, 0, nullptr, 0,
GRPC_ERROR_REF(error));
}
- GRPC_CALL_STACK_UNREF(calld->owning_call, "cancel_call");
}
static void recv_initial_metadata_ready(void* arg, grpc_error* error) {
@@ -169,7 +181,6 @@ static void recv_initial_metadata_ready(void* arg, grpc_error* error) {
if (chand->creds != nullptr && chand->creds->processor.process != nullptr) {
// We're calling out to the application, so we need to make sure
// to drop the call combiner early if we get cancelled.
- GRPC_CALL_STACK_REF(calld->owning_call, "cancel_call");
GRPC_CLOSURE_INIT(&calld->cancel_closure, cancel_call, elem,
grpc_schedule_on_exec_ctx);
grpc_call_combiner_set_notify_on_cancel(calld->call_combiner,
@@ -178,13 +189,36 @@ static void recv_initial_metadata_ready(void* arg, grpc_error* error) {
calld->md = metadata_batch_to_md_array(
batch->payload->recv_initial_metadata.recv_initial_metadata);
chand->creds->processor.process(
- chand->creds->processor.state, calld->auth_context,
+ chand->creds->processor.state, chand->auth_context,
calld->md.metadata, calld->md.count, on_md_processing_done, elem);
return;
}
}
- GRPC_CLOSURE_RUN(calld->original_recv_initial_metadata_ready,
- GRPC_ERROR_REF(error));
+ grpc_closure* closure = calld->original_recv_initial_metadata_ready;
+ calld->original_recv_initial_metadata_ready = nullptr;
+ if (calld->seen_recv_trailing_metadata_ready) {
+ GRPC_CALL_COMBINER_START(calld->call_combiner,
+ &calld->recv_trailing_metadata_ready,
+ calld->recv_trailing_metadata_error,
+ "continue recv_trailing_metadata_ready");
+ }
+ GRPC_CLOSURE_RUN(closure, GRPC_ERROR_REF(error));
+}
+
+static void recv_trailing_metadata_ready(void* user_data, grpc_error* err) {
+ grpc_call_element* elem = static_cast<grpc_call_element*>(user_data);
+ call_data* calld = static_cast<call_data*>(elem->call_data);
+ if (calld->original_recv_initial_metadata_ready != nullptr) {
+ calld->recv_trailing_metadata_error = GRPC_ERROR_REF(err);
+ calld->seen_recv_trailing_metadata_ready = true;
+ GRPC_CALL_COMBINER_STOP(calld->call_combiner,
+ "deferring recv_trailing_metadata_ready until "
+ "after recv_initial_metadata_ready");
+ return;
+ }
+ err = grpc_error_add_child(
+ GRPC_ERROR_REF(err), GRPC_ERROR_REF(calld->recv_initial_metadata_error));
+ GRPC_CLOSURE_RUN(calld->original_recv_trailing_metadata_ready, err);
}
static void auth_start_transport_stream_op_batch(
@@ -198,6 +232,12 @@ static void auth_start_transport_stream_op_batch(
batch->payload->recv_initial_metadata.recv_initial_metadata_ready =
&calld->recv_initial_metadata_ready;
}
+ if (batch->recv_trailing_metadata) {
+ calld->original_recv_trailing_metadata_ready =
+ batch->payload->recv_trailing_metadata.recv_trailing_metadata_ready;
+ batch->payload->recv_trailing_metadata.recv_trailing_metadata_ready =
+ &calld->recv_trailing_metadata_ready;
+ }
grpc_call_next_op(elem, batch);
}
@@ -211,12 +251,15 @@ static grpc_error* init_call_elem(grpc_call_element* elem,
GRPC_CLOSURE_INIT(&calld->recv_initial_metadata_ready,
recv_initial_metadata_ready, elem,
grpc_schedule_on_exec_ctx);
+ GRPC_CLOSURE_INIT(&calld->recv_trailing_metadata_ready,
+ recv_trailing_metadata_ready, elem,
+ grpc_schedule_on_exec_ctx);
// Create server security context. Set its auth context from channel
// data and save it in the call context.
grpc_server_security_context* server_ctx =
- grpc_server_security_context_create();
- server_ctx->auth_context = grpc_auth_context_create(chand->auth_context);
- calld->auth_context = server_ctx->auth_context;
+ grpc_server_security_context_create(args->arena);
+ server_ctx->auth_context =
+ GRPC_AUTH_CONTEXT_REF(chand->auth_context, "server_auth_filter");
if (args->context[GRPC_CONTEXT_SECURITY].value != nullptr) {
args->context[GRPC_CONTEXT_SECURITY].destroy(
args->context[GRPC_CONTEXT_SECURITY].value);
@@ -230,7 +273,10 @@ static grpc_error* init_call_elem(grpc_call_element* elem,
/* Destructor for call_data */
static void destroy_call_elem(grpc_call_element* elem,
const grpc_call_final_info* final_info,
- grpc_closure* ignored) {}
+ grpc_closure* ignored) {
+ call_data* calld = static_cast<call_data*>(elem->call_data);
+ GRPC_ERROR_UNREF(calld->recv_initial_metadata_error);
+}
/* Constructor for channel_data */
static grpc_error* init_channel_elem(grpc_channel_element* elem,
diff --git a/src/core/lib/security/util/json_util.cc b/src/core/lib/security/util/json_util.cc
index 75512a19c9..fe9f5fe3d3 100644
--- a/src/core/lib/security/util/json_util.cc
+++ b/src/core/lib/security/util/json_util.cc
@@ -29,6 +29,10 @@ const char* grpc_json_get_string_property(const grpc_json* json,
const char* prop_name) {
grpc_json* child;
for (child = json->child; child != nullptr; child = child->next) {
+ if (child->key == nullptr) {
+ gpr_log(GPR_ERROR, "Invalid (null) JSON key encountered");
+ return nullptr;
+ }
if (strcmp(child->key, prop_name) == 0) break;
}
if (child == nullptr || child->type != GRPC_JSON_STRING) {
diff --git a/src/core/lib/slice/slice_buffer.cc b/src/core/lib/slice/slice_buffer.cc
index fd56997388..1f1c08b159 100644
--- a/src/core/lib/slice/slice_buffer.cc
+++ b/src/core/lib/slice/slice_buffer.cc
@@ -333,14 +333,26 @@ void grpc_slice_buffer_trim_end(grpc_slice_buffer* sb, size_t n,
size_t slice_len = GRPC_SLICE_LENGTH(slice);
if (slice_len > n) {
sb->slices[idx] = grpc_slice_split_head(&slice, slice_len - n);
- grpc_slice_buffer_add_indexed(garbage, slice);
+ if (garbage) {
+ grpc_slice_buffer_add_indexed(garbage, slice);
+ } else {
+ grpc_slice_unref_internal(slice);
+ }
return;
} else if (slice_len == n) {
- grpc_slice_buffer_add_indexed(garbage, slice);
+ if (garbage) {
+ grpc_slice_buffer_add_indexed(garbage, slice);
+ } else {
+ grpc_slice_unref_internal(slice);
+ }
sb->count = idx;
return;
} else {
- grpc_slice_buffer_add_indexed(garbage, slice);
+ if (garbage) {
+ grpc_slice_buffer_add_indexed(garbage, slice);
+ } else {
+ grpc_slice_unref_internal(slice);
+ }
n -= slice_len;
sb->count = idx;
}
diff --git a/src/core/lib/surface/call.cc b/src/core/lib/surface/call.cc
index da488034ca..a9349afa68 100644
--- a/src/core/lib/surface/call.cc
+++ b/src/core/lib/surface/call.cc
@@ -34,6 +34,7 @@
#include "src/core/lib/channel/channel_stack.h"
#include "src/core/lib/compression/algorithm_metadata.h"
#include "src/core/lib/debug/stats.h"
+#include "src/core/lib/gpr/alloc.h"
#include "src/core/lib/gpr/arena.h"
#include "src/core/lib/gpr/string.h"
#include "src/core/lib/gpr/useful.h"
@@ -47,6 +48,7 @@
#include "src/core/lib/surface/call_test_only.h"
#include "src/core/lib/surface/channel.h"
#include "src/core/lib/surface/completion_queue.h"
+#include "src/core/lib/surface/server.h"
#include "src/core/lib/surface/validate_metadata.h"
#include "src/core/lib/transport/error_utils.h"
#include "src/core/lib/transport/metadata.h"
@@ -67,45 +69,8 @@
#define MAX_SEND_EXTRA_METADATA_COUNT 3
-/* Status data for a request can come from several sources; this
- enumerates them all, and acts as a priority sorting for which
- status to return to the application - earlier entries override
- later ones */
-typedef enum {
- /* Status came from the application layer overriding whatever
- the wire says */
- STATUS_FROM_API_OVERRIDE = 0,
- /* Status came from 'the wire' - or somewhere below the surface
- layer */
- STATUS_FROM_WIRE,
- /* Status was created by some internal channel stack operation: must come via
- add_batch_error */
- STATUS_FROM_CORE,
- /* Status was created by some surface error */
- STATUS_FROM_SURFACE,
- /* Status came from the server sending status */
- STATUS_FROM_SERVER_STATUS,
- STATUS_SOURCE_COUNT
-} status_source;
-
-typedef struct {
- bool is_set;
- grpc_error* error;
-} received_status;
-
-static gpr_atm pack_received_status(received_status r) {
- return r.is_set ? (1 | (gpr_atm)r.error) : 0;
-}
-
-static received_status unpack_received_status(gpr_atm atm) {
- if ((atm & 1) == 0) {
- return {false, GRPC_ERROR_NONE};
- } else {
- return {true, (grpc_error*)(atm & ~static_cast<gpr_atm>(1))};
- }
-}
-
-#define MAX_ERRORS_PER_BATCH 4
+// Used to create arena for the first call.
+#define ESTIMATED_MDELEM_COUNT 16
typedef struct batch_control {
grpc_call* call;
@@ -131,10 +96,7 @@ typedef struct batch_control {
grpc_closure start_batch;
grpc_closure finish_batch;
gpr_refcount steps_to_complete;
-
- grpc_error* errors[MAX_ERRORS_PER_BATCH];
- gpr_atm num_errors;
-
+ gpr_atm batch_error;
grpc_transport_stream_op_batch op;
} batch_control;
@@ -197,9 +159,6 @@ struct grpc_call {
// A char* indicating the peer name.
gpr_atm peer_string;
- /* Packed received call statuses from various sources */
- gpr_atm status[STATUS_SOURCE_COUNT];
-
/* Call data useful used for reporting. Only valid after the call has
* completed */
grpc_call_final_info final_info;
@@ -230,7 +189,9 @@ struct grpc_call {
grpc_closure receiving_slice_ready;
grpc_closure receiving_stream_ready;
grpc_closure receiving_initial_metadata_ready;
+ grpc_closure receiving_trailing_metadata_ready;
uint32_t test_only_last_message_flags;
+ gpr_atm cancelled;
grpc_closure release_call;
@@ -242,8 +203,11 @@ struct grpc_call {
} client;
struct {
int* cancelled;
+ // backpointer to owning server if this is a server side call.
+ grpc_server* server;
} server;
} final_op;
+ gpr_atm status_error;
/* recv_state can contain one of the following values:
RECV_NONE : : no initial metadata and messages received
@@ -267,8 +231,13 @@ struct grpc_call {
grpc_core::TraceFlag grpc_call_error_trace(false, "call_error");
grpc_core::TraceFlag grpc_compression_trace(false, "compression");
-#define CALL_STACK_FROM_CALL(call) ((grpc_call_stack*)((call) + 1))
-#define CALL_FROM_CALL_STACK(call_stack) (((grpc_call*)(call_stack)) - 1)
+#define CALL_STACK_FROM_CALL(call) \
+ (grpc_call_stack*)((char*)(call) + \
+ GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(grpc_call)))
+#define CALL_FROM_CALL_STACK(call_stack) \
+ (grpc_call*)(((char*)(call_stack)) - \
+ GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(grpc_call)))
+
#define CALL_ELEM_FROM_CALL(call, idx) \
grpc_call_stack_element(CALL_STACK_FROM_CALL(call), idx)
#define CALL_FROM_TOP_ELEM(top_elem) \
@@ -276,23 +245,15 @@ grpc_core::TraceFlag grpc_compression_trace(false, "compression");
static void execute_batch(grpc_call* call, grpc_transport_stream_op_batch* op,
grpc_closure* start_batch_closure);
-static void cancel_with_status(grpc_call* c, status_source source,
- grpc_status_code status,
+
+static void cancel_with_status(grpc_call* c, grpc_status_code status,
const char* description);
-static void cancel_with_error(grpc_call* c, status_source source,
- grpc_error* error);
+static void cancel_with_error(grpc_call* c, grpc_error* error);
static void destroy_call(void* call_stack, grpc_error* error);
static void receiving_slice_ready(void* bctlp, grpc_error* error);
-static void get_final_status(
- grpc_call* call, void (*set_value)(grpc_status_code code, void* user_data),
- void* set_value_user_data, grpc_slice* details, const char** error_string);
-static void set_status_value_directly(grpc_status_code status, void* dest);
-static void set_status_from_error(grpc_call* call, status_source source,
- grpc_error* error);
+static void set_final_status(grpc_call* call, grpc_error* error);
static void process_data_after_md(batch_control* bctl);
static void post_batch_completion(batch_control* bctl);
-static void add_batch_error(batch_control* bctl, grpc_error* error,
- bool has_cancelled);
static void add_init_error(grpc_error** composite, grpc_error* new_err) {
if (new_err == GRPC_ERROR_NONE) return;
@@ -323,6 +284,11 @@ static parent_call* get_parent_call(grpc_call* call) {
return (parent_call*)gpr_atm_acq_load(&call->parent_call_atm);
}
+size_t grpc_call_get_initial_size_estimate() {
+ return sizeof(grpc_call) + sizeof(batch_control) * MAX_CONCURRENT_BATCHES +
+ sizeof(grpc_linked_mdelem) * ESTIMATED_MDELEM_COUNT;
+}
+
grpc_error* grpc_call_create(const grpc_call_create_args* args,
grpc_call** out_call) {
GPR_TIMER_SCOPE("grpc_call_create", 0);
@@ -334,9 +300,11 @@ grpc_error* grpc_call_create(const grpc_call_create_args* args,
size_t initial_size = grpc_channel_get_call_size_estimate(args->channel);
GRPC_STATS_INC_CALL_INITIAL_SIZE(initial_size);
gpr_arena* arena = gpr_arena_create(initial_size);
- call = static_cast<grpc_call*>(gpr_arena_alloc(
- arena, sizeof(grpc_call) + channel_stack->call_stack_size));
+ call = static_cast<grpc_call*>(
+ gpr_arena_alloc(arena, GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(grpc_call)) +
+ channel_stack->call_stack_size));
gpr_ref_init(&call->ext_ref, 1);
+ gpr_atm_no_barrier_store(&call->cancelled, 0);
call->arena = arena;
grpc_call_combiner_init(&call->call_combiner);
*out_call = call;
@@ -346,14 +314,10 @@ grpc_error* grpc_call_create(const grpc_call_create_args* args,
/* Always support no compression */
GPR_BITSET(&call->encodings_accepted_by_peer, GRPC_MESSAGE_COMPRESS_NONE);
call->is_client = args->server_transport_data == nullptr;
- if (call->is_client) {
- GRPC_STATS_INC_CLIENT_CALLS_CREATED();
- } else {
- GRPC_STATS_INC_SERVER_CALLS_CREATED();
- }
call->stream_op_payload.context = call->context;
grpc_slice path = grpc_empty_slice();
if (call->is_client) {
+ GRPC_STATS_INC_CLIENT_CALLS_CREATED();
GPR_ASSERT(args->add_initial_metadata_count <
MAX_SEND_EXTRA_METADATA_COUNT);
for (i = 0; i < args->add_initial_metadata_count; i++) {
@@ -367,6 +331,8 @@ grpc_error* grpc_call_create(const grpc_call_create_args* args,
call->send_extra_metadata_count =
static_cast<int>(args->add_initial_metadata_count);
} else {
+ GRPC_STATS_INC_SERVER_CALLS_CREATED();
+ call->final_op.server.server = args->server;
GPR_ASSERT(args->add_initial_metadata_count == 0);
call->send_extra_metadata_count = 0;
}
@@ -448,10 +414,10 @@ grpc_error* grpc_call_create(const grpc_call_create_args* args,
gpr_mu_unlock(&pc->child_list_mu);
}
if (error != GRPC_ERROR_NONE) {
- cancel_with_error(call, STATUS_FROM_SURFACE, GRPC_ERROR_REF(error));
+ cancel_with_error(call, GRPC_ERROR_REF(error));
}
if (immediately_cancel) {
- cancel_with_error(call, STATUS_FROM_API_OVERRIDE, GRPC_ERROR_CANCELLED);
+ cancel_with_error(call, GRPC_ERROR_CANCELLED);
}
if (args->cq != nullptr) {
GPR_ASSERT(args->pollset_set_alternative == nullptr &&
@@ -470,6 +436,20 @@ grpc_error* grpc_call_create(const grpc_call_create_args* args,
&call->pollent);
}
+ if (call->is_client) {
+ grpc_core::channelz::ChannelNode* channelz_channel =
+ grpc_channel_get_channelz_node(call->channel);
+ if (channelz_channel != nullptr) {
+ channelz_channel->RecordCallStarted();
+ }
+ } else {
+ grpc_core::channelz::ServerNode* channelz_server =
+ grpc_server_get_channelz_node(call->final_op.server.server);
+ if (channelz_server != nullptr) {
+ channelz_server->RecordCallStarted();
+ }
+ }
+
grpc_slice_unref_internal(path);
return error;
@@ -507,13 +487,12 @@ void grpc_call_internal_unref(grpc_call* c REF_ARG) {
static void release_call(void* call, grpc_error* error) {
grpc_call* c = static_cast<grpc_call*>(call);
grpc_channel* channel = c->channel;
+ gpr_free(static_cast<void*>(const_cast<char*>(c->final_info.error_string)));
grpc_call_combiner_destroy(&c->call_combiner);
- gpr_free((char*)c->peer_string);
grpc_channel_update_call_size_estimate(channel, gpr_arena_destroy(c->arena));
GRPC_CHANNEL_INTERNAL_UNREF(channel, "call");
}
-static void set_status_value_directly(grpc_status_code status, void* dest);
static void destroy_call(void* call, grpc_error* error) {
GPR_TIMER_SCOPE("destroy_call", 0);
size_t i;
@@ -540,16 +519,15 @@ static void destroy_call(void* call, grpc_error* error) {
GRPC_CQ_INTERNAL_UNREF(c->cq, "bind");
}
- get_final_status(c, set_status_value_directly, &c->final_info.final_status,
- nullptr, c->final_info.error_string);
+ grpc_error* status_error =
+ reinterpret_cast<grpc_error*>(gpr_atm_acq_load(&c->status_error));
+ grpc_error_get_status(status_error, c->send_deadline,
+ &c->final_info.final_status, nullptr, nullptr,
+ &(c->final_info.error_string));
+ GRPC_ERROR_UNREF(status_error);
c->final_info.stats.latency =
gpr_time_sub(gpr_now(GPR_CLOCK_MONOTONIC), c->start_time);
- for (i = 0; i < STATUS_SOURCE_COUNT; i++) {
- GRPC_ERROR_UNREF(
- unpack_received_status(gpr_atm_acq_load(&c->status[i])).error);
- }
-
grpc_call_stack_destroy(CALL_STACK_FROM_CALL(c), &c->final_info,
GRPC_CLOSURE_INIT(&c->release_call, release_call, c,
grpc_schedule_on_exec_ctx));
@@ -587,13 +565,16 @@ void grpc_call_unref(grpc_call* c) {
bool cancel = gpr_atm_acq_load(&c->any_ops_sent_atm) != 0 &&
gpr_atm_acq_load(&c->received_final_op_atm) == 0;
if (cancel) {
- cancel_with_error(c, STATUS_FROM_API_OVERRIDE, GRPC_ERROR_CANCELLED);
+ cancel_with_error(c, GRPC_ERROR_CANCELLED);
} else {
// Unset the call combiner cancellation closure. This has the
// effect of scheduling the previously set cancellation closure, if
// any, so that it can release any internal references it may be
- // holding to the call stack.
+ // holding to the call stack. Also flush the closures on exec_ctx so that
+ // filters that schedule cancel notification closures on exec_ctx do not
+ // need to take a ref of the call stack to guarantee closure liveness.
grpc_call_combiner_set_notify_on_cancel(&c->call_combiner, nullptr);
+ grpc_core::ExecCtx::Get()->Flush();
}
GRPC_CALL_INTERNAL_UNREF(c, "destroy");
}
@@ -602,8 +583,7 @@ grpc_call_error grpc_call_cancel(grpc_call* call, void* reserved) {
GRPC_API_TRACE("grpc_call_cancel(call=%p, reserved=%p)", 2, (call, reserved));
GPR_ASSERT(!reserved);
grpc_core::ExecCtx exec_ctx;
- cancel_with_error(call, STATUS_FROM_API_OVERRIDE, GRPC_ERROR_CANCELLED);
-
+ cancel_with_error(call, GRPC_ERROR_CANCELLED);
return GRPC_CALL_OK;
}
@@ -657,8 +637,7 @@ grpc_call_error grpc_call_cancel_with_status(grpc_call* c,
"c=%p, status=%d, description=%s, reserved=%p)",
4, (c, (int)status, description, reserved));
GPR_ASSERT(reserved == nullptr);
- cancel_with_status(c, STATUS_FROM_API_OVERRIDE, status, description);
-
+ cancel_with_status(c, status, description);
return GRPC_CALL_OK;
}
@@ -678,15 +657,17 @@ static void done_termination(void* arg, grpc_error* error) {
gpr_free(state);
}
-static void cancel_with_error(grpc_call* c, status_source source,
- grpc_error* error) {
+static void cancel_with_error(grpc_call* c, grpc_error* error) {
+ if (!gpr_atm_rel_cas(&c->cancelled, 0, 1)) {
+ GRPC_ERROR_UNREF(error);
+ return;
+ }
GRPC_CALL_INTERNAL_REF(c, "termination");
// Inform the call combiner of the cancellation, so that it can cancel
// any in-flight asynchronous actions that may be holding the call
// combiner. This ensures that the cancel_stream batch can be sent
// down the filter stack in a timely manner.
grpc_call_combiner_cancel(&c->call_combiner, GRPC_ERROR_REF(error));
- set_status_from_error(c, source, GRPC_ERROR_REF(error));
cancel_state* state = static_cast<cancel_state*>(gpr_malloc(sizeof(*state)));
state->call = c;
GRPC_CLOSURE_INIT(&state->finish_batch, done_termination, state,
@@ -709,90 +690,47 @@ static grpc_error* error_from_status(grpc_status_code status,
GRPC_ERROR_INT_GRPC_STATUS, status);
}
-static void cancel_with_status(grpc_call* c, status_source source,
- grpc_status_code status,
+static void cancel_with_status(grpc_call* c, grpc_status_code status,
const char* description) {
- cancel_with_error(c, source, error_from_status(status, description));
+ cancel_with_error(c, error_from_status(status, description));
}
-/*******************************************************************************
- * FINAL STATUS CODE MANIPULATION
- */
-
-static bool get_final_status_from(
- grpc_call* call, grpc_error* error, bool allow_ok_status,
- void (*set_value)(grpc_status_code code, void* user_data),
- void* set_value_user_data, grpc_slice* details, const char** error_string) {
- grpc_status_code code;
- grpc_slice slice = grpc_empty_slice();
- grpc_error_get_status(error, call->send_deadline, &code, &slice, nullptr,
- error_string);
- if (code == GRPC_STATUS_OK && !allow_ok_status) {
- return false;
- }
-
- set_value(code, set_value_user_data);
- if (details != nullptr) {
- *details = grpc_slice_ref_internal(slice);
- }
- return true;
-}
-
-static void get_final_status(
- grpc_call* call, void (*set_value)(grpc_status_code code, void* user_data),
- void* set_value_user_data, grpc_slice* details, const char** error_string) {
- int i;
- received_status status[STATUS_SOURCE_COUNT];
- for (i = 0; i < STATUS_SOURCE_COUNT; i++) {
- status[i] = unpack_received_status(gpr_atm_acq_load(&call->status[i]));
- }
+static void set_final_status(grpc_call* call, grpc_error* error) {
if (grpc_call_error_trace.enabled()) {
- gpr_log(GPR_INFO, "get_final_status %s", call->is_client ? "CLI" : "SVR");
- for (i = 0; i < STATUS_SOURCE_COUNT; i++) {
- if (status[i].is_set) {
- gpr_log(GPR_INFO, " %d: %s", i, grpc_error_string(status[i].error));
- }
- }
+ gpr_log(GPR_DEBUG, "set_final_status %s", call->is_client ? "CLI" : "SVR");
+ gpr_log(GPR_DEBUG, "%s", grpc_error_string(error));
}
- /* first search through ignoring "OK" statuses: if something went wrong,
- * ensure we report it */
- for (int allow_ok_status = 0; allow_ok_status < 2; allow_ok_status++) {
- /* search for the best status we can present: ideally the error we use has a
- clearly defined grpc-status, and we'll prefer that. */
- for (i = 0; i < STATUS_SOURCE_COUNT; i++) {
- if (status[i].is_set &&
- grpc_error_has_clear_grpc_status(status[i].error)) {
- if (get_final_status_from(call, status[i].error, allow_ok_status != 0,
- set_value, set_value_user_data, details,
- error_string)) {
- return;
- }
+ if (call->is_client) {
+ grpc_error_get_status(error, call->send_deadline,
+ call->final_op.client.status,
+ call->final_op.client.status_details, nullptr,
+ call->final_op.client.error_string);
+ // explicitly take a ref
+ grpc_slice_ref_internal(*call->final_op.client.status_details);
+ gpr_atm_rel_store(&call->status_error, reinterpret_cast<gpr_atm>(error));
+ grpc_core::channelz::ChannelNode* channelz_channel =
+ grpc_channel_get_channelz_node(call->channel);
+ if (channelz_channel != nullptr) {
+ if (*call->final_op.client.status != GRPC_STATUS_OK) {
+ channelz_channel->RecordCallFailed();
+ } else {
+ channelz_channel->RecordCallSucceeded();
}
}
- /* If no clearly defined status exists, search for 'anything' */
- for (i = 0; i < STATUS_SOURCE_COUNT; i++) {
- if (status[i].is_set) {
- if (get_final_status_from(call, status[i].error, allow_ok_status != 0,
- set_value, set_value_user_data, details,
- error_string)) {
- return;
- }
+ } else {
+ *call->final_op.server.cancelled =
+ error != GRPC_ERROR_NONE ||
+ reinterpret_cast<grpc_error*>(gpr_atm_acq_load(&call->status_error)) !=
+ GRPC_ERROR_NONE;
+ grpc_core::channelz::ServerNode* channelz_server =
+ grpc_server_get_channelz_node(call->final_op.server.server);
+ if (channelz_server != nullptr) {
+ if (*call->final_op.server.cancelled) {
+ channelz_server->RecordCallFailed();
+ } else {
+ channelz_server->RecordCallSucceeded();
}
}
- }
- /* If nothing exists, set some default */
- if (call->is_client) {
- set_value(GRPC_STATUS_UNKNOWN, set_value_user_data);
- } else {
- set_value(GRPC_STATUS_OK, set_value_user_data);
- }
-}
-
-static void set_status_from_error(grpc_call* call, status_source source,
- grpc_error* error) {
- if (!gpr_atm_rel_cas(&call->status[source],
- pack_received_status({false, GRPC_ERROR_NONE}),
- pack_received_status({true, error}))) {
GRPC_ERROR_UNREF(error);
}
}
@@ -1011,6 +949,7 @@ static grpc_stream_compression_algorithm decode_stream_compression(
static void publish_app_metadata(grpc_call* call, grpc_metadata_batch* b,
int is_trailing) {
if (b->list.count == 0) return;
+ if (!call->is_client && is_trailing) return;
if (is_trailing && call->buffered_metadata[1] == nullptr) return;
GPR_TIMER_SCOPE("publish_app_metadata", 0);
grpc_metadata_array* dest;
@@ -1064,18 +1003,20 @@ static void recv_initial_filter(grpc_call* call, grpc_metadata_batch* b) {
publish_app_metadata(call, b, false);
}
-static void recv_trailing_filter(void* args, grpc_metadata_batch* b) {
+static void recv_trailing_filter(void* args, grpc_metadata_batch* b,
+ grpc_error* batch_error) {
grpc_call* call = static_cast<grpc_call*>(args);
- if (b->idx.named.grpc_status != nullptr) {
+ if (batch_error != GRPC_ERROR_NONE) {
+ set_final_status(call, batch_error);
+ } else if (b->idx.named.grpc_status != nullptr) {
grpc_status_code status_code =
grpc_get_status_code_from_metadata(b->idx.named.grpc_status->md);
- grpc_error* error =
- status_code == GRPC_STATUS_OK
- ? GRPC_ERROR_NONE
- : grpc_error_set_int(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
- "Error received from peer"),
- GRPC_ERROR_INT_GRPC_STATUS,
- static_cast<intptr_t>(status_code));
+ grpc_error* error = GRPC_ERROR_NONE;
+ if (status_code != GRPC_STATUS_OK) {
+ error = grpc_error_set_int(
+ GRPC_ERROR_CREATE_FROM_STATIC_STRING("Error received from peer"),
+ GRPC_ERROR_INT_GRPC_STATUS, static_cast<intptr_t>(status_code));
+ }
if (b->idx.named.grpc_message != nullptr) {
error = grpc_error_set_str(
error, GRPC_ERROR_STR_GRPC_MESSAGE,
@@ -1085,12 +1026,24 @@ static void recv_trailing_filter(void* args, grpc_metadata_batch* b) {
error = grpc_error_set_str(error, GRPC_ERROR_STR_GRPC_MESSAGE,
grpc_empty_slice());
}
- set_status_from_error(call, STATUS_FROM_WIRE, error);
+ set_final_status(call, GRPC_ERROR_REF(error));
grpc_metadata_batch_remove(b, b->idx.named.grpc_status);
+ GRPC_ERROR_UNREF(error);
+ } else if (!call->is_client) {
+ set_final_status(call, GRPC_ERROR_NONE);
+ } else {
+ gpr_log(GPR_DEBUG,
+ "Received trailing metadata with no error and no status");
+ set_final_status(
+ call, grpc_error_set_int(
+ GRPC_ERROR_CREATE_FROM_STATIC_STRING("No status received"),
+ GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_UNKNOWN));
}
publish_app_metadata(call, b, true);
}
+gpr_arena* grpc_call_get_arena(grpc_call* call) { return call->arena; }
+
grpc_call_stack* grpc_call_get_call_stack(grpc_call* call) {
return CALL_STACK_FROM_CALL(call);
}
@@ -1099,14 +1052,6 @@ grpc_call_stack* grpc_call_get_call_stack(grpc_call* call) {
* BATCH API IMPLEMENTATION
*/
-static void set_status_value_directly(grpc_status_code status, void* dest) {
- *static_cast<grpc_status_code*>(dest) = status;
-}
-
-static void set_cancelled_value(grpc_status_code status, void* dest) {
- *static_cast<int*>(dest) = (status != GRPC_STATUS_OK);
-}
-
static bool are_write_flags_valid(uint32_t flags) {
/* check that only bits in GRPC_WRITE_(INTERNAL?)_USED_MASK are set */
const uint32_t allowed_write_positions =
@@ -1124,7 +1069,7 @@ static bool are_initial_metadata_flags_valid(uint32_t flags, bool is_client) {
return !(flags & invalid_positions);
}
-static int batch_slot_for_op(grpc_op_type type) {
+static size_t batch_slot_for_op(grpc_op_type type) {
switch (type) {
case GRPC_OP_SEND_INITIAL_METADATA:
return 0;
@@ -1144,20 +1089,23 @@ static int batch_slot_for_op(grpc_op_type type) {
GPR_UNREACHABLE_CODE(return 123456789);
}
-static batch_control* allocate_batch_control(grpc_call* call,
- const grpc_op* ops,
- size_t num_ops) {
- int slot = batch_slot_for_op(ops[0].op);
- batch_control** pslot = &call->active_batches[slot];
- if (*pslot == nullptr) {
- *pslot = static_cast<batch_control*>(
+static batch_control* reuse_or_allocate_batch_control(grpc_call* call,
+ const grpc_op* ops,
+ size_t num_ops) {
+ size_t slot_idx = batch_slot_for_op(ops[0].op);
+ batch_control** pslot = &call->active_batches[slot_idx];
+ batch_control* bctl;
+ if (*pslot != nullptr) {
+ bctl = *pslot;
+ if (bctl->call != nullptr) {
+ return nullptr;
+ }
+ memset(bctl, 0, sizeof(*bctl));
+ } else {
+ bctl = static_cast<batch_control*>(
gpr_arena_alloc(call->arena, sizeof(batch_control)));
+ *pslot = bctl;
}
- batch_control* bctl = *pslot;
- if (bctl->call != nullptr) {
- return nullptr;
- }
- memset(bctl, 0, sizeof(*bctl));
bctl->call = call;
bctl->op.payload = &call->stream_op_payload;
return bctl;
@@ -1171,35 +1119,21 @@ static void finish_batch_completion(void* user_data,
GRPC_CALL_INTERNAL_UNREF(call, "completion");
}
-static grpc_error* consolidate_batch_errors(batch_control* bctl) {
- size_t n = static_cast<size_t>(gpr_atm_acq_load(&bctl->num_errors));
- if (n == 0) {
- return GRPC_ERROR_NONE;
- } else if (n == 1) {
- /* Skip creating a composite error in the case that only one error was
- logged */
- grpc_error* e = bctl->errors[0];
- bctl->errors[0] = nullptr;
- return e;
- } else {
- grpc_error* error = GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING(
- "Call batch failed", bctl->errors, n);
- for (size_t i = 0; i < n; i++) {
- GRPC_ERROR_UNREF(bctl->errors[i]);
- bctl->errors[i] = nullptr;
- }
- return error;
- }
+static void reset_batch_errors(batch_control* bctl) {
+ GRPC_ERROR_UNREF(
+ reinterpret_cast<grpc_error*>(gpr_atm_acq_load(&bctl->batch_error)));
+ gpr_atm_rel_store(&bctl->batch_error,
+ reinterpret_cast<gpr_atm>(GRPC_ERROR_NONE));
}
static void post_batch_completion(batch_control* bctl) {
grpc_call* next_child_call;
grpc_call* call = bctl->call;
- grpc_error* error = consolidate_batch_errors(bctl);
+ grpc_error* error = GRPC_ERROR_REF(
+ reinterpret_cast<grpc_error*>(gpr_atm_acq_load(&bctl->batch_error)));
if (bctl->op.send_initial_metadata) {
grpc_metadata_batch_destroy(
-
&call->metadata_batch[0 /* is_receiving */][0 /* is_trailing */]);
}
if (bctl->op.send_message) {
@@ -1207,14 +1141,9 @@ static void post_batch_completion(batch_control* bctl) {
}
if (bctl->op.send_trailing_metadata) {
grpc_metadata_batch_destroy(
-
&call->metadata_batch[0 /* is_receiving */][1 /* is_trailing */]);
}
if (bctl->op.recv_trailing_metadata) {
- grpc_metadata_batch* md =
- &call->metadata_batch[1 /* is_receiving */][1 /* is_trailing */];
- recv_trailing_filter(call, md);
-
/* propagate cancellation to any interested children */
gpr_atm_rel_store(&call->received_final_op_atm, 1);
parent_call* pc = get_parent_call(call);
@@ -1227,8 +1156,7 @@ static void post_batch_completion(batch_control* bctl) {
next_child_call = child->child->sibling_next;
if (child->cancellation_is_inherited) {
GRPC_CALL_INTERNAL_REF(child, "propagate_cancel");
- cancel_with_error(child, STATUS_FROM_API_OVERRIDE,
- GRPC_ERROR_CANCELLED);
+ cancel_with_error(child, GRPC_ERROR_CANCELLED);
GRPC_CALL_INTERNAL_UNREF(child, "propagate_cancel");
}
child = next_child_call;
@@ -1236,17 +1164,6 @@ static void post_batch_completion(batch_control* bctl) {
}
gpr_mu_unlock(&pc->child_list_mu);
}
-
- if (call->is_client) {
- get_final_status(call, set_status_value_directly,
- call->final_op.client.status,
- call->final_op.client.status_details,
- call->final_op.client.error_string);
- } else {
- get_final_status(call, set_cancelled_value,
- call->final_op.server.cancelled, nullptr, nullptr);
- }
-
GRPC_ERROR_UNREF(error);
error = GRPC_ERROR_NONE;
}
@@ -1255,15 +1172,20 @@ static void post_batch_completion(batch_control* bctl) {
grpc_byte_buffer_destroy(*call->receiving_buffer);
*call->receiving_buffer = nullptr;
}
+ reset_batch_errors(bctl);
if (bctl->completion_data.notify_tag.is_closure) {
- /* unrefs bctl->error */
+ /* unrefs error */
bctl->call = nullptr;
- GRPC_CLOSURE_RUN((grpc_closure*)bctl->completion_data.notify_tag.tag,
- error);
+ /* This closure may be meant to be run within some combiner. Since we aren't
+ * running in any combiner here, we need to use GRPC_CLOSURE_SCHED instead
+ * of GRPC_CLOSURE_RUN.
+ */
+ GRPC_CLOSURE_SCHED((grpc_closure*)bctl->completion_data.notify_tag.tag,
+ error);
GRPC_CALL_INTERNAL_UNREF(call, "completion");
} else {
- /* unrefs bctl->error */
+ /* unrefs error */
grpc_cq_end_op(bctl->call->cq, bctl->completion_data.notify_tag.tag, error,
finish_batch_completion, bctl,
&bctl->completion_data.cq_completion);
@@ -1372,8 +1294,12 @@ static void receiving_stream_ready(void* bctlp, grpc_error* error) {
grpc_call* call = bctl->call;
if (error != GRPC_ERROR_NONE) {
call->receiving_stream.reset();
- add_batch_error(bctl, GRPC_ERROR_REF(error), true);
- cancel_with_error(call, STATUS_FROM_SURFACE, GRPC_ERROR_REF(error));
+ if (reinterpret_cast<grpc_error*>(gpr_atm_acq_load(&bctl->batch_error)) ==
+ GRPC_ERROR_NONE) {
+ gpr_atm_rel_store(&bctl->batch_error,
+ reinterpret_cast<gpr_atm>(GRPC_ERROR_REF(error)));
+ }
+ cancel_with_error(call, GRPC_ERROR_REF(error));
}
/* If recv_state is RECV_NONE, we will save the batch_control
* object with rel_cas, and will not use it after the cas. Its corresponding
@@ -1409,8 +1335,7 @@ static void validate_filtered_metadata(batch_control* bctl) {
call->incoming_stream_compression_algorithm,
call->incoming_message_compression_algorithm);
gpr_log(GPR_ERROR, "%s", error_msg);
- cancel_with_status(call, STATUS_FROM_SURFACE, GRPC_STATUS_INTERNAL,
- error_msg);
+ cancel_with_status(call, GRPC_STATUS_INTERNAL, error_msg);
gpr_free(error_msg);
} else if (
grpc_compression_algorithm_from_message_stream_compression_algorithm(
@@ -1422,8 +1347,7 @@ static void validate_filtered_metadata(batch_control* bctl) {
"compression (%d).",
call->incoming_stream_compression_algorithm,
call->incoming_message_compression_algorithm);
- cancel_with_status(call, STATUS_FROM_SURFACE, GRPC_STATUS_INTERNAL,
- error_msg);
+ cancel_with_status(call, GRPC_STATUS_INTERNAL, error_msg);
gpr_free(error_msg);
} else {
char* error_msg = nullptr;
@@ -1433,8 +1357,7 @@ static void validate_filtered_metadata(batch_control* bctl) {
gpr_asprintf(&error_msg, "Invalid compression algorithm value '%d'.",
compression_algorithm);
gpr_log(GPR_ERROR, "%s", error_msg);
- cancel_with_status(call, STATUS_FROM_SURFACE, GRPC_STATUS_UNIMPLEMENTED,
- error_msg);
+ cancel_with_status(call, GRPC_STATUS_UNIMPLEMENTED, error_msg);
} else if (grpc_compression_options_is_algorithm_enabled(
&compression_options, compression_algorithm) == 0) {
/* check if algorithm is supported by current channel config */
@@ -1443,8 +1366,7 @@ static void validate_filtered_metadata(batch_control* bctl) {
gpr_asprintf(&error_msg, "Compression algorithm '%s' is disabled.",
algo_name);
gpr_log(GPR_ERROR, "%s", error_msg);
- cancel_with_status(call, STATUS_FROM_SURFACE, GRPC_STATUS_UNIMPLEMENTED,
- error_msg);
+ cancel_with_status(call, GRPC_STATUS_UNIMPLEMENTED, error_msg);
}
gpr_free(error_msg);
@@ -1462,23 +1384,12 @@ static void validate_filtered_metadata(batch_control* bctl) {
}
}
-static void add_batch_error(batch_control* bctl, grpc_error* error,
- bool has_cancelled) {
- if (error == GRPC_ERROR_NONE) return;
- int idx = static_cast<int>(gpr_atm_full_fetch_add(&bctl->num_errors, 1));
- if (idx == 0 && !has_cancelled) {
- cancel_with_error(bctl->call, STATUS_FROM_CORE, GRPC_ERROR_REF(error));
- }
- bctl->errors[idx] = error;
-}
-
static void receiving_initial_metadata_ready(void* bctlp, grpc_error* error) {
batch_control* bctl = static_cast<batch_control*>(bctlp);
grpc_call* call = bctl->call;
GRPC_CALL_COMBINER_STOP(&call->call_combiner, "recv_initial_metadata_ready");
- add_batch_error(bctl, GRPC_ERROR_REF(error), false);
if (error == GRPC_ERROR_NONE) {
grpc_metadata_batch* md =
&call->metadata_batch[1 /* is_receiving */][0 /* is_trailing */];
@@ -1491,6 +1402,13 @@ static void receiving_initial_metadata_ready(void* bctlp, grpc_error* error) {
if (md->deadline != GRPC_MILLIS_INF_FUTURE && !call->is_client) {
call->send_deadline = md->deadline;
}
+ } else {
+ if (reinterpret_cast<grpc_error*>(gpr_atm_acq_load(&bctl->batch_error)) ==
+ GRPC_ERROR_NONE) {
+ gpr_atm_rel_store(&bctl->batch_error,
+ reinterpret_cast<gpr_atm>(GRPC_ERROR_REF(error)));
+ }
+ cancel_with_error(call, GRPC_ERROR_REF(error));
}
grpc_closure* saved_rsr_closure = nullptr;
@@ -1524,11 +1442,28 @@ static void receiving_initial_metadata_ready(void* bctlp, grpc_error* error) {
finish_batch_step(bctl);
}
+static void receiving_trailing_metadata_ready(void* bctlp, grpc_error* error) {
+ batch_control* bctl = static_cast<batch_control*>(bctlp);
+ grpc_call* call = bctl->call;
+ GRPC_CALL_COMBINER_STOP(&call->call_combiner, "recv_trailing_metadata_ready");
+ grpc_metadata_batch* md =
+ &call->metadata_batch[1 /* is_receiving */][1 /* is_trailing */];
+ recv_trailing_filter(call, md, GRPC_ERROR_REF(error));
+ finish_batch_step(bctl);
+}
+
static void finish_batch(void* bctlp, grpc_error* error) {
batch_control* bctl = static_cast<batch_control*>(bctlp);
grpc_call* call = bctl->call;
GRPC_CALL_COMBINER_STOP(&call->call_combiner, "on_complete");
- add_batch_error(bctl, GRPC_ERROR_REF(error), false);
+ if (reinterpret_cast<grpc_error*>(gpr_atm_acq_load(&bctl->batch_error)) ==
+ GRPC_ERROR_NONE) {
+ gpr_atm_rel_store(&bctl->batch_error,
+ reinterpret_cast<gpr_atm>(GRPC_ERROR_REF(error)));
+ }
+ if (error != GRPC_ERROR_NONE) {
+ cancel_with_error(call, GRPC_ERROR_REF(error));
+ }
finish_batch_step(bctl);
}
@@ -1544,7 +1479,8 @@ static grpc_call_error call_start_batch(grpc_call* call, const grpc_op* ops,
size_t i;
const grpc_op* op;
batch_control* bctl;
- int num_completion_callbacks_needed = 1;
+ bool has_send_ops = false;
+ int num_recv_ops = 0;
grpc_call_error error = GRPC_CALL_OK;
grpc_transport_stream_op_batch* stream_op;
grpc_transport_stream_op_batch_payload* stream_op_payload;
@@ -1565,7 +1501,7 @@ static grpc_call_error call_start_batch(grpc_call* call, const grpc_op* ops,
goto done;
}
- bctl = allocate_batch_control(call, ops, nops);
+ bctl = reuse_or_allocate_batch_control(call, ops, nops);
if (bctl == nullptr) {
return GRPC_CALL_ERROR_TOO_MANY_OPERATIONS;
}
@@ -1650,6 +1586,7 @@ static grpc_call_error call_start_batch(grpc_call* call, const grpc_op* ops,
stream_op_payload->send_initial_metadata.peer_string =
&call->peer_string;
}
+ has_send_ops = true;
break;
}
case GRPC_OP_SEND_MESSAGE: {
@@ -1679,6 +1616,7 @@ static grpc_call_error call_start_batch(grpc_call* call, const grpc_op* ops,
&op->data.send_message.send_message->data.raw.slice_buffer, flags);
stream_op_payload->send_message.send_message.reset(
call->sending_stream.get());
+ has_send_ops = true;
break;
}
case GRPC_OP_SEND_CLOSE_FROM_CLIENT: {
@@ -1699,6 +1637,7 @@ static grpc_call_error call_start_batch(grpc_call* call, const grpc_op* ops,
call->sent_final_op = true;
stream_op_payload->send_trailing_metadata.send_trailing_metadata =
&call->metadata_batch[0 /* is_receiving */][1 /* is_trailing */];
+ has_send_ops = true;
break;
}
case GRPC_OP_SEND_STATUS_FROM_SERVER: {
@@ -1726,28 +1665,33 @@ static grpc_call_error call_start_batch(grpc_call* call, const grpc_op* ops,
call->send_extra_metadata_count = 1;
call->send_extra_metadata[0].md = grpc_channel_get_reffed_status_elem(
call->channel, op->data.send_status_from_server.status);
- {
- grpc_error* override_error = GRPC_ERROR_NONE;
- if (op->data.send_status_from_server.status != GRPC_STATUS_OK) {
- override_error =
- error_from_status(op->data.send_status_from_server.status,
- "Returned non-ok status");
- }
- if (op->data.send_status_from_server.status_details != nullptr) {
- call->send_extra_metadata[1].md = grpc_mdelem_from_slices(
- GRPC_MDSTR_GRPC_MESSAGE,
- grpc_slice_ref_internal(
- *op->data.send_status_from_server.status_details));
- call->send_extra_metadata_count++;
+ grpc_error* status_error =
+ op->data.send_status_from_server.status == GRPC_STATUS_OK
+ ? GRPC_ERROR_NONE
+ : grpc_error_set_int(
+ GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+ "Server returned error"),
+ GRPC_ERROR_INT_GRPC_STATUS,
+ static_cast<intptr_t>(
+ op->data.send_status_from_server.status));
+ if (op->data.send_status_from_server.status_details != nullptr) {
+ call->send_extra_metadata[1].md = grpc_mdelem_from_slices(
+ GRPC_MDSTR_GRPC_MESSAGE,
+ grpc_slice_ref_internal(
+ *op->data.send_status_from_server.status_details));
+ call->send_extra_metadata_count++;
+ if (status_error != GRPC_ERROR_NONE) {
char* msg = grpc_slice_to_c_string(
GRPC_MDVALUE(call->send_extra_metadata[1].md));
- override_error =
- grpc_error_set_str(override_error, GRPC_ERROR_STR_GRPC_MESSAGE,
+ status_error =
+ grpc_error_set_str(status_error, GRPC_ERROR_STR_GRPC_MESSAGE,
grpc_slice_from_copied_string(msg));
gpr_free(msg);
}
- set_status_from_error(call, STATUS_FROM_API_OVERRIDE, override_error);
}
+
+ gpr_atm_rel_store(&call->status_error,
+ reinterpret_cast<gpr_atm>(status_error));
if (!prepare_application_metadata(
call,
static_cast<int>(
@@ -1763,6 +1707,7 @@ static grpc_call_error call_start_batch(grpc_call* call, const grpc_op* ops,
}
stream_op_payload->send_trailing_metadata.send_trailing_metadata =
&call->metadata_batch[0 /* is_receiving */][1 /* is_trailing */];
+ has_send_ops = true;
break;
}
case GRPC_OP_RECV_INITIAL_METADATA: {
@@ -1790,7 +1735,7 @@ static grpc_call_error call_start_batch(grpc_call* call, const grpc_op* ops,
stream_op_payload->recv_initial_metadata.peer_string =
&call->peer_string;
}
- num_completion_callbacks_needed++;
+ ++num_recv_ops;
break;
}
case GRPC_OP_RECV_MESSAGE: {
@@ -1812,7 +1757,7 @@ static grpc_call_error call_start_batch(grpc_call* call, const grpc_op* ops,
grpc_schedule_on_exec_ctx);
stream_op_payload->recv_message.recv_message_ready =
&call->receiving_stream_ready;
- num_completion_callbacks_needed++;
+ ++num_recv_ops;
break;
}
case GRPC_OP_RECV_STATUS_ON_CLIENT: {
@@ -1838,11 +1783,16 @@ static grpc_call_error call_start_batch(grpc_call* call, const grpc_op* ops,
call->final_op.client.error_string =
op->data.recv_status_on_client.error_string;
stream_op->recv_trailing_metadata = true;
- stream_op->collect_stats = true;
stream_op_payload->recv_trailing_metadata.recv_trailing_metadata =
&call->metadata_batch[1 /* is_receiving */][1 /* is_trailing */];
- stream_op_payload->collect_stats.collect_stats =
+ stream_op_payload->recv_trailing_metadata.collect_stats =
&call->final_info.stats.transport_stream_stats;
+ GRPC_CLOSURE_INIT(&call->receiving_trailing_metadata_ready,
+ receiving_trailing_metadata_ready, bctl,
+ grpc_schedule_on_exec_ctx);
+ stream_op_payload->recv_trailing_metadata.recv_trailing_metadata_ready =
+ &call->receiving_trailing_metadata_ready;
+ ++num_recv_ops;
break;
}
case GRPC_OP_RECV_CLOSE_ON_SERVER: {
@@ -1863,11 +1813,16 @@ static grpc_call_error call_start_batch(grpc_call* call, const grpc_op* ops,
call->final_op.server.cancelled =
op->data.recv_close_on_server.cancelled;
stream_op->recv_trailing_metadata = true;
- stream_op->collect_stats = true;
stream_op_payload->recv_trailing_metadata.recv_trailing_metadata =
&call->metadata_batch[1 /* is_receiving */][1 /* is_trailing */];
- stream_op_payload->collect_stats.collect_stats =
+ stream_op_payload->recv_trailing_metadata.collect_stats =
&call->final_info.stats.transport_stream_stats;
+ GRPC_CLOSURE_INIT(&call->receiving_trailing_metadata_ready,
+ receiving_trailing_metadata_ready, bctl,
+ grpc_schedule_on_exec_ctx);
+ stream_op_payload->recv_trailing_metadata.recv_trailing_metadata_ready =
+ &call->receiving_trailing_metadata_ready;
+ ++num_recv_ops;
break;
}
}
@@ -1877,20 +1832,22 @@ static grpc_call_error call_start_batch(grpc_call* call, const grpc_op* ops,
if (!is_notify_tag_closure) {
GPR_ASSERT(grpc_cq_begin_op(call->cq, notify_tag));
}
- gpr_ref_init(&bctl->steps_to_complete, num_completion_callbacks_needed);
+ gpr_ref_init(&bctl->steps_to_complete, (has_send_ops ? 1 : 0) + num_recv_ops);
- GRPC_CLOSURE_INIT(&bctl->finish_batch, finish_batch, bctl,
- grpc_schedule_on_exec_ctx);
- stream_op->on_complete = &bctl->finish_batch;
- gpr_atm_rel_store(&call->any_ops_sent_atm, 1);
+ if (has_send_ops) {
+ GRPC_CLOSURE_INIT(&bctl->finish_batch, finish_batch, bctl,
+ grpc_schedule_on_exec_ctx);
+ stream_op->on_complete = &bctl->finish_batch;
+ }
+ gpr_atm_rel_store(&call->any_ops_sent_atm, 1);
execute_batch(call, stream_op, &bctl->start_batch);
done:
return error;
done_with_error:
- /* reverse any mutations that occured */
+ /* reverse any mutations that occurred */
if (stream_op->send_initial_metadata) {
call->sent_initial_metadata = false;
grpc_metadata_batch_clear(&call->metadata_batch[0][0]);
diff --git a/src/core/lib/surface/call.h b/src/core/lib/surface/call.h
index 793cce4efa..b34260505a 100644
--- a/src/core/lib/surface/call.h
+++ b/src/core/lib/surface/call.h
@@ -33,6 +33,7 @@ typedef void (*grpc_ioreq_completion_func)(grpc_call* call, int success,
typedef struct grpc_call_create_args {
grpc_channel* channel;
+ grpc_server* server;
grpc_call* parent;
uint32_t propagation_mask;
@@ -71,6 +72,8 @@ void grpc_call_internal_unref(grpc_call* call);
#define GRPC_CALL_INTERNAL_UNREF(call, reason) grpc_call_internal_unref(call)
#endif
+gpr_arena* grpc_call_get_arena(grpc_call* call);
+
grpc_call_stack* grpc_call_get_call_stack(grpc_call* call);
grpc_call_error grpc_call_start_batch_and_execute(grpc_call* call,
@@ -98,6 +101,11 @@ void* grpc_call_context_get(grpc_call* call, grpc_context_index elem);
uint8_t grpc_call_is_client(grpc_call* call);
+/* Get the estimated memory size for a call BESIDES the call stack. Combined
+ * with the size of the call stack, it helps estimate the arena size for the
+ * initial call. */
+size_t grpc_call_get_initial_size_estimate();
+
/* Return an appropriate compression algorithm for the requested compression \a
* level in the context of \a call. */
grpc_compression_algorithm grpc_call_compression_for_level(
diff --git a/src/core/lib/surface/channel.cc b/src/core/lib/surface/channel.cc
index d740ebd411..054fe105c3 100644
--- a/src/core/lib/surface/channel.cc
+++ b/src/core/lib/surface/channel.cc
@@ -32,6 +32,7 @@
#include "src/core/lib/channel/channel_args.h"
#include "src/core/lib/channel/channel_trace.h"
+#include "src/core/lib/channel/channelz.h"
#include "src/core/lib/debug/stats.h"
#include "src/core/lib/gpr/string.h"
#include "src/core/lib/gprpp/manual_constructor.h"
@@ -66,16 +67,12 @@ struct grpc_channel {
gpr_mu registered_call_mu;
registered_call* registered_calls;
- grpc_core::RefCountedPtr<grpc_core::ChannelTrace> tracer;
+ grpc_core::RefCountedPtr<grpc_core::channelz::ChannelNode> channelz_channel;
char* target;
};
#define CHANNEL_STACK_FROM_CHANNEL(c) ((grpc_channel_stack*)((c) + 1))
-#define CHANNEL_FROM_CHANNEL_STACK(channel_stack) \
- (((grpc_channel*)(channel_stack)) - 1)
-#define CHANNEL_FROM_TOP_ELEM(top_elem) \
- CHANNEL_FROM_CHANNEL_STACK(grpc_channel_stack_from_top_element(top_elem))
static void destroy_channel(void* arg, grpc_error* error);
@@ -103,16 +100,22 @@ grpc_channel* grpc_channel_create_with_builder(
return channel;
}
- memset(channel, 0, sizeof(*channel));
channel->target = target;
channel->is_client = grpc_channel_stack_type_is_client(channel_stack_type);
size_t channel_tracer_max_nodes = 0; // default to off
+ bool channelz_enabled = false;
+ bool internal_channel = false;
+ // this creates the default ChannelNode. Different types of channels may
+ // override this to ensure a correct ChannelNode is created.
+ grpc_core::channelz::ChannelNodeCreationFunc channel_node_create_func =
+ grpc_core::channelz::ChannelNode::MakeChannelNode;
gpr_mu_init(&channel->registered_call_mu);
channel->registered_calls = nullptr;
gpr_atm_no_barrier_store(
&channel->call_size_estimate,
- (gpr_atm)CHANNEL_STACK_FROM_CHANNEL(channel)->call_stack_size);
+ (gpr_atm)CHANNEL_STACK_FROM_CHANNEL(channel)->call_stack_size +
+ grpc_call_get_initial_size_estimate());
grpc_compression_options_init(&channel->compression_options);
for (size_t i = 0; i < args->num_args; i++) {
@@ -144,15 +147,33 @@ grpc_channel* grpc_channel_create_with_builder(
const grpc_integer_options options = {0, 0, INT_MAX};
channel_tracer_max_nodes =
(size_t)grpc_channel_arg_get_integer(&args->args[i], options);
+ } else if (0 == strcmp(args->args[i].key, GRPC_ARG_ENABLE_CHANNELZ)) {
+ // channelz will not be enabled by default until all concerns in
+ // https://github.com/grpc/grpc/issues/15986 are addressed.
+ channelz_enabled = grpc_channel_arg_get_bool(&args->args[i], false);
+ } else if (0 == strcmp(args->args[i].key,
+ GRPC_ARG_CHANNELZ_CHANNEL_NODE_CREATION_FUNC)) {
+ GPR_ASSERT(args->args[i].type == GRPC_ARG_POINTER);
+ GPR_ASSERT(args->args[i].value.pointer.p != nullptr);
+ channel_node_create_func =
+ reinterpret_cast<grpc_core::channelz::ChannelNodeCreationFunc>(
+ args->args[i].value.pointer.p);
+ } else if (0 == strcmp(args->args[i].key,
+ GRPC_ARG_CHANNELZ_CHANNEL_IS_INTERNAL_CHANNEL)) {
+ internal_channel = grpc_channel_arg_get_bool(&args->args[i], false);
}
}
grpc_channel_args_destroy(args);
- channel->tracer = grpc_core::MakeRefCounted<grpc_core::ChannelTrace>(
- channel_tracer_max_nodes);
- channel->tracer->AddTraceEvent(
- grpc_core::ChannelTrace::Severity::Info,
- grpc_slice_from_static_string("Channel created"));
+ // we only need to do the channelz bookkeeping for clients here. The channelz
+ // bookkeeping for server channels occurs in src/core/lib/surface/server.cc
+ if (channelz_enabled && channel->is_client) {
+ channel->channelz_channel = channel_node_create_func(
+ channel, channel_tracer_max_nodes, !internal_channel);
+ channel->channelz_channel->AddTraceEvent(
+ grpc_core::channelz::ChannelTrace::Severity::Info,
+ grpc_slice_from_static_string("Channel created"));
+ }
return channel;
}
@@ -187,12 +208,9 @@ static grpc_channel_args* build_channel_args(
return grpc_channel_args_copy_and_add(input_args, new_args, num_new_args);
}
-char* grpc_channel_get_trace(grpc_channel* channel) {
- return channel->tracer->RenderTrace();
-}
-
-intptr_t grpc_channel_get_uuid(grpc_channel* channel) {
- return channel->tracer->GetUuid();
+grpc_core::channelz::ChannelNode* grpc_channel_get_channelz_node(
+ grpc_channel* channel) {
+ return channel->channelz_channel.get();
}
grpc_channel* grpc_channel_create(const char* target,
@@ -263,6 +281,17 @@ void grpc_channel_get_info(grpc_channel* channel,
elem->filter->get_channel_info(elem, channel_info);
}
+void grpc_channel_reset_connect_backoff(grpc_channel* channel) {
+ grpc_core::ExecCtx exec_ctx;
+ GRPC_API_TRACE("grpc_channel_reset_connect_backoff(channel=%p)", 1,
+ (channel));
+ grpc_transport_op* op = grpc_make_transport_op(nullptr);
+ op->reset_connect_backoff = true;
+ grpc_channel_element* elem =
+ grpc_channel_stack_element(CHANNEL_STACK_FROM_CHANNEL(channel), 0);
+ elem->filter->start_transport_op(elem, op);
+}
+
static grpc_call* grpc_channel_create_call_internal(
grpc_channel* channel, grpc_call* parent_call, uint32_t propagation_mask,
grpc_completion_queue* cq, grpc_pollset_set* pollset_set_alternative,
@@ -398,6 +427,13 @@ void grpc_channel_internal_unref(grpc_channel* c REF_ARG) {
static void destroy_channel(void* arg, grpc_error* error) {
grpc_channel* channel = static_cast<grpc_channel*>(arg);
+ if (channel->channelz_channel != nullptr) {
+ channel->channelz_channel->AddTraceEvent(
+ grpc_core::channelz::ChannelTrace::Severity::Info,
+ grpc_slice_from_static_string("Channel destroyed"));
+ channel->channelz_channel->MarkChannelDestroyed();
+ channel->channelz_channel.reset();
+ }
grpc_channel_stack_destroy(CHANNEL_STACK_FROM_CHANNEL(channel));
while (channel->registered_calls) {
registered_call* rc = channel->registered_calls;
@@ -406,7 +442,6 @@ static void destroy_channel(void* arg, grpc_error* error) {
GRPC_MDELEM_UNREF(rc->authority);
gpr_free(rc);
}
- channel->tracer.reset();
gpr_mu_destroy(&channel->registered_call_mu);
gpr_free(channel->target);
gpr_free(channel);
diff --git a/src/core/lib/surface/channel.h b/src/core/lib/surface/channel.h
index 288313951e..e5ff2c3596 100644
--- a/src/core/lib/surface/channel.h
+++ b/src/core/lib/surface/channel.h
@@ -23,6 +23,7 @@
#include "src/core/lib/channel/channel_stack.h"
#include "src/core/lib/channel/channel_stack_builder.h"
+#include "src/core/lib/channel/channelz.h"
#include "src/core/lib/surface/channel_stack_type.h"
grpc_channel* grpc_channel_create(const char* target,
@@ -50,6 +51,9 @@ grpc_call* grpc_channel_create_pollset_set_call(
/** Get a (borrowed) pointer to this channels underlying channel stack */
grpc_channel_stack* grpc_channel_get_channel_stack(grpc_channel* channel);
+grpc_core::channelz::ChannelNode* grpc_channel_get_channelz_node(
+ grpc_channel* channel);
+
/** Get a grpc_mdelem of grpc-status: X where X is the numeric value of
status_code.
diff --git a/src/core/lib/surface/completion_queue.cc b/src/core/lib/surface/completion_queue.cc
index f751741712..5dc9991f70 100644
--- a/src/core/lib/surface/completion_queue.cc
+++ b/src/core/lib/surface/completion_queue.cc
@@ -184,7 +184,8 @@ static const cq_poller_vtable g_poller_vtable_by_poller_type[] = {
typedef struct cq_vtable {
grpc_cq_completion_type cq_completion_type;
size_t data_size;
- void (*init)(void* data);
+ void (*init)(void* data,
+ grpc_experimental_completion_queue_functor* shutdown_callback);
void (*shutdown)(grpc_completion_queue* cq);
void (*destroy)(void* data);
bool (*begin_op)(grpc_completion_queue* cq, void* tag);
@@ -253,6 +254,23 @@ typedef struct cq_pluck_data {
plucker pluckers[GRPC_MAX_COMPLETION_QUEUE_PLUCKERS];
} cq_pluck_data;
+typedef struct cq_callback_data {
+ /** No actual completed events queue, unlike other types */
+
+ /** Number of pending events (+1 if we're not shutdown) */
+ gpr_atm pending_events;
+
+ /** Counter of how many things have ever been queued on this completion queue
+ useful for avoiding locks to check the queue */
+ gpr_atm things_queued_ever;
+
+ /** 0 initially. 1 once we initiated shutdown */
+ bool shutdown_called;
+
+ /** A callback that gets invoked when the CQ completes shutdown */
+ grpc_experimental_completion_queue_functor* shutdown_callback;
+} cq_callback_data;
+
/* Completion queue structure */
struct grpc_completion_queue {
/** Once owning_refs drops to zero, we will destroy the cq */
@@ -276,12 +294,21 @@ struct grpc_completion_queue {
/* Forward declarations */
static void cq_finish_shutdown_next(grpc_completion_queue* cq);
static void cq_finish_shutdown_pluck(grpc_completion_queue* cq);
+static void cq_finish_shutdown_callback(grpc_completion_queue* cq);
static void cq_shutdown_next(grpc_completion_queue* cq);
static void cq_shutdown_pluck(grpc_completion_queue* cq);
+static void cq_shutdown_callback(grpc_completion_queue* cq);
static bool cq_begin_op_for_next(grpc_completion_queue* cq, void* tag);
static bool cq_begin_op_for_pluck(grpc_completion_queue* cq, void* tag);
-
+static bool cq_begin_op_for_callback(grpc_completion_queue* cq, void* tag);
+
+// A cq_end_op function is called when an operation on a given CQ with
+// a given tag has completed. The storage argument is a reference to the
+// space reserved for this completion as it is placed into the corresponding
+// queue. The done argument is a callback that will be invoked when it is
+// safe to free up that storage. The storage MUST NOT be freed until the
+// done callback is invoked.
static void cq_end_op_for_next(grpc_completion_queue* cq, void* tag,
grpc_error* error,
void (*done)(void* done_arg,
@@ -294,16 +321,28 @@ static void cq_end_op_for_pluck(grpc_completion_queue* cq, void* tag,
grpc_cq_completion* storage),
void* done_arg, grpc_cq_completion* storage);
+static void cq_end_op_for_callback(grpc_completion_queue* cq, void* tag,
+ grpc_error* error,
+ void (*done)(void* done_arg,
+ grpc_cq_completion* storage),
+ void* done_arg, grpc_cq_completion* storage);
+
static grpc_event cq_next(grpc_completion_queue* cq, gpr_timespec deadline,
void* reserved);
static grpc_event cq_pluck(grpc_completion_queue* cq, void* tag,
gpr_timespec deadline, void* reserved);
-static void cq_init_next(void* data);
-static void cq_init_pluck(void* data);
+// Note that cq_init_next and cq_init_pluck do not use the shutdown_callback
+static void cq_init_next(
+ void* data, grpc_experimental_completion_queue_functor* shutdown_callback);
+static void cq_init_pluck(
+ void* data, grpc_experimental_completion_queue_functor* shutdown_callback);
+static void cq_init_callback(
+ void* data, grpc_experimental_completion_queue_functor* shutdown_callback);
static void cq_destroy_next(void* data);
static void cq_destroy_pluck(void* data);
+static void cq_destroy_callback(void* data);
/* Completion queue vtables based on the completion-type */
static const cq_vtable g_cq_vtable[] = {
@@ -315,14 +354,17 @@ static const cq_vtable g_cq_vtable[] = {
{GRPC_CQ_PLUCK, sizeof(cq_pluck_data), cq_init_pluck, cq_shutdown_pluck,
cq_destroy_pluck, cq_begin_op_for_pluck, cq_end_op_for_pluck, nullptr,
cq_pluck},
+ /* GRPC_CQ_CALLBACK */
+ {GRPC_CQ_CALLBACK, sizeof(cq_callback_data), cq_init_callback,
+ cq_shutdown_callback, cq_destroy_callback, cq_begin_op_for_callback,
+ cq_end_op_for_callback, nullptr, nullptr},
};
#define DATA_FROM_CQ(cq) ((void*)(cq + 1))
#define POLLSET_FROM_CQ(cq) \
((grpc_pollset*)(cq->vtable->data_size + (char*)DATA_FROM_CQ(cq)))
-grpc_core::TraceFlag grpc_cq_pluck_trace(true, "queue_pluck");
-grpc_core::TraceFlag grpc_cq_event_timeout_trace(true, "queue_timeout");
+grpc_core::TraceFlag grpc_cq_pluck_trace(false, "queue_pluck");
#define GRPC_SURFACE_TRACE_RETURNED_EVENT(cq, event) \
if (grpc_api_trace.enabled() && (grpc_cq_pluck_trace.enabled() || \
@@ -420,8 +462,8 @@ static long cq_event_queue_num_items(grpc_cq_event_queue* q) {
}
grpc_completion_queue* grpc_completion_queue_create_internal(
- grpc_cq_completion_type completion_type,
- grpc_cq_polling_type polling_type) {
+ grpc_cq_completion_type completion_type, grpc_cq_polling_type polling_type,
+ grpc_experimental_completion_queue_functor* shutdown_callback) {
GPR_TIMER_SCOPE("grpc_completion_queue_create_internal", 0);
grpc_completion_queue* cq;
@@ -449,15 +491,16 @@ grpc_completion_queue* grpc_completion_queue_create_internal(
gpr_ref_init(&cq->owning_refs, 2);
poller_vtable->init(POLLSET_FROM_CQ(cq), &cq->mu);
- vtable->init(DATA_FROM_CQ(cq));
+ vtable->init(DATA_FROM_CQ(cq), shutdown_callback);
GRPC_CLOSURE_INIT(&cq->pollset_shutdown_done, on_pollset_shutdown_done, cq,
grpc_schedule_on_exec_ctx);
return cq;
}
-static void cq_init_next(void* ptr) {
- cq_next_data* cqd = static_cast<cq_next_data*>(ptr);
+static void cq_init_next(
+ void* data, grpc_experimental_completion_queue_functor* shutdown_callback) {
+ cq_next_data* cqd = static_cast<cq_next_data*>(data);
/* Initial count is dropped by grpc_completion_queue_shutdown */
gpr_atm_no_barrier_store(&cqd->pending_events, 1);
cqd->shutdown_called = false;
@@ -465,14 +508,15 @@ static void cq_init_next(void* ptr) {
cq_event_queue_init(&cqd->queue);
}
-static void cq_destroy_next(void* ptr) {
- cq_next_data* cqd = static_cast<cq_next_data*>(ptr);
+static void cq_destroy_next(void* data) {
+ cq_next_data* cqd = static_cast<cq_next_data*>(data);
GPR_ASSERT(cq_event_queue_num_items(&cqd->queue) == 0);
cq_event_queue_destroy(&cqd->queue);
}
-static void cq_init_pluck(void* ptr) {
- cq_pluck_data* cqd = static_cast<cq_pluck_data*>(ptr);
+static void cq_init_pluck(
+ void* data, grpc_experimental_completion_queue_functor* shutdown_callback) {
+ cq_pluck_data* cqd = static_cast<cq_pluck_data*>(data);
/* Initial count is dropped by grpc_completion_queue_shutdown */
gpr_atm_no_barrier_store(&cqd->pending_events, 1);
cqd->completed_tail = &cqd->completed_head;
@@ -483,11 +527,23 @@ static void cq_init_pluck(void* ptr) {
gpr_atm_no_barrier_store(&cqd->things_queued_ever, 0);
}
-static void cq_destroy_pluck(void* ptr) {
- cq_pluck_data* cqd = static_cast<cq_pluck_data*>(ptr);
+static void cq_destroy_pluck(void* data) {
+ cq_pluck_data* cqd = static_cast<cq_pluck_data*>(data);
GPR_ASSERT(cqd->completed_head.next == (uintptr_t)&cqd->completed_head);
}
+static void cq_init_callback(
+ void* data, grpc_experimental_completion_queue_functor* shutdown_callback) {
+ cq_callback_data* cqd = static_cast<cq_callback_data*>(data);
+ /* Initial count is dropped by grpc_completion_queue_shutdown */
+ gpr_atm_no_barrier_store(&cqd->pending_events, 1);
+ cqd->shutdown_called = false;
+ gpr_atm_no_barrier_store(&cqd->things_queued_ever, 0);
+ cqd->shutdown_callback = shutdown_callback;
+}
+
+static void cq_destroy_callback(void* data) {}
+
grpc_cq_completion_type grpc_get_cq_completion_type(grpc_completion_queue* cq) {
return cq->vtable->cq_completion_type;
}
@@ -597,6 +653,11 @@ static bool cq_begin_op_for_pluck(grpc_completion_queue* cq, void* tag) {
return atm_inc_if_nonzero(&cqd->pending_events);
}
+static bool cq_begin_op_for_callback(grpc_completion_queue* cq, void* tag) {
+ cq_callback_data* cqd = static_cast<cq_callback_data*> DATA_FROM_CQ(cq);
+ return atm_inc_if_nonzero(&cqd->pending_events);
+}
+
bool grpc_cq_begin_op(grpc_completion_queue* cq, void* tag) {
#ifndef NDEBUG
gpr_mu_lock(cq->mu);
@@ -760,6 +821,49 @@ static void cq_end_op_for_pluck(grpc_completion_queue* cq, void* tag,
GRPC_ERROR_UNREF(error);
}
+/* Complete an event on a completion queue of type GRPC_CQ_CALLBACK */
+static void cq_end_op_for_callback(
+ grpc_completion_queue* cq, void* tag, grpc_error* error,
+ void (*done)(void* done_arg, grpc_cq_completion* storage), void* done_arg,
+ grpc_cq_completion* storage) {
+ GPR_TIMER_SCOPE("cq_end_op_for_callback", 0);
+
+ cq_callback_data* cqd = static_cast<cq_callback_data*> DATA_FROM_CQ(cq);
+ bool is_success = (error == GRPC_ERROR_NONE);
+
+ if (grpc_api_trace.enabled() ||
+ (grpc_trace_operation_failures.enabled() && error != GRPC_ERROR_NONE)) {
+ const char* errmsg = grpc_error_string(error);
+ GRPC_API_TRACE(
+ "cq_end_op_for_callback(cq=%p, tag=%p, error=%s, "
+ "done=%p, done_arg=%p, storage=%p)",
+ 6, (cq, tag, errmsg, done, done_arg, storage));
+ if (grpc_trace_operation_failures.enabled() && error != GRPC_ERROR_NONE) {
+ gpr_log(GPR_ERROR, "Operation failed: tag=%p, error=%s", tag, errmsg);
+ }
+ }
+
+ // The callback-based CQ isn't really a queue at all and thus has no need
+ // for reserved storage. Invoke the done callback right away to release it.
+ done(done_arg, storage);
+
+ gpr_mu_lock(cq->mu);
+ cq_check_tag(cq, tag, false); /* Used in debug builds only */
+
+ gpr_atm_no_barrier_fetch_add(&cqd->things_queued_ever, 1);
+ if (gpr_atm_full_fetch_add(&cqd->pending_events, -1) == 1) {
+ cq_finish_shutdown_callback(cq);
+ gpr_mu_unlock(cq->mu);
+ } else {
+ gpr_mu_unlock(cq->mu);
+ }
+
+ GRPC_ERROR_UNREF(error);
+
+ auto* functor = static_cast<grpc_experimental_completion_queue_functor*>(tag);
+ (*functor->functor_run)(functor, is_success);
+}
+
void grpc_cq_end_op(grpc_completion_queue* cq, void* tag, grpc_error* error,
void (*done)(void* done_arg, grpc_cq_completion* storage),
void* done_arg, grpc_cq_completion* storage) {
@@ -1234,6 +1338,42 @@ static void cq_shutdown_pluck(grpc_completion_queue* cq) {
GRPC_CQ_INTERNAL_UNREF(cq, "shutting_down (pluck cq)");
}
+static void cq_finish_shutdown_callback(grpc_completion_queue* cq) {
+ cq_callback_data* cqd = static_cast<cq_callback_data*> DATA_FROM_CQ(cq);
+ auto* callback = cqd->shutdown_callback;
+
+ GPR_ASSERT(cqd->shutdown_called);
+
+ cq->poller_vtable->shutdown(POLLSET_FROM_CQ(cq), &cq->pollset_shutdown_done);
+ (*callback->functor_run)(callback, true);
+}
+
+static void cq_shutdown_callback(grpc_completion_queue* cq) {
+ cq_callback_data* cqd = static_cast<cq_callback_data*> DATA_FROM_CQ(cq);
+
+ /* Need an extra ref for cq here because:
+ * We call cq_finish_shutdown_callback() below, which calls pollset shutdown.
+ * Pollset shutdown decrements the cq ref count which can potentially destroy
+ * the cq (if that happens to be the last ref).
+ * Creating an extra ref here prevents the cq from getting destroyed while
+ * this function is still active */
+ GRPC_CQ_INTERNAL_REF(cq, "shutting_down (callback cq)");
+ gpr_mu_lock(cq->mu);
+ if (cqd->shutdown_called) {
+ gpr_mu_unlock(cq->mu);
+ GRPC_CQ_INTERNAL_UNREF(cq, "shutting_down (callback cq)");
+ return;
+ }
+ cqd->shutdown_called = true;
+ if (gpr_atm_full_fetch_add(&cqd->pending_events, -1) == 1) {
+ gpr_mu_unlock(cq->mu);
+ cq_finish_shutdown_callback(cq);
+ } else {
+ gpr_mu_unlock(cq->mu);
+ }
+ GRPC_CQ_INTERNAL_UNREF(cq, "shutting_down (callback cq)");
+}
+
/* Shutdown simply drops a ref that we reserved at creation time; if we drop
to zero here, then enter shutdown mode and wake up any waiters */
void grpc_completion_queue_shutdown(grpc_completion_queue* cq) {
diff --git a/src/core/lib/surface/completion_queue.h b/src/core/lib/surface/completion_queue.h
index c9dc2d93c1..d60fe6d6ef 100644
--- a/src/core/lib/surface/completion_queue.h
+++ b/src/core/lib/surface/completion_queue.h
@@ -25,12 +25,12 @@
#include <grpc/grpc.h>
#include "src/core/lib/debug/trace.h"
+#include "src/core/lib/gprpp/abstract.h"
#include "src/core/lib/iomgr/pollset.h"
/* These trace flags default to 1. The corresponding lines are only traced
if grpc_api_trace is also truthy */
extern grpc_core::TraceFlag grpc_cq_pluck_trace;
-extern grpc_core::TraceFlag grpc_cq_event_timeout_trace;
extern grpc_core::TraceFlag grpc_trace_operation_failures;
extern grpc_core::DebugOnlyTraceFlag grpc_trace_pending_tags;
extern grpc_core::DebugOnlyTraceFlag grpc_trace_cq_refcount;
@@ -88,6 +88,7 @@ grpc_cq_completion_type grpc_get_cq_completion_type(grpc_completion_queue* cc);
int grpc_get_cq_poll_num(grpc_completion_queue* cc);
grpc_completion_queue* grpc_completion_queue_create_internal(
- grpc_cq_completion_type completion_type, grpc_cq_polling_type polling_type);
+ grpc_cq_completion_type completion_type, grpc_cq_polling_type polling_type,
+ grpc_experimental_completion_queue_functor* shutdown_callback);
#endif /* GRPC_CORE_LIB_SURFACE_COMPLETION_QUEUE_H */
diff --git a/src/core/lib/surface/completion_queue_factory.cc b/src/core/lib/surface/completion_queue_factory.cc
index 51c1183c5f..2616c156e4 100644
--- a/src/core/lib/surface/completion_queue_factory.cc
+++ b/src/core/lib/surface/completion_queue_factory.cc
@@ -30,8 +30,8 @@
static grpc_completion_queue* default_create(
const grpc_completion_queue_factory* factory,
const grpc_completion_queue_attributes* attr) {
- return grpc_completion_queue_create_internal(attr->cq_completion_type,
- attr->cq_polling_type);
+ return grpc_completion_queue_create_internal(
+ attr->cq_completion_type, attr->cq_polling_type, attr->cq_shutdown_cb);
}
static grpc_completion_queue_factory_vtable default_vtable = {default_create};
@@ -60,14 +60,23 @@ const grpc_completion_queue_factory* grpc_completion_queue_factory_lookup(
grpc_completion_queue* grpc_completion_queue_create_for_next(void* reserved) {
GPR_ASSERT(!reserved);
grpc_completion_queue_attributes attr = {1, GRPC_CQ_NEXT,
- GRPC_CQ_DEFAULT_POLLING};
+ GRPC_CQ_DEFAULT_POLLING, nullptr};
return g_default_cq_factory.vtable->create(&g_default_cq_factory, &attr);
}
grpc_completion_queue* grpc_completion_queue_create_for_pluck(void* reserved) {
GPR_ASSERT(!reserved);
grpc_completion_queue_attributes attr = {1, GRPC_CQ_PLUCK,
- GRPC_CQ_DEFAULT_POLLING};
+ GRPC_CQ_DEFAULT_POLLING, nullptr};
+ return g_default_cq_factory.vtable->create(&g_default_cq_factory, &attr);
+}
+
+grpc_completion_queue* grpc_completion_queue_create_for_callback(
+ grpc_experimental_completion_queue_functor* shutdown_callback,
+ void* reserved) {
+ GPR_ASSERT(!reserved);
+ grpc_completion_queue_attributes attr = {
+ 2, GRPC_CQ_CALLBACK, GRPC_CQ_DEFAULT_POLLING, shutdown_callback};
return g_default_cq_factory.vtable->create(&g_default_cq_factory, &attr);
}
diff --git a/src/core/lib/surface/init.cc b/src/core/lib/surface/init.cc
index bd436d6857..0ad82fed99 100644
--- a/src/core/lib/surface/init.cc
+++ b/src/core/lib/surface/init.cc
@@ -27,13 +27,12 @@
#include <grpc/support/log.h>
#include <grpc/support/time.h>
#include "src/core/lib/channel/channel_stack.h"
-#include "src/core/lib/channel/channel_trace_registry.h"
+#include "src/core/lib/channel/channelz_registry.h"
#include "src/core/lib/channel/connected_channel.h"
#include "src/core/lib/channel/handshaker_registry.h"
#include "src/core/lib/debug/stats.h"
#include "src/core/lib/debug/trace.h"
-#include "src/core/lib/gpr/fork.h"
-#include "src/core/lib/gprpp/thd.h"
+#include "src/core/lib/gprpp/fork.h"
#include "src/core/lib/http/parser.h"
#include "src/core/lib/iomgr/call_combiner.h"
#include "src/core/lib/iomgr/combiner.h"
@@ -65,12 +64,10 @@ static int g_initializations;
static void do_basic_init(void) {
gpr_log_verbosity_init();
- grpc_fork_support_init();
gpr_mu_init(&g_init_mu);
grpc_register_built_in_plugins();
grpc_cq_global_init();
g_initializations = 0;
- grpc_fork_handlers_auto_register();
}
static bool append_filter(grpc_channel_stack_builder* builder, void* arg) {
@@ -123,13 +120,14 @@ void grpc_init(void) {
gpr_mu_lock(&g_init_mu);
if (++g_initializations == 1) {
+ grpc_core::Fork::GlobalInit();
+ grpc_fork_handlers_auto_register();
gpr_time_init();
- grpc_core::Thread::Init();
grpc_stats_init();
grpc_slice_intern_init();
grpc_mdctx_global_init();
grpc_channel_init_init();
- grpc_channel_trace_registry_init();
+ grpc_core::channelz::ChannelzRegistry::Init();
grpc_security_pre_init();
grpc_core::ExecCtx::GlobalInit();
grpc_iomgr_init();
@@ -178,8 +176,9 @@ void grpc_shutdown(void) {
grpc_mdctx_global_shutdown();
grpc_handshaker_factory_registry_shutdown();
grpc_slice_intern_shutdown();
- grpc_channel_trace_registry_shutdown();
+ grpc_core::channelz::ChannelzRegistry::Shutdown();
grpc_stats_shutdown();
+ grpc_core::Fork::GlobalShutdown();
}
grpc_core::ExecCtx::GlobalShutdown();
}
diff --git a/src/core/lib/surface/init.h b/src/core/lib/surface/init.h
index 9353208332..193f51447d 100644
--- a/src/core/lib/surface/init.h
+++ b/src/core/lib/surface/init.h
@@ -22,6 +22,5 @@
void grpc_register_security_filters(void);
void grpc_security_pre_init(void);
void grpc_security_init(void);
-int grpc_is_initialized(void);
#endif /* GRPC_CORE_LIB_SURFACE_INIT_H */
diff --git a/src/core/lib/surface/server.cc b/src/core/lib/surface/server.cc
index cb34def740..72ddc2648d 100644
--- a/src/core/lib/surface/server.cc
+++ b/src/core/lib/surface/server.cc
@@ -149,10 +149,16 @@ struct call_data {
grpc_closure server_on_recv_initial_metadata;
grpc_closure kill_zombie_closure;
grpc_closure* on_done_recv_initial_metadata;
+ grpc_closure recv_trailing_metadata_ready;
+ grpc_error* recv_initial_metadata_error;
+ grpc_closure* original_recv_trailing_metadata_ready;
+ grpc_error* recv_trailing_metadata_error;
+ bool seen_recv_trailing_metadata_ready;
grpc_closure publish;
call_data* pending_next;
+ grpc_call_combiner* call_combiner;
};
struct request_matcher {
@@ -219,6 +225,8 @@ struct grpc_server {
/** when did we print the last shutdown progress message */
gpr_timespec last_shutdown_message_time;
+
+ grpc_core::RefCountedPtr<grpc_core::channelz::ServerNode> channelz_server;
};
#define SERVER_FROM_CALL_ELEM(elem) \
@@ -364,6 +372,7 @@ static void server_ref(grpc_server* server) {
static void server_delete(grpc_server* server) {
registered_method* rm;
size_t i;
+ server->channelz_server.reset();
grpc_channel_args_destroy(server->channel_args);
gpr_mu_destroy(&server->mu_global);
gpr_mu_destroy(&server->mu_call);
@@ -721,13 +730,43 @@ static void server_on_recv_initial_metadata(void* ptr, grpc_error* error) {
if (calld->host_set && calld->path_set) {
/* do nothing */
} else {
+ /* Pass the error reference to calld->recv_initial_metadata_error */
grpc_error* src_error = error;
error = GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING(
- "Missing :authority or :path", &error, 1);
+ "Missing :authority or :path", &src_error, 1);
GRPC_ERROR_UNREF(src_error);
+ calld->recv_initial_metadata_error = GRPC_ERROR_REF(error);
+ }
+ grpc_closure* closure = calld->on_done_recv_initial_metadata;
+ calld->on_done_recv_initial_metadata = nullptr;
+ if (calld->seen_recv_trailing_metadata_ready) {
+ GRPC_CALL_COMBINER_START(calld->call_combiner,
+ &calld->recv_trailing_metadata_ready,
+ calld->recv_trailing_metadata_error,
+ "continue server_recv_trailing_metadata_ready");
}
+ GRPC_CLOSURE_RUN(closure, error);
+}
- GRPC_CLOSURE_RUN(calld->on_done_recv_initial_metadata, error);
+static void server_recv_trailing_metadata_ready(void* user_data,
+ grpc_error* error) {
+ grpc_call_element* elem = static_cast<grpc_call_element*>(user_data);
+ call_data* calld = static_cast<call_data*>(elem->call_data);
+ if (calld->on_done_recv_initial_metadata != nullptr) {
+ calld->recv_trailing_metadata_error = GRPC_ERROR_REF(error);
+ calld->seen_recv_trailing_metadata_ready = true;
+ GRPC_CLOSURE_INIT(&calld->recv_trailing_metadata_ready,
+ server_recv_trailing_metadata_ready, elem,
+ grpc_schedule_on_exec_ctx);
+ GRPC_CALL_COMBINER_STOP(calld->call_combiner,
+ "deferring server_recv_trailing_metadata_ready "
+ "until after server_on_recv_initial_metadata");
+ return;
+ }
+ error =
+ grpc_error_add_child(GRPC_ERROR_REF(error),
+ GRPC_ERROR_REF(calld->recv_initial_metadata_error));
+ GRPC_CLOSURE_RUN(calld->original_recv_trailing_metadata_ready, error);
}
static void server_mutate_op(grpc_call_element* elem,
@@ -745,6 +784,12 @@ static void server_mutate_op(grpc_call_element* elem,
op->payload->recv_initial_metadata.recv_flags =
&calld->recv_initial_metadata_flags;
}
+ if (op->recv_trailing_metadata) {
+ calld->original_recv_trailing_metadata_ready =
+ op->payload->recv_trailing_metadata.recv_trailing_metadata_ready;
+ op->payload->recv_trailing_metadata.recv_trailing_metadata_ready =
+ &calld->recv_trailing_metadata_ready;
+ }
}
static void server_start_transport_stream_op_batch(
@@ -779,6 +824,7 @@ static void accept_stream(void* cd, grpc_transport* transport,
args.channel = chand->channel;
args.server_transport_data = transport_server_data;
args.send_deadline = GRPC_MILLIS_INF_FUTURE;
+ args.server = chand->server;
grpc_call* call;
grpc_error* error = grpc_call_create(&args, &call);
grpc_call_element* elem =
@@ -824,11 +870,14 @@ static grpc_error* init_call_elem(grpc_call_element* elem,
memset(calld, 0, sizeof(call_data));
calld->deadline = GRPC_MILLIS_INF_FUTURE;
calld->call = grpc_call_from_top_element(elem);
+ calld->call_combiner = args->call_combiner;
GRPC_CLOSURE_INIT(&calld->server_on_recv_initial_metadata,
server_on_recv_initial_metadata, elem,
grpc_schedule_on_exec_ctx);
-
+ GRPC_CLOSURE_INIT(&calld->recv_trailing_metadata_ready,
+ server_recv_trailing_metadata_ready, elem,
+ grpc_schedule_on_exec_ctx);
server_ref(chand->server);
return GRPC_ERROR_NONE;
}
@@ -840,7 +889,7 @@ static void destroy_call_elem(grpc_call_element* elem,
call_data* calld = static_cast<call_data*>(elem->call_data);
GPR_ASSERT(calld->state != PENDING);
-
+ GRPC_ERROR_UNREF(calld->recv_initial_metadata_error);
if (calld->host_set) {
grpc_slice_unref_internal(calld->host);
}
@@ -941,6 +990,7 @@ void grpc_server_register_completion_queue(grpc_server* server,
}
grpc_server* grpc_server_create(const grpc_channel_args* args, void* reserved) {
+ grpc_core::ExecCtx exec_ctx;
GRPC_API_TRACE("grpc_server_create(%p, %p)", 2, (args, reserved));
grpc_server* server =
@@ -957,6 +1007,20 @@ grpc_server* grpc_server_create(const grpc_channel_args* args, void* reserved) {
server->channel_args = grpc_channel_args_copy(args);
+ const grpc_arg* arg = grpc_channel_args_find(args, GRPC_ARG_ENABLE_CHANNELZ);
+ if (grpc_channel_arg_get_bool(arg, false)) {
+ arg = grpc_channel_args_find(args,
+ GRPC_ARG_MAX_CHANNEL_TRACE_EVENTS_PER_NODE);
+ size_t trace_events_per_node =
+ grpc_channel_arg_get_integer(arg, {0, 0, INT_MAX});
+ server->channelz_server =
+ grpc_core::MakeRefCounted<grpc_core::channelz::ServerNode>(
+ trace_events_per_node);
+ server->channelz_server->AddTraceEvent(
+ grpc_core::channelz::ChannelTrace::Severity::Info,
+ grpc_slice_from_static_string("Server created"));
+ }
+
return server;
}
@@ -1459,3 +1523,11 @@ int grpc_server_has_open_connections(grpc_server* server) {
gpr_mu_unlock(&server->mu_global);
return r;
}
+
+grpc_core::channelz::ServerNode* grpc_server_get_channelz_node(
+ grpc_server* server) {
+ if (server == nullptr) {
+ return nullptr;
+ }
+ return server->channelz_server.get();
+}
diff --git a/src/core/lib/surface/server.h b/src/core/lib/surface/server.h
index c617cc223e..0196743ff9 100644
--- a/src/core/lib/surface/server.h
+++ b/src/core/lib/surface/server.h
@@ -23,6 +23,7 @@
#include <grpc/grpc.h>
#include "src/core/lib/channel/channel_stack.h"
+#include "src/core/lib/channel/channelz.h"
#include "src/core/lib/debug/trace.h"
#include "src/core/lib/transport/transport.h"
@@ -46,6 +47,9 @@ void grpc_server_setup_transport(grpc_server* server, grpc_transport* transport,
grpc_pollset* accepting_pollset,
const grpc_channel_args* args);
+grpc_core::channelz::ServerNode* grpc_server_get_channelz_node(
+ grpc_server* server);
+
const grpc_channel_args* grpc_server_get_channel_args(grpc_server* server);
int grpc_server_has_open_connections(grpc_server* server);
diff --git a/src/core/lib/surface/version.cc b/src/core/lib/surface/version.cc
index 306b7c395e..a44f9acdc3 100644
--- a/src/core/lib/surface/version.cc
+++ b/src/core/lib/surface/version.cc
@@ -25,4 +25,4 @@
const char* grpc_version_string(void) { return "6.0.0-dev"; }
-const char* grpc_g_stands_for(void) { return "gloriosa"; }
+const char* grpc_g_stands_for(void) { return "gao"; }
diff --git a/src/core/lib/transport/byte_stream.cc b/src/core/lib/transport/byte_stream.cc
index cb15a71a91..16b85ca0db 100644
--- a/src/core/lib/transport/byte_stream.cc
+++ b/src/core/lib/transport/byte_stream.cc
@@ -45,7 +45,7 @@ SliceBufferByteStream::SliceBufferByteStream(grpc_slice_buffer* slice_buffer,
SliceBufferByteStream::~SliceBufferByteStream() {}
void SliceBufferByteStream::Orphan() {
- grpc_slice_buffer_destroy(&backing_buffer_);
+ grpc_slice_buffer_destroy_internal(&backing_buffer_);
GRPC_ERROR_UNREF(shutdown_error_);
// Note: We do not actually delete the object here, since
// SliceBufferByteStream is usually allocated as part of a larger
diff --git a/src/core/lib/transport/metadata.cc b/src/core/lib/transport/metadata.cc
index d10194a2fe..d164502280 100644
--- a/src/core/lib/transport/metadata.cc
+++ b/src/core/lib/transport/metadata.cc
@@ -342,24 +342,6 @@ grpc_mdelem grpc_mdelem_from_grpc_metadata(grpc_metadata* metadata) {
changed ? nullptr : reinterpret_cast<grpc_mdelem_data*>(metadata));
}
-static size_t get_base64_encoded_size(size_t raw_length) {
- static const uint8_t tail_xtra[3] = {0, 2, 3};
- return raw_length / 3 * 4 + tail_xtra[raw_length % 3];
-}
-
-size_t grpc_mdelem_get_size_in_hpack_table(grpc_mdelem elem,
- bool use_true_binary_metadata) {
- size_t overhead_and_key = 32 + GRPC_SLICE_LENGTH(GRPC_MDKEY(elem));
- size_t value_len = GRPC_SLICE_LENGTH(GRPC_MDVALUE(elem));
- if (grpc_is_binary_header(GRPC_MDKEY(elem))) {
- return overhead_and_key + (use_true_binary_metadata
- ? value_len + 1
- : get_base64_encoded_size(value_len));
- } else {
- return overhead_and_key + value_len;
- }
-}
-
grpc_mdelem grpc_mdelem_ref(grpc_mdelem gmd DEBUG_ARGS) {
switch (GRPC_MDELEM_STORAGE(gmd)) {
case GRPC_MDELEM_STORAGE_EXTERNAL:
diff --git a/src/core/lib/transport/metadata.h b/src/core/lib/transport/metadata.h
index 78df4bc3a3..338082276c 100644
--- a/src/core/lib/transport/metadata.h
+++ b/src/core/lib/transport/metadata.h
@@ -125,9 +125,6 @@ grpc_mdelem grpc_mdelem_create(
bool grpc_mdelem_eq(grpc_mdelem a, grpc_mdelem b);
-size_t grpc_mdelem_get_size_in_hpack_table(grpc_mdelem elem,
- bool use_true_binary_metadata);
-
/* Mutator and accessor for grpc_mdelem user data. The destructor function
is used as a type tag and is checked during user_data fetch. */
void* grpc_mdelem_get_user_data(grpc_mdelem md, void (*if_destroy_func)(void*));
diff --git a/src/core/lib/transport/metadata_batch.cc b/src/core/lib/transport/metadata_batch.cc
index 49740fcd1e..761aad19c4 100644
--- a/src/core/lib/transport/metadata_batch.cc
+++ b/src/core/lib/transport/metadata_batch.cc
@@ -105,7 +105,7 @@ static grpc_error* maybe_link_callout(grpc_metadata_batch* batch,
return GRPC_ERROR_NONE;
}
if (batch->idx.array[idx] == nullptr) {
- if (grpc_static_callout_is_default[idx]) ++batch->list.default_count;
+ ++batch->list.default_count;
batch->idx.array[idx] = storage;
return GRPC_ERROR_NONE;
}
@@ -121,7 +121,7 @@ static void maybe_unlink_callout(grpc_metadata_batch* batch,
if (idx == GRPC_BATCH_CALLOUTS_COUNT) {
return;
}
- if (grpc_static_callout_is_default[idx]) --batch->list.default_count;
+ --batch->list.default_count;
GPR_ASSERT(batch->idx.array[idx] != nullptr);
batch->idx.array[idx] = nullptr;
}
diff --git a/src/core/lib/transport/metadata_batch.h b/src/core/lib/transport/metadata_batch.h
index 7068750b6f..0bcbb32d1f 100644
--- a/src/core/lib/transport/metadata_batch.h
+++ b/src/core/lib/transport/metadata_batch.h
@@ -82,6 +82,7 @@ void grpc_metadata_batch_set_value(grpc_linked_mdelem* storage,
grpc_error* grpc_metadata_batch_link_head(grpc_metadata_batch* batch,
grpc_linked_mdelem* storage)
GRPC_MUST_USE_RESULT;
+
/** Add \a storage to the end of \a batch. storage->md is
assumed to be valid.
\a storage is owned by the caller and must survive for the
@@ -100,6 +101,7 @@ grpc_error* grpc_metadata_batch_link_tail(grpc_metadata_batch* batch,
grpc_error* grpc_metadata_batch_add_head(
grpc_metadata_batch* batch, grpc_linked_mdelem* storage,
grpc_mdelem elem_to_add) GRPC_MUST_USE_RESULT;
+
/** Add \a elem_to_add as the last element in \a batch, using
\a storage as backing storage for the linked list element.
\a storage is owned by the caller and must survive for the
diff --git a/src/core/lib/transport/service_config.cc b/src/core/lib/transport/service_config.cc
index e1a55d98ab..405e336028 100644
--- a/src/core/lib/transport/service_config.cc
+++ b/src/core/lib/transport/service_config.cc
@@ -65,8 +65,8 @@ const char* ServiceConfig::GetLoadBalancingPolicyName() const {
return lb_policy_name;
}
-size_t ServiceConfig::CountNamesInMethodConfig(grpc_json* json) {
- size_t num_names = 0;
+int ServiceConfig::CountNamesInMethodConfig(grpc_json* json) {
+ int num_names = 0;
for (grpc_json* field = json->child; field != nullptr; field = field->next) {
if (field->key != nullptr && strcmp(field->key, "name") == 0) {
if (field->type != GRPC_JSON_ARRAY) return -1;
diff --git a/src/core/lib/transport/service_config.h b/src/core/lib/transport/service_config.h
index a65b267d46..2c0dd75845 100644
--- a/src/core/lib/transport/service_config.h
+++ b/src/core/lib/transport/service_config.h
@@ -103,7 +103,7 @@ class ServiceConfig {
ServiceConfig(UniquePtr<char> json_string, grpc_json* json_tree);
// Returns the number of names specified in the method config \a json.
- static size_t CountNamesInMethodConfig(grpc_json* json);
+ static int CountNamesInMethodConfig(grpc_json* json);
// Returns a path string for the JSON name object specified by \a json.
// Returns null on error.
@@ -188,9 +188,9 @@ ServiceConfig::CreateMethodConfigTable(CreateValue<T> create_value) {
// Find number of entries.
for (grpc_json* method = field->child; method != nullptr;
method = method->next) {
- size_t count = CountNamesInMethodConfig(method);
+ int count = CountNamesInMethodConfig(method);
if (count <= 0) return nullptr;
- num_entries += count;
+ num_entries += static_cast<size_t>(count);
}
// Populate method config table entries.
entries = static_cast<typename SliceHashTable<RefCountedPtr<T>>::Entry*>(
diff --git a/src/core/lib/transport/static_metadata.cc b/src/core/lib/transport/static_metadata.cc
index 6a5144f21a..ed88aa3f28 100644
--- a/src/core/lib/transport/static_metadata.cc
+++ b/src/core/lib/transport/static_metadata.cc
@@ -568,33 +568,6 @@ grpc_mdelem_data grpc_static_mdelem_table[GRPC_STATIC_MDELEM_COUNT] = {
{{&grpc_static_metadata_refcounts[16], {{g_bytes + 186, 15}}},
{&grpc_static_metadata_refcounts[102], {{g_bytes + 1101, 13}}}},
};
-bool grpc_static_callout_is_default[GRPC_BATCH_CALLOUTS_COUNT] = {
- true, // :path
- true, // :method
- true, // :status
- true, // :authority
- true, // :scheme
- true, // te
- true, // grpc-message
- true, // grpc-status
- true, // grpc-payload-bin
- true, // grpc-encoding
- true, // grpc-accept-encoding
- true, // grpc-server-stats-bin
- true, // grpc-tags-bin
- true, // grpc-trace-bin
- true, // content-type
- true, // content-encoding
- true, // accept-encoding
- true, // grpc-internal-encoding-request
- true, // grpc-internal-stream-encoding-request
- true, // user-agent
- true, // host
- true, // lb-token
- true, // grpc-previous-rpc-attempts
- true, // grpc-retry-pushback-ms
-};
-
const uint8_t grpc_static_accept_encoding_metadata[8] = {0, 76, 77, 78,
79, 80, 81, 82};
diff --git a/src/core/lib/transport/static_metadata.h b/src/core/lib/transport/static_metadata.h
index b3a10f5873..1e95f4e5bb 100644
--- a/src/core/lib/transport/static_metadata.h
+++ b/src/core/lib/transport/static_metadata.h
@@ -587,8 +587,6 @@ typedef union {
GRPC_BATCH_CALLOUTS_COUNT) \
: GRPC_BATCH_CALLOUTS_COUNT)
-extern bool grpc_static_callout_is_default[GRPC_BATCH_CALLOUTS_COUNT];
-
extern const uint8_t grpc_static_accept_encoding_metadata[8];
#define GRPC_MDELEM_ACCEPT_ENCODING_FOR_ALGORITHMS(algs) \
(GRPC_MAKE_MDELEM( \
diff --git a/src/core/lib/transport/transport.cc b/src/core/lib/transport/transport.cc
index 6b41e4b37e..cbdb77c844 100644
--- a/src/core/lib/transport/transport.cc
+++ b/src/core/lib/transport/transport.cc
@@ -184,7 +184,8 @@ void grpc_transport_set_pops(grpc_transport* transport, grpc_stream* stream,
nullptr) {
transport->vtable->set_pollset_set(transport, stream, pollset_set);
} else {
- abort();
+ // No-op for empty pollset. Empty pollset is possible when using
+ // non-fd-based event engines such as CFStream.
}
}
@@ -211,21 +212,32 @@ void grpc_transport_stream_op_batch_finish_with_failure(
if (batch->send_message) {
batch->payload->send_message.send_message.reset();
}
- if (batch->recv_message) {
- GRPC_CALL_COMBINER_START(
- call_combiner, batch->payload->recv_message.recv_message_ready,
- GRPC_ERROR_REF(error), "failing recv_message_ready");
+ if (batch->cancel_stream) {
+ GRPC_ERROR_UNREF(batch->payload->cancel_stream.cancel_error);
}
+ // Construct a list of closures to execute.
+ grpc_core::CallCombinerClosureList closures;
if (batch->recv_initial_metadata) {
- GRPC_CALL_COMBINER_START(
- call_combiner,
+ closures.Add(
batch->payload->recv_initial_metadata.recv_initial_metadata_ready,
GRPC_ERROR_REF(error), "failing recv_initial_metadata_ready");
}
- GRPC_CLOSURE_SCHED(batch->on_complete, error);
- if (batch->cancel_stream) {
- GRPC_ERROR_UNREF(batch->payload->cancel_stream.cancel_error);
+ if (batch->recv_message) {
+ closures.Add(batch->payload->recv_message.recv_message_ready,
+ GRPC_ERROR_REF(error), "failing recv_message_ready");
+ }
+ if (batch->recv_trailing_metadata) {
+ closures.Add(
+ batch->payload->recv_trailing_metadata.recv_trailing_metadata_ready,
+ GRPC_ERROR_REF(error), "failing recv_trailing_metadata_ready");
+ }
+ if (batch->on_complete != nullptr) {
+ closures.Add(batch->on_complete, GRPC_ERROR_REF(error),
+ "failing on_complete");
}
+ // Execute closures.
+ closures.RunClosures(call_combiner);
+ GRPC_ERROR_UNREF(error);
}
typedef struct {
diff --git a/src/core/lib/transport/transport.h b/src/core/lib/transport/transport.h
index 10e9df0f7c..9e784635c6 100644
--- a/src/core/lib/transport/transport.h
+++ b/src/core/lib/transport/transport.h
@@ -122,9 +122,15 @@ typedef struct grpc_transport_stream_op_batch_payload
/* Transport stream op: a set of operations to perform on a transport
against a single stream */
typedef struct grpc_transport_stream_op_batch {
- /** Should be enqueued when all requested operations (excluding recv_message
- and recv_initial_metadata which have their own closures) in a given batch
- have been completed. */
+ /** Should be scheduled when all of the non-recv operations in the batch
+ are complete.
+
+ The recv ops (recv_initial_metadata, recv_message, and
+ recv_trailing_metadata) each have their own callbacks. If a batch
+ contains both recv ops and non-recv ops, on_complete should be
+ scheduled as soon as the non-recv ops are complete, regardless of
+ whether or not the recv ops are complete. If a batch contains
+ only recv ops, on_complete can be null. */
grpc_closure* on_complete;
/** Values for the stream op (fields set are determined by flags above) */
@@ -149,9 +155,6 @@ typedef struct grpc_transport_stream_op_batch {
*/
bool recv_trailing_metadata : 1;
- /** Collect any stats into provided buffer, zero internal stat counters */
- bool collect_stats : 1;
-
/** Cancel this stream with the provided error */
bool cancel_stream : 1;
@@ -168,13 +171,11 @@ struct grpc_transport_stream_op_batch_payload {
/** Iff send_initial_metadata != NULL, flags associated with
send_initial_metadata: a bitfield of GRPC_INITIAL_METADATA_xxx */
uint32_t send_initial_metadata_flags;
- // If non-NULL, will be set by the transport to the peer string
- // (a char*, which the caller takes ownership of).
+ // If non-NULL, will be set by the transport to the peer string (a char*).
+ // The transport retains ownership of the string.
// Note: This pointer may be used by the transport after the
// send_initial_metadata op is completed. It must remain valid
// until the call is destroyed.
- // Note: When a transport sets this, it must free the previous
- // value, if any.
gpr_atm* peer_string;
} send_initial_metadata;
@@ -202,13 +203,11 @@ struct grpc_transport_stream_op_batch_payload {
// immediately available. This may be a signal that we received a
// Trailers-Only response.
bool* trailing_metadata_available;
- // If non-NULL, will be set by the transport to the peer string
- // (a char*, which the caller takes ownership of).
+ // If non-NULL, will be set by the transport to the peer string (a char*).
+ // The transport retains ownership of the string.
// Note: This pointer may be used by the transport after the
// recv_initial_metadata op is completed. It must remain valid
// until the call is destroyed.
- // Note: When a transport sets this, it must free the previous
- // value, if any.
gpr_atm* peer_string;
} recv_initial_metadata;
@@ -223,11 +222,10 @@ struct grpc_transport_stream_op_batch_payload {
struct {
grpc_metadata_batch* recv_trailing_metadata;
- } recv_trailing_metadata;
-
- struct {
grpc_transport_stream_stats* collect_stats;
- } collect_stats;
+ /** Should be enqueued when initial metadata is ready to be processed. */
+ grpc_closure* recv_trailing_metadata_ready;
+ } recv_trailing_metadata;
/** Forcefully close this stream.
The HTTP2 semantics should be:
@@ -284,6 +282,8 @@ typedef struct grpc_transport_op {
/** Called when the ping ack is received */
grpc_closure* on_ack;
} send_ping;
+ // If true, will reset the channel's connection backoff.
+ bool reset_connect_backoff;
/***************************************************************************
* remaining fields are initialized and used at the discretion of the
diff --git a/src/core/lib/transport/transport_op_string.cc b/src/core/lib/transport/transport_op_string.cc
index 99af7c1931..8c7db642a5 100644
--- a/src/core/lib/transport/transport_op_string.cc
+++ b/src/core/lib/transport/transport_op_string.cc
@@ -52,7 +52,7 @@ static void put_metadata_list(gpr_strvec* b, grpc_metadata_batch md) {
}
if (md.deadline != GRPC_MILLIS_INF_FUTURE) {
char* tmp;
- gpr_asprintf(&tmp, " deadline=%" PRIdPTR, md.deadline);
+ gpr_asprintf(&tmp, " deadline=%" PRId64, md.deadline);
gpr_strvec_add(b, tmp);
}
}
@@ -120,13 +120,6 @@ char* grpc_transport_stream_op_batch_string(
gpr_strvec_add(&b, tmp);
}
- if (op->collect_stats) {
- gpr_strvec_add(&b, gpr_strdup(" "));
- gpr_asprintf(&tmp, "COLLECT_STATS:%p",
- op->payload->collect_stats.collect_stats);
- gpr_strvec_add(&b, tmp);
- }
-
out = gpr_strvec_flatten(&b, nullptr);
gpr_strvec_destroy(&b);