From 93dbdacafb07b674467aa30c4aefd38bb3871601 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 26 Jan 2017 16:01:58 +0200 Subject: Add scheduling calls to operation's match() --- build2/b.cxx | 24 +++----- build2/config/operation.cxx | 31 ++++------ build2/context | 88 ++++++++++++++++++++++---- build2/context.cxx | 4 +- build2/dist/operation.cxx | 102 ++++++++++++++++--------------- build2/operation | 24 +++----- build2/operation.cxx | 54 ++++++---------- build2/scheduler | 42 ++++++++++--- build2/scheduler.cxx | 26 +++++++- build2/scheduler.txx | 8 +-- build2/variable | 45 +++++++++----- unit-tests/function/buildfile | 2 +- unit-tests/function/driver.cxx | 4 +- unit-tests/test/script/parser/driver.cxx | 11 ++-- 14 files changed, 278 insertions(+), 187 deletions(-) diff --git a/build2/b.cxx b/build2/b.cxx index 54defb3..6f52a0e 100644 --- a/build2/b.cxx +++ b/build2/b.cxx @@ -96,8 +96,6 @@ main (int argc, char* argv[]) << system_error (errno, system_category ()); // Sanitize. #endif - ulock ml (model); - // Parse the command line. We want to be able to specify options, vars, // and buildspecs in any order (it is really handy to just add -v at the // end of the command line). @@ -364,7 +362,7 @@ main (int argc, char* argv[]) // if (dirty) { - var_ovs = reset (ml, cmd_vars); + var_ovs = reset (cmd_vars); dirty = false; } @@ -975,7 +973,7 @@ main (int argc, char* argv[]) // Load the buildfile. // - mif->load (ml, rs, ts.buildfile, ts.out_base, ts.src_base, l); + mif->load (rs, ts.buildfile, ts.out_base, ts.src_base, l); // Next search and match the targets. We don't want to start // building before we know how to for all the targets in this @@ -1009,11 +1007,7 @@ main (int argc, char* argv[]) ? out_src (d, rs) : dir_path ()); - mif->search (ml, - rs, - target_key {ti, &d, &out, &tn.value, e}, - l, - tgs); + mif->search (rs, target_key {ti, &d, &out, &tn.value, e}, l, tgs); } } // target @@ -1032,8 +1026,8 @@ main (int argc, char* argv[]) action a (mid, pre_oid, oid); - mif->match (ml, a, tgs); - mif->execute (ml, a, tgs, true); // Run quiet. + mif->match (a, tgs); + mif->execute (a, tgs, true); // Run quiet. if (mif->operation_post != nullptr) mif->operation_post (pre_oid); @@ -1047,8 +1041,8 @@ main (int argc, char* argv[]) action a (mid, oid, 0); - if (mif->match != nullptr) mif->match (ml, a, tgs); - if (mif->execute != nullptr) mif->execute (ml, a, tgs, verb == 0); + if (mif->match != nullptr) mif->match (a, tgs); + if (mif->execute != nullptr) mif->execute (a, tgs, verb == 0); if (post_oid != 0) { @@ -1063,8 +1057,8 @@ main (int argc, char* argv[]) action a (mid, post_oid, oid); - mif->match (ml, a, tgs); - mif->execute (ml, a, tgs, true); // Run quiet. + mif->match (a, tgs); + mif->execute (a, tgs, true); // Run quiet. if (mif->operation_post != nullptr) mif->operation_post (post_oid); diff --git a/build2/config/operation.cxx b/build2/config/operation.cxx index 175338a..5339ef9 100644 --- a/build2/config/operation.cxx +++ b/build2/config/operation.cxx @@ -345,13 +345,13 @@ namespace build2 } static void - configure_match (ulock&, action, action_targets&) + configure_match (action, action_targets&) { // Don't match anything -- see execute (). } static void - configure_execute (ulock& ml, action a, const action_targets& ts, bool) + configure_execute (action a, const action_targets& ts, bool) { // Match rules to configure every operation supported by each // project. Note that we are not calling operation_pre/post() @@ -360,9 +360,13 @@ namespace build2 // set projects; - // Relock for shared access. + // Note that we cannot do this in parallel. We cannot parallelize the + // outer loop because we should match for a single action at a time. + // And we cannot swap the loops because the list of operations is + // target-specific. However, inside match(), things can proceed in + // parallel. // - ml.unlock (); + model_slock ml; for (void* v: ts) { @@ -372,9 +376,6 @@ namespace build2 if (rs == nullptr) fail << "out of project target " << t; - slock sl (*ml.mutex ()); - model_lock = &sl; // @@ Guard? - for (operations::size_type id (default_id + 1); // Skip default_id id < rs->operations.size (); ++id) @@ -386,15 +387,11 @@ namespace build2 set_current_oif (*oif); dependency_count = 0; - match (sl, action (configure_id, id), t); + match (ml, action (configure_id, id), t); } - model_lock = nullptr; - configure_project (a, *rs, projects); } - - ml.lock (); } const meta_operation_info configure { @@ -425,8 +422,7 @@ namespace build2 } static void - disfigure_load (ulock&, - scope&, + disfigure_load (scope&, const path& bf, const dir_path&, const dir_path&, @@ -437,8 +433,7 @@ namespace build2 } static void - disfigure_search (ulock&, - scope& root, + disfigure_search (scope& root, const target_key&, const location&, action_targets& ts) @@ -449,7 +444,7 @@ namespace build2 } static void - disfigure_match (ulock&, action, action_targets&) + disfigure_match (action, action_targets&) { } @@ -565,7 +560,7 @@ namespace build2 } static void - disfigure_execute (ulock&, action a, const action_targets& ts, bool quiet) + disfigure_execute (action a, const action_targets& ts, bool quiet) { tracer trace ("disfigure_execute"); diff --git a/build2/context b/build2/context index 33b99a0..9f1e956 100644 --- a/build2/context +++ b/build2/context @@ -28,6 +28,80 @@ namespace build2 #endif slock* model_lock; + // A shared model lock. If there is already an instance of model_slock in + // this thread, then the new instance simply references it (asserting that + // it is locked). + // + // The reason for this semantics is to support the following scheduling + // pattern: + // + // scheduler::atomic_count task_count (0); + // + // { + // model_slock ml; // (1) + // + // for (...) + // { + // sched.async (task_count, + // [] (...) + // { + // model_slock ml; // (2) + // ... + // }, + // ...); + // } + // } + // + // sched.wait (); // (3) + // + // Here is what's going on here: + // + // 1. We first get a shared lock "for ourselves" since after the first + // iteration of the loop, things may become asynchronous (including + // attempts to relock for exclusive access and change the structure we + // are iteration upon). + // + // 2. The task can be queued or it can be executed synchronously inside + // async() (refer to the scheduler class for details on this semantics). + // + // If this is an async()-synchronous execution, then the task will create + // a referencing model_slock. If, however, this is a queued execution + // (including wait()-synchronous), then the task will create a top-level + // model_slock. + // + // Note that we only acquire the lock once the task starts executing + // (there is no reason to hold the lock while the task is sitting in the + // queue). This optimization assumes that whatever else we pass to the + // task (for example, a reference to a target) is immutable (so such a + // reference cannot become invalid). + // + // 3. Before calling wait(), we release our shared lock to allow re-locking + // for exclusive access. And once wait() returns we are again running + // serially. + // + struct model_slock + { + model_slock () + { + if (slock* l = model_lock) + assert (l->owns_lock ()); + else + model_lock = &(l_ = slock (model)); + } + + ~model_slock () + { + if (&l_ == model_lock) + model_lock = nullptr; + } + + operator slock& () {return *model_lock;} + operator const slock& () const {return *model_lock;} + + private: + slock l_; + }; + // Cached variables. // extern const variable* var_src_root; @@ -80,18 +154,6 @@ namespace build2 // extern uint64_t dependency_count; - // Project-wide (as opposed to global) variable overrides. Returned by - // reset(). - // - struct variable_override - { - const variable& var; // Original variable. - const variable& ovr; // Override variable. - value val; - }; - - using variable_overrides = vector; - // Variable override value cache. // extern variable_override_cache var_override_cache; @@ -100,7 +162,7 @@ namespace build2 // scopes, and variables. // variable_overrides - reset (const ulock&, const strings& cmd_vars); + reset (const strings& cmd_vars); // Return the project name or empty string if unnamed. // diff --git a/build2/context.cxx b/build2/context.cxx index 1cf3dd9..e3f728e 100644 --- a/build2/context.cxx +++ b/build2/context.cxx @@ -57,7 +57,7 @@ namespace build2 variable_override_cache var_override_cache; variable_overrides - reset (const ulock& ml, const strings& cmd_vars) + reset (const strings& cmd_vars) { tracer trace ("reset"); @@ -66,7 +66,7 @@ namespace build2 l6 ([&]{trace << "resetting build state";}); - auto& vp (var_pool.rw (ml)); + auto& vp (variable_pool::instance); variable_overrides vos; diff --git a/build2/dist/operation.cxx b/build2/dist/operation.cxx index 83a5b01..06df0be 100644 --- a/build2/dist/operation.cxx +++ b/build2/dist/operation.cxx @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -20,21 +21,6 @@ namespace build2 { namespace dist { - static operation_id - dist_operation_pre (operation_id o) - { - if (o != default_id) - fail << "explicit operation specified for dist meta-operation"; - - return o; - } - - static void - dist_match (ulock&, action, action_targets&) - { - // Don't match anything -- see execute (). - } - // install -d // static void @@ -53,8 +39,23 @@ namespace build2 const dir_path& dir, const string& ext); + static operation_id + dist_operation_pre (operation_id o) + { + if (o != default_id) + fail << "explicit operation specified for dist meta-operation"; + + return o; + } + + static void + dist_match (action, action_targets&) + { + // Don't match anything -- see execute (). + } + static void - dist_execute (ulock& ml, action, const action_targets& ts, bool) + dist_execute (action, const action_targets& ts, bool) { tracer trace ("dist_execute"); @@ -97,57 +98,60 @@ namespace build2 const string& dist_package (cast (l)); const process_path& dist_cmd (cast (rs->vars["dist.cmd"])); - // Relock for shared access. @@ BOGUS - // - ml.unlock (); - - // Get the list of operations supported by this project. Skip + // Match a rule for every operation supported by this project. Skip // default_id. // - for (operations::size_type id (default_id + 1); - id < rs->operations.size (); - ++id) + auto match = [&trace, &rs, &ts] (const operation_info& o) { - const operation_info* oif (rs->operations[id]); - if (oif == nullptr) - continue; - // Note that we are not calling operation_pre/post() callbacks // here since the meta operation is dist and we know what we // are doing. // - set_current_oif (*oif); + set_current_oif (o); dependency_count = 0; - action a (dist_id, id); + action a (dist_id, o.id); if (verb >= 6) dump (a); - slock sl (*ml.mutex ()); - model_lock = &sl; // @@ Guard? - - for (void* v: ts) + scheduler::atomic_count task_count (0); { - target& t (*static_cast (v)); + model_slock ml; - if (rs != t.base_scope ().root_scope ()) - fail << "target " << t << " is from a different project" << - info << "one dist() meta-operation can handle one project" << - info << "consider using several dist() meta-operations"; - - l5 ([&]{trace << diag_doing (a, t);}); - - match (sl, a, t); + for (void* v: ts) + { + target& t (*static_cast (v)); + + if (rs != t.base_scope ().root_scope ()) + fail << "target " << t << " is from a different project" << + info << "one dist() meta-operation can handle one project" << + info << "consider using several dist() meta-operations"; + + l5 ([&]{trace << diag_doing (a, t);}); + + sched.async (task_count, + [a] (target& t) + { + model_slock ml; + build2::match (ml, a, t); // @@ MT exception. + }, + ref (t)); + } } - - model_lock = nullptr; + sched.wait (task_count); if (verb >= 6) dump (a); - } + }; - ml.lock (); + for (operations::size_type id (default_id + 1); + id < rs->operations.size (); + ++id) + { + if (const operation_info* oif = rs->operations[id]) + match (*oif); + } // Add buildfiles that are not normally loaded as part of the // project, for example, the export stub. They will still be @@ -258,8 +262,8 @@ namespace build2 action a (perform_id, update_id); - perform.match (ml, a, files); - perform.execute (ml, a, files, true); // Run quiet. + perform.match (a, files); + perform.execute (a, files, true); // Run quiet. if (perform.operation_post != nullptr) perform.operation_post (update_id); diff --git a/build2/operation b/build2/operation index d52c748..22bb8a7 100644 --- a/build2/operation +++ b/build2/operation @@ -110,7 +110,7 @@ namespace build2 // something here remember to update the man page. // const operation_id default_id = 1; // Shall be first. - const operation_id update_id = 2; + const operation_id update_id = 2; // Shall be second. const operation_id clean_id = 3; const operation_id test_id = 4; const operation_id install_id = 5; @@ -198,25 +198,20 @@ namespace build2 // Meta-operation-specific logic to load the buildfile, search and match // the targets, and execute the action on the targets. // - // Note that the model lock is passed locked and is expected to also be - // locked on return (but it can be released and re-acquired inside). - // - void (*load) (ulock&, - scope& root, + void (*load) (scope& root, const path& buildfile, const dir_path& out_base, const dir_path& src_base, const location&); - void (*search) (ulock&, - scope& root, + void (*search) (scope& root, const target_key&, const location&, action_targets&); - void (*match) (ulock&, action, action_targets&); + void (*match) (action, action_targets&); - void (*execute) (ulock&, action, const action_targets&, bool quiet); + void (*execute) (action, const action_targets&, bool quiet); void (*operation_post) (operation_id); // End of operation batch. void (*meta_operation_post) (); // End of meta-operation batch. @@ -234,8 +229,7 @@ namespace build2 // scope. // void - load (ulock&, - scope& root, + load (scope& root, const path& buildfile, const dir_path& out_base, const dir_path& src_base, @@ -245,17 +239,17 @@ namespace build2 // that does just that and adds a pointer to the target to the list. // void - search (ulock&, scope&, const target_key&, const location&, action_targets&); + search (scope&, const target_key&, const location&, action_targets&); void - match (ulock&, action, action_targets&); + match (action, action_targets&); // Execute the action on the list of targets. This is the default // implementation that does just that while issuing appropriate // diagnostics (unless quiet). // void - execute (ulock&, action, const action_targets&, bool quiet); + execute (action, const action_targets&, bool quiet); extern const meta_operation_info noop; extern const meta_operation_info perform; diff --git a/build2/operation.cxx b/build2/operation.cxx index 3871970..0163e04 100644 --- a/build2/operation.cxx +++ b/build2/operation.cxx @@ -4,12 +4,13 @@ #include +#include +#include #include #include -#include #include +#include #include -#include using namespace std; using namespace butl; @@ -44,8 +45,7 @@ namespace build2 // perform // void - load (ulock&, - scope& root, + load (scope& root, const path& bf, const dir_path& out_base, const dir_path& src_base, @@ -67,8 +67,7 @@ namespace build2 } void - search (ulock&, - scope&, + search (scope&, const target_key& tk, const location& l, action_targets& ts) @@ -83,38 +82,39 @@ namespace build2 } void - match (ulock& ml, action a, action_targets& ts) + match (action a, action_targets& ts) { tracer trace ("match"); if (verb >= 6) dump (a); - // Relock for shared access. - // - ml.unlock (); - + scheduler::atomic_count task_count (0); { + model_slock ml; + for (void* vt: ts) { target& t (*static_cast (vt)); l5 ([&]{trace << "matching " << t;}); - slock sl (*ml.mutex ()); - model_lock = &sl; // @@ Guard? - match (sl, a, t); - model_lock = nullptr; + sched.async (task_count, + [a] (target& t) + { + model_slock ml; + match (ml, a, t); // @@ MT exception handling. + }, + ref (t)); } } - - ml.lock (); + sched.wait (task_count); if (verb >= 6) dump (a); } void - execute (ulock& ml, action a, const action_targets& ts, bool quiet) + execute (action a, const action_targets& ts, bool quiet) { tracer trace ("execute"); @@ -123,21 +123,13 @@ namespace build2 // vector> psp; - auto body = [&ml, a, quiet, &psp, &trace] (void* v) + auto body = [a, quiet, &psp, &trace] (void* v) { target& t (*static_cast (v)); l5 ([&]{trace << diag_doing (a, t);}); - target_state ts; - { - slock sl (*ml.mutex ()); - model_lock = &sl; // @@ Guard? - ts = execute (a, t); - model_lock = nullptr; - } - - switch (ts) + switch (execute (a, t)) { case target_state::unchanged: { @@ -157,17 +149,11 @@ namespace build2 } }; - // Relock for shared access. - // - ml.unlock (); - if (current_mode == execution_mode::first) for (void* v: ts) body (v); else for (void* v: reverse_iterate (ts)) body (v); - ml.lock (); - // We should have executed every target that we matched. // assert (dependency_count == 0); diff --git a/build2/scheduler b/build2/scheduler index 8416940..563ec31 100644 --- a/build2/scheduler +++ b/build2/scheduler @@ -25,15 +25,16 @@ namespace build2 // tests). To acomplish this, the master, via a call to async(), can ask the // scheduler to run a task in another thread (called "helper"). If a helper // is available, then the task is executed asynchronously by such helper. - // Otherwise, the task is executed synchronously as part of the async() - // call. Once the master thread has scheduled all the tasks, it calls wait() - // to await for their completion. + // Otherwise, the task is (normally) executed synchronously as part of the + // wait() call below. However, in certain cases (serial execution or full + // queue), the task may be executed synchronously as part of the async() + // call itself. Once the master thread has scheduled all the tasks, it calls + // wait() to await for their completion. // // The scheduler makes sure that only a certain number of threads (for // example, the number of available hardware threads) are "active" at any - // given time (thus the reason why async() may choose to perform the task - // synchronously). When a master thread calls wait(), it is "suspended" - // until all its asynchronous tasks are completed (at which point it becomes + // given time. When a master thread calls wait(), it is "suspended" until + // all its asynchronous tasks are completed (at which point it becomes // "ready"). A suspension of a master results in either another ready master // being "resumed" or another helper thread becoming available. // @@ -46,7 +47,7 @@ namespace build2 // helper thread is always created if none is available. This is done to // allow a ready master to continue as soon as possible. If it were reused // as a helper, then it could be blocked on a nested wait() further down the - // stack. This means that the number of threads created by the scheduler + // stack. All this means that the number of threads created by the scheduler // will normally exceed the maximum active allowed. // class scheduler @@ -61,7 +62,10 @@ namespace build2 // // The argument passing semantics is the same as for std::thread. In // particular, lvalue-references are passed as copies (use ref()/cref() - // for the by-reference semantics). + // for the by-reference semantics), except the case where the task is + // executed synchronously and as part of the async() call itself (this + // subtlety can become important when passing shared locks; you would + // only want it to be copied if the task is queued). // // If the scheduler is shutdown, throw system_error(ECANCELED). // @@ -111,6 +115,19 @@ namespace build2 size_t max_threads = 0, size_t queue_depth = 0); + // Tune a started up scheduler. + // + // Currently one cannot increase the number of max_active. Pass 0 to + // restore the initial value. + // + // Note that tuning can only be done while the scheduler is inactive, that + // is, no threads are executing a task or are suspended. For example, in a + // setup with a single initial active thread that would be after a return + // from the top-level wait() call. + // + void + tune (size_t max_active); + // Wait for all the helper threads to terminate. Throw system_error on // failure. Note that the initially active threads are not waited for. // Return scheduling statistics. @@ -221,8 +238,8 @@ namespace build2 private: std::mutex mutex_; - bool shutdown_ = true; // Shutdown flag. - bool task_ = false; // Task queued flag (see below). + bool shutdown_ = true; // Shutdown flag. + bool task_ = false; // Task queued flag (see below). // The constraints that we must maintain: // @@ -247,6 +264,11 @@ namespace build2 size_t ready_ = 0; // Ready master thread waiting to become active. size_t starting_ = 0; // Helper threads starting up. + // Original values (as specified during startup) that can be altered via + // tuning. + // + size_t orig_max_active_ = 0; + std::condition_variable idle_condv_; // Idle helpers queue. std::condition_variable ready_condv_; // Ready masters queue. diff --git a/build2/scheduler.cxx b/build2/scheduler.cxx index be50b28..9abafbb 100644 --- a/build2/scheduler.cxx +++ b/build2/scheduler.cxx @@ -146,7 +146,7 @@ namespace build2 max_active <= max_threads); active_ = init_active_ = init_active; - max_active_ = max_active; + max_active_ = orig_max_active_ = max_active; max_threads_ = max_threads; // This value should be proportional to the amount of hardware concurrency @@ -212,12 +212,32 @@ namespace build2 wait_queue_[i].shutdown = false; } + void scheduler:: + tune (size_t max_active) + { + lock l (mutex_); + + if (max_active) + max_active = orig_max_active_; + + assert (max_active >= init_active_ && + max_active <= orig_max_active_); + + // The schduler must not be active. + // + assert (active_ == init_active_); + assert (waiting_ == 0); + assert (ready_ == 0); + + max_active_ = max_active; + } + auto scheduler:: shutdown () -> stat { // Our overall approach to shutdown is not to try and stop everything as // quickly as possible but rather to avoid performing any tasks. This - // avoids having code littered with if(shutdown) on every second line. + // avoids having code littered with if(shutdown) on every other line. stat r; lock l (mutex_); @@ -274,7 +294,7 @@ namespace build2 wait_queue_.reset (); task_queues_.clear (); - r.thread_max_active = max_active_; + r.thread_max_active = orig_max_active_; r.thread_max_total = max_threads_; r.thread_max_waiting = stat_max_waiters_; diff --git a/build2/scheduler.txx b/build2/scheduler.txx index fd1106b..127ce48 100644 --- a/build2/scheduler.txx +++ b/build2/scheduler.txx @@ -40,8 +40,8 @@ namespace build2 // new (&td->data) task { &task_count, - decay_copy (forward (f)), - typename task::args_type (decay_copy (forward (a))...)}; + decay_copy (forward (f)), + typename task::args_type (decay_copy (forward (a))...)}; td->thunk = &task_thunk; } @@ -49,8 +49,8 @@ namespace build2 tq->stat_full++; } - // If serial/full, then run the task synchronously. In this case - // there is no need to mess with task count. + // If serial/full, then run the task synchronously. In this case there is + // no need to mess with task count. // if (td == nullptr) { diff --git a/build2/variable b/build2/variable index 4377d7b..e296e37 100644 --- a/build2/variable +++ b/build2/variable @@ -755,6 +755,30 @@ namespace build2 static const value_type_ex value_type; }; + // Variable override cache. + // + struct variable_override_value + { + build2::value value; + const variable_map* stem_vars = nullptr; // NULL means there is no stem. + }; + + using variable_override_cache = std::map, + variable_override_value>; + + // Project-wide (as opposed to global) variable overrides. Returned by + // context.cxx:reset(). + // + struct variable_override + { + const variable& var; // Original variable. + const variable& ovr; // Override variable. + value val; + }; + + using variable_overrides = vector; + // Variable pool. // // Protected by the model mutex. @@ -841,10 +865,6 @@ namespace build2 rw (scope&) const {return const_cast (*this);} private: - // Classes that can access bypassing the lock. - // - friend class scope; - static variable_pool instance; const variable& @@ -853,6 +873,11 @@ namespace build2 const variable_visibility* = nullptr, const bool* overridable = nullptr); + // Entities that can access bypassing the lock. + // + friend class scope; + friend variable_overrides reset (const strings&); + public: static const variable_pool& cinstance; // For var_pool initialization. @@ -1034,18 +1059,6 @@ namespace build2 mutable std::map, value> cache; }; - - // Override cache. - // - struct variable_override_value - { - build2::value value; - const variable_map* stem_vars = nullptr; // NULL means there is no stem. - }; - - using variable_override_cache = std::map, - variable_override_value>; } #include diff --git a/unit-tests/function/buildfile b/unit-tests/function/buildfile index 27c73d1..3631958 100644 --- a/unit-tests/function/buildfile +++ b/unit-tests/function/buildfile @@ -10,7 +10,7 @@ import libs = libbutl%lib{butl} src = token lexer diagnostics utility variable name b-options types-parsers \ context scope parser target operation rule prerequisite file module function \ functions-builtin functions-path functions-process-path functions-string \ -functions-target-triplet algorithm search dump filesystem \ +functions-target-triplet algorithm search dump filesystem scheduler \ config/{utility init operation} diff --git a/unit-tests/function/driver.cxx b/unit-tests/function/driver.cxx index 4fbbdb6..2f605b0 100644 --- a/unit-tests/function/driver.cxx +++ b/unit-tests/function/driver.cxx @@ -26,9 +26,7 @@ namespace build2 main (int, char* argv[]) { init (argv[0], 1); // Fake build system driver, default verbosity. - - ulock ml (model); - reset (ml, strings ()); // No command line variables. + reset (strings ()); // No command line variables. function_family f ("dummy"); diff --git a/unit-tests/test/script/parser/driver.cxx b/unit-tests/test/script/parser/driver.cxx index 1e959c1..f1e7483 100644 --- a/unit-tests/test/script/parser/driver.cxx +++ b/unit-tests/test/script/parser/driver.cxx @@ -142,10 +142,9 @@ namespace build2 { tracer trace ("main"); - init (argv[0], 1); // Fake build system driver, default verbosity. - ulock ml (model); - sched.startup (1); // Serial execution. - reset (ml, strings ()); // No command line variables. + init (argv[0], 1); // Fake build system driver, default verbosity. + sched.startup (1); // Serial execution. + reset (strings ()); // No command line variables. bool scope (false); bool id (false); @@ -183,6 +182,8 @@ namespace build2 // be absolute. However, the testscript implementation doesn't // really care. // + ulock ml (model); + file& tt ( targets.insert (work, dir_path (), @@ -204,6 +205,8 @@ namespace build2 name.leaf ().extension (), trace)); + ml.unlock (); + tt.path (path ("driver")); st.path (name); -- cgit v1.1