* A flag `--keep-going / -k' to keep building goals if one fails, as
much as possible. (This is similar to GNU Make's `-k' flag.) * Refactoring to implement this: previously we just bombed out when a build failed, but now we have to clean up. In particular this means that goals must be freed quickly --- they shouldn't hang around until the worker exits. So the worker now maintains weak pointers in order not to prevent garbage collection. * Documented the `-k' and `-j' flags.
This commit is contained in:
		
							parent
							
								
									e4883211f9
								
							
						
					
					
						commit
						b113edeab7
					
				
					 10 changed files with 209 additions and 144 deletions
				
			
		|  | @ -4,5 +4,14 @@ | |||
| <arg rep='repeat'><option>-v</option></arg> | ||||
| <arg><option>--build-output</option></arg> | ||||
| <arg><option>-B</option></arg> | ||||
| <arg> | ||||
|   <group choice='req'> | ||||
|     <arg choice='plain'><option>--max-jobs</option></arg> | ||||
|     <arg choice='plain'><option>-j</option></arg> | ||||
|   </group> | ||||
|   <replaceable>number</replaceable> | ||||
| </arg> | ||||
| <arg><option>--keep-going</option></arg> | ||||
| <arg><option>-k</option></arg> | ||||
| <arg><option>--keep-failed</option></arg> | ||||
| <arg><option>-K</option></arg> | ||||
|  |  | |||
|  | @ -108,6 +108,33 @@ | |||
| </varlistentry> | ||||
| 
 | ||||
| 
 | ||||
| <varlistentry> | ||||
|   <term><option>--max-jobs</option> / <option>-j</option></term> | ||||
|   <listitem> | ||||
|     <para> | ||||
|       Sets the maximum number of build jobs that Nix will perform in | ||||
|       parallel to the specified number.  The default is 1.  A higher | ||||
|       value is useful on SMP systems or to exploit I/O latency. | ||||
|     </para> | ||||
|   </listitem> | ||||
| </varlistentry> | ||||
| 
 | ||||
| 
 | ||||
| <varlistentry> | ||||
|   <term><option>--keep-going</option> / <option>-k</option></term> | ||||
|   <listitem> | ||||
|     <para> | ||||
|       Keep going in case of failed builds, to the greatest extent | ||||
|       possible.  That is, if building an input of some derivation | ||||
|       fails, Nix will still build the other inputs, but not the | ||||
|       derivation itself.  Without this option, Nix stops if any build | ||||
|       fails (except for builds of substitutes), possibly killing | ||||
|       builds in progress (in case of parallel or distributed builds). | ||||
|     </para> | ||||
|   </listitem> | ||||
| </varlistentry> | ||||
| 
 | ||||
| 
 | ||||
| <varlistentry> | ||||
|   <term><option>--keep-failed</option> / <option>-K</option></term> | ||||
|   <listitem> | ||||
|  |  | |||
|  | @ -106,3 +106,4 @@ The hook `nix-mode-hook' is run when Nix mode is started. | |||
| 
 | ||||
| 
 | ||||
| (setq auto-mode-alist (cons '("\\.nix\\'" . nix-mode) auto-mode-alist)) | ||||
| (setq auto-mode-alist (cons '("\\.nix.in\\'" . nix-mode) auto-mode-alist)) | ||||
|  |  | |||
|  | @ -137,6 +137,8 @@ static void initAndRun(int argc, char * * argv) | |||
|         } | ||||
|         else if (arg == "--keep-failed" || arg == "-K") | ||||
|             keepFailed = true; | ||||
|         else if (arg == "--keep-going" || arg == "-k") | ||||
|             keepGoing = true; | ||||
|         else if (arg == "--max-jobs" || arg == "-j") { | ||||
|             ++i; | ||||
|             if (i == args.end()) throw UsageError("`--max-jobs' requires an argument"); | ||||
|  |  | |||
|  | @ -8,6 +8,8 @@ string nixDBPath = "/UNINIT"; | |||
| 
 | ||||
| bool keepFailed = false; | ||||
| 
 | ||||
| bool keepGoing = false; | ||||
| 
 | ||||
| Verbosity buildVerbosity = lvlDebug; | ||||
| 
 | ||||
| unsigned int maxBuildJobs = 1; | ||||
|  |  | |||
|  | @ -29,6 +29,10 @@ extern string nixDBPath; | |||
| /* Whether to keep temporary directories of failed builds. */ | ||||
| extern bool keepFailed; | ||||
| 
 | ||||
