diff options
| author | robot-piglet <[email protected]> | 2024-12-05 09:35:47 +0300 | 
|---|---|---|
| committer | robot-piglet <[email protected]> | 2024-12-05 09:48:57 +0300 | 
| commit | 1fef94960daef6658da8883f15004a0c2e9be983 (patch) | |
| tree | 1ea968d1fd85f97f43a5e80148c2563385e59c35 | |
| parent | d2daa783976633567d093f5c5cb8051c4640f680 (diff) | |
Intermediate changes
commit_hash:1202dc6865ea9a18b824d684fc2c165ad191e1bd
| -rw-r--r-- | yt/yt/library/oom/oom.h | 17 | ||||
| -rw-r--r-- | yt/yt/library/oom/tcmalloc_memory_limit_handler.cpp | 312 | ||||
| -rw-r--r-- | yt/yt/library/oom/ya.make | 2 | ||||
| -rw-r--r-- | yt/yt/library/tcmalloc/config.cpp | 14 | ||||
| -rw-r--r-- | yt/yt/library/tcmalloc/config.h | 16 | ||||
| -rw-r--r-- | yt/yt/library/tcmalloc/tcmalloc_manager.cpp | 338 | ||||
| -rw-r--r-- | yt/yt/library/tcmalloc/ya.make | 2 | 
7 files changed, 336 insertions, 365 deletions
diff --git a/yt/yt/library/oom/oom.h b/yt/yt/library/oom/oom.h index 1fa01194dc9..7a5892918aa 100644 --- a/yt/yt/library/oom/oom.h +++ b/yt/yt/library/oom/oom.h @@ -2,9 +2,7 @@  #include <optional> -#include <util/system/types.h>  #include <util/generic/string.h> -#include <util/datetime/base.h>  namespace NYT { @@ -20,19 +18,4 @@ void EnableEarlyOomWatchdog(TOomWatchdogOptions options);  //////////////////////////////////////////////////////////////////////////////// -struct TTCMallocLimitHandlerOptions -{ -    TString HeapDumpDirectory; - -    // Files structure would have the following form: -    // HeapDumpDirectory/<ActualName>_FilenameSuffix_Timestamp.ext. -    std::optional<TString> FilenameSuffix; -    TDuration Timeout = TDuration::Minutes(5); -}; - -void EnableTCMallocLimitHandler(TTCMallocLimitHandlerOptions options); -void DisableTCMallocLimitHandler(); - -//////////////////////////////////////////////////////////////////////////////// -  } // namespace NYT diff --git a/yt/yt/library/oom/tcmalloc_memory_limit_handler.cpp b/yt/yt/library/oom/tcmalloc_memory_limit_handler.cpp deleted file mode 100644 index cad154d211d..00000000000 --- a/yt/yt/library/oom/tcmalloc_memory_limit_handler.cpp +++ /dev/null @@ -1,312 +0,0 @@ -#include "oom.h" - -#include <yt/yt/library/ytprof/external_pprof.h> -#include <yt/yt/library/ytprof/heap_profiler.h> -#include <yt/yt/library/ytprof/profile.h> -#include <yt/yt/library/ytprof/spinlock_profiler.h> -#include <yt/yt/library/ytprof/symbolize.h> - -#include <yt/yt/core/misc/crash_handler.h> -#include <yt/yt/core/misc/error.h> - -#include <yt/yt/core/ytree/yson_struct.h> - -#include <yt/yt/core/yson/writer.h> - -#include <library/cpp/yt/string/format.h> - -#include <library/cpp/yt/memory/atomic_intrusive_ptr.h> - -#include <library/cpp/yt/system/exit.h> - -#include <util/datetime/base.h> -#include <util/stream/file.h> -#include <util/stream/output.h> -#include <util/string/split.h> -#include <util/system/env.h> -#include <util/system/file.h> -#include <util/system/fs.h> -#include <util/system/shellcommand.h> - -#include <mutex> -#include <thread> - -namespace NYT { - -//////////////////////////////////////////////////////////////////////////////// - -namespace { - -TString MakeIncompletePath(const TString& path) -{ -    return NYT::Format("%v_incomplete", path); -} - -} // namespace - -//////////////////////////////////////////////////////////////////////////////// - -void CollectAndDumpMemoryProfile(const TString& memoryProfilePath, tcmalloc::ProfileType profileType) -{ -    auto profile = NYTProf::ReadHeapProfile(profileType); -    SymbolizeByExternalPProf(&profile, NYTProf::TSymbolizationOptions{ -        .RunTool = [] (const std::vector<TString>& args) { -            TShellCommand command{args[0], TList<TString>{args.begin()+1, args.end()}}; -            command.Run(); -        }, -    }); - -    auto incompletePath = NYT::MakeIncompletePath(memoryProfilePath); - -    TFileOutput output(incompletePath); -    NYTProf::WriteProfile(&output, profile); -    output.Finish(); -    NFs::Rename(incompletePath, memoryProfilePath); -} - -//////////////////////////////////////////////////////////////////////////////// - -namespace { - -void MemoryProfileTimeoutHandler(int /*signal*/) -{ -    AbortProcessDramatically( -        EProcessExitCode::GenericError, -        "Process hung while dumping heap profile"); -} - -} // namespace - -void SetupMemoryProfileTimeout(int timeout) -{ -    ::signal(SIGALRM, &MemoryProfileTimeoutHandler); -    ::alarm(timeout); -} - -//////////////////////////////////////////////////////////////////////////////// - -DECLARE_REFCOUNTED_STRUCT(TOomProfilePaths) - -struct TOomProfilePaths -    : public NYTree::TYsonStruct -{ -    TString HeapProfilePath; -    TString PeakProfilePath; - -    REGISTER_YSON_STRUCT(TOomProfilePaths); - -    static void Register(TRegistrar registrar) -    { -        registrar.Parameter("heap_profile_path", &TThis::HeapProfilePath) -            .Default(); -        registrar.Parameter("peak_profile_path", &TThis::PeakProfilePath) -            .Default(); -    } -}; - -DEFINE_REFCOUNTED_TYPE(TOomProfilePaths) - -//////////////////////////////////////////////////////////////////////////////// - -void DumpProfilePaths(const TOomProfilePathsPtr& links, const TString& fileName) -{ -    TFileOutput output(fileName); -    NYson::TYsonWriter writer(&output, NYson::EYsonFormat::Pretty); -    Serialize(links, &writer); -    writer.Flush(); -    output.Finish(); -} - -//////////////////////////////////////////////////////////////////////////////// - -class TTCMallocLimitHandler -    : public TRefCounted -{ -public: -    explicit TTCMallocLimitHandler(TTCMallocLimitHandlerOptions options) -        : Options_(options) -    { -        Thread_ = std::thread([this] { -            Handle(); -        }); -    } - -    ~TTCMallocLimitHandler() -    { -        { -            std::unique_lock<std::mutex> lock(Mutex_); -            Fired_ = true; -            CV_.notify_all(); -        } - -        Thread_.join(); -    } - -    void Fire() -    { -        std::unique_lock<std::mutex> lock(Mutex_); -        Fired_ = true; -        NeedToHandle_ = true; -        CV_.notify_all(); -    } - -private: -    const TTCMallocLimitHandlerOptions Options_; - -    bool Fired_ = false; -    bool NeedToHandle_ = false; -    std::mutex Mutex_; -    std::condition_variable CV_; -    std::thread Thread_; - -    void Handle() -    { -        std::unique_lock<std::mutex> lock(Mutex_); -        CV_.wait(lock, [&] { -            return Fired_; -        }); - -        if (!NeedToHandle_) { -            return; -        } - -        auto timestamp = TInstant::Now().FormatLocalTime("%Y%m%dT%H%M%S"); -        auto profilePaths = New<TOomProfilePaths>(); -        auto profilePathsFile = GetProfilePaths(timestamp); -        profilePaths->HeapProfilePath = GetHeapDumpPath(timestamp); -        profilePaths->PeakProfilePath = GetPeakDumpPath(timestamp); - -        Cerr << "TTCMallocLimitHandler: Fork process to write heap profile: " -            << profilePaths->HeapProfilePath -            << " peak profile path: " << profilePaths->PeakProfilePath -            << " profiles path file: " << profilePathsFile -            << Endl; - -        SetupMemoryProfileTimeout(Options_.Timeout.Seconds()); -        auto childPid = fork(); - -        if (childPid == 0) { -            NFs::MakeDirectoryRecursive(Options_.HeapDumpDirectory); -            SetupMemoryProfileTimeout(Options_.Timeout.Seconds()); -            CollectAndDumpMemoryProfile(profilePaths->HeapProfilePath, tcmalloc::ProfileType::kHeap); -            CollectAndDumpMemoryProfile(profilePaths->PeakProfilePath, tcmalloc::ProfileType::kPeakHeap); -            DumpProfilePaths(profilePaths, profilePathsFile); - -            Cerr << "TTCMallocLimitHandler: Heap profiles are written" << Endl; -            AbortProcessSilently(EProcessExitCode::OK); -        } - -        if (childPid < 0) { -            Cerr << "TTCMallocLimitHandler: Fork failed: " << LastSystemErrorText() << Endl; -            AbortProcessSilently(EProcessExitCode::GenericError); -        } - -        ExecWaitForChild(childPid); -        AbortProcessSilently(EProcessExitCode::OK); -    } - -    auto MakeSuffixFormatter(const TString& timestamp) const -    { -        return NYT::MakeFormatterWrapper([this, ×tamp] (TStringBuilderBase* builder) { -            if (Options_.FilenameSuffix) { -                builder->AppendFormat("%v_", *Options_.FilenameSuffix); -            } -            FormatValue(builder, timestamp, "v"); -        }); -    } - -    TString GetHeapDumpPath(const TString& timestamp) const -    { -        return Format( -            "%v/heap_%v.pb.gz", -            Options_.HeapDumpDirectory, -            MakeSuffixFormatter(timestamp)); -    } - -    TString GetPeakDumpPath(const TString& timestamp) const -    { -        return Format( -            "%v/peak_%v.pb.gz", -            Options_.HeapDumpDirectory, -            MakeSuffixFormatter(timestamp)); -    } - -    TString GetProfilePaths(const TString& timestamp) const -    { -        return Format( -            "%v/oom_profile_paths_%v.yson", -            Options_.HeapDumpDirectory, -            MakeSuffixFormatter(timestamp)); -    } - -    void ExecWaitForChild(int pid) -    { -        Cerr << "TTCMallocLimitHandler: Start waiting for the child" << Endl; - -        auto command = Format("while [ -e /proc/%v ]; do sleep 1; done;", pid); -        execl("/bin/bash", "/bin/bash", "-c",  command.c_str(), (void*)nullptr); - -        Cerr << "TTCMallocLimitHandler: Failed to switch main process to dummy child waiter: " -            << LastSystemErrorText() << Endl; -    } -}; - -DEFINE_REFCOUNTED_TYPE(TTCMallocLimitHandler); - -//////////////////////////////////////////////////////////////////////////////// - -template <class TMallocExtension> -concept CSupportsLimitHandler = requires (TMallocExtension extension) -{ -    { extension.GetSoftMemoryLimitHandler() }; -}; - -template <typename TMallocExtension, typename THandler> -void SetSoftMemoryLimitHandler(THandler) -{ -    WriteToStderr("TCMalloc does not support memory limit handler\n"); -} - -template <CSupportsLimitHandler TMallocExtension, typename THandler> -void SetSoftMemoryLimitHandler(THandler handler) -{ -    TMallocExtension::SetSoftMemoryLimitHandler(handler); -} - -//////////////////////////////////////////////////////////////////////////////// - -namespace { - -YT_DEFINE_GLOBAL(TAtomicIntrusivePtr<TTCMallocLimitHandler>, LimitHandler); - -void HandleTCMallocLimit() -{ -    if (auto handler = LimitHandler().Acquire()) { -        handler->Fire(); -    } -} - -} // namespace - -void EnableTCMallocLimitHandler(TTCMallocLimitHandlerOptions options) -{ -    { -        if (LimitHandler().Acquire()) { -            return; -        } - -        TAtomicIntrusivePtr<TTCMallocLimitHandler>::TRawPtr expected = nullptr; -        LimitHandler().CompareAndSwap(expected, New<TTCMallocLimitHandler>(options)); -    } - -    SetSoftMemoryLimitHandler<tcmalloc::MallocExtension>(&HandleTCMallocLimit); -} - -void DisableTCMallocLimitHandler() -{ -    LimitHandler().Reset(); -} - -//////////////////////////////////////////////////////////////////////////////// - -} // namespace NYT diff --git a/yt/yt/library/oom/ya.make b/yt/yt/library/oom/ya.make index 5ad7018578c..f4845495d8c 100644 --- a/yt/yt/library/oom/ya.make +++ b/yt/yt/library/oom/ya.make @@ -4,13 +4,11 @@ INCLUDE(${ARCADIA_ROOT}/yt/ya_cpp.make.inc)  SRCS(      oom.cpp -    tcmalloc_memory_limit_handler.cpp  )  PEERDIR(      yt/yt/core      yt/yt/library/ytprof -    library/cpp/yt/logging  )  END() diff --git a/yt/yt/library/tcmalloc/config.cpp b/yt/yt/library/tcmalloc/config.cpp index 7629617ba2c..58e3927e5aa 100644 --- a/yt/yt/library/tcmalloc/config.cpp +++ b/yt/yt/library/tcmalloc/config.cpp @@ -16,17 +16,25 @@ void THeapSizeLimitConfig::Register(TRegistrar registrar)          .Default(false);      registrar.Parameter("dump_memory_profile_on_violation", &TThis::DumpMemoryProfileOnViolation)          .Default(false); -    registrar.Parameter("dump_memory_profile_timeout", &TThis::DumpMemoryProfileTimeout) +    registrar.Parameter("memory_profile_dump_timeout", &TThis::MemoryProfileDumpTimeout)          .Default(TDuration::Minutes(10)); -    registrar.Parameter("dump_memory_profile_path", &TThis::DumpMemoryProfilePath) +    registrar.Parameter("memory_profile_dump_path", &TThis::MemoryProfileDumpPath)          .Default(); +    registrar.Parameter("emory_profile_dump_filename_suffix", &TThis::MemoryProfileDumpFilenameSuffix) +        .Default(); + +    registrar.Postprocessor([] (THeapSizeLimitConfig* config) { +        if (config->DumpMemoryProfileOnViolation && !config->MemoryProfileDumpPath) { +            THROW_ERROR_EXCEPTION("\"memory_profile_dump_path\" must be set when \"dump_memory_profile_on_violation\" is true"); +        } +    });  }  TTCMallocConfigPtr TTCMallocConfig::ApplyDynamic(const TTCMallocConfigPtr& dynamicConfig) const  {      // TODO(babenko): fix this mess      auto mergedConfig = CloneYsonStruct(dynamicConfig); -    mergedConfig->HeapSizeLimit->DumpMemoryProfilePath = HeapSizeLimit->DumpMemoryProfilePath; +    mergedConfig->HeapSizeLimit->MemoryProfileDumpPath = HeapSizeLimit->MemoryProfileDumpPath;      return mergedConfig;  } diff --git a/yt/yt/library/tcmalloc/config.h b/yt/yt/library/tcmalloc/config.h index c2060c33bd0..a4bf5a313e8 100644 --- a/yt/yt/library/tcmalloc/config.h +++ b/yt/yt/library/tcmalloc/config.h @@ -12,21 +12,27 @@ struct THeapSizeLimitConfig      : public NYTree::TYsonStruct  {      //! Limit program memory in terms of container memory. -    // If program heap size exceeds the limit tcmalloc is instructed to release memory to the kernel. +    //! If program heap size exceeds the limit tcmalloc is instructed to release memory to the kernel.      std::optional<double> ContainerMemoryRatio;      //! Similar to #ContainerMemoryRatio, but is set in terms of absolute difference from      //! the container memory limit.      //! For example, if ContainerMemoryLimit=200Gb and ContainerMemoryMargin=1Gb -    // then tcmalloc limit will be 199Gb. -    std::optional<double> ContainerMemoryMargin; +    //! then tcmalloc limit will be 199Gb. +    std::optional<i64> ContainerMemoryMargin;      //! If true tcmalloc crashes when system allocates more memory than #ContainerMemoryRatio/#ContainerMemoryMargin.      bool Hard;      bool DumpMemoryProfileOnViolation; -    TDuration DumpMemoryProfileTimeout; -    TString DumpMemoryProfilePath; + +    TDuration MemoryProfileDumpTimeout; + +    //! Filenames are as follows: +    //! $(MemoryProfileDumpPath)/$(Name)_$(MemoryProfileDumpFilenameSuffix)_$(Timestamp).$(Ext) or +    //! $(MemoryProfileDumpPath)/$(Name)_$(Timestamp).$(Ext) (if MemoryProfileDumpFilenameSuffix is missing) +    std::optional<TString> MemoryProfileDumpPath; +    std::optional<TString> MemoryProfileDumpFilenameSuffix;      REGISTER_YSON_STRUCT(THeapSizeLimitConfig); diff --git a/yt/yt/library/tcmalloc/tcmalloc_manager.cpp b/yt/yt/library/tcmalloc/tcmalloc_manager.cpp index e50e8b59ef3..55dd203f1af 100644 --- a/yt/yt/library/tcmalloc/tcmalloc_manager.cpp +++ b/yt/yt/library/tcmalloc/tcmalloc_manager.cpp @@ -4,11 +4,20 @@  #include <yt/yt/library/profiling/resource_tracker/resource_tracker.h> -#include <yt/yt/library/oom/oom.h> +#include <yt/yt/library/ytprof/external_pprof.h> +#include <yt/yt/library/ytprof/heap_profiler.h> +#include <yt/yt/library/ytprof/profile.h>  #include <library/cpp/yt/memory/leaky_singleton.h> +#include <library/cpp/yt/memory/atomic_intrusive_ptr.h> + +#include <library/cpp/yt/system/exit.h> + +#include <yt/yt/core/misc/fs.h> +#include <yt/yt/core/misc/crash_handler.h>  #include <util/system/thread.h> +#include <util/system/shellcommand.h>  #include <tcmalloc/malloc_extension.h> @@ -19,19 +28,305 @@ namespace NYT::NTCMalloc {  //////////////////////////////////////////////////////////////////////////////// -static YT_DEFINE_GLOBAL(const NLogging::TLogger, Logger, "TCMalloc"); +namespace { + +//////////////////////////////////////////////////////////////////////////////// + +YT_DEFINE_GLOBAL(const NLogging::TLogger, Logger, "TCMalloc"); + +//////////////////////////////////////////////////////////////////////////////// + +DECLARE_REFCOUNTED_STRUCT(TOomProfileManifest) + +struct TOomProfileManifest +    : public NYTree::TYsonStruct +{ +    TString CurrentProfilePath; +    TString PeakProfilePath; + +    REGISTER_YSON_STRUCT(TOomProfileManifest); + +    static void Register(TRegistrar registrar) +    { +        // TODO(babenko): fix names, see ytserver_mail_profile_link.py +        registrar.Parameter("heap_profile_path", &TThis::CurrentProfilePath) +            .Default(); +        registrar.Parameter("peak_profile_path", &TThis::PeakProfilePath) +            .Default(); +    } +}; + +DEFINE_REFCOUNTED_TYPE(TOomProfileManifest) + +//////////////////////////////////////////////////////////////////////////////// + +TString MakeIncompletePath(const TString& path) +{ +    return NYT::Format("%v_incomplete", path); +} + +void CollectAndDumpMemoryProfile(const TString& memoryProfilePath, tcmalloc::ProfileType profileType) +{ +    auto profile = NYTProf::ReadHeapProfile(profileType); +    SymbolizeByExternalPProf(&profile, NYTProf::TSymbolizationOptions{ +        .RunTool = [] (const std::vector<TString>& args) { +            TShellCommand command{args[0], TList<TString>{args.begin() + 1, args.end()}}; +            command.Run(); +        }, +    }); + +    auto incompletePath = MakeIncompletePath(memoryProfilePath); + +    TFileOutput output(incompletePath); +    NYTProf::WriteProfile(&output, profile); +    output.Finish(); +    NFS::Rename(incompletePath, memoryProfilePath); +} + +void MemoryProfileTimeoutHandler(int /*signal*/) +{ +    AbortProcessDramatically( +        EProcessExitCode::GenericError, +        "Process hung while dumping heap profile"); +} + +void SetupMemoryProfileTimeout(int timeout) +{ +    ::signal(SIGALRM, &MemoryProfileTimeoutHandler); +    ::alarm(timeout); +} + +void DumpManifest(const TOomProfileManifestPtr& manifest, const TString& fileName) +{ +    TFileOutput output(fileName); +    NYson::TYsonWriter writer(&output, NYson::EYsonFormat::Pretty); +    Serialize(manifest, &writer); +    writer.Flush(); +    output.Finish(); +}  //////////////////////////////////////////////////////////////////////////////// -class TCMallocLimitsAdjuster +class TMemoryLimitHandler +    : public TRefCounted  {  public: -    void Adjust(const TTCMallocConfigPtr& config) +    explicit TMemoryLimitHandler(THeapSizeLimitConfigPtr config) +        : Config_(std::move(config)) +    { +        Thread_ = std::thread([this] { +            Handle(); +        }); +    } + +    ~TMemoryLimitHandler() +    { +        { +            std::unique_lock<std::mutex> lock(Mutex_); +            Fired_ = true; +            CV_.notify_all(); +        } + +        Thread_.join(); +    } + +    void Fire() +    { +        std::unique_lock<std::mutex> lock(Mutex_); +        Fired_ = true; +        NeedToHandle_ = true; +        CV_.notify_all(); +    } + +private: +    const THeapSizeLimitConfigPtr Config_; + +    bool Fired_ = false; +    bool NeedToHandle_ = false; +    std::mutex Mutex_; +    std::condition_variable CV_; +    std::thread Thread_; + +    void Handle() +    { +        std::unique_lock<std::mutex> lock(Mutex_); +        CV_.wait(lock, [&] { +            return Fired_; +        }); + +        if (!NeedToHandle_) { +            return; +        } + +        auto timestamp = TInstant::Now().FormatLocalTime("%Y%m%dT%H%M%S"); +        auto manifest = New<TOomProfileManifest>(); +        auto manifestPath = GetManifestPath(timestamp); +        manifest->CurrentProfilePath = GetCurrentDumpPath(timestamp); +        manifest->PeakProfilePath = GetPeakDumpPath(timestamp); + +        Cerr << "*** Forking process to write heap profiles" << Endl +            << "  current:  " << manifest->CurrentProfilePath << Endl +            << "  peak:     " << manifest->PeakProfilePath << Endl +            << "  manifest: " << manifestPath << Endl; + +        SetupMemoryProfileTimeout(Config_->MemoryProfileDumpTimeout.Seconds()); + +        auto childPid = fork(); +        if (childPid == 0) { +            NFS::MakeDirRecursive(*Config_->MemoryProfileDumpPath); +            SetupMemoryProfileTimeout(Config_->MemoryProfileDumpTimeout.Seconds()); +            CollectAndDumpMemoryProfile(manifest->CurrentProfilePath, tcmalloc::ProfileType::kHeap); +            CollectAndDumpMemoryProfile(manifest->PeakProfilePath, tcmalloc::ProfileType::kPeakHeap); +            DumpManifest(manifest, manifestPath); + +            Cerr << "*** Heap profiles are written" << Endl; +            AbortProcessSilently(EProcessExitCode::OK); +        } + +        if (childPid < 0) { +            Cerr << "*** Fork failed: " << LastSystemErrorText() << Endl; +            AbortProcessSilently(EProcessExitCode::GenericError); +        } + +        ExecWaitForChild(childPid); +        AbortProcessSilently(EProcessExitCode::OK); +    } + +    auto MakeSuffixFormatter(const TString& timestamp) const +    { +        return NYT::MakeFormatterWrapper([this, ×tamp] (TStringBuilderBase* builder) { +            if (Config_->MemoryProfileDumpFilenameSuffix) { +                builder->AppendFormat("%v_", *Config_->MemoryProfileDumpFilenameSuffix); +            } +            FormatValue(builder, timestamp, "v"); +        }); +    } + +    TString GetCurrentDumpPath(const TString& timestamp) const +    { +        return Format( +            "%v/current_%v.pb.gz", +            Config_->MemoryProfileDumpPath, +            MakeSuffixFormatter(timestamp)); +    } + +    TString GetPeakDumpPath(const TString& timestamp) const +    { +        return Format( +            "%v/peak_%v.pb.gz", +            Config_->MemoryProfileDumpPath, +            MakeSuffixFormatter(timestamp)); +    } + +    TString GetManifestPath(const TString& timestamp) const +    { +        return Format( +            "%v/oom_profile_paths_%v.yson", +            Config_->MemoryProfileDumpPath, +            MakeSuffixFormatter(timestamp)); +    } + +    void ExecWaitForChild(int pid) +    { +        Cerr << "*** Start waiting for the child" << Endl; + +        auto command = Format("while [ -e /proc/%v ]; do sleep 1; done;", pid); +        execl("/bin/bash", "/bin/bash", "-c",  command.c_str(), (void*)nullptr); + +        Cerr << "*** Failed to switch main process to dummy child waiter: " +            << LastSystemErrorText() << Endl; +    } +}; + +DEFINE_REFCOUNTED_TYPE(TMemoryLimitHandler); + +//////////////////////////////////////////////////////////////////////////////// + +template <class TMallocExtension> +concept CSupportsLimitHandler = requires (TMallocExtension extension) +{ +    { extension.GetSoftMemoryLimitHandler() }; +}; + +template <typename TMallocExtension, typename THandler> +void SetSoftMemoryLimitHandler(THandler) +{ +    WriteToStderr("TCMalloc does not support memory limit handler\n"); +} + +template <CSupportsLimitHandler TMallocExtension, typename THandler> +void SetSoftMemoryLimitHandler(THandler handler) +{ +    TMallocExtension::SetSoftMemoryLimitHandler(handler); +} + +//////////////////////////////////////////////////////////////////////////////// + +class TMemoryLimitHandlerInstaller +{ +public: +    static TMemoryLimitHandlerInstaller* Get() +    { +        return LeakySingleton<TMemoryLimitHandlerInstaller>(); +    } + +    void Reconfigure(const THeapSizeLimitConfigPtr& config) +    { +        if (config->DumpMemoryProfileOnViolation) { +            Enable(config); +        } else { +            Disable(); +        } +    } + +private: +    DECLARE_LEAKY_SINGLETON_FRIEND(); + +    TAtomicIntrusivePtr<TMemoryLimitHandler> LimitHandler_; + +    TMemoryLimitHandlerInstaller() +    { +        SetSoftMemoryLimitHandler<tcmalloc::MallocExtension>(&HandleTCMallocLimit); +    } + +    static void HandleTCMallocLimit() +    { +        if (auto handler = Get()->LimitHandler_.Acquire()) { +            handler->Fire(); +        } +    } + +    void Enable(const THeapSizeLimitConfigPtr& config) +    { +        if (LimitHandler_.Acquire()) { +            return; +        } + +        TAtomicIntrusivePtr<TMemoryLimitHandler>::TRawPtr expected = nullptr; +        LimitHandler_.CompareAndSwap(expected, New<TMemoryLimitHandler>(config)); +    } + +    void Disable() +    { +        LimitHandler_.Reset(); +    } +}; + +//////////////////////////////////////////////////////////////////////////////// + +class TLimitsAdjuster +{ +public: +    static TLimitsAdjuster* Get() +    { +        return LeakySingleton<TLimitsAdjuster>(); +    } + +    void Reconfigure(const TTCMallocConfigPtr& config)      {          i64 totalMemory = GetAnonymousMemoryLimit();          AdjustPageHeapLimit(totalMemory, config);          AdjustAggressiveReleaseThreshold(totalMemory, config); -        SetupMemoryLimitHandler(config);      }      i64 GetAggressiveReleaseThreshold() @@ -40,6 +335,10 @@ public:      }  private: +    DECLARE_LEAKY_SINGLETON_FRIEND(); + +    TLimitsAdjuster() = default; +      using TAllocatorMemoryLimit = tcmalloc::MallocExtension::MemoryLimit;      TAllocatorMemoryLimit AppliedLimit_; @@ -51,7 +350,7 @@ private:          auto proposed = ProposeHeapMemoryLimit(totalMemory, config);          if (proposed.limit == AppliedLimit_.limit && proposed.hard == AppliedLimit_.hard) { -            // Already applied +            // Already applied.              return;          } @@ -72,20 +371,6 @@ private:          }      } -    void SetupMemoryLimitHandler(const TTCMallocConfigPtr& config) -    { -        TTCMallocLimitHandlerOptions handlerOptions { -            .HeapDumpDirectory = config->HeapSizeLimit->DumpMemoryProfilePath, -            .Timeout = config->HeapSizeLimit->DumpMemoryProfileTimeout, -        }; - -        if (config->HeapSizeLimit->DumpMemoryProfileOnViolation) { -            EnableTCMallocLimitHandler(handlerOptions); -        } else { -            DisableTCMallocLimitHandler(); -        } -    } -      i64 GetAnonymousMemoryLimit() const      {          return NProfiling::TResourceTracker::GetAnonymousMemoryLimit(); @@ -114,6 +399,10 @@ private:  //////////////////////////////////////////////////////////////////////////////// +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +  class TTCMallocManagerImpl  {  public: @@ -141,20 +430,19 @@ public:                  std::thread([&] {                      ::TThread::SetCurrentThreadName("TCMallocBack"); -                    TCMallocLimitsAdjuster limitsAdjuster; -                      while (true) {                          auto config = Config_.Acquire(); -                        limitsAdjuster.Adjust(config); +                        TLimitsAdjuster::Get()->Reconfigure(config); +                        TMemoryLimitHandlerInstaller::Get()->Reconfigure(config->HeapSizeLimit);                          auto freeBytes = tcmalloc::MallocExtension::GetNumericProperty("tcmalloc.page_heap_free");                          YT_VERIFY(freeBytes); -                        if (static_cast<i64>(*freeBytes) > limitsAdjuster.GetAggressiveReleaseThreshold()) { +                        if (static_cast<i64>(*freeBytes) > TLimitsAdjuster::Get()->GetAggressiveReleaseThreshold()) {                              YT_LOG_DEBUG("Aggressively releasing memory (FreeBytes: %v, Threshold: %v)",                                  static_cast<i64>(*freeBytes), -                                limitsAdjuster.GetAggressiveReleaseThreshold()); +                                TLimitsAdjuster::Get()->GetAggressiveReleaseThreshold());                              tcmalloc::MallocExtension::ReleaseMemoryToSystem(config->AggressiveReleaseSize);                          } diff --git a/yt/yt/library/tcmalloc/ya.make b/yt/yt/library/tcmalloc/ya.make index e92d0fb1376..4042a089719 100644 --- a/yt/yt/library/tcmalloc/ya.make +++ b/yt/yt/library/tcmalloc/ya.make @@ -9,7 +9,7 @@ SRCS(  PEERDIR(      yt/yt/core -    yt/yt/library/oom +    yt/yt/library/ytprof      yt/yt/library/profiling/resource_tracker      contrib/libs/tcmalloc/malloc_extension  )  | 
