Skip to content

Commit 92b6c62

Browse files
fix(alog): prevent child deadlock on rotation lock across fork() (#1573) (#1575) (#1577)
The log rotation mutex was a function-local static in write(). If fork() happened while another thread was rotating, the child inherited the mutex permanently locked and deadlocked on its next rotation. Hoist the lock to file scope and serialize it against fork() with pthread_atfork(): prepare acquires it (waiting out any in-flight rotation), and both parent and child release it afterwards. Add a fork-during-rotation regression test. Co-authored-by: Coldwings <coldwings@me.com>
1 parent b7edc80 commit 92b6c62

2 files changed

Lines changed: 59 additions & 1 deletion

File tree

common/alog.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ limitations under the License.
2424
#include <mutex>
2525
#include <condition_variable>
2626
#include <unistd.h>
27+
#include <pthread.h>
2728
#include <sys/types.h>
2829
#include <sys/stat.h>
2930
#include <fcntl.h>
@@ -220,6 +221,21 @@ void LogFormatter::put_integer_dec(ALogBuffer& buf, ALogInteger x)
220221
}
221222
}
222223

224+
// Protects log file rotation. It used to be a function-local static in
225+
// write(), but if fork() happened while a thread was rotating, the child
226+
// would inherit the mutex in locked state forever. So it is hoisted here
227+
// and serialized against fork() with pthread_atfork(): prepare acquires
228+
// the lock (waiting for any in-flight rotation), and both parent and
229+
// child release it after fork().
230+
static mutex log_file_lock;
231+
232+
__attribute__((constructor))
233+
static void __register_log_file_lock_atfork() {
234+
pthread_atfork([] { log_file_lock.lock(); },
235+
[] { log_file_lock.unlock(); },
236+
[] { log_file_lock.unlock(); });
237+
}
238+
223239
class LogOutputFile final : public BaseLogOutput {
224240
public:
225241
uint64_t log_file_size_limit = 0;
@@ -250,7 +266,6 @@ class LogOutputFile final : public BaseLogOutput {
250266
if (log_file_name && log_file_size_limit) {
251267
log_file_size += length;
252268
if (log_file_size > log_file_size_limit) {
253-
static mutex log_file_lock;
254269
lock_guard<mutex> guard(log_file_lock);
255270
if (log_file_size > log_file_size_limit) {
256271
log_file_rotate();

common/test/test_alog.cpp

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,13 @@ limitations under the License.
2424
#include <photon/net/utils-stdstring.h>
2525
#include <chrono>
2626
#include <vector>
27+
#include <atomic>
28+
#include <thread>
2729
#include <stdint.h>
2830
#include <unistd.h>
2931
#include <fcntl.h>
32+
#include <sys/wait.h>
33+
#include <signal.h>
3034
#include "../../test/ci-tools.h"
3135
#include "../../test/gtest.h"
3236

@@ -177,6 +181,45 @@ TEST(ALog, log_to_file) {
177181
::close(fd);
178182
}
179183

184+
static bool wait_child_exited(pid_t pid, int timeout_sec) {
185+
for (int i = 0; i < timeout_sec * 100; ++i) {
186+
int st;
187+
if (::waitpid(pid, &st, WNOHANG) == pid)
188+
return WIFEXITED(st) && WEXITSTATUS(st) == 0;
189+
::usleep(10 * 1000);
190+
}
191+
::kill(pid, SIGKILL);
192+
::waitpid(pid, nullptr, 0);
193+
return false;
194+
}
195+
196+
TEST(ALog, fork_during_rotation) {
197+
const char* fn = "/tmp/test_alog_fork.log";
198+
// minimum rotate limit is 1MB
199+
ASSERT_EQ(0, log_output_file(fn, 1024 * 1024, 3));
200+
DEFER(log_output_file_close());
201+
// keep triggering rotation in background, so that fork() below
202+
// is likely to happen while the rotation lock is held
203+
std::atomic<bool> stop{false};
204+
std::thread writer([&] {
205+
while (!stop)
206+
LOG_INFO("background writer keeps rotating the log file, padding padding padding padding");
207+
});
208+
DEFER({ stop = true; writer.join(); });
209+
for (int i = 0; i < 8; ++i) {
210+
pid_t pid = fork();
211+
ASSERT_GE(pid, 0);
212+
if (pid == 0) {
213+
// child: write enough to trigger a rotation of its own;
214+
// it deadlocks here if the rotation lock was inherited locked
215+
for (int j = 0; j < 16 * 1024; ++j)
216+
LOG_INFO("child writer must not deadlock on the rotation lock, padding padding padding");
217+
::_exit(0);
218+
}
219+
EXPECT_TRUE(wait_child_exited(pid, 10)) << "child " << pid << " deadlocked";
220+
}
221+
}
222+
180223
TEST(ALog, float_point)
181224
{
182225
log_output = &log_output_test;

0 commit comments

Comments
 (0)