| /* Whether to keep building subgoals when a sibling (another subgoal
 | ||||
|    of the same goal) fails. */ | ||||
| extern bool keepGoing; | ||||
| 
 | ||||
| /* Verbosity level for build output. */ | ||||
| extern Verbosity buildVerbosity; | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| #include <map> | ||||
| #include <boost/shared_ptr.hpp> | ||||
| #include <boost/weak_ptr.hpp> | ||||
| #include <boost/enable_shared_from_this.hpp> | ||||
| 
 | ||||
| #include <sys/types.h> | ||||
|  | @ -26,13 +27,14 @@ class Worker; | |||
| /* A pointer to a goal. */ | ||||
| class Goal; | ||||
| typedef shared_ptr<Goal> GoalPtr; | ||||
| typedef weak_ptr<Goal> WeakGoalPtr; | ||||
| 
 | ||||
| /* A set of goals. */ | ||||
| /* Set of goals. */ | ||||
| typedef set<GoalPtr> Goals; | ||||
| typedef set<WeakGoalPtr> WeakGoals; | ||||
| 
 | ||||
| /* A map of paths to goals (and the other way around). */ | ||||
| typedef map<Path, GoalPtr> GoalMap; | ||||
| typedef map<GoalPtr, Path> GoalMapRev; | ||||
| typedef map<Path, WeakGoalPtr> WeakGoalMap; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|  | @ -43,8 +45,12 @@ protected: | |||
|     /* Backlink to the worker. */ | ||||
|     Worker & worker; | ||||
| 
 | ||||
|     /* Goals waiting for this one to finish. */ | ||||
|     Goals waiters; | ||||
|     /* Goals that this goal is waiting for. */ | ||||
|     Goals waitees; | ||||
| 
 | ||||
|     /* Goals waiting for this one to finish.  Must use weak pointers
 | ||||
|        here to prevent cycles. */ | ||||
|     WeakGoals waiters; | ||||
| 
 | ||||
|     /* Number of goals we are waiting for. */ | ||||
|     unsigned int nrWaitees; | ||||
|  | @ -75,20 +81,15 @@ protected: | |||
| public: | ||||
|     virtual void work() = 0; | ||||
| 
 | ||||
|     virtual string name() | ||||
|     { | ||||
|         return "(noname)"; | ||||
|     } | ||||
|     virtual string name() = 0; | ||||
| 
 | ||||
|     void addWaiter(GoalPtr waiter); | ||||
|     void addWaitee(GoalPtr waitee); | ||||
| 
 | ||||
|     virtual void waiteeDone(bool success); | ||||
|     virtual void waiteeDone(GoalPtr waitee, bool success); | ||||
| 
 | ||||
|     void trace(const format & f); | ||||
|      | ||||
| protected: | ||||
|     virtual void waiteeFailed() | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     void amDone(bool success = true); | ||||
| }; | ||||
| 
 | ||||
