mirror of
				https://github.com/ohwgiles/laminar.git
				synced 2025-06-13 12:54:29 +00:00 
			
		
		
		
	use kj's onChildExit mechanism
This reduces code and allows for more idiosyncratic use of Promises. Requires latest capnproto git. Part of #49 refactor
This commit is contained in:
		
							parent
							
								
									4ffc22c657
								
							
						
					
					
						commit
						a81492e5bc
					
				| @ -19,10 +19,10 @@ export PATH=/opt/rh/devtoolset-7/root/usr/bin:\$PATH | |||||||
| mkdir /build | mkdir /build | ||||||
| cd /build | cd /build | ||||||
| 
 | 
 | ||||||
| wget -O capnproto.tar.gz https://github.com/capnproto/capnproto/archive/06a7136708955d91f8ddc1fa3d54e620eacba13e.tar.gz | wget -O capnproto.tar.gz https://github.com/capnproto/capnproto/archive/df67f26862c011c6efb31a28fb0d2a2ca1b94ac8.tar.gz | ||||||
| wget -O rapidjson.tar.gz https://github.com/miloyip/rapidjson/archive/v1.1.0.tar.gz | wget -O rapidjson.tar.gz https://github.com/miloyip/rapidjson/archive/v1.1.0.tar.gz | ||||||
| md5sum -c <<EOF | md5sum -c <<EOF | ||||||
| a24b4d6e671d97c8a2aacd0dd4677726  capnproto.tar.gz | 80c28dc26842b84dcbe8d930a97c70cf  capnproto.tar.gz | ||||||
| badd12c511e081fec6c89c43a7027bce  rapidjson.tar.gz | badd12c511e081fec6c89c43a7027bce  rapidjson.tar.gz | ||||||
| EOF | EOF | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -17,10 +17,10 @@ docker run --rm -i -v $SOURCE_DIR:/laminar:ro -v $OUTPUT_DIR:/output $DOCKER_TAG | |||||||
| mkdir /build | mkdir /build | ||||||
| cd /build | cd /build | ||||||
| 
 | 
 | ||||||
| wget -O capnproto.tar.gz https://github.com/capnproto/capnproto/archive/06a7136708955d91f8ddc1fa3d54e620eacba13e.tar.gz | wget -O capnproto.tar.gz https://github.com/capnproto/capnproto/archive/df67f26862c011c6efb31a28fb0d2a2ca1b94ac8.tar.gz | ||||||
| wget -O rapidjson.tar.gz https://github.com/miloyip/rapidjson/archive/v1.1.0.tar.gz | wget -O rapidjson.tar.gz https://github.com/miloyip/rapidjson/archive/v1.1.0.tar.gz | ||||||
| md5sum -c <<EOF | md5sum -c <<EOF | ||||||
| a24b4d6e671d97c8a2aacd0dd4677726  capnproto.tar.gz | 80c28dc26842b84dcbe8d930a97c70cf  capnproto.tar.gz | ||||||
| badd12c511e081fec6c89c43a7027bce  rapidjson.tar.gz | badd12c511e081fec6c89c43a7027bce  rapidjson.tar.gz | ||||||
| EOF | EOF | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -137,9 +137,6 @@ struct LaminarInterface { | |||||||
|     // Abort all running jobs
 |     // Abort all running jobs
 | ||||||
|     virtual void abortAll() = 0; |     virtual void abortAll() = 0; | ||||||
| 
 | 
 | ||||||
|     // Callback for laminar to reap child processes.
 |  | ||||||
|     virtual void reapChildren() = 0; |  | ||||||
| 
 |  | ||||||
|     // Callback to handle a configuration modification notification
 |     // Callback to handle a configuration modification notification
 | ||||||
|     virtual void notifyConfigChanged() = 0; |     virtual void notifyConfigChanged() = 0; | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -360,7 +360,6 @@ void Laminar::sendStatus(LaminarClient* client) { | |||||||
|     } |     } | ||||||
|     j.EndObject(); |     j.EndObject(); | ||||||
|     client->sendMessage(j.str()); |     client->sendMessage(j.str()); | ||||||
| 
 |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Laminar::~Laminar() { | Laminar::~Laminar() { | ||||||
| @ -501,21 +500,6 @@ std::shared_ptr<Run> Laminar::queueJob(std::string name, ParamMap params) { | |||||||
|     return run; |     return run; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Reaps a zombie and steps the corresponding Run to its next state.
 |  | ||||||
| // Should be called on SIGCHLD
 |  | ||||||
| void Laminar::reapChildren() { |  | ||||||
|     int ret = 0; |  | ||||||
|     pid_t pid; |  | ||||||
|     while((pid = waitpid(-1, &ret, WNOHANG)) > 0) { |  | ||||||
|         LLOG(INFO, "Reaping", pid); |  | ||||||
|         auto it = pids.find(pid); |  | ||||||
|         it->second->fulfill(kj::mv(ret)); |  | ||||||
|         pids.erase(it); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     assignNewJobs(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void Laminar::notifyConfigChanged() | void Laminar::notifyConfigChanged() | ||||||
| { | { | ||||||
|     loadConfiguration(); |     loadConfiguration(); | ||||||
| @ -688,6 +672,7 @@ bool Laminar::tryStartRun(std::shared_ptr<Run> run, int queueIndex) { | |||||||
|             // notify the rpc client if the start command was used
 |             // notify the rpc client if the start command was used
 | ||||||
|             run->started.fulfiller->fulfill(); |             run->started.fulfiller->fulfill(); | ||||||
| 
 | 
 | ||||||
|  |             // this actually spawns the first step
 | ||||||
|             srv->addTask(handleRunStep(run.get()).then([this,run]{ |             srv->addTask(handleRunStep(run.get()).then([this,run]{ | ||||||
|                 runFinished(run.get()); |                 runFinished(run.get()); | ||||||
|             })); |             })); | ||||||
| @ -716,11 +701,9 @@ kj::Promise<void> Laminar::handleRunStep(Run* run) { | |||||||
|         return kj::READY_NOW; |         return kj::READY_NOW; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     kj::Promise<int> exited = srv->onChildExit(run->current_pid); | ||||||
|     // promise is fulfilled when the process is reaped. But first we wait for all
 |     // promise is fulfilled when the process is reaped. But first we wait for all
 | ||||||
|     // output from the pipe (Run::output_fd) to be consumed.
 |     // output from the pipe (Run::output_fd) to be consumed.
 | ||||||
|     auto paf = kj::newPromiseAndFulfiller<int>(); |  | ||||||
|     pids.emplace(run->current_pid, kj::mv(paf.fulfiller)); |  | ||||||
| 
 |  | ||||||
|     return srv->readDescriptor(run->output_fd, [this,run](const char*b,size_t n){ |     return srv->readDescriptor(run->output_fd, [this,run](const char*b,size_t n){ | ||||||
|         // handle log output
 |         // handle log output
 | ||||||
|         std::string s(b, n); |         std::string s(b, n); | ||||||
| @ -729,7 +712,7 @@ kj::Promise<void> Laminar::handleRunStep(Run* run) { | |||||||
|             if(c->scope.wantsLog(run->name, run->build)) |             if(c->scope.wantsLog(run->name, run->build)) | ||||||
|                 c->sendMessage(s); |                 c->sendMessage(s); | ||||||
|         } |         } | ||||||
|     }).then([p = std::move(paf.promise)]() mutable { |     }).then([p = std::move(exited)]() mutable { | ||||||
|         // wait until the process is reaped
 |         // wait until the process is reaped
 | ||||||
|         return kj::mv(p); |         return kj::mv(p); | ||||||
|     }).then([this, run](int status){ |     }).then([this, run](int status){ | ||||||
| @ -791,7 +774,9 @@ void Laminar::runFinished(Run * r) { | |||||||
|         w->complete(r); |         w->complete(r); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // erase reference to run from activeJobs
 |     // erase reference to run from activeJobs. Since runFinished is called in a
 | ||||||
|  |     // lambda whose context contains a shared_ptr<Run>, the run won't be deleted
 | ||||||
|  |     // until the context is destroyed at the end of the lambda execution.
 | ||||||
|     activeJobs.byRunPtr().erase(r); |     activeJobs.byRunPtr().erase(r); | ||||||
| 
 | 
 | ||||||
|     // remove old run directories
 |     // remove old run directories
 | ||||||
| @ -813,6 +798,9 @@ void Laminar::runFinished(Run * r) { | |||||||
|             break; |             break; | ||||||
|         fs::remove_all(d); |         fs::remove_all(d); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     // in case we freed up an executor, check the queue
 | ||||||
|  |     assignNewJobs(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| class MappedFileImpl : public MappedFile { | class MappedFileImpl : public MappedFile { | ||||||
|  | |||||||
| @ -58,7 +58,6 @@ public: | |||||||
|     kj::Own<MappedFile> getArtefact(std::string path) override; |     kj::Own<MappedFile> getArtefact(std::string path) override; | ||||||
|     std::string getCustomCss() override; |     std::string getCustomCss() override; | ||||||
|     void abortAll() override; |     void abortAll() override; | ||||||
|     void reapChildren() override; |  | ||||||
|     void notifyConfigChanged() override; |     void notifyConfigChanged() override; | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
| @ -83,7 +82,6 @@ private: | |||||||
|     std::unordered_map<std::string, std::set<std::string>> jobTags; |     std::unordered_map<std::string, std::set<std::string>> jobTags; | ||||||
| 
 | 
 | ||||||
|     RunSet activeJobs; |     RunSet activeJobs; | ||||||
|     std::map<pid_t, kj::Own<kj::PromiseFulfiller<int>>> pids; |  | ||||||
|     Database* db; |     Database* db; | ||||||
|     Server* srv; |     Server* srv; | ||||||
|     NodeMap nodes; |     NodeMap nodes; | ||||||
|  | |||||||
| @ -18,8 +18,8 @@ | |||||||
| ///
 | ///
 | ||||||
| #include "laminar.h" | #include "laminar.h" | ||||||
| #include "log.h" | #include "log.h" | ||||||
| 
 |  | ||||||
| #include <signal.h> | #include <signal.h> | ||||||
|  | #include <kj/async-unix.h> | ||||||
| 
 | 
 | ||||||
| static Laminar* laminar; | static Laminar* laminar; | ||||||
| 
 | 
 | ||||||
| @ -35,9 +35,10 @@ int main(int argc, char** argv) { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     laminar = new Laminar; |     laminar = new Laminar; | ||||||
| 
 |     kj::UnixEventPort::captureChildExit(); | ||||||
|     signal(SIGINT, &laminar_quit); |     signal(SIGINT, &laminar_quit); | ||||||
|     signal(SIGTERM, &laminar_quit); |     signal(SIGTERM, &laminar_quit); | ||||||
|  | 
 | ||||||
|     laminar->run(); |     laminar->run(); | ||||||
| 
 | 
 | ||||||
|     delete laminar; |     delete laminar; | ||||||
|  | |||||||
| @ -139,7 +139,10 @@ void Run::addEnv(std::string path) { | |||||||
| void Run::abort() { | void Run::abort() { | ||||||
|     // clear all pending scripts
 |     // clear all pending scripts
 | ||||||
|     std::queue<Script>().swap(scripts); |     std::queue<Script>().swap(scripts); | ||||||
|     kill(-current_pid, SIGTERM); |     // if the Maybe is empty, wait() was already called on this process
 | ||||||
|  |     KJ_IF_MAYBE(p, current_pid) { | ||||||
|  |         kill(-*p, SIGTERM); | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Run::reaped(int status) { | void Run::reaped(int status) { | ||||||
|  | |||||||
| @ -84,7 +84,7 @@ public: | |||||||
|     std::string reasonMsg; |     std::string reasonMsg; | ||||||
|     uint build = 0; |     uint build = 0; | ||||||
|     std::string log; |     std::string log; | ||||||
|     pid_t current_pid; |     kj::Maybe<pid_t> current_pid; | ||||||
|     int output_fd; |     int output_fd; | ||||||
|     std::unordered_map<std::string, std::string> params; |     std::unordered_map<std::string, std::string> params; | ||||||
|     kj::Promise<void> timeout = kj::NEVER_DONE; |     kj::Promise<void> timeout = kj::NEVER_DONE; | ||||||
|  | |||||||
| @ -26,6 +26,7 @@ | |||||||
| #include <capnp/rpc-twoparty.h> | #include <capnp/rpc-twoparty.h> | ||||||
| #include <capnp/rpc.capnp.h> | #include <capnp/rpc.capnp.h> | ||||||
| #include <kj/async-io.h> | #include <kj/async-io.h> | ||||||
|  | #include <kj/async-unix.h> | ||||||
| #include <kj/threadlocal.h> | #include <kj/threadlocal.h> | ||||||
| 
 | 
 | ||||||
| #include <sys/eventfd.h> | #include <sys/eventfd.h> | ||||||
| @ -386,30 +387,12 @@ Server::Server(LaminarInterface& li, kj::StringPtr rpcBindAddress, | |||||||
|         return httpServer->listenHttp(*listener).attach(kj::mv(listener)); |         return httpServer->listenHttp(*listener).attach(kj::mv(listener)); | ||||||
|     })); |     })); | ||||||
| 
 | 
 | ||||||
|     // handle SIGCHLD
 |  | ||||||
|     { |  | ||||||
|         sigset_t mask; |  | ||||||
|         sigemptyset(&mask); |  | ||||||
|         sigaddset(&mask, SIGCHLD); |  | ||||||
|         sigprocmask(SIG_BLOCK, &mask, nullptr); |  | ||||||
|         int sigchld = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC); |  | ||||||
|         auto event = ioContext.lowLevelProvider->wrapInputFd(sigchld, kj::LowLevelAsyncIoProvider::TAKE_OWNERSHIP); |  | ||||||
|         auto buffer = kj::heapArrayBuilder<char>(PROC_IO_BUFSIZE); |  | ||||||
|         reapWatch = handleFdRead(event, buffer.asPtr().begin(), [this](const char* buf, size_t){ |  | ||||||
|             const struct signalfd_siginfo* siginfo = reinterpret_cast<const struct signalfd_siginfo*>(buf); |  | ||||||
|             KJ_ASSERT(siginfo->ssi_signo == SIGCHLD); |  | ||||||
|             laminarInterface.reapChildren(); |  | ||||||
|         }).attach(std::move(event)).attach(std::move(buffer)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // handle watched paths
 |     // handle watched paths
 | ||||||
|     { |     { | ||||||
|         inotify_fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); |         inotify_fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); | ||||||
|         auto event = ioContext.lowLevelProvider->wrapInputFd(inotify_fd, kj::LowLevelAsyncIoProvider::TAKE_OWNERSHIP); |         pathWatch = readDescriptor(inotify_fd, [this](const char*, size_t){ | ||||||
|         auto buffer = kj::heapArrayBuilder<char>(PROC_IO_BUFSIZE); |  | ||||||
|         pathWatch = handleFdRead(event, buffer.asPtr().begin(), [this](const char*, size_t){ |  | ||||||
|             laminarInterface.notifyConfigChanged(); |             laminarInterface.notifyConfigChanged(); | ||||||
|         }).attach(std::move(event)).attach(std::move(buffer)); |         }); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -438,7 +421,7 @@ void Server::start() { | |||||||
|     childTasks.onEmpty().wait(ioContext.waitScope); |     childTasks.onEmpty().wait(ioContext.waitScope); | ||||||
|     // 4. run the loop once more to send any pending output to websocket clients
 |     // 4. run the loop once more to send any pending output to websocket clients
 | ||||||
|     ioContext.waitScope.poll(); |     ioContext.waitScope.poll(); | ||||||
|     // 5. return: websockets will be destructed
 |     // 5. return: websockets will be destructed when class is deleted
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Server::stop() { | void Server::stop() { | ||||||
| @ -453,8 +436,7 @@ kj::Promise<void> Server::readDescriptor(int fd, std::function<void(const char*, | |||||||
|     return handleFdRead(event, buffer.asPtr().begin(), cb).attach(std::move(event)).attach(std::move(buffer)); |     return handleFdRead(event, buffer.asPtr().begin(), cb).attach(std::move(event)).attach(std::move(buffer)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Server::addTask(kj::Promise<void>&& task) | void Server::addTask(kj::Promise<void>&& task) { | ||||||
| { |  | ||||||
|     childTasks.add(kj::mv(task)); |     childTasks.add(kj::mv(task)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -464,6 +446,10 @@ kj::Promise<void> Server::addTimeout(int seconds, std::function<void ()> cb) { | |||||||
|     }).eagerlyEvaluate(nullptr); |     }).eagerlyEvaluate(nullptr); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | kj::Promise<int> Server::onChildExit(kj::Maybe<pid_t> &pid) { | ||||||
|  |     return ioContext.unixEventPort.onChildExit(pid); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void Server::addWatchPath(const char* dpath) { | void Server::addWatchPath(const char* dpath) { | ||||||
|     inotify_add_watch(inotify_fd, dpath, IN_ONLYDIR | IN_CLOSE_WRITE | IN_CREATE | IN_DELETE); |     inotify_add_watch(inotify_fd, dpath, IN_ONLYDIR | IN_CLOSE_WRITE | IN_CREATE | IN_DELETE); | ||||||
| } | } | ||||||
|  | |||||||
| @ -48,6 +48,8 @@ public: | |||||||
|     // add a one-shot timer callback
 |     // add a one-shot timer callback
 | ||||||
|     kj::Promise<void> addTimeout(int seconds, std::function<void()> cb); |     kj::Promise<void> addTimeout(int seconds, std::function<void()> cb); | ||||||
| 
 | 
 | ||||||
|  |     // get a promise which resolves when a child process exits
 | ||||||
|  |     kj::Promise<int> onChildExit(kj::Maybe<pid_t>& pid); | ||||||
|     // add a path to be watched for changes
 |     // add a path to be watched for changes
 | ||||||
|     void addWatchPath(const char* dpath); |     void addWatchPath(const char* dpath); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -31,7 +31,7 @@ protected: | |||||||
|     } |     } | ||||||
|     void wait() { |     void wait() { | ||||||
|         int state = -1; |         int state = -1; | ||||||
|         waitpid(run.current_pid, &state, 0); |         waitpid(run.current_pid.orDefault(0), &state, 0); | ||||||
|         run.reaped(state); |         run.reaped(state); | ||||||
|     } |     } | ||||||
|     void runAll() { |     void runAll() { | ||||||
|  | |||||||
| @ -66,7 +66,6 @@ public: | |||||||
|     MOCK_METHOD4(setParam, bool(std::string job, uint buildNum, std::string param, std::string value)); |     MOCK_METHOD4(setParam, bool(std::string job, uint buildNum, std::string param, std::string value)); | ||||||
|     MOCK_METHOD0(getCustomCss, std::string()); |     MOCK_METHOD0(getCustomCss, std::string()); | ||||||
|     MOCK_METHOD0(abortAll, void()); |     MOCK_METHOD0(abortAll, void()); | ||||||
|     MOCK_METHOD0(reapChildren, void()); |  | ||||||
|     MOCK_METHOD0(notifyConfigChanged, void()); |     MOCK_METHOD0(notifyConfigChanged, void()); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user