Index: lib/asan/asan_rtl.cc =================================================================== --- lib/asan/asan_rtl.cc +++ lib/asan/asan_rtl.cc @@ -459,11 +459,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(const char *suffix); + +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 @@ -561,6 +561,7 @@ } param.leak_report.ApplySuppressions(); uptr unsuppressed_count = param.leak_report.UnsuppressedLeakCount(); + ScopedErrorReportLock l; if (unsuppressed_count > 0) { Decorator d; Printf("\n" @@ -588,6 +589,7 @@ static bool already_done; if (already_done) return; already_done = true; + MaybeRestoreLogPath(); has_reported_leaks = CheckForLeaks(); if (has_reported_leaks) HandleLeaks(); } @@ -757,6 +759,25 @@ return result; } +void MaybeRestoreLogPath() { + if (common_flags()->leak_check_interval_s == kLeaksIntervalNotSet) return; + __sanitizer_set_report_path(common_flags()->log_path); +} + +void MaybeAppendToLogPath(const char *suffix) { + 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'}; + 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 @@ -17,6 +17,7 @@ #if CAN_SANITIZE_LEAKS && SANITIZER_LINUX #include +#include #include "sanitizer_common/sanitizer_common.h" #include "sanitizer_common/sanitizer_flags.h" @@ -135,6 +136,20 @@ dl_iterate_phdr(DoStopTheWorldCallback, ¶m); } +static void LeakCheckTimerHandler(int sig, void* siginfo, void* ucontext) { + ScopedInterceptorDisabler disabler; + DoRecoverableLeakCheckVoid(); +} + +void MaybeStartBackgroundLeakCheckingThread() { +#if !SANITIZER_GO + if (common_flags()->leak_check_interval_s == kLeaksIntervalNotSet) return; + MaybeAppendToLogPath("in-progress"); + internal_timer_create(LeakCheckTimerHandler, + common_flags()->leak_check_interval_s, SIGALRM); +#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 @@ -201,6 +201,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 @@ -215,6 +215,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, ...); @@ -830,6 +831,8 @@ } void *internal_start_thread(void(*func)(void*), void *arg); +void *internal_timer_create(SignalHandlerType handler, uptr seconds, + int signal); void internal_join_thread(void *th); void MaybeStartBackgroudThread(); Index: lib/sanitizer_common/sanitizer_common_libcdep.cc =================================================================== --- lib/sanitizer_common/sanitizer_common_libcdep.cc +++ lib/sanitizer_common/sanitizer_common_libcdep.cc @@ -177,6 +177,10 @@ } #endif +void BackgroundTimerLeakChecking() { + return; +} + #if !SANITIZER_FUCHSIA && !SANITIZER_GO void StartReportDeadlySignal() { // Write the first message using fd=2, just in case. 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,10 @@ 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 +97,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 background thread 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 @@ -1674,12 +1674,44 @@ return th; } -void internal_join_thread(void *th) { - real_pthread_join(th, nullptr); +void *internal_timer_create(SignalHandlerType handler, uptr seconds, + int signal) { +#if SANITIZER_LINUX + __sanitizer_sigset_t set, old; + internal_sigfillset(&set); + internal_sigdelset(&set, signal); + internal_sigprocmask(SIG_SETMASK, &set, &old); + __sanitizer_sigaction sa; + sa.sa_flags = SA_SIGINFO; + sa.handler = (__sanitizer_sighandler_ptr)(handler); + CHECK_EQ(0, internal_sigaction(signal, &sa, nullptr)); + sigevent sev; + sev.sigev_notify = SIGEV_SIGNAL; + sev.sigev_signo = signal; + timer_t timer_id; + sev.sigev_value.sival_ptr = &timer_id; + CHECK_EQ(0, internal_syscall(SYSCALL(timer_create), CLOCK_REALTIME, &sev, + &timer_id)); + itimerspec its; + its.it_value.tv_sec = seconds; + 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)); + internal_sigprocmask(SIG_SETMASK, &old, nullptr); + VReport(1, "Installed timer for leak checking with %d seconds interval\n", + seconds); + return timer_id; +#endif } + +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_timer_create(SignalHandlerType handler, + uptr seconds, int signal) { 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 @@ -163,6 +163,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,24 @@ +// Test for LSan background leak checking during application runtime every 1 second. +// RUN: LSAN_BASE="use_stacks=0:use_registers=0" +// RUN: %clangxx_lsan %s -o %t +// RUN: %env_lsan_opts=$LSAN_BASE":leak_check_interval_s=1:leak_check_at_exit=1" not %run %t 2>&1 | FileCheck %s +// RUN: %env_lsan_opts=$LSAN_BASE":leak_check_interval_s=1:leak_check_at_exit=0" %run %t 2>&1 | FileCheck %s +// RUN: %env_lsan_opts=$LSAN_BASE":leak_check_interval_s=0:leak_check_at_exit=1" not %run %t 2>&1 | FileCheck %s +// RUN: %env_lsan_opts=$LSAN_BASE":leak_check_interval_s=0:leak_check_at_exit=0" %run %t 2>&1 + +#include +#include + +int f() { + volatile int *a = (int *)malloc(20); + a[0] = 1; + return a[0]; +} + +int main() { + f(); + sleep(3); + return 0; +} + +// CHECK: LeakSanitizer: detected memory leaks