* Goal cancellation inside the waitForInput() loop needs to be handled
very carefully, since it can invalidate iterators into the `children' map.
This commit is contained in:
		
							parent
							
								
									06c4929958
								
							
						
					
					
						commit
						fa33303146
					
				
					 1 changed files with 85 additions and 38 deletions
				
			
		| 
						 | 
					@ -123,10 +123,10 @@ public:
 | 
				
			||||||
        return exitCode;
 | 
					        return exitCode;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    void cancel()
 | 
					    /* Cancel the goal.  It should wake up its waiters, get rid of any
 | 
				
			||||||
    {
 | 
					       running child processes that are being monitored by the worker
 | 
				
			||||||
        amDone(ecFailed);
 | 
					       (important!), etc. */
 | 
				
			||||||
    }
 | 
					    virtual void cancel() = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
protected:
 | 
					protected:
 | 
				
			||||||
    void amDone(ExitCode result);
 | 
					    void amDone(ExitCode result);
 | 
				
			||||||
| 
						 | 
					@ -592,6 +592,8 @@ public:
 | 
				
			||||||
    DerivationGoal(const Path & drvPath, Worker & worker);
 | 
					    DerivationGoal(const Path & drvPath, Worker & worker);
 | 
				
			||||||
    ~DerivationGoal();
 | 
					    ~DerivationGoal();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void cancel();
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    void work();
 | 
					    void work();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Path getDrvPath()
 | 
					    Path getDrvPath()
 | 
				
			||||||
| 
						 | 
					@ -646,6 +648,9 @@ private:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* Return the set of (in)valid paths. */
 | 
					    /* Return the set of (in)valid paths. */
 | 
				
			||||||
    PathSet checkPathValidity(bool returnValid);
 | 
					    PathSet checkPathValidity(bool returnValid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* Forcibly kill the child process, if any. */
 | 
				
			||||||
 | 
					    void killChild();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -664,36 +669,48 @@ DerivationGoal::~DerivationGoal()
 | 
				
			||||||
    /* Careful: we should never ever throw an exception from a
 | 
					    /* Careful: we should never ever throw an exception from a
 | 
				
			||||||
       destructor. */
 | 
					       destructor. */
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
        if (pid != -1) {
 | 
					        killChild();
 | 
				
			||||||
            worker.childTerminated(pid);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (buildUser.enabled()) {
 | 
					 | 
				
			||||||
                /* Note that we can't let pid's destructor kill the
 | 
					 | 
				
			||||||
                   the child process, since it may not have the
 | 
					 | 
				
			||||||
                   appropriate privilege (i.e., the setuid helper
 | 
					 | 
				
			||||||
                   should do it).
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                   However, if we're using a build user, then there is
 | 
					 | 
				
			||||||
                   a tricky race condition: if we kill the build user
 | 
					 | 
				
			||||||
                   before the child has done its setuid() to the build
 | 
					 | 
				
			||||||
                   user uid, then it won't be killed, and we'll
 | 
					 | 
				
			||||||
                   potentially lock up in pid.wait().  So also send a
 | 
					 | 
				
			||||||
                   conventional kill to the child. */
 | 
					 | 
				
			||||||
                ::kill(-pid, SIGKILL); /* ignore the result */
 | 
					 | 
				
			||||||
                buildUser.kill();
 | 
					 | 
				
			||||||
                pid.wait(true);
 | 
					 | 
				
			||||||
                assert(pid == -1);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        deleteTmpDir(false);
 | 
					        deleteTmpDir(false);
 | 
				
			||||||
        
 | 
					 | 
				
			||||||
    } catch (Error & e) {
 | 
					    } catch (Error & e) {
 | 
				
			||||||
        printMsg(lvlError, format("error (ignored): %1%") % e.msg());
 | 
					        printMsg(lvlError, format("error (ignored): %1%") % e.msg());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void DerivationGoal::killChild()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    if (pid != -1) {
 | 
				
			||||||
 | 
					        worker.childTerminated(pid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (buildUser.enabled()) {
 | 
				
			||||||
 | 
					            /* We can't use pid.kill(), since we may not have the
 | 
				
			||||||
 | 
					               appropriate privilege.  I.e., if we're not root, then
 | 
				
			||||||
 | 
					               setuid helper should do it).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					               Also, if we're using a build user, then there is a
 | 
				
			||||||
 | 
					               tricky race condition: if we kill the build user before
 | 
				
			||||||
 | 
					               the child has done its setuid() to the build user uid,
 | 
				
			||||||
 | 
					               then it won't be killed, and we'll potentially lock up
 | 
				
			||||||
 | 
					               in pid.wait().  So also send a conventional kill to the
 | 
				
			||||||
 | 
					               child. */
 | 
				
			||||||
 | 
					            ::kill(-pid, SIGKILL); /* ignore the result */
 | 
				
			||||||
 | 
					            buildUser.kill();
 | 
				
			||||||
 | 
					            pid.wait(true);
 | 
				
			||||||
 | 
					        } else
 | 
				
			||||||
 | 
					            pid.kill();
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        assert(pid == -1);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void DerivationGoal::cancel()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    killChild();
 | 
				
			||||||
 | 
					    amDone(ecFailed);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void DerivationGoal::work()
 | 
					void DerivationGoal::work()
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    (this->*state)();
 | 
					    (this->*state)();
 | 
				
			||||||
| 
						 | 
					@ -1813,6 +1830,8 @@ public:
 | 
				
			||||||
    SubstitutionGoal(const Path & storePath, Worker & worker);
 | 
					    SubstitutionGoal(const Path & storePath, Worker & worker);
 | 
				
			||||||
    ~SubstitutionGoal();
 | 
					    ~SubstitutionGoal();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void cancel();
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    void work();
 | 
					    void work();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* The states. */
 | 
					    /* The states. */
 | 
				
			||||||
| 
						 | 
					@ -1840,10 +1859,24 @@ SubstitutionGoal::SubstitutionGoal(const Path & storePath, Worker & worker)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
SubstitutionGoal::~SubstitutionGoal()
 | 
					SubstitutionGoal::~SubstitutionGoal()
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
					    /* !!! Once we let substitution goals run under a build user, we
 | 
				
			||||||
 | 
					       need to do use the setuid helper just as in ~DerivationGoal().
 | 
				
			||||||
 | 
					       Idem for cancel. */
 | 
				
			||||||
    if (pid != -1) worker.childTerminated(pid);
 | 
					    if (pid != -1) worker.childTerminated(pid);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void SubstitutionGoal::cancel()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    if (pid != -1) {
 | 
				
			||||||
 | 
					        pid_t savedPid = pid;
 | 
				
			||||||
 | 
					        pid.kill();
 | 
				
			||||||
 | 
					        worker.childTerminated(savedPid);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    amDone(ecFailed);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void SubstitutionGoal::work()
 | 
					void SubstitutionGoal::work()
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    (this->*state)();
 | 
					    (this->*state)();
 | 
				
			||||||
| 
						 | 
					@ -2189,6 +2222,8 @@ void Worker::childStarted(GoalPtr goal,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Worker::childTerminated(pid_t pid, bool wakeSleepers)
 | 
					void Worker::childTerminated(pid_t pid, bool wakeSleepers)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
					    assert(pid != -1); /* common mistake */
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    Children::iterator i = children.find(pid);
 | 
					    Children::iterator i = children.find(pid);
 | 
				
			||||||
    assert(i != children.end());
 | 
					    assert(i != children.end());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2329,38 +2364,50 @@ void Worker::waitForInput()
 | 
				
			||||||
    time_t now = time(0);
 | 
					    time_t now = time(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* Process all available file descriptors. */
 | 
					    /* Process all available file descriptors. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* Since goals may be canceled from inside the loop below (causing
 | 
				
			||||||
 | 
					       them go be erased from the `children' map), we have to be
 | 
				
			||||||
 | 
					       careful that we don't keep iterators alive across calls to
 | 
				
			||||||
 | 
					       cancel(). */
 | 
				
			||||||
 | 
					    set<pid_t> pids;
 | 
				
			||||||
    for (Children::iterator i = children.begin();
 | 
					    for (Children::iterator i = children.begin();
 | 
				
			||||||
         i != children.end(); ++i)
 | 
					         i != children.end(); ++i)
 | 
				
			||||||
 | 
					        pids.insert(i->first);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					    for (set<pid_t>::iterator i = pids.begin();
 | 
				
			||||||
 | 
					         i != pids.end(); ++i)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        checkInterrupt();
 | 
					        checkInterrupt();
 | 
				
			||||||
        GoalPtr goal = i->second.goal.lock();
 | 
					        Children::iterator j = children.find(*i);
 | 
				
			||||||
 | 
					        if (j == children.end()) continue; // child destroyed
 | 
				
			||||||
 | 
					        GoalPtr goal = j->second.goal.lock();
 | 
				
			||||||
        assert(goal);
 | 
					        assert(goal);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        set<int> fds2(i->second.fds);
 | 
					        set<int> fds2(j->second.fds);
 | 
				
			||||||
        for (set<int>::iterator j = fds2.begin(); j != fds2.end(); ++j) {
 | 
					        for (set<int>::iterator k = fds2.begin(); k != fds2.end(); ++k) {
 | 
				
			||||||
            if (FD_ISSET(*j, &fds)) {
 | 
					            if (FD_ISSET(*k, &fds)) {
 | 
				
			||||||
                unsigned char buffer[4096];
 | 
					                unsigned char buffer[4096];
 | 
				
			||||||
                ssize_t rd = read(*j, buffer, sizeof(buffer));
 | 
					                ssize_t rd = read(*k, buffer, sizeof(buffer));
 | 
				
			||||||
                if (rd == -1) {
 | 
					                if (rd == -1) {
 | 
				
			||||||
                    if (errno != EINTR)
 | 
					                    if (errno != EINTR)
 | 
				
			||||||
                        throw SysError(format("reading from %1%")
 | 
					                        throw SysError(format("reading from %1%")
 | 
				
			||||||
                            % goal->getName());
 | 
					                            % goal->getName());
 | 
				
			||||||
                } else if (rd == 0) {
 | 
					                } else if (rd == 0) {
 | 
				
			||||||
                    debug(format("%1%: got EOF") % goal->getName());
 | 
					                    debug(format("%1%: got EOF") % goal->getName());
 | 
				
			||||||
                    goal->handleEOF(*j);
 | 
					                    goal->handleEOF(*k);
 | 
				
			||||||
                    i->second.fds.erase(*j);
 | 
					                    j->second.fds.erase(*k);
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    printMsg(lvlVomit, format("%1%: read %2% bytes")
 | 
					                    printMsg(lvlVomit, format("%1%: read %2% bytes")
 | 
				
			||||||
                        % goal->getName() % rd);
 | 
					                        % goal->getName() % rd);
 | 
				
			||||||
                    string data((char *) buffer, rd);
 | 
					                    string data((char *) buffer, rd);
 | 
				
			||||||
                    goal->handleChildOutput(*j, data);
 | 
					                    goal->handleChildOutput(*k, data);
 | 
				
			||||||
                    i->second.lastOutput = now;
 | 
					                    j->second.lastOutput = now;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (maxSilentTime != 0 &&
 | 
					        if (maxSilentTime != 0 &&
 | 
				
			||||||
            now - i->second.lastOutput >= (time_t) maxSilentTime)
 | 
					            now - j->second.lastOutput >= (time_t) maxSilentTime)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            printMsg(lvlError,
 | 
					            printMsg(lvlError,
 | 
				
			||||||
                format("%1% timed out after %2% seconds of silence")
 | 
					                format("%1% timed out after %2% seconds of silence")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue