mirror of https://github.com/grpc/grpc.git
EventEngine Forkables (#30473)
A (currently) pthread_atfork-based fork support mechanism, allowing EventEngines - or any other object that wants to implement the Forkable interface - respond to forks.pull/30546/head
parent
5d0e744da6
commit
0c5b6171ad
26 changed files with 501 additions and 20 deletions
@ -0,0 +1,98 @@ |
|||||||
|
// Copyright 2022 gRPC authors.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include <grpc/support/port_platform.h> |
||||||
|
|
||||||
|
#include "src/core/lib/event_engine/forkable.h" |
||||||
|
|
||||||
|
#ifdef GRPC_POSIX_FORK_ALLOW_PTHREAD_ATFORK |
||||||
|
|
||||||
|
#include <pthread.h> |
||||||
|
|
||||||
|
#include "absl/container/flat_hash_set.h" |
||||||
|
|
||||||
|
#include "src/core/lib/gprpp/sync.h" |
||||||
|
|
||||||
|
namespace grpc_event_engine { |
||||||
|
namespace experimental { |
||||||
|
|
||||||
|
namespace { |
||||||
|
grpc_core::Mutex g_mu; |
||||||
|
bool g_registered{false}; |
||||||
|
absl::flat_hash_set<Forkable*> g_forkables; |
||||||
|
} // namespace
|
||||||
|
|
||||||
|
Forkable::Forkable() { ManageForkable(this); } |
||||||
|
|
||||||
|
Forkable::~Forkable() { StopManagingForkable(this); } |
||||||
|
|
||||||
|
void RegisterForkHandlers() { |
||||||
|
grpc_core::MutexLock lock(&g_mu); |
||||||
|
GPR_ASSERT(!absl::exchange(g_registered, true)); |
||||||
|
pthread_atfork(PrepareFork, PostforkParent, PostforkChild); |
||||||
|
}; |
||||||
|
|
||||||
|
void PrepareFork() { |
||||||
|
grpc_core::MutexLock lock(&g_mu); |
||||||
|
for (auto* forkable : g_forkables) { |
||||||
|
forkable->PrepareFork(); |
||||||
|
} |
||||||
|
} |
||||||
|
void PostforkParent() { |
||||||
|
grpc_core::MutexLock lock(&g_mu); |
||||||
|
for (auto* forkable : g_forkables) { |
||||||
|
forkable->PostforkParent(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void PostforkChild() { |
||||||
|
grpc_core::MutexLock lock(&g_mu); |
||||||
|
for (auto* forkable : g_forkables) { |
||||||
|
forkable->PostforkChild(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void ManageForkable(Forkable* forkable) { |
||||||
|
grpc_core::MutexLock lock(&g_mu); |
||||||
|
g_forkables.insert(forkable); |
||||||
|
} |
||||||
|
|
||||||
|
void StopManagingForkable(Forkable* forkable) { |
||||||
|
grpc_core::MutexLock lock(&g_mu); |
||||||
|
g_forkables.erase(forkable); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace experimental
|
||||||
|
} // namespace grpc_event_engine
|
||||||
|
|
||||||
|
#else // GRPC_POSIX_FORK_ALLOW_PTHREAD_ATFORK
|
||||||
|
|
||||||
|
namespace grpc_event_engine { |
||||||
|
namespace experimental { |
||||||
|
|
||||||
|
Forkable::Forkable() {} |
||||||
|
Forkable::~Forkable() {} |
||||||
|
|
||||||
|
void RegisterForkHandlers() {} |
||||||
|
void PrepareFork() {} |
||||||
|
void PostforkParent() {} |
||||||
|
void PostforkChild() {} |
||||||
|
|
||||||
|
void ManageForkable(Forkable* /* forkable */) {} |
||||||
|
void StopManagingForkable(Forkable* /* forkable */) {} |
||||||
|
|
||||||
|
} // namespace experimental
|
||||||
|
} // namespace grpc_event_engine
|
||||||
|
|
||||||
|
#endif // GRPC_POSIX_FORK_ALLOW_PTHREAD_ATFORK
|
@ -0,0 +1,61 @@ |
|||||||
|
// Copyright 2022 gRPC authors.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
#ifndef GRPC_CORE_LIB_EVENT_ENGINE_FORKABLE_H |
||||||
|
#define GRPC_CORE_LIB_EVENT_ENGINE_FORKABLE_H |
||||||
|
|
||||||
|
#include <grpc/support/port_platform.h> |
||||||
|
|
||||||
|
namespace grpc_event_engine { |
||||||
|
namespace experimental { |
||||||
|
|
||||||
|
// Register fork handlers with the system, enabling fork support.
|
||||||
|
//
|
||||||
|
// This provides pthread-based support for fork events. Any objects that
|
||||||
|
// implement Forkable can register themselves with this system using
|
||||||
|
// ManageForkable, and their respective methods will be called upon fork.
|
||||||
|
//
|
||||||
|
// This should be called once upon grpc_initialization.
|
||||||
|
void RegisterForkHandlers(); |
||||||
|
|
||||||
|
// Global callback for pthread_atfork's *prepare argument
|
||||||
|
void PrepareFork(); |
||||||
|
// Global callback for pthread_atfork's *parent argument
|
||||||
|
void PostforkParent(); |
||||||
|
// Global callback for pthread_atfork's *child argument
|
||||||
|
void PostforkChild(); |
||||||
|
|
||||||
|
// An interface to be implemented by EventEngines that wish to have managed fork
|
||||||
|
// support.
|
||||||
|
class Forkable { |
||||||
|
public: |
||||||
|
Forkable(); |
||||||
|
virtual ~Forkable(); |
||||||
|
virtual void PrepareFork() = 0; |
||||||
|
virtual void PostforkParent() = 0; |
||||||
|
virtual void PostforkChild() = 0; |
||||||
|
}; |
||||||
|
|
||||||
|
// Add Forkables from the set of objects that are supported.
|
||||||
|
// Upon fork, each forkable will have its respective fork hooks called on
|
||||||
|
// the thread that invoked the fork.
|
||||||
|
//
|
||||||
|
// Relative ordering of fork callback operations is not guaranteed.
|
||||||
|
void ManageForkable(Forkable* forkable); |
||||||
|
// Remove a forkable from the managed set.
|
||||||
|
void StopManagingForkable(Forkable* forkable); |
||||||
|
|
||||||
|
} // namespace experimental
|
||||||
|
} // namespace grpc_event_engine
|
||||||
|
|
||||||
|
#endif // GRPC_CORE_LIB_EVENT_ENGINE_FORKABLE_H
|
@ -0,0 +1,103 @@ |
|||||||
|
// Copyright 2022 gRPC authors.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include <grpc/support/port_platform.h> |
||||||
|
|
||||||
|
#ifndef GRPC_ENABLE_FORK_SUPPORT |
||||||
|
|
||||||
|
// Test nothing, everything is fine
|
||||||
|
int main(int /* argc */, char** /* argv */) { return 0; } |
||||||
|
|
||||||
|
#else // GRPC_ENABLE_FORK_SUPPORT
|
||||||
|
|
||||||
|
#include <sys/wait.h> |
||||||
|
#include <unistd.h> |
||||||
|
|
||||||
|
#include <gtest/gtest.h> |
||||||
|
|
||||||
|
#include "absl/time/clock.h" |
||||||
|
|
||||||
|
#include <grpc/grpc.h> |
||||||
|
#include <grpc/support/log.h> |
||||||
|
|
||||||
|
#include "src/core/lib/event_engine/forkable.h" |
||||||
|
|
||||||
|
namespace { |
||||||
|
using ::grpc_event_engine::experimental::Forkable; |
||||||
|
using ::grpc_event_engine::experimental::RegisterForkHandlers; |
||||||
|
} // namespace
|
||||||
|
|
||||||
|
class ForkableTest : public testing::Test {}; |
||||||
|
|
||||||
|
#ifdef GRPC_POSIX_FORK_ALLOW_PTHREAD_ATFORK |
||||||
|
TEST_F(ForkableTest, BasicPthreadAtForkOperations) { |
||||||
|
class SomeForkable : public Forkable { |
||||||
|
public: |
||||||
|
void PrepareFork() override { prepare_called_ = true; } |
||||||
|
void PostforkParent() override { parent_called_ = true; } |
||||||
|
void PostforkChild() override { child_called_ = true; } |
||||||
|
|
||||||
|
void CheckParent() { |
||||||
|
EXPECT_TRUE(prepare_called_); |
||||||
|
EXPECT_TRUE(parent_called_); |
||||||
|
EXPECT_FALSE(child_called_); |
||||||
|
} |
||||||
|
|
||||||
|
void CheckChild() { |
||||||
|
EXPECT_TRUE(prepare_called_); |
||||||
|
EXPECT_FALSE(parent_called_); |
||||||
|
EXPECT_TRUE(child_called_); |
||||||
|
} |
||||||
|
|
||||||
|
private: |
||||||
|
bool prepare_called_ = false; |
||||||
|
bool parent_called_ = false; |
||||||
|
bool child_called_ = false; |
||||||
|
}; |
||||||
|
|
||||||
|
SomeForkable forkable; |
||||||
|
int child_pid = fork(); |
||||||
|
ASSERT_NE(child_pid, -1); |
||||||
|
if (child_pid == 0) { |
||||||
|
gpr_log(GPR_DEBUG, "I am child pid: %d", getpid()); |
||||||
|
forkable.CheckChild(); |
||||||
|
exit(testing::Test::HasFailure()); |
||||||
|
} else { |
||||||
|
gpr_log(GPR_DEBUG, "I am parent pid: %d", getpid()); |
||||||
|
forkable.CheckParent(); |
||||||
|
int status; |
||||||
|
gpr_log(GPR_DEBUG, "Waiting for child pid: %d", child_pid); |
||||||
|
do { |
||||||
|
// retry on EINTR, and fail otherwise
|
||||||
|
if (waitpid(child_pid, &status, 0) != -1) break; |
||||||
|
ASSERT_EQ(errno, EINTR); |
||||||
|
} while (true); |
||||||
|
if (WIFEXITED(status)) { |
||||||
|
ASSERT_EQ(WEXITSTATUS(status), 0); |
||||||
|
} else { |
||||||
|
// exited abnormally, fail and print the exit status
|
||||||
|
ASSERT_EQ(-99, status); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
#endif // GRPC_POSIX_FORK_ALLOW_PTHREAD_ATFORK
|
||||||
|
|
||||||
|
int main(int argc, char** argv) { |
||||||
|
testing::InitGoogleTest(&argc, argv); |
||||||
|
RegisterForkHandlers(); |
||||||
|
auto result = RUN_ALL_TESTS(); |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
#endif // GRPC_ENABLE_FORK_SUPPORT
|
Loading…
Reference in new issue