|  | @ -97,7 +98,7 @@ protected: | |||
|    belongs, and a file descriptor for receiving log data. */ | ||||
| struct Child | ||||
| { | ||||
|     GoalPtr goal; | ||||
|     WeakGoalPtr goal; | ||||
|     int fdOutput; | ||||
|     bool inBuildSlot; | ||||
| }; | ||||
|  | @ -110,14 +111,17 @@ class Worker | |||
| { | ||||
| private: | ||||
| 
 | ||||
|     /* The goals of the worker. */ | ||||
|     Goals goals; | ||||
|     /* Note: the worker should only have strong pointers to the
 | ||||
|        top-level goals. */ | ||||
| 
 | ||||
|     /* The top-level goals of the worker. */ | ||||
|     Goals topGoals; | ||||
| 
 | ||||
|     /* Goals that are ready to do some work. */ | ||||
|     Goals awake; | ||||
|     WeakGoals awake; | ||||
| 
 | ||||
|     /* Goals waiting for a build slot. */ | ||||
|     Goals wantingToBuild; | ||||
|     WeakGoals wantingToBuild; | ||||
| 
 | ||||
|     /* Child processes currently running. */ | ||||
|     Children children; | ||||
|  | @ -128,24 +132,21 @@ private: | |||
| 
 | ||||
|     /* Maps used to prevent multiple instantiation of a goal for the
 | ||||
|        same expression / path. */ | ||||
|     GoalMap normalisationGoals; | ||||
|     GoalMapRev normalisationGoalsRev; | ||||
|     GoalMap realisationGoals; | ||||
|     GoalMapRev realisationGoalsRev; | ||||
|     GoalMap substitutionGoals; | ||||
|     GoalMapRev substitutionGoalsRev; | ||||
|     WeakGoalMap normalisationGoals; | ||||
|     WeakGoalMap realisationGoals; | ||||
|     WeakGoalMap substitutionGoals; | ||||
| 
 | ||||
| public: | ||||
| 
 | ||||
|     Worker(); | ||||
|     ~Worker(); | ||||
| 
 | ||||
|     /* Add a goal. */ | ||||
|     void addNormalisationGoal(const Path & nePath, GoalPtr waiter); | ||||
|     void addRealisationGoal(const Path & nePath, GoalPtr waiter); | ||||
|     void addSubstitutionGoal(const Path & storePath, GoalPtr waiter); | ||||
|     /* Make a goal (with caching). */ | ||||
|     GoalPtr makeNormalisationGoal(const Path & nePath); | ||||
|     GoalPtr makeRealisationGoal(const Path & nePath); | ||||
|     GoalPtr makeSubstitutionGoal(const Path & storePath); | ||||
| 
 | ||||
|     /* Remove a finished goal. */ | ||||
|     /* Remove a dead goal. */ | ||||
|     void removeGoal(GoalPtr goal); | ||||
| 
 | ||||
|     /* Wake up a goal (i.e., there is something for it to do). */ | ||||
|  | @ -162,8 +163,9 @@ public: | |||
|     /* Add a goal to the set of goals waiting for a build slot. */ | ||||
|     void waitForBuildSlot(GoalPtr goal); | ||||
|      | ||||
|     /* Loop until all goals have been realised. */ | ||||
|     void run(); | ||||
|     /* Loop until the specified top-level goal has finished.  Returns
 | ||||
|        true if it has finished succesfully. */ | ||||
|     bool run(GoalPtr topGoal); | ||||
| 
 | ||||
|     /* Wait for input to become available. */ | ||||
|     void waitForInput(); | ||||
|  | @ -188,35 +190,44 @@ public: | |||
| //////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| 
 | ||||
| void Goal::addWaiter(GoalPtr waiter) | ||||
| void Goal::addWaitee(GoalPtr waitee) | ||||
| { | ||||
|     waiters.insert(waiter); | ||||
|     waitees.insert(waitee); | ||||
|     waitee->waiters.insert(shared_from_this()); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void Goal::waiteeDone(bool success) | ||||
| void Goal::waiteeDone(GoalPtr waitee, bool success) | ||||
| { | ||||
|     assert(waitees.find(waitee) != waitees.end()); | ||||
|     waitees.erase(waitee); | ||||
|     assert(nrWaitees > 0); | ||||
|     /* Note: waiteeFailed should never call amDone()! */ | ||||
|     if (!success) { | ||||
|         ++nrFailed; | ||||
|         waiteeFailed(); | ||||
|     } | ||||
|     if (!--nrWaitees) worker.wakeUp(shared_from_this()); | ||||
|     if (!success) ++nrFailed; | ||||
|     if (!--nrWaitees || (!success && !keepGoing)) | ||||
|         worker.wakeUp(shared_from_this()); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void Goal::amDone(bool success) | ||||
| { | ||||
|     printMsg(lvlVomit, "done"); | ||||
|     trace("done"); | ||||
|     assert(!done); | ||||
|     done = true; | ||||
|     for (Goals::iterator i = waiters.begin(); i != waiters.end(); ++i) | ||||
|         (*i)->waiteeDone(success); | ||||
|     for (WeakGoals::iterator i = waiters.begin(); i != waiters.end(); ++i) { | ||||
|         GoalPtr goal = i->lock(); | ||||
|         if (goal) goal->waiteeDone(shared_from_this(), success); | ||||
|     } | ||||
|     waiters.clear(); | ||||
|     worker.removeGoal(shared_from_this()); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void Goal::trace(const format & f) | ||||
| { | ||||
|     debug(format("%1%: %2%") % name() % f); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| //////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
|  | @ -356,12 +367,7 @@ private: | |||
|     /* Delete the temporary directory, if we have one. */ | ||||
|     void deleteTmpDir(bool force); | ||||
| 
 | ||||
|     string name() | ||||
|     { | ||||
|         return nePath; | ||||
|     } | ||||
| 
 | ||||
|     void trace(const format & f); | ||||
|     string name(); | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
|  | @ -375,6 +381,8 @@ NormalisationGoal::NormalisationGoal(const Path & _nePath, Worker & _worker) | |||
| 
 | ||||
| NormalisationGoal::~NormalisationGoal() | ||||
| { | ||||
|     if (pid != -1) worker.childTerminated(pid); | ||||
|      | ||||
|     /* Careful: we should never ever throw an exception from a
 | ||||
|        destructor. */ | ||||
|     try { | ||||
|  | @ -407,7 +415,7 @@ void NormalisationGoal::init() | |||
|        exists.  If it doesn't, it may be created through a | ||||
|        substitute. */ | ||||
|     resetWaitees(1); | ||||
|     worker.addSubstitutionGoal(nePath, shared_from_this()); | ||||
|     addWaitee(worker.makeSubstitutionGoal(nePath)); | ||||
| 
 | ||||
|     state = &NormalisationGoal::haveStoreExpr; | ||||
| } | ||||
|  | @ -440,7 +448,7 @@ void NormalisationGoal::haveStoreExpr() | |||
|     /* Inputs must be normalised before we can build this goal. */ | ||||
|     for (PathSet::iterator i = expr.derivation.inputs.begin(); | ||||
|          i != expr.derivation.inputs.end(); ++i) | ||||
|         worker.addNormalisationGoal(*i, shared_from_this()); | ||||
|         addWaitee(worker.makeNormalisationGoal(*i)); | ||||
| 
 | ||||
|     resetWaitees(expr.derivation.inputs.size()); | ||||
| 
 | ||||
|  | @ -469,7 +477,7 @@ void NormalisationGoal::inputNormalised() | |||
|         if (querySuccessor(neInput, nfInput)) | ||||
|             neInput = nfInput; | ||||
|         /* Otherwise the input must be a closure. */ | ||||
|         worker.addRealisationGoal(neInput, shared_from_this()); | ||||
|         addWaitee(worker.makeRealisationGoal(neInput)); | ||||
|     } | ||||
|      | ||||
|     resetWaitees(expr.derivation.inputs.size()); | ||||
|  | @ -1153,9 +1161,9 @@ void NormalisationGoal::deleteTmpDir(bool force) | |||
| } | ||||
| 
 | ||||
| 
 | ||||
| void NormalisationGoal::trace(const format & f) | ||||
| string NormalisationGoal::name() | ||||
| { | ||||
|     debug(format("normalisation of `%1%': %2%") % nePath % f); | ||||
|     return (format("normalisation of `%1%'") % nePath).str(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  | @ -1186,7 +1194,7 @@ public: | |||
|     void haveStoreExpr(); | ||||
|     void elemFinished(); | ||||
| 
 | ||||
|     void trace(const format & f); | ||||
|     string name(); | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
|  | @ -1217,7 +1225,7 @@ void RealisationGoal::init() | |||
|        exists.  If it doesn't, it may be created through a | ||||
|        substitute. */ | ||||
|     resetWaitees(1); | ||||
|     worker.addSubstitutionGoal(nePath, shared_from_this()); | ||||
|     addWaitee(worker.makeSubstitutionGoal(nePath)); | ||||
| 
 | ||||
|     state = &RealisationGoal::haveStoreExpr; | ||||
| } | ||||
|  | @ -1248,7 +1256,7 @@ void RealisationGoal::haveStoreExpr() | |||
|        through a substitute. */ | ||||
|     for (ClosureElems::const_iterator i = expr.closure.elems.begin(); | ||||
|          i != expr.closure.elems.end(); ++i) | ||||
|         worker.addSubstitutionGoal(i->first, shared_from_this()); | ||||
|         addWaitee(worker.makeSubstitutionGoal(i->first)); | ||||
|      | ||||
|     resetWaitees(expr.closure.elems.size()); | ||||
| 
 | ||||
|  | @ -1274,9 +1282,9 @@ void RealisationGoal::elemFinished() | |||
| } | ||||
| 
 | ||||
| 
 | ||||
| void RealisationGoal::trace(const format & f) | ||||
| string RealisationGoal::name() | ||||
| { | ||||
|     debug(format("realisation of `%1%': %2%") % nePath % f); | ||||
|     return (format("realisation of `%1%'") % nePath).str(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  | @ -1313,6 +1321,7 @@ private: | |||
| 
 | ||||
| public: | ||||
|     SubstitutionGoal(const Path & _nePath, Worker & _worker); | ||||
|     ~SubstitutionGoal(); | ||||
| 
 | ||||
|     void work(); | ||||
| 
 | ||||
|  | @ -1324,7 +1333,7 @@ public: | |||
|     void tryToRun(); | ||||
|     void finished(); | ||||
| 
 | ||||
|     void trace(const format & f); | ||||
|     string name(); | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
|  | @ -1336,6 +1345,12 @@ SubstitutionGoal::SubstitutionGoal(const Path & _storePath, Worker & _worker) | |||
| } | ||||
| 
 | ||||
| 
 | ||||
| SubstitutionGoal::~SubstitutionGoal() | ||||
| { | ||||
|     if (pid != -1) worker.childTerminated(pid); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void SubstitutionGoal::work() | ||||
| { | ||||
|     (this->*state)(); | ||||
|  | @ -1377,7 +1392,7 @@ void SubstitutionGoal::tryNext() | |||
|     subs.pop_front(); | ||||
| 
 | ||||
|     /* Normalise the substitute store expression. */ | ||||
|     worker.addNormalisationGoal(sub.storeExpr, shared_from_this()); | ||||
|     addWaitee(worker.makeNormalisationGoal(sub.storeExpr)); | ||||
|     resetWaitees(1); | ||||
| 
 | ||||
|     state = &SubstitutionGoal::exprNormalised; | ||||
|  | @ -1396,7 +1411,7 @@ void SubstitutionGoal::exprNormalised() | |||
|     /* Realise the substitute store expression. */ | ||||
|     if (!querySuccessor(sub.storeExpr, nfSub)) | ||||
|         nfSub = sub.storeExpr; | ||||
|     worker.addRealisationGoal(nfSub, shared_from_this()); | ||||
|     addWaitee(worker.makeRealisationGoal(nfSub)); | ||||
| 
 | ||||
|     resetWaitees(1); | ||||
| 
 | ||||
|  | @ -1557,9 +1572,9 @@ void SubstitutionGoal::finished() | |||
| } | ||||
| 
 | ||||
| 
 | ||||
| void SubstitutionGoal::trace(const format & f) | ||||
| string SubstitutionGoal::name() | ||||
| { | ||||
|     debug(format("substitution of `%1%': %2%") % storePath % f); | ||||
|     return (format("substitution of `%1%'") % storePath).str(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  | @ -1585,7 +1600,7 @@ public: | |||
|         abort(); | ||||
|     } | ||||
| 
 | ||||
|     void waiteeDone(bool success) | ||||
|     void waiteeDone(GoalPtr waitee, bool success) | ||||
|     { | ||||
|         if (!success) this->success = false; | ||||
|     } | ||||
|  | @ -1594,6 +1609,11 @@ public: | |||
|     { | ||||
|         return success; | ||||
|     } | ||||
| 
 | ||||
|     string name() | ||||
|     { | ||||
|         return "pseudo-goal"; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
|  | @ -1616,71 +1636,67 @@ Worker::Worker() | |||
| Worker::~Worker() | ||||
| { | ||||
|     working = false; | ||||
| 
 | ||||
|     /* Explicitly get rid of all strong pointers now.  After this all
 | ||||
|        goals that refer to this worker should be gone.  (Otherwise we | ||||
|        are in trouble, since goals may call childTerminated() etc. in | ||||
|        their destructors). */ | ||||
|     topGoals.clear(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| template<class T> | ||||
| static void addGoal(const Path & path, GoalPtr waiter, | ||||
|     Worker & worker, Goals & goals, | ||||
|     GoalMap & goalMap, GoalMapRev & goalMapRev) | ||||
| static GoalPtr addGoal(const Path & path, | ||||
|     Worker & worker, WeakGoalMap & goalMap) | ||||
| { | ||||
|     GoalPtr goal; | ||||
|     goal = goalMap[path]; | ||||
|     GoalPtr goal = goalMap[path].lock(); | ||||
|     if (!goal) { | ||||
|         goal = GoalPtr(new T(path, worker)); | ||||
|         goals.insert(goal); | ||||
|         goalMap[path] = goal; | ||||
|         goalMapRev[goal] = path; | ||||
|         worker.wakeUp(goal); | ||||
|     } | ||||
|     if (waiter) goal->addWaiter(waiter); | ||||
|     return goal; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void Worker::addNormalisationGoal(const Path & nePath, GoalPtr waiter) | ||||
| GoalPtr Worker::makeNormalisationGoal(const Path & nePath) | ||||
| { | ||||
|     addGoal<NormalisationGoal>(nePath, waiter, *this, goals, | ||||
|         normalisationGoals, normalisationGoalsRev); | ||||
|     return addGoal<NormalisationGoal>(nePath, *this, normalisationGoals); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void Worker::addRealisationGoal(const Path & nePath, GoalPtr waiter) | ||||
| GoalPtr Worker::makeRealisationGoal(const Path & nePath) | ||||
| { | ||||
|     addGoal<RealisationGoal>(nePath, waiter, *this, goals, | ||||
|         realisationGoals, realisationGoalsRev); | ||||
|     return addGoal<RealisationGoal>(nePath, *this, realisationGoals); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void Worker::addSubstitutionGoal(const Path & storePath, GoalPtr waiter) | ||||
| GoalPtr Worker::makeSubstitutionGoal(const Path & storePath) | ||||
| { | ||||
|     addGoal<SubstitutionGoal>(storePath, waiter, *this, goals, | ||||
|         substitutionGoals, substitutionGoalsRev); | ||||
|     return addGoal<SubstitutionGoal>(storePath, *this, substitutionGoals); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static void removeGoal(GoalPtr goal, | ||||
|     GoalMap & goalMap, GoalMapRev & goalMapRev) | ||||
| static void removeGoal(GoalPtr goal, WeakGoalMap & goalMap) | ||||
| { | ||||
|     GoalMapRev::iterator i = goalMapRev.find(goal); | ||||
|     if (i != goalMapRev.end()) { | ||||
|         goalMapRev.erase(i); | ||||
|         goalMap.erase(i->second); | ||||
|     } | ||||
|     /* !!! For now we just let dead goals accumulate.  We should
 | ||||
|        probably periodically sweep the goalMap to remove dead | ||||
|        goals. */ | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void Worker::removeGoal(GoalPtr goal) | ||||
| { | ||||
|     goals.erase(goal); | ||||
|     ::removeGoal(goal, normalisationGoals, normalisationGoalsRev); | ||||
|     ::removeGoal(goal, realisationGoals, realisationGoalsRev); | ||||
|     ::removeGoal(goal, substitutionGoals, substitutionGoalsRev); | ||||
|     topGoals.erase(goal); | ||||
|     ::removeGoal(goal, normalisationGoals); | ||||
|     ::removeGoal(goal, realisationGoals); | ||||
|     ::removeGoal(goal, substitutionGoals); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void Worker::wakeUp(GoalPtr goal) | ||||
| { | ||||
|     printMsg(lvlVomit, "wake up"); | ||||
|     goal->trace("woken up"); | ||||
|     awake.insert(goal); | ||||
| } | ||||
| 
 | ||||
|  | @ -1716,9 +1732,12 @@ void Worker::childTerminated(pid_t pid) | |||
|     children.erase(pid); | ||||
| 
 | ||||
|     /* Wake up goals waiting for a build slot. */ | ||||
|     for (Goals::iterator i = wantingToBuild.begin(); | ||||
|     for (WeakGoals::iterator i = wantingToBuild.begin(); | ||||
|          i != wantingToBuild.end(); ++i) | ||||
|         wakeUp(*i); | ||||
|     { | ||||
|         GoalPtr goal = i->lock(); | ||||
|         if (goal) wakeUp(goal); | ||||
|     } | ||||
|     wantingToBuild.clear(); | ||||
| } | ||||
| 
 | ||||
|  | @ -1733,8 +1752,18 @@ void Worker::waitForBuildSlot(GoalPtr goal) | |||
| } | ||||
| 
 | ||||
| 
 | ||||
| void Worker::run() | ||||
| bool Worker::run(GoalPtr topGoal) | ||||
| { | ||||
|     assert(topGoal); | ||||
|      | ||||
|     /* Wrap the specified top-level goal in a pseudo-goal so that we
 | ||||
|        can check whether it succeeded. */ | ||||
|     shared_ptr<PseudoGoal> pseudo(new PseudoGoal(*this)); | ||||
|     pseudo->addWaitee(topGoal); | ||||
|      | ||||
|     /* For now, we have only one top-level goal. */ | ||||
|     topGoals.insert(topGoal); | ||||
|      | ||||
|     startNest(nest, lvlDebug, format("entered goal loop")); | ||||
| 
 | ||||
|     while (1) { | ||||
|  | @ -1743,18 +1772,16 @@ void Worker::run() | |||
| 
 | ||||
|         /* Call every wake goal. */ | ||||
|         while (!awake.empty()) { | ||||
|             Goals awake2(awake); /* !!! why is this necessary? */ | ||||
|             WeakGoals awake2(awake); | ||||
|             awake.clear(); | ||||
|             for (Goals::iterator i = awake2.begin(); i != awake2.end(); ++i) { | ||||
|                 printMsg(lvlVomit, | ||||
|                     format("running goal (%1% left)") % goals.size()); | ||||
|             for (WeakGoals::iterator i = awake2.begin(); i != awake2.end(); ++i) { | ||||
|                 checkInterrupt(); | ||||
|                 GoalPtr goal = *i; | ||||
|                 goal->work(); | ||||
|                 GoalPtr goal = i->lock(); | ||||
|                 if (goal) goal->work(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (goals.empty()) break; | ||||
|         if (topGoals.empty()) break; | ||||
| 
 | ||||
|         /* !!! not when we're polling */ | ||||
|         assert(!children.empty()); | ||||
|  | @ -1763,9 +1790,14 @@ void Worker::run() | |||
|         waitForInput(); | ||||
|     } | ||||
| 
 | ||||
|     assert(awake.empty()); | ||||
|     assert(wantingToBuild.empty()); | ||||
|     assert(children.empty()); | ||||
|     /* If --keep-going is not set, it's possible that the main goal
 | ||||
|        exited while some of its subgoals were still active.  But if | ||||
|        --keep-going *is* set, then they must all be finished now. */ | ||||
|     assert(!keepGoing || awake.empty()); | ||||
|     assert(!keepGoing || wantingToBuild.empty()); | ||||
|     assert(!keepGoing || children.empty()); | ||||
| 
 | ||||
|     return pseudo->isOkay(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  | @ -1801,22 +1833,23 @@ void Worker::waitForInput() | |||
|          i != children.end(); ++i) | ||||
|     { | ||||
|         checkInterrupt(); | ||||
|         GoalPtr goal = i->second.goal; | ||||
|         GoalPtr goal = i->second.goal.lock(); | ||||
|         assert(goal); | ||||
|         int fd = i->second.fdOutput; | ||||
|         if (FD_ISSET(fd, &fds)) { | ||||
|             unsigned char buffer[4096]; | ||||
|             ssize_t rd = read(fd, buffer, sizeof(buffer)); | ||||
|             if (rd == -1) { | ||||
|                 if (errno != EINTR) | ||||
|                     throw SysError(format("reading from `%1%'") | ||||
|                     throw SysError(format("reading from %1%") | ||||
|                         % goal->name()); | ||||
|             } else if (rd == 0) { | ||||
|                 debug(format("EOF on `%1%'") % goal->name()); | ||||
|                 debug(format("%1%: got EOF") % goal->name()); | ||||
|                 wakeUp(goal); | ||||
|             } else { | ||||
|                 printMsg(lvlVomit, format("read %1% bytes from `%2%'") | ||||
|                     % rd % goal->name()); | ||||
| //                 writeFull(goal.fdLogFile, buffer, rd);
 | ||||
|                 printMsg(lvlVomit, format("%1%: read %2% bytes") | ||||
|                     % goal->name() % rd); | ||||
| //                 writeFull(goal.fdLogFile, buffer, rd); !!!
 | ||||
|                 if (verbosity >= buildVerbosity) | ||||
|                     writeFull(STDERR_FILENO, buffer, rd); | ||||
|             } | ||||
|  | @ -1833,11 +1866,7 @@ Path normaliseStoreExpr(const Path & nePath) | |||
|     startNest(nest, lvlDebug, format("normalising `%1%'") % nePath); | ||||
| 
 | ||||
|     Worker worker; | ||||
|     shared_ptr<PseudoGoal> pseudo(new PseudoGoal(worker)); | ||||
|     worker.addNormalisationGoal(nePath, pseudo); | ||||
|     worker.run(); | ||||
| 
 | ||||
|     if (!pseudo->isOkay()) | ||||
|     if (!worker.run(worker.makeNormalisationGoal(nePath))) | ||||
|         throw Error(format("normalisation of store expression `%1%' failed") % nePath); | ||||
| 
 | ||||
|     Path nfPath; | ||||
|  | @ -1851,11 +1880,7 @@ void realiseClosure(const Path & nePath) | |||
|     startNest(nest, lvlDebug, format("realising closure `%1%'") % nePath); | ||||
| 
 | ||||
|     Worker worker; | ||||
|     shared_ptr<PseudoGoal> pseudo(new PseudoGoal(worker)); | ||||
|     worker.addRealisationGoal(nePath, pseudo); | ||||
|     worker.run(); | ||||
| 
 | ||||
|     if (!pseudo->isOkay()) | ||||
|     if (!worker.run(worker.makeRealisationGoal(nePath))) | ||||
|         throw Error(format("realisation of closure `%1%' failed") % nePath); | ||||
| } | ||||
| 
 | ||||
|  | @ -1866,10 +1891,6 @@ void ensurePath(const Path & path) | |||
|     if (isValidPath(path)) return; | ||||
| 
 | ||||
|     Worker worker; | ||||
|     shared_ptr<PseudoGoal> pseudo(new PseudoGoal(worker)); | ||||
|     worker.addSubstitutionGoal(path, pseudo); | ||||
|     worker.run(); | ||||
| 
 | ||||
|     if (!pseudo->isOkay()) | ||||
|     if (!worker.run(worker.makeSubstitutionGoal(path))) | ||||
|         throw Error(format("path `%1%' does not exist and cannot be created") % path); | ||||
| } | ||||
|  |  | |||
|  | @ -492,20 +492,19 @@ void Pid::kill() | |||
| { | ||||
|     if (pid == -1) return; | ||||
|      | ||||
|     printMsg(lvlError, format("killing child process %1%") % pid); | ||||
|     printMsg(lvlError, format("killing process %1%") % pid); | ||||
| 
 | ||||
|     /* Send a KILL signal to the child.  If it has its own process
 | ||||
|        group, send the signal to every process in the child process | ||||
|        group (which hopefully includes *all* its children). */ | ||||
|     if (::kill(separatePG ? -pid : pid, SIGKILL) != 0) | ||||
|         printMsg(lvlError, format("killing process %1%") % pid); | ||||
|     else { | ||||
|         printMsg(lvlError, (SysError(format("killing process %1%") % pid).msg())); | ||||
| 
 | ||||
|     /* Wait until the child dies, disregarding the exit status. */ | ||||
|     int status; | ||||
|     while (waitpid(pid, &status, 0) == -1) | ||||
|         if (errno != EINTR) printMsg(lvlError, | ||||
|                 format("waiting for process %1%") % pid); | ||||
|     } | ||||
|             (SysError(format("waiting for process %1%") % pid).msg())); | ||||
| 
 | ||||
|     pid = -1; | ||||
| } | ||||
|  |  | |||
|  | @ -21,9 +21,8 @@ substitutes.sh: substitutes.nix substituter.nix | |||
| substitutes2.sh: substitutes2.nix substituter.nix substituter2.nix | ||||
| fall-back.sh: fall-back.nix | ||||
| 
 | ||||
| #TESTS = init.sh simple.sh dependencies.sh locking.sh parallel.sh \ | ||||
| #  build-hook.sh substitutes.sh substitutes2.sh | ||||
| TESTS = init.sh fall-back.sh | ||||
| TESTS = init.sh simple.sh dependencies.sh locking.sh parallel.sh \ | ||||
|   build-hook.sh substitutes.sh substitutes2.sh | ||||
| 
 | ||||
| XFAIL_TESTS = | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,11 +2,12 @@ storeExpr=$($TOP/src/nix-instantiate/nix-instantiate fall-back.nix) | |||
| 
 | ||||
| echo "store expr is $storeExpr" | ||||
| 
 | ||||
| # Register a non-existant successor. | ||||
| # Register a non-existant successor (and a nox-existant substitute). | ||||
| suc=$NIX_STORE_DIR/deadbeafdeadbeafdeadbeafdeadbeaf-s.store | ||||
| (echo $suc && echo $NIX_STORE_DIR/ffffffffffffffffffffffffffffffff.store && echo "/bla" && echo 0) | $TOP/src/nix-store/nix-store --substitute | ||||
| $TOP/src/nix-store/nix-store --successor $storeExpr $suc | ||||
| 
 | ||||
| outPath=$($TOP/src/nix-store/nix-store -qnfvvvvv "$storeExpr") | ||||
| outPath=$($TOP/src/nix-store/nix-store -qnf "$storeExpr") | ||||
| 
 | ||||
| echo "output path is $outPath" | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue