From 5d615c7ae1761d1511e01d0b4bea4920fda190a7 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 12 Feb 2018 18:34:31 +0200 Subject: Add default capping of stack size for all POSIX platforms, --max-stack --- build2/b-options.cxx | 20 ++++++++++++++++++++ build2/b-options.hxx | 8 ++++++++ build2/b-options.ixx | 12 ++++++++++++ build2/b.cli | 15 +++++++++++++++ build2/b.cxx | 8 +++++++- build2/scheduler.cxx | 42 +++++++++++++++++++++++++++++++++--------- build2/scheduler.hxx | 10 +++++++--- 7 files changed, 102 insertions(+), 13 deletions(-) diff --git a/build2/b-options.cxx b/build2/b-options.cxx index f3a15c3..9b4823f 100644 --- a/build2/b-options.cxx +++ b/build2/b-options.cxx @@ -582,6 +582,8 @@ namespace build2 max_jobs_specified_ (false), queue_depth_ (4), queue_depth_specified_ (false), + max_stack_ (), + max_stack_specified_ (false), serial_stop_ (), structured_result_ (), match_only_ (), @@ -727,6 +729,21 @@ namespace build2 << " the build system scheduler implementation for details." << ::std::endl; os << std::endl + << "\033[1m--max-stack\033[0m \033[4mnum\033[0m The maximum stack size in KBytes to allow for newly" << ::std::endl + << " created threads. For \033[4mpthreads\033[0m-based systems the driver" << ::std::endl + << " queries the stack size of the main thread and uses the" << ::std::endl + << " same size for creating additional threads. This allows" << ::std::endl + << " adjusting the stack size using familiar mechanisms, such" << ::std::endl + << " as \033[1mulimit\033[0m. Sometimes, however, the stack size of the main" << ::std::endl + << " thread is excessively large. As a result, the driver" << ::std::endl + << " checks if it is greater than a predefined limit (64MB on" << ::std::endl + << " 64-bit systems and 32MB on 32-bit ones) and caps it to a" << ::std::endl + << " more sensible value (8MB) if that's the case. This option" << ::std::endl + << " allows you to override this check with the special zero" << ::std::endl + << " value indicating that the main thread stack size should be" << ::std::endl + << " used as is." << ::std::endl; + + os << std::endl << "\033[1m--serial-stop\033[0m|\033[1m-s\033[0m Run serially and stop at the first error. This mode is" << ::std::endl << " useful to investigate build failures that are caused by" << ::std::endl << " build system errors rather than compilation errors. Note" << ::std::endl @@ -858,6 +875,9 @@ namespace build2 _cli_options_map_["-Q"] = &::build2::cl::thunk< options, size_t, &options::queue_depth_, &options::queue_depth_specified_ >; + _cli_options_map_["--max-stack"] = + &::build2::cl::thunk< options, size_t, &options::max_stack_, + &options::max_stack_specified_ >; _cli_options_map_["--serial-stop"] = &::build2::cl::thunk< options, bool, &options::serial_stop_ >; _cli_options_map_["-s"] = diff --git a/build2/b-options.hxx b/build2/b-options.hxx index b6edae8..12a029c 100644 --- a/build2/b-options.hxx +++ b/build2/b-options.hxx @@ -434,6 +434,12 @@ namespace build2 bool queue_depth_specified () const; + const size_t& + max_stack () const; + + bool + max_stack_specified () const; + const bool& serial_stop () const; @@ -517,6 +523,8 @@ namespace build2 bool max_jobs_specified_; size_t queue_depth_; bool queue_depth_specified_; + size_t max_stack_; + bool max_stack_specified_; bool serial_stop_; bool structured_result_; bool match_only_; diff --git a/build2/b-options.ixx b/build2/b-options.ixx index ef304d3..7a6f33e 100644 --- a/build2/b-options.ixx +++ b/build2/b-options.ixx @@ -294,6 +294,18 @@ namespace build2 return this->queue_depth_specified_; } + inline const size_t& options:: + max_stack () const + { + return this->max_stack_; + } + + inline bool options:: + max_stack_specified () const + { + return this->max_stack_specified_; + } + inline const bool& options:: serial_stop () const { diff --git a/build2/b.cli b/build2/b.cli index e7f06cb..6114763 100644 --- a/build2/b.cli +++ b/build2/b.cli @@ -435,6 +435,21 @@ namespace build2 details." } + size_t --max-stack + { + "", + "The maximum stack size in KBytes to allow for newly created threads. + For \i{pthreads}-based systems the driver queries the stack size of + the main thread and uses the same size for creating additional threads. + This allows adjusting the stack size using familiar mechanisms, such + as \cb{ulimit}. Sometimes, however, the stack size of the main thread + is excessively large. As a result, the driver checks if it is greater + than a predefined limit (64MB on 64-bit systems and 32MB on 32-bit + ones) and caps it to a more sensible value (8MB) if that's the case. + This option allows you to override this check with the special zero + value indicating that the main thread stack size should be used as is." + } + bool --serial-stop|-s { "Run serially and stop at the first error. This mode is useful to diff --git a/build2/b.cxx b/build2/b.cxx index b0a9abf..945d375 100644 --- a/build2/b.cxx +++ b/build2/b.cxx @@ -451,7 +451,13 @@ main (int argc, char* argv[]) fail << "invalid --max-jobs|-J value"; } - sched.startup (jobs, 1, max_jobs, jobs * ops.queue_depth ()); + sched.startup (jobs, + 1, + max_jobs, + jobs * ops.queue_depth (), + (ops.max_stack_specified () + ? optional (ops.max_stack () * 1024) + : nullopt)); variable_cache_mutex_shard_size = sched.shard_size (); variable_cache_mutex_shard.reset ( diff --git a/build2/scheduler.cxx b/build2/scheduler.cxx index e6a0bc6..79951b6 100644 --- a/build2/scheduler.cxx +++ b/build2/scheduler.cxx @@ -13,6 +13,8 @@ #include +#include + using namespace std; namespace build2 @@ -257,13 +259,16 @@ namespace build2 startup (size_t max_active, size_t init_active, size_t max_threads, - size_t queue_depth) + size_t queue_depth, + optional max_stack) { // Lock the mutex to make sure our changes are visible in (other) active // threads. // lock l (mutex_); + max_stack_ = max_stack; + // Use 8x max_active on 32-bit and 32x max_active on 64-bit. Unless we // were asked to run serially. // @@ -475,11 +480,16 @@ namespace build2 // MinGW : 2048 / 2048 // VC : 1024 / 1024 // - // We will make sure that the new thread stack size is the same as for the - // main thread. For FreeBSD we will also cap it at 8MB. + // Provided the main thread size is less-equal than BUILD2_SANE_STACK_SIZE + // (default: sizeof(void*) * BUILD2_DEFAULT_STACK_SIZE), we make sure that + // the new thread stack is the same as for the main thread. Otherwise, we + // cap it at BUILD2_DEFAULT_STACK_SIZE (default: 8MB). This can also be + // overridden at runtime with the --max-stack option (remember to update + // its documentation of changing anything here). // // On Windows the stack size is the same for all threads and is customized - // at the linking stage (see build2/buildfile). + // at the linking stage (see build2/buildfile). Thus neither *_STACK_SIZE + // nor --max-stack have any effect here. // // On Linux, FreeBSD and MacOS there is no way to change it once and for // all newly created threads. Thus we will use pthreads, creating threads @@ -488,6 +498,15 @@ namespace build2 // created by the main thread). // #if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__) + +#ifndef BUILD2_DEFAULT_STACK_SIZE +# define BUILD2_DEFAULT_STACK_SIZE 8388608 // 8MB +#endif + +#ifndef BUILD2_SANE_STACK_SIZE +# define BUILD2_SANE_STACK_SIZE (sizeof(void*) * BUILD2_DEFAULT_STACK_SIZE) +#endif + // Auto-deleter. // struct attr_deleter @@ -543,16 +562,21 @@ namespace build2 if (r != 0) throw_system_error (r); - // Cap at 8MB. - // - if (stack_size > 8388608) - stack_size = 8388608; - #else // defined(__APPLE__) stack_size = pthread_get_stacksize_np (pthread_self ()); #endif } + // Cap the size if necessary. + // + if (max_stack_) + { + if (*max_stack_ != 0 && stack_size > *max_stack_) + stack_size = *max_stack_; + } + else if (stack_size > BUILD2_SANE_STACK_SIZE) + stack_size = BUILD2_DEFAULT_STACK_SIZE; + pthread_attr_t attr; int r (pthread_attr_init (&attr)); diff --git a/build2/scheduler.hxx b/build2/scheduler.hxx index 4880392..d90a26f 100644 --- a/build2/scheduler.hxx +++ b/build2/scheduler.hxx @@ -164,9 +164,10 @@ namespace build2 scheduler (size_t max_active, size_t init_active = 1, size_t max_threads = 0, - size_t queue_depth = 0) + size_t queue_depth = 0, + optional max_stack = nullopt) { - startup (max_active, init_active, max_threads, queue_depth); + startup (max_active, init_active, max_threads, queue_depth, max_stack); } // Start the scheduler. @@ -175,7 +176,8 @@ namespace build2 startup (size_t max_active, size_t init_active = 1, size_t max_threads = 0, - size_t queue_depth = 0); + size_t queue_depth = 0, + optional max_stack = nullopt); // Return true if the scheduler was started up. // @@ -386,6 +388,8 @@ namespace build2 std::mutex mutex_; bool shutdown_ = true; // Shutdown flag. + optional max_stack_; + // The constraints that we must maintain: // // active <= max_active -- cgit v1.1