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 @@ -100,8 +100,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; +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,24 @@ 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); + __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 @@ -19,6 +19,7 @@ #include #include "sanitizer_common/sanitizer_common.h" +#include "sanitizer_common/sanitizer_file.h" #include "sanitizer_common/sanitizer_flags.h" #include "sanitizer_common/sanitizer_getauxval.h" #include "sanitizer_common/sanitizer_linux.h" @@ -135,6 +136,24 @@ dl_iterate_phdr(DoStopTheWorldCallback, ¶m); } +void BackgroundLeakCheckingThread(void *arg) { + ScopedInterceptorDisabler disabler; + uptr interval_s = common_flags()->leak_check_interval_s; + MaybeAppendToLogPath("in-progress"); + while (true) { + SleepForSeconds(interval_s); + // Do not save previous reports + TruncateReportFile(); + DoRecoverableLeakCheckVoid(); + } + MaybeRestoreLogPath(); +} + +void MaybeStartBackgroundLeakCheckingThread() { + if (common_flags()->leak_check_interval_s == kLeaksIntervalNotSet) return; + internal_start_thread(BackgroundLeakCheckingThread, nullptr); +} + } // 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 @@ -194,6 +194,10 @@ StopTheWorld(callback, argument); } +void MaybeStartBackgroundLeakCheckingThread() { + UNIMPLEMENTED(); +} + } // namespace __lsan #endif // CAN_SANITIZE_LEAKS && SANITIZER_MAC Index: lib/lsan/lsan_interceptors.cc =================================================================== --- lib/lsan/lsan_interceptors.cc +++ lib/lsan/lsan_interceptors.cc @@ -411,6 +411,8 @@ return res; } +DEFINE_REAL_PTHREAD_FUNCTIONS + INTERCEPTOR(void, _exit, int status) { if (status == 0 && HasReportedLeaks()) status = common_flags()->exitcode; REAL(_exit)(status); Index: lib/sanitizer_common/sanitizer_common.h =================================================================== --- lib/sanitizer_common/sanitizer_common.h +++ lib/sanitizer_common/sanitizer_common.h @@ -217,6 +217,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, ...); 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_posix.cc =================================================================== --- lib/sanitizer_common/sanitizer_posix.cc +++ lib/sanitizer_common/sanitizer_posix.cc @@ -159,6 +159,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(2); + return 0; +} + +// CHECK: LeakSanitizer: detected memory leaks