Files
OrcaSlicer/src/libslic3r/Thread.hpp
Noisyfox f3cb1992d6 fix(threads): bump worker thread stack to 16MB to survive CGAL emboss (#13772)
The Emboss text-cut workflow can crash with SIGBUS at a stack-guard page
on macOS (and equivalent on Linux) when CGAL's
Polygon_mesh_processing::corefine falls back from filtered interval
arithmetic (Epick) to exact rational arithmetic (Epeck / mpq_class).

On near-degenerate inputs -- coplanar triangles in the projection
footprint, very thin font stems, sharp edges or seams under the text --
CGAL's Filtered_predicate_with_state cascade ends up inside
Triangulation_2<Projection_traits_3<Epeck>>::march_locate_2D, whose
recursive walk plus mpq_class arithmetic frames overflows the worker's
4MB default stack. The fault address sits exactly inside the next
thread's guard page, which is the textbook macOS signature.

Crash trace (BambuStudio v02.07.00.55, macOS 26.4.1 arm64, embossing
text into a model):

  __gmpn_mul_1
  __gmpz_mul / __gmpq_mul
  CGAL::determinant<mpq_class>
  Projected_orientation_with_normal_3
  Filtered_predicate_with_state::operator()
  Triangulation_2<...>::orientation
  Triangulation_2<...>::march_locate_2D
  Surface_intersection_visitor::triangulate_intersected_faces
  Polygon_mesh_processing::corefine
  Slic3r::cut_surface
  Emboss::cut_surface_to_its
  Emboss::GenerateTextJob::get_text_mesh
  PlaterWorker::PlaterJob::process

The thread's stack region in the report was exactly 4128K -- the
default 4MB plus a small TLS overhead -- and the faulting address hit
the adjacent guard page. We have one observed reproducer; the 16 MB
value is chosen as 4x defensive headroom over that, not as a measured
upper bound. Future heavier emboss inputs may need more.

Cumulative cost on a 64-bit target. Slic3r::create_thread has 22
callsites across the codebase. Realistic peak concurrent live count is
on the order of 10-15 workers (Plater UI worker, slicing process, FDM-
support gizmo, STEP loader, network sync helpers, per-task sender
threads in TaskManager up to MaxSendingAtSameTime, per-machine info
threads in device-list dialogs, long-lived sync helpers in GUI_App).
At 16 MB reserve x ~15 = ~240 MB of address-space commitment in the
worst case, which is bounded on any 64-bit target.

Resident memory remains proportional to actual stack depth on all three
platforms: macOS / Linux mmap the thread stack and defer-commit pages on
touch, and Boost.Thread on Win32 passes STACK_SIZE_PARAM_IS_A_RESERVATION
to _beginthreadex (verified at libs/thread/src/win32/thread.cpp), so on
Windows the bumped value is the reserve, not the initial commit.

The 32-bit branch of the previous (sizeof(void*) == 4) ternary is
removed: BambuStudio doesn't ship a 32-bit build today, and the literal
makes the value easier to read at the callsite.

(cherry picked from commit e150b502b3d2afc98b83dcc9e5720e998f9eb79a)

Co-authored-by: Abdel Gomez-Perez <nabdel07@icloud.com>
2026-05-21 16:24:54 +08:00

78 lines
3.4 KiB
C++

#ifndef GUI_THREAD_HPP
#define GUI_THREAD_HPP
#include <utility>
#include <string>
#include <thread>
#include <boost/thread.hpp>
namespace Slic3r {
// Set / get thread name.
// Returns false if the API is not supported.
//
// It is a good idea to name the main thread before spawning children threads, because dynamic linking is used on Windows 10
// to initialize Get/SetThreadDescription functions, which is not thread safe.
//
// pthread_setname_np supports maximum 15 character thread names! (16th character is the null terminator)
//
// Methods taking the thread as an argument are not supported by OSX.
// Naming threads is only supported on newer Windows 10.
bool set_thread_name(std::thread &thread, const char *thread_name);
inline bool set_thread_name(std::thread &thread, const std::string &thread_name) { return set_thread_name(thread, thread_name.c_str()); }
bool set_thread_name(boost::thread &thread, const char *thread_name);
inline bool set_thread_name(boost::thread &thread, const std::string &thread_name) { return set_thread_name(thread, thread_name.c_str()); }
bool set_current_thread_name(const char *thread_name);
inline bool set_current_thread_name(const std::string &thread_name) { return set_current_thread_name(thread_name.c_str()); }
// To be called at the start of the application to save the current thread ID as the main (UI) thread ID.
void save_main_thread_id();
// Retrieve the cached main (UI) thread ID.
boost::thread::id get_main_thread_id();
// Checks whether the main (UI) thread is active.
bool is_main_thread_active();
// Returns nullopt if not supported.
// Not supported by OSX.
// Naming threads is only supported on newer Windows 10.
std::optional<std::string> get_current_thread_name();
// To be called somewhere before the TBB threads are spinned for the first time, to
// give them names recognizible in the debugger.
// Also it sets locale of the worker threads to "C" for the G-code generator to produce "." as a decimal separator.
void name_tbb_thread_pool_threads_set_locale();
template<class Fn>
inline boost::thread create_thread(boost::thread::attributes &attrs, Fn &&fn)
{
// Stack size for our worker threads. Originally duplicated TBB's pool
// default (4 MB), but the Emboss text-cut path calls into CGAL's
// Polygon_mesh_processing::corefine, which falls back from filtered
// interval arithmetic to exact rational arithmetic (mpq_class) on
// near-degenerate input, and the constrained 2D triangulation walker
// (Triangulation_2::march_locate_2D) can recurse deeply enough to
// exceed 4 MB on real models -- producing a SIGBUS at the next thread's
// stack guard page on macOS / Linux.
//
// 16 MB chosen as 4x defensive headroom over the observed crash
// threshold (n=1 reproducer at exactly 4 MB on macOS arm64). All three
// platforms defer-commit reserved stack pages: macOS / Linux mmap the
// stack and only fault in pages on touch; Boost.Thread on Win32 passes
// STACK_SIZE_PARAM_IS_A_RESERVATION to _beginthreadex, so the value is
// a reserve, not the initial commit. Resident memory therefore stays
// proportional to actual stack depth on every target.
attrs.set_stack_size(16 * 1024 * 1024);
return boost::thread{attrs, std::forward<Fn>(fn)};
}
template<class Fn> inline boost::thread create_thread(Fn &&fn)
{
boost::thread::attributes attrs;
return create_thread(attrs, std::forward<Fn>(fn));
}
}
#endif // GUI_THREAD_HPP