Skip to content

Commit

Permalink
grpc: Separate out grpc_init into its own class (envoyproxy#8293)
Browse files Browse the repository at this point in the history
* Separate out grpc init to a separate class.

Signed-off-by: Joshua Marantz <[email protected]>
  • Loading branch information
jmarantz committed Sep 23, 2019
1 parent 91185e6 commit 7beb1f8
Show file tree
Hide file tree
Showing 14 changed files with 162 additions and 14 deletions.
14 changes: 14 additions & 0 deletions source/common/grpc/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ envoy_cc_library(
],
deps = [
":context_lib",
":google_grpc_context_lib",
":google_grpc_creds_lib",
":google_grpc_utils_lib",
":typed_async_client_lib",
Expand All @@ -157,6 +158,19 @@ envoy_cc_library(
],
)

envoy_cc_library(
name = "google_grpc_context_lib",
srcs = ["google_grpc_context.cc"],
hdrs = ["google_grpc_context.h"],
external_deps = ["grpc"],
deps = [
"//source/common/common:assert_lib",
"//source/common/common:lock_guard_lib",
"//source/common/common:macros",
"//source/common/common:thread_lib",
],
)

envoy_cc_library(
name = "google_grpc_creds_lib",
srcs = ["google_grpc_creds_impl.cc"],
Expand Down
4 changes: 4 additions & 0 deletions source/common/grpc/google_async_client_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "common/common/linked_object.h"
#include "common/common/thread.h"
#include "common/common/thread_annotations.h"
#include "common/grpc/google_grpc_context.h"
#include "common/grpc/typed_async_client.h"
#include "common/tracing/http_tracer_impl.h"

Expand Down Expand Up @@ -84,6 +85,9 @@ class GoogleAsyncClientThreadLocal : public ThreadLocal::ThreadLocalObject,
private:
void completionThread();

// Instantiate this first to ensure grpc_init() is called.
GoogleGrpcContext google_grpc_context_;

// The CompletionQueue for in-flight operations. This must precede completion_thread_ to ensure it
// is constructed before the thread runs.
grpc::CompletionQueue cq_;
Expand Down
44 changes: 44 additions & 0 deletions source/common/grpc/google_grpc_context.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#include "common/grpc/google_grpc_context.h"

#include <atomic>

#include "common/common/assert.h"
#include "common/common/lock_guard.h"
#include "common/common/macros.h"
#include "common/common/thread.h"

#include "grpcpp/grpcpp.h"

namespace Envoy {
namespace Grpc {

GoogleGrpcContext::GoogleGrpcContext() : instance_tracker_(instanceTracker()) {
Thread::LockGuard lock(instance_tracker_.mutex_);
if (++instance_tracker_.live_instances_ == 1) {
#ifdef ENVOY_GOOGLE_GRPC
grpc_init();
#endif
}
}

GoogleGrpcContext::~GoogleGrpcContext() {
// Per https://github.com/grpc/grpc/issues/20303 it is OK to call
// grpc_shutdown_blocking() as long as no one can concurrently call
// grpc_init(). We use check_format.py to ensure that this file contains the
// only callers to grpc_init(), and the mutex to then make that guarantee
// across users of this class.
Thread::LockGuard lock(instance_tracker_.mutex_);
ASSERT(instance_tracker_.live_instances_ > 0);
if (--instance_tracker_.live_instances_ == 0) {
#ifdef ENVOY_GOOGLE_GRPC
grpc_shutdown_blocking(); // Waiting for quiescence avoids non-determinism in tests.
#endif
}
}

GoogleGrpcContext::InstanceTracker& GoogleGrpcContext::instanceTracker() {
MUTABLE_CONSTRUCT_ON_FIRST_USE(InstanceTracker);
}

} // namespace Grpc
} // namespace Envoy
33 changes: 33 additions & 0 deletions source/common/grpc/google_grpc_context.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#pragma once

#include "common/common/thread.h"

namespace Envoy {
namespace Grpc {

// Captures global grpc initialization and shutdown. Note that grpc
// initialization starts several threads, so it is a little annoying to run them
// alongside unrelated tests, particularly if they are trying to track memory
// usage, or you are exploiting otherwise consistent run-to-run pointer values
// during debug.
//
// Instantiating this class makes it easy to ensure classes that depend on grpc
// libraries get them initialized.
class GoogleGrpcContext {
public:
GoogleGrpcContext();
~GoogleGrpcContext();

private:
struct InstanceTracker {
Thread::MutexBasicLockable mutex_;
uint64_t live_instances_ GUARDED_BY(mutex_) = 0;
};

static InstanceTracker& instanceTracker();

InstanceTracker& instance_tracker_;
};

} // namespace Grpc
} // namespace Envoy
4 changes: 2 additions & 2 deletions source/exe/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ load(
"envoy_cc_platform_dep",
"envoy_cc_posix_library",
"envoy_cc_win32_library",
"envoy_google_grpc_external_deps",
"envoy_package",
)
load(
Expand Down Expand Up @@ -72,6 +71,7 @@ envoy_cc_library(
"//source/common/api:os_sys_calls_lib",
"//source/common/common:compiler_requirements_lib",
"//source/common/common:perf_annotation_lib",
"//source/common/grpc:google_grpc_context_lib",
"//source/common/stats:symbol_table_creator_lib",
"//source/server:hot_restart_lib",
"//source/server:hot_restart_nop_lib",
Expand All @@ -95,7 +95,7 @@ envoy_cc_library(
"//source/common/event:libevent_lib",
"//source/common/http/http2:nghttp2_lib",
"//source/server:proto_descriptors_lib",
] + envoy_google_grpc_external_deps(),
],
)

envoy_cc_library(
Expand Down
4 changes: 3 additions & 1 deletion source/exe/main_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include "common/common/thread.h"
#include "common/event/real_time_system.h"
#include "common/grpc/google_grpc_context.h"
#include "common/stats/fake_symbol_table_impl.h"
#include "common/stats/thread_local_store.h"
#include "common/thread_local/thread_local_impl.h"
Expand Down Expand Up @@ -65,7 +66,8 @@ class MainCommonBase {
const AdminRequestFn& handler);

protected:
ProcessWide process_wide_; // Process-wide state setup/teardown.
ProcessWide process_wide_; // Process-wide state setup/teardown (excluding grpc).
Grpc::GoogleGrpcContext google_grpc_context_;
const Envoy::OptionsImpl& options_;
Server::ComponentFactory& component_factory_;
Thread::ThreadFactory& thread_factory_;
Expand Down
25 changes: 15 additions & 10 deletions source/exe/process_wide.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@

#include "ares.h"

#ifdef ENVOY_GOOGLE_GRPC
#include "grpc/grpc.h"
#endif

namespace Envoy {
namespace {
// Static variable to count initialization pairs. For tests like
Expand All @@ -22,13 +18,25 @@ uint32_t process_wide_initialized;

ProcessWide::ProcessWide() : initialization_depth_(process_wide_initialized) {
if (process_wide_initialized++ == 0) {
#ifdef ENVOY_GOOGLE_GRPC
grpc_init();
#endif
ares_library_init(ARES_LIB_INIT_ALL);
Event::Libevent::Global::initialize();
Envoy::Server::validateProtoDescriptors();
Http::Http2::initializeNghttp2Logging();

// We do not initialize Google gRPC here -- we instead instantiate
// Grpc::GoogleGrpcContext in MainCommon immediately after instantiating
// ProcessWide. This is because ProcessWide is instantiated in the unit-test
// flow in test/test_runner.h, and grpc_init() instantiates threads which
// allocate memory asynchronous to running tests, making it hard to
// accurately measure memory consumption, and making unit-test debugging
// non-deterministic. See https://github.com/envoyproxy/envoy/issues/8282
// for details. Of course we also need grpc_init called in unit-tests that
// test Google gRPC, and the relevant classes must also instantiate
// Grpc::GoogleGrpcContext, which allows for nested instantiation.
//
// It appears that grpc_init() started instantiating threads in grpc 1.22.1,
// which was integrated in https://github.com/envoyproxy/envoy/pull/8196,
// around the time the flakes in #8282 started being reported.
}
}

Expand All @@ -37,9 +45,6 @@ ProcessWide::~ProcessWide() {
if (--process_wide_initialized == 0) {
process_wide_initialized = false;
ares_library_cleanup();
#ifdef ENVOY_GOOGLE_GRPC
grpc_shutdown();
#endif
}
ASSERT(process_wide_initialized == initialization_depth_);
}
Expand Down
2 changes: 1 addition & 1 deletion source/exe/process_wide.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace Envoy {

// Process-wide lifecycle events for global state in third-party dependencies,
// e.g. gRPC, c-ares. There should only ever be a single instance of this.
// e.g. c-ares. There should only ever be a single instance of this.
class ProcessWide {
public:
ProcessWide();
Expand Down
1 change: 1 addition & 0 deletions source/extensions/transport_sockets/alts/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ envoy_cc_library(
":tsi_socket",
"//include/envoy/registry",
"//include/envoy/server:transport_socket_config_interface",
"//source/common/grpc:google_grpc_context_lib",
"//source/extensions/transport_sockets:well_known_names",
"@envoy_api//envoy/config/transport_socket/alts/v2alpha:alts_cc",
],
Expand Down
4 changes: 4 additions & 0 deletions source/extensions/transport_sockets/alts/config.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "envoy/server/transport_socket_config.h"

#include "common/common/assert.h"
#include "common/grpc/google_grpc_context.h"
#include "common/protobuf/protobuf.h"
#include "common/protobuf/utility.h"

Expand Down Expand Up @@ -65,6 +66,9 @@ class AltsSharedState : public Singleton::Instance {
AltsSharedState() { grpc_alts_shared_resource_dedicated_init(); }

~AltsSharedState() override { grpc_alts_shared_resource_dedicated_shutdown(); }

private:
Grpc::GoogleGrpcContext google_grpc_context_;
};

SINGLETON_MANAGER_REGISTRATION(alts_shared_state);
Expand Down
19 changes: 19 additions & 0 deletions tools/check_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@
"./source/extensions/filters/http/squash/squash_filter.cc",
"./source/server/http/admin.h", "./source/server/http/admin.cc")

# Only one C++ file should instantiate grpc_init
GRPC_INIT_WHITELIST = ("./source/common/grpc/google_grpc_context.cc")

CLANG_FORMAT_PATH = os.getenv("CLANG_FORMAT", "clang-format-8")
BUILDIFIER_PATH = os.getenv("BUILDIFIER_BIN", "$GOPATH/bin/buildifier")
ENVOY_BUILD_FIXER_PATH = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])),
Expand Down Expand Up @@ -331,6 +334,10 @@ def whitelistedForStdRegex(file_path):
return file_path.startswith("./test") or file_path in STD_REGEX_WHITELIST


def whitelistedForGrpcInit(file_path):
return file_path in GRPC_INIT_WHITELIST


def findSubstringAndReturnError(pattern, file_path, error_message):
with open(file_path) as f:
text = f.read()
Expand Down Expand Up @@ -584,6 +591,18 @@ def checkSourceLine(line, file_path, reportError):
if not whitelistedForStdRegex(file_path) and "std::regex" in line:
reportError("Don't use std::regex in code that handles untrusted input. Use RegexMatcher")

if not whitelistedForGrpcInit(file_path):
grpc_init_or_shutdown = line.find("grpc_init()")
grpc_shutdown = line.find("grpc_shutdown()")
if grpc_init_or_shutdown == -1 or (grpc_shutdown != -1 and
grpc_shutdown < grpc_init_or_shutdown):
grpc_init_or_shutdown = grpc_shutdown
if grpc_init_or_shutdown != -1:
comment = line.find("// ")
if comment == -1 or comment > grpc_init_or_shutdown:
reportError("Don't call grpc_init() or grpc_shutdown() directly, instantiate " +
"Grpc::GoogleGrpcContext. See #8282")


def checkBuildLine(line, file_path, reportError):
if "@bazel_tools" in line and not (isSkylarkFile(file_path) or file_path.startswith("./bazel/")):
Expand Down
8 changes: 8 additions & 0 deletions tools/check_format_test_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,14 @@ def checkFileExpectingOK(filename):
"Don't lookup stats by name at runtime; use StatName saved during construction")
errors += checkUnfixableError(
"regex.cc", "Don't use std::regex in code that handles untrusted input. Use RegexMatcher")
errors += checkUnfixableError(
"grpc_init.cc",
"Don't call grpc_init() or grpc_shutdown() directly, instantiate Grpc::GoogleGrpcContext. " +
"See #8282")
errors += checkUnfixableError(
"grpc_shutdown.cc",
"Don't call grpc_init() or grpc_shutdown() directly, instantiate Grpc::GoogleGrpcContext. " +
"See #8282")

errors += fixFileExpectingFailure(
"api/missing_package.proto",
Expand Down
7 changes: 7 additions & 0 deletions tools/testdata/check_format/grpc_init.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Envoy {

void foo() {
grpc_init();
}

} // namespace Envoy
7 changes: 7 additions & 0 deletions tools/testdata/check_format/grpc_shutdown.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Envoy {

void foo() {
grpc_shutdown();
}

} // namespace Envoy

0 comments on commit 7beb1f8

Please sign in to comment.