Index: lib/asan/asan_rtl.cc =================================================================== --- lib/asan/asan_rtl.cc +++ lib/asan/asan_rtl.cc @@ -469,11 +469,14 @@ if (CAN_SANITIZE_LEAKS) { __lsan::InitCommonLsan(); - if (common_flags()->detect_leaks && common_flags()->leak_check_at_exit) { - if (flags()->halt_on_error) - Atexit(__lsan::DoLeakCheck); - else - Atexit(__lsan::DoRecoverableLeakCheckVoid); + if (common_flags()->detect_leaks) { + __lsan::MaybeStartBackgroundLeakCheckingThread(); + if (common_flags()->leak_check_at_exit) { + if (flags()->halt_on_error) + Atexit(__lsan::DoLeakCheck); + else + Atexit(__lsan::DoRecoverableLeakCheckVoid); + } } } Index: lib/lsan/lsan.cc =================================================================== --- lib/lsan/lsan.cc +++ lib/lsan/lsan.cc @@ -102,8 +102,10 @@ ThreadStart(tid, GetTid()); SetCurrentThread(tid); - if (common_flags()->detect_leaks && common_flags()->leak_check_at_exit) - Atexit(DoLeakCheck); + if (common_flags()->detect_leaks) { + MaybeStartBackgroundLeakCheckingThread(); + if (common_flags()->leak_check_at_exit) Atexit(DoLeakCheck); + } InitializeCoverage(common_flags()->coverage, common_flags()->coverage_dir); Index: lib/lsan/lsan_common.h =================================================================== --- lib/lsan/lsan_common.h +++ lib/lsan/lsan_common.h @@ -149,6 +149,11 @@ void DoRecoverableLeakCheckVoid(); void DisableCounterUnderflow(); bool DisabledInThisThread(); +void MaybeRestoreLogPath(); +void MaybeAppendToLogPath(); + +static const uptr kLeaksIntervalNotSet = 0; +void MaybeStartBackgroundLeakCheckingThread(); // Used to implement __lsan::ScopedDisabler. void DisableInThisThread(); Index: lib/lsan/lsan_common.cc =================================================================== --- lib/lsan/lsan_common.cc +++ lib/lsan/lsan_common.cc @@ -588,6 +588,7 @@ } param.leak_report.ApplySuppressions(); uptr unsuppressed_count = param.leak_report.UnsuppressedLeakCount(); + ScopedErrorReportLock l; if (unsuppressed_count > 0) { Decorator d; Printf("\n" @@ -784,6 +785,26 @@ return result; } +void MaybeRestoreLogPath() { + if (common_flags()->leak_check_interval_s == kLeaksIntervalNotSet) return; + __sanitizer_set_report_path(common_flags()->log_path); +} + +void MaybeAppendToLogPath() { + if (common_flags()->leak_check_interval_s == kLeaksIntervalNotSet) return; + const char *log_path = common_flags()->log_path; + if (internal_strcmp(log_path, "stderr") == 0 || + internal_strcmp(log_path, "stdout") == 0 || !log_path) + return; + static char new_log_path[kMaxPathLength] = {'\0'}; + static const char suffix[] = "in-progress"; + if (UNLIKELY(new_log_path[0] == '\0')) { + internal_snprintf(new_log_path, kMaxPathLength, "%s.%s", log_path, suffix); + ScopedErrorReportLock l; + __sanitizer_set_report_path(new_log_path); + } +} + } // namespace __lsan #else // CAN_SANITIZE_LEAKS namespace __lsan { Index: lib/lsan/lsan_common_linux.cc =================================================================== --- lib/lsan/lsan_common_linux.cc +++ lib/lsan/lsan_common_linux.cc @@ -135,6 +135,25 @@ dl_iterate_phdr(DoStopTheWorldCallback, ¶m); } +static const int kLeakCheckHandlerSignal = 14; // SIGALRM + +static void LeakCheckTimerHandler(int sig, void *siginfo, void *ucontext) { + if (sig != kLeakCheckHandlerSignal) return; + ScopedInterceptorDisabler disabler; + MaybeAppendToLogPath(); + TruncateReportFile(); + DoRecoverableLeakCheckVoid(); + MaybeRestoreLogPath(); +} + +void MaybeStartBackgroundLeakCheckingThread() { +#if !SANITIZER_GO && SANITIZER_LINUX + if (common_flags()->leak_check_interval_s == kLeaksIntervalNotSet) return; + internal_start_signal_timer(LeakCheckTimerHandler, kLeakCheckHandlerSignal, + common_flags()->leak_check_interval_s); +#endif +} + } // namespace __lsan #endif // CAN_SANITIZE_LEAKS && SANITIZER_LINUX Index: lib/lsan/lsan_common_mac.cc =================================================================== --- lib/lsan/lsan_common_mac.cc +++ lib/lsan/lsan_common_mac.cc @@ -202,6 +202,10 @@ StopTheWorld(callback, argument); } +void MaybeStartBackgroundLeakCheckingThread() { + UNIMPLEMENTED(); +} + } // namespace __lsan #endif // CAN_SANITIZE_LEAKS && SANITIZER_MAC Index: lib/sanitizer_common/sanitizer_common.h =================================================================== --- lib/sanitizer_common/sanitizer_common.h +++ lib/sanitizer_common/sanitizer_common.h @@ -172,6 +172,7 @@ // IO void CatastrophicErrorWrite(const char *buffer, uptr length); void RawWrite(const char *buffer); +void TruncateReportFile(); bool ColorizeReports(); void RemoveANSIEscapeSequencesFromString(char *buffer); void Printf(const char *format, ...); @@ -831,6 +832,8 @@ } void *internal_start_thread(void(*func)(void*), void *arg); +void *internal_start_signal_timer(SignalHandlerType handler, int signal, + uptr interval_s); void internal_join_thread(void *th); void MaybeStartBackgroudThread(); Index: lib/sanitizer_common/sanitizer_file.h =================================================================== --- lib/sanitizer_common/sanitizer_file.h +++ lib/sanitizer_common/sanitizer_file.h @@ -27,6 +27,7 @@ void Write(const char *buffer, uptr length); bool SupportsColors(); void SetReportPath(const char *path); + void Truncate(); // Don't use fields directly. They are only declared public to allow // aggregate initialization. @@ -52,7 +53,8 @@ enum FileAccessMode { RdOnly, WrOnly, - RdWr + RdWr, + WrTrunc }; // Returns kInvalidFd on error. Index: lib/sanitizer_common/sanitizer_file.cc =================================================================== --- lib/sanitizer_common/sanitizer_file.cc +++ lib/sanitizer_common/sanitizer_file.cc @@ -34,6 +34,8 @@ report_file.Write(buffer, internal_strlen(buffer)); } +void TruncateReportFile() { report_file.Truncate(); } + void ReportFile::ReopenIfNecessary() { mu->CheckLocked(); if (fd == kStdoutFd || fd == kStderrFd) return; @@ -93,6 +95,16 @@ } } +void ReportFile::Truncate() { + if (fd == kStdoutFd || fd == kStderrFd) return; + if (!full_path) return; + + SpinMutexLock l(mu); + if (fd != kInvalidFd) CloseFile(fd); + + fd = OpenFile(full_path, WrTrunc); +} + bool ReadFileToBuffer(const char *file_name, char **buff, uptr *buff_size, uptr *read_len, uptr max_len, error_t *errno_p) { uptr PageSize = GetPageSizeCached(); Index: lib/sanitizer_common/sanitizer_flags.inc =================================================================== --- lib/sanitizer_common/sanitizer_flags.inc +++ lib/sanitizer_common/sanitizer_flags.inc @@ -71,6 +71,10 @@ "Invoke leak checking in an atexit handler. Has no effect if " "detect_leaks=false, or if __lsan_do_leak_check() is called before the " "handler has a chance to run.") +COMMON_FLAG(uptr, leak_check_interval_s, 0, + "Experimental. If set, creates signal timer that performs " + "recoverable leak checking every leak_check_interval_s seconds. " + "Disabled if zero or detect_leaks=false.\n") COMMON_FLAG(bool, allocator_may_return_null, false, "If false, the allocator will crash instead of returning 0 on " "out-of-memory.") Index: lib/sanitizer_common/sanitizer_linux.cc =================================================================== --- lib/sanitizer_common/sanitizer_linux.cc +++ lib/sanitizer_common/sanitizer_linux.cc @@ -1700,12 +1700,55 @@ return th; } +static void *internal_timer_create(int signal, uptr interval_s) { + sigevent sev; + sev.sigev_notify = SIGEV_SIGNAL; + sev.sigev_signo = signal; + void *timer_id; + sev.sigev_value.sival_ptr = &timer_id; + CHECK_EQ(0, internal_syscall(SYSCALL(timer_create), CLOCK_REALTIME, &sev, + &timer_id)); + VPrintf(1, "Created timer for %d signal\n", signal, interval_s); + itimerspec its; + its.it_value.tv_sec = interval_s; + its.it_value.tv_nsec = 0; + its.it_interval.tv_sec = its.it_value.tv_sec; + its.it_interval.tv_nsec = 0; + CHECK_EQ( + 0, internal_syscall(SYSCALL(timer_settime), timer_id, 0, &its, nullptr)); + VPrintf(1, "Armed timer for %d signal, interval = %ul \n", signal, + interval_s); + return timer_id; +} + +void *internal_start_signal_timer(SignalHandlerType handler, int signal, + uptr interval_s) { + __sanitizer_sigset_t set, old; + internal_sigfillset(&set); + internal_sigdelset(&set, signal); + CHECK_EQ(0, internal_sigprocmask(SIG_SETMASK, &set, &old)); + // Setting up sigaction + __sanitizer_sigaction sa; + internal_memset(&sa, 0, sizeof(sa)); + sa.sa_flags = SA_SIGINFO | SA_RESTART; + sa.sigaction = (__sanitizer_sigactionhandler_ptr)(handler); + CHECK_EQ(0, internal_sigaction_syscall(signal, &sa, nullptr)); + void *timer_id = internal_timer_create(signal, interval_s); + CHECK_EQ(0, internal_sigprocmask(SIG_SETMASK, &old, nullptr)); + return timer_id; +} + void internal_join_thread(void *th) { real_pthread_join(th, nullptr); } #else void *internal_start_thread(void (*func)(void *), void *arg) { return 0; } +void *internal_start_signal_timer(SignalHandlerType handler, int signal, + uptr interval_s) { + return 0; +} +void *internal_timer_create(int signal, uptr interval_s) { return 0; } void internal_join_thread(void *th) {} #endif Index: lib/sanitizer_common/sanitizer_posix.cc =================================================================== --- lib/sanitizer_common/sanitizer_posix.cc +++ lib/sanitizer_common/sanitizer_posix.cc @@ -162,6 +162,7 @@ case RdOnly: flags = O_RDONLY; break; case WrOnly: flags = O_WRONLY | O_CREAT; break; case RdWr: flags = O_RDWR | O_CREAT; break; + case WrTrunc: flags = O_WRONLY | O_CREAT | O_TRUNC; break; } fd_t res = internal_open(filename, flags, 0660); if (internal_iserror(res, errno_p)) Index: test/lsan/TestCases/Linux/leak_check_interval.cc =================================================================== --- /dev/null +++ test/lsan/TestCases/Linux/leak_check_interval.cc @@ -0,0 +1,37 @@ +// Test for LSan background leak checking during application runtime +// RUN: LSAN_BASE="use_stacks=0:use_registers=0" +// RUN: %clangxx_lsan %s -o %t +// RUN: LSAN_OPT="$LSAN_BASE:leak_check_interval_s=1:leak_check_at_exit=1" +// RUN: %env_lsan_opts=$LSAN_BASE":leak_check_interval_s=1:leak_check_at_exit=1" not %run %t 2>&1 | FileCheck --check-prefix=CHECK-interval-exit %s +// RUN: %env_lsan_opts=$LSAN_BASE":leak_check_interval_s=1:leak_check_at_exit=0" %run %t 2>&1 | FileCheck --check-prefix=CHECK-interval %s +// RUN: %env_lsan_opts=$LSAN_BASE":leak_check_interval_s=0:leak_check_at_exit=1" not %run %t 2>&1 | FileCheck --check-prefix=CHECK-exit %s +// RUN: %env_lsan_opts=$LSAN_BASE":leak_check_interval_s=0:leak_check_at_exit=0" %run %t 2>&1 + +#include +#include +#include + +int Leak() { + volatile int *a = (int *)malloc(20); + a[0] = 1; + return a[0]; +} + +int main() { + Leak(); + for (int i = 0; i < 3; ++i) { + sleep(1); + } + fprintf(stderr, "END_HERE\n"); + return 0; +} + +// CHECK-interval-exit: LeakSanitizer: detected memory leaks +// CHECK-interval-exit: END_HERE +// CHECK-interval-exit: LeakSanitizer: detected memory leaks + +// CHECK-interval: LeakSanitizer: detected memory leaks +// CHECK-interval: END_HERE + +// CHECK-exit: END_HERE +// CHECK-exit: LeakSanitizer: detected memory leaks