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