blob: f4f7aa631f679e784bf3c8561e0d3ae62f82f40e [file] [log] [blame]
// Copyright 2011 The Goma Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "auto_updater.h"
#include <vector>
#include "autolock_timer.h"
#include "callback.h"
#include "file_helper.h"
#include "glog/logging.h"
#include "ioutil.h"
#include "mypath.h"
#include "path.h"
#include "subprocess_task.h"
#include "threadpool_http_server.h"
#ifdef _WIN32
#include "posix_helper_win.h"
#endif
namespace devtools_goma {
AutoUpdater::AutoUpdater(const string& goma_ctl)
: dir_(GetMyDirectory()),
my_version_(-1),
pulled_version_(-1),
idle_counter_(-1),
server_(nullptr),
pull_closure_id_(ThreadpoolHttpServer::kInvalidClosureId),
subproc_(nullptr),
goma_ctl_(goma_ctl) {
ReadManifest(file::JoinPath(dir_, "MANIFEST"), &my_version_);
}
AutoUpdater::~AutoUpdater() {
Stop();
Wait();
}
void AutoUpdater::SetEnv(const char* envp[]) {
for (const char** p = envp; *p != nullptr; ++p) {
env_.push_back(*p);
}
}
void AutoUpdater::Start(ThreadpoolHttpServer* server, int count) {
if (my_version_ <= 0) {
LOG(INFO) << "no goma version, disable auto update";
return;
}
if (count <= 0) {
LOG(INFO) << "disable auto_updater.";
return;
}
if (access(file::JoinPath(dir_, "no_auto_update").c_str(), R_OK) == 0) {
LOG(INFO) << "no_auto_update exists, disable auto update";
return;
}
LOG(INFO) << "start autoupdate in " << count << " idle count.";
server_ = server;
idle_counter_ = count;
std::unique_ptr<PermanentClosure> pull_closure(
NewPermanentCallback(this, &AutoUpdater::CheckUpdate));
pull_closure_id_ = server_->RegisterIdleClosure(
ThreadpoolHttpServer::SOCKET_IPC, count, std::move(pull_closure));
}
void AutoUpdater::Stop() {
if (server_) {
server_->UnregisterIdleClosure(pull_closure_id_);
}
pull_closure_id_ = ThreadpoolHttpServer::kInvalidClosureId;
server_ = nullptr;
AUTOLOCK(lock, &mu_);
if (subproc_) {
subproc_->Kill();
}
}
void AutoUpdater::Wait() {
AUTOLOCK(lock, &mu_);
while (subproc_ != nullptr) {
cond_.Wait(&mu_);
}
}
bool AutoUpdater::ReadManifest(const string& path, int* version) {
string manifest;
if (!ReadFileToString(path.c_str(), &manifest))
return false;
static const int kVersionLen = strlen("VERSION=");
size_t version_start = manifest.find("VERSION=");
if (version_start == string::npos)
return false;
size_t version_end = manifest.find("\n", version_start + kVersionLen);
if (version_end == string::npos)
return false;
string version_str = manifest.substr(
version_start + kVersionLen,
version_end - (version_start + kVersionLen));
*version = atoi(version_str.c_str());
LOG(INFO) << "manifest " << path << " VERSION=" << *version;
return (*version > 0);
}
void AutoUpdater::CheckUpdate() {
{
AUTOLOCK(lock, &mu_);
if (subproc_ != nullptr)
return;
if (server_ == nullptr)
return;
}
int last_idle_counter =
server_->idle_counter(ThreadpoolHttpServer::SOCKET_IPC);
if (last_idle_counter < idle_counter_) {
LOG(WARNING) << "not idle:" << last_idle_counter << " < " << idle_counter_;
return;
}
StartGomaCtlPull();
}
void AutoUpdater::StartGomaCtlPull() {
CHECK(server_ != nullptr);
string goma_ctl = file::JoinPath(dir_, goma_ctl_);
std::vector<const char*> args;
args.push_back(goma_ctl.c_str());
args.push_back("pull");
args.push_back(nullptr);
SubProcessTask* subproc = nullptr;
{
AUTOLOCK(lock, &mu_);
if (subproc_ != nullptr)
return;
subproc_ = new SubProcessTask(
"auto_updater", goma_ctl.c_str(), const_cast<char**>(&args[0]));
subproc = subproc_;
}
SubProcessReq* req = subproc->mutable_req();
req->set_cwd(dir_);
req->set_stdout_filename(file::JoinPath(dir_, "goma_pull.out"));
req->set_stderr_filename(file::JoinPath(dir_, "goma_pull.err"));
for (size_t i = 0; i < env_.size(); ++i) {
req->add_env(env_[i]);
}
req->set_weight(SubProcessReq::HEAVY_WEIGHT);
req->set_priority(SubProcessReq::LOW_PRIORITY);
subproc->Start(NewCallback(this, &AutoUpdater::GomaCtlPullDone));
}
void AutoUpdater::GomaCtlPullDone() {
int status = -1;
{
AUTOLOCK(lock, &mu_);
if (subproc_ == nullptr)
return;
status = subproc_->terminated().status();
subproc_ = nullptr;
cond_.Signal();
}
if (status != 0) {
LOG(ERROR) << goma_ctl_ << " pull failed. exit=" << status;
return;
}
if (!ReadManifest(file::JoinPath(dir_, "latest/MANIFEST"),
&pulled_version_)) {
LOG(ERROR) << "failed to read latest/MANIFEST";
return;
}
if (my_version_ == pulled_version_) {
LOG(INFO) << "no update";
return;
}
if (my_version_ > pulled_version_) {
LOG(ERROR) << "Version downgrade? " << my_version_
<< "=>" << pulled_version_
<< " ignored";
return;
}
if (server_ == nullptr) {
LOG(ERROR) << "Auto updater already stopped.";
return;
}
// Check if server is still idle. If it processes some requests, postpone
// updating.
int last_idle_counter =
server_->idle_counter(ThreadpoolHttpServer::SOCKET_IPC);
if (last_idle_counter < idle_counter_) {
LOG(WARNING) << "not idle:" << last_idle_counter << " < " << idle_counter_;
return;
}
StartGomaCtlUpdate();
}
void AutoUpdater::StartGomaCtlUpdate() {
CHECK(server_ != nullptr);
LOG(INFO) << "Update version " << my_version_ << " to " << pulled_version_;
string goma_ctl = file::JoinPath(dir_, goma_ctl_);
std::vector<const char*> args;
args.push_back(goma_ctl.c_str());
args.push_back("update");
args.push_back(nullptr);
SubProcessTask* subproc = new SubProcessTask(
"auto_updater", goma_ctl.c_str(), const_cast<char**>(&args[0]));
SubProcessReq* req = subproc->mutable_req();
req->set_cwd(dir_);
req->set_stdout_filename(file::JoinPath(dir_, "goma_update.out"));
req->set_stderr_filename(file::JoinPath(dir_, "goma_update.err"));
for (size_t i = 0; i < env_.size(); ++i) {
req->add_env(env_[i]);
}
req->set_weight(SubProcessReq::HEAVY_WEIGHT);
req->set_priority(SubProcessReq::LOW_PRIORITY);
req->set_detach(true);
subproc->Start(nullptr);
// "goma_ctl.py update" runs in detached mode.
// subproc will be deleted in Start(), and never send feedback from
// subprocess_controller_server.
// In "goma_ctl.py update", it will stop the compiler_proxy, updates
// new binaries, and restart compiler_proxy again.
}
} // namespace devtools_goma