diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index aef83f21f..1f7eaba24 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -35,6 +35,8 @@ #include "SVG.hpp" #include +#include +#include // Intel redesigned some TBB interface considerably when merging TBB with their oneAPI set of libraries, see GH #7332. // We are using quite an old TBB 2017 U7. Before we update our build servers, let's use the old API, which is deprecated in up to date TBB. @@ -1488,6 +1490,32 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato print.throw_if_canceled(); } +// For unknown reasons and in sporadic cases when GCode export is processing, some participating thread +// in tbb::parallel_pipeline has not set locales to "C", probably because this thread is newly spawned. +// So in this class method on_scheduler_entry is called for every thread before it starts participating +// in tbb::parallel_pipeline to ensure that locales are set correctly + +// For tbb::parallel_pipeline, it seems that on_scheduler_entry is called for every layer and every filter. +// We ensure using thread-local storage that locales will be set to "C" just once for any participating thread. +class TBBLocalesSetter : public tbb::task_scheduler_observer +{ +public: + TBBLocalesSetter() { this->observe(true); } + ~TBBLocalesSetter() override = default; + + void on_scheduler_entry(bool is_worker) override + { + if (bool &is_locales_sets = m_is_locales_sets.local(); !is_locales_sets) { + // Set locales of the worker thread to "C". + set_c_locales(); + is_locales_sets = true; + } + } + +private: + tbb::enumerable_thread_specific, tbb::ets_key_usage_type::ets_key_per_instance> m_is_locales_sets; +}; + // Process all layers of all objects (non-sequential mode) with a parallel pipeline: // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser // and export G-code into file. @@ -1531,6 +1559,10 @@ void GCode::process_layers( [&output_stream](std::string s) { output_stream.write(s); } ); + // It registers a handler that sets locales to "C" before any TBB thread starts participating in tbb::parallel_pipeline. + // Handler is unregistered when the destructor is called. + TBBLocalesSetter locales_setter; + // The pipeline elements are joined using const references, thus no copying is performed. output_stream.find_replace_supress(); if (m_spiral_vase && m_find_replace) @@ -1584,6 +1616,10 @@ void GCode::process_layers( [&output_stream](std::string s) { output_stream.write(s); } ); + // It registers a handler that sets locales to "C" before any TBB thread starts participating in tbb::parallel_pipeline. + // Handler is unregistered when the destructor is called. + TBBLocalesSetter locales_setter; + // The pipeline elements are joined using const references, thus no copying is performed. output_stream.find_replace_supress(); if (m_spiral_vase && m_find_replace) diff --git a/src/libslic3r/LocalesUtils.hpp b/src/libslic3r/LocalesUtils.hpp index f63c3572f..ce8030ceb 100644 --- a/src/libslic3r/LocalesUtils.hpp +++ b/src/libslic3r/LocalesUtils.hpp @@ -43,6 +43,25 @@ std::string float_to_string_decimal_point(double value, int precision = -1); //std::string float_to_string_decimal_point(float value, int precision = -1); double string_to_double_decimal_point(const std::string_view str, size_t* pos = nullptr); +// Set locales to "C". +inline void set_c_locales() +{ +#ifdef _WIN32 + _configthreadlocale(_ENABLE_PER_THREAD_LOCALE); + std::setlocale(LC_ALL, "C"); +#else + // We are leaking some memory here, because the newlocale() produced memory will never be released. + // This is not a problem though, as there will be a maximum one worker thread created per physical thread. + uselocale(newlocale( +#ifdef __APPLE__ + LC_ALL_MASK +#else // some Unix / Linux / BSD + LC_ALL +#endif + , "C", nullptr)); +#endif +} + } // namespace Slic3r #endif // slic3r_LocalesUtils_hpp_ diff --git a/src/libslic3r/Thread.cpp b/src/libslic3r/Thread.cpp index 4e7bd073a..c099f8de6 100644 --- a/src/libslic3r/Thread.cpp +++ b/src/libslic3r/Thread.cpp @@ -15,6 +15,7 @@ #include "Thread.hpp" #include "Utils.hpp" +#include "LocalesUtils.hpp" namespace Slic3r { @@ -234,21 +235,8 @@ void name_tbb_thread_pool_threads_set_locale() std::ostringstream name; name << "slic3r_tbb_" << range.begin(); set_current_thread_name(name.str().c_str()); - // Set locales of the worker thread to "C". -#ifdef _WIN32 - _configthreadlocale(_ENABLE_PER_THREAD_LOCALE); - std::setlocale(LC_ALL, "C"); -#else - // We are leaking some memory here, because the newlocale() produced memory will never be released. - // This is not a problem though, as there will be a maximum one worker thread created per physical thread. - uselocale(newlocale( -#ifdef __APPLE__ - LC_ALL_MASK -#else // some Unix / Linux / BSD - LC_ALL -#endif - , "C", nullptr)); -#endif + // Set locales of the worker thread to "C". + set_c_locales(); } }); }