| // Copyright 2010 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. |
| |
| // |
| // Compiler proxy reimplemented as asynchronous. |
| |
| // #define HAVE_HEAP_PROFILER 1 |
| // #define HAVE_CPU_PROFILER 1 |
| |
| #include <stdio.h> |
| #include <time.h> |
| |
| #ifndef _WIN32 |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <signal.h> |
| #include <sys/file.h> |
| #include <sys/resource.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <unistd.h> |
| #endif |
| #ifdef __MACH__ |
| #include <sys/sysctl.h> |
| #endif |
| |
| #include <algorithm> |
| #include <iostream> |
| #include <iterator> |
| #include <memory> |
| #include <set> |
| #include <sstream> |
| #include <string> |
| #include <unordered_set> |
| #include <utility> |
| |
| #include "absl/memory/memory.h" |
| #include "absl/strings/ascii.h" |
| #include "absl/strings/match.h" |
| #include "absl/strings/str_replace.h" |
| #include "absl/strings/str_split.h" |
| #include "arfile_reader.h" |
| #include "auto_updater.h" |
| #include "autolock_timer.h" |
| #include "basictypes.h" |
| #include "breakpad.h" |
| #include "callback.h" |
| #include "compile_service.h" |
| #include "compile_stats.h" |
| #include "compiler_flags.h" |
| #include "compiler_flags_util.h" |
| #include "compiler_info.h" |
| #include "compiler_info_cache.h" |
| #include "compiler_proxy_contentionz_script.h" |
| #include "compiler_proxy_histogram.h" |
| #include "compiler_proxy_info.h" |
| #include "compiler_proxy_status_html5.h" |
| #include "compiler_proxy_status_script.h" |
| #include "compiler_proxy_status_style.h" |
| #include "compiler_specific.h" |
| #include "compilerz_html.h" |
| #include "compilerz_script.h" |
| #include "compilerz_style.h" |
| #include "counterz.h" |
| #include "cpp_directive_optimizer.h" |
| #include "cpp_macro.h" |
| #include "deps_cache.h" |
| #include "env_flags.h" |
| #include "file_hash_cache.h" |
| #include "file_helper.h" |
| #include "file_stat_cache.h" |
| #include "glog/logging.h" |
| #include "goma_file.h" |
| #include "goma_file_http.h" |
| #include "goma_hash.h" |
| #include "goma_init.h" |
| #include "hash_rewrite_parser.h" |
| #include "http.h" |
| #include "http_init.h" |
| #include "http_rpc.h" |
| #include "http_rpc_init.h" |
| #include "include_cache.h" |
| #include "include_file_finder.h" |
| #include "ioutil.h" |
| #include "java/jarfile_reader.h" |
| #include "jquery.min.h" |
| #include "list_dir_cache.h" |
| #include "local_output_cache.h" |
| #include "log_cleaner.h" |
| #include "log_service_client.h" |
| #include "multi_http_rpc.h" |
| #include "mypath.h" |
| #include "oauth2.h" |
| #include "oauth2_token.h" |
| #include "path.h" |
| #include "platform_thread.h" |
| MSVC_PUSH_DISABLE_WARNING_FOR_PROTO() |
| #include "prototmp/goma_data.pb.h" |
| MSVC_POP_WARNING() |
| #include "rand_util.h" |
| #include "scoped_fd.h" |
| #include "settings.h" |
| #include "socket_factory.h" |
| #include "subprocess.h" |
| #include "subprocess_controller.h" |
| #include "subprocess_controller_client.h" |
| #include "subprocess_option_setter.h" |
| #include "subprocess_task.h" |
| #include "threadpool_http_server.h" |
| #include "trustedipsmanager.h" |
| #include "util.h" |
| #include "watchdog.h" |
| #include "worker_thread_manager.h" |
| |
| |
| #if HAVE_HEAP_PROFILER |
| #include <gperftools/heap-profiler.h> |
| #endif |
| #if HAVE_CPU_PROFILER |
| #include <gperftools/profiler.h> |
| #endif |
| |
| using devtools_goma::CompileService; |
| using devtools_goma::ExecReq; |
| using devtools_goma::ExecResp; |
| using devtools_goma::ScopedFd; |
| using devtools_goma::ThreadpoolHttpServer; |
| |
| using std::string; |
| |
| #ifndef _WIN32 |
| using devtools_goma::Daemonize; |
| #endif |
| |
| namespace { |
| |
| #ifdef _WIN32 |
| |
| string FindLogFile(string log_dir, string base_name, string log_type) { |
| // Log file is in log_dir and its name is like |
| // <base_name>.<host_name>.<user_name>.log.<log_type>.<timestamp>.<pid> |
| const string pid = std::to_string(GetCurrentProcessId()); |
| |
| string pattern = log_dir; |
| pattern.append("\\"); |
| pattern.append(base_name); |
| pattern.append("*"); |
| |
| string found_file; |
| WIN32_FIND_DATAA find_data = {}; |
| HANDLE find_handle = FindFirstFileA(pattern.c_str(), &find_data); |
| if (find_handle != INVALID_HANDLE_VALUE) { |
| do { |
| if (absl::EndsWith(find_data.cFileName, pid) && |
| strstr(find_data.cFileName, log_type.c_str())) { |
| found_file = file::JoinPath(log_dir, find_data.cFileName); |
| break; |
| } |
| } while (FindNextFileA(find_handle, &find_data) != 0); |
| FindClose(find_handle); |
| } |
| return found_file; |
| } |
| |
| #endif |
| |
| } // anonymous namespace |
| |
| namespace devtools_goma { |
| |
| // This class is reused for every request. |
| class CompilerProxyHttpHandler : public ThreadpoolHttpServer::HttpHandler, |
| public ThreadpoolHttpServer::Monitor { |
| public: |
| CompilerProxyHttpHandler(string myname, |
| string setting, |
| string tmpdir, |
| WorkerThreadManager* wm) |
| : myname_(std::move(myname)), |
| setting_(std::move(setting)), |
| service_(wm, FLAGS_COMPILER_INFO_POOL), |
| log_cleaner_closure_id_(kInvalidPeriodicClosureId), |
| memory_tracker_closure_id_(kInvalidPeriodicClosureId), |
| rpc_sent_count_(0), |
| tmpdir_(std::move(tmpdir)) |
| #if HAVE_HEAP_PROFILER |
| , |
| compiler_proxy_heap_profile_file_(file::JoinPathRespectAbsolute( |
| tmpdir_, |
| FLAGS_COMPILER_PROXY_HEAP_PROFILE_FILE)) |
| #endif |
| #if HAVE_CPU_PROFILER |
| , |
| compiler_proxy_cpu_profile_file_(file::JoinPathRespectAbsolute( |
| tmpdir_, |
| FLAGS_COMPILER_PROXY_CPU_PROFILE_FILE)), |
| cpu_profiling_(false) |
| #endif |
| { |
| if (FLAGS_SEND_USER_INFO) { |
| service_.AllowToSendUserInfo(); |
| } |
| service_.SetActiveTaskThrottle(FLAGS_MAX_ACTIVE_TASKS); |
| service_.SetCompileTaskHistorySize( |
| FLAGS_MAX_FINISHED_TASKS, |
| FLAGS_MAX_FAILED_TASKS, |
| FLAGS_MAX_LONG_TASKS); |
| if (!FLAGS_HASH_REWRITE_RULE_FILE.empty()) { |
| string rewrite_rule; |
| CHECK(ReadFileToString(FLAGS_HASH_REWRITE_RULE_FILE.c_str(), |
| &rewrite_rule)) |
| << "You need rewrite rule in " |
| << FLAGS_HASH_REWRITE_RULE_FILE |
| << " or unset GOMA_HASH_REWRITE_RULE_FILE"; |
| std::map<string, string> mapping; |
| if (!ParseRewriteRule(rewrite_rule, &mapping)) { |
| LOG(ERROR) << "failed to parse rewrite rule in a file " |
| << FLAGS_HASH_REWRITE_RULE_FILE; |
| } else { |
| service_.SetHashRewriteRule(mapping); |
| } |
| } |
| int network_error_margin = 0; |
| if (FLAGS_FAIL_FAST) { |
| LOG(INFO) << "fail fast mode"; |
| if (FLAGS_ALLOWED_NETWORK_ERROR_DURATION < 0) { |
| FLAGS_ALLOWED_NETWORK_ERROR_DURATION = 60; |
| network_error_margin = 30; |
| LOG(INFO) << "override GOMA_ALLOWED_NETWORK_ERROR_DURATION to " |
| << FLAGS_ALLOWED_NETWORK_ERROR_DURATION << " secs"; |
| } else { |
| network_error_margin = FLAGS_ALLOWED_NETWORK_ERROR_DURATION / 2; |
| LOG(INFO) << "use GOMA_ALLOWED_NETWORK_ERROR_DURATION=" |
| << FLAGS_ALLOWED_NETWORK_ERROR_DURATION << " secs"; |
| } |
| if (FLAGS_MAX_ACTIVE_FAIL_FALLBACK_TASKS < 0) { |
| // TODO: consider using this for fail fallback caused by |
| // remote goma backend's execution failure not network error. |
| FLAGS_MAX_ACTIVE_FAIL_FALLBACK_TASKS = FLAGS_BURST_MAX_SUBPROCS; |
| LOG(INFO) << "override GOMA_MAX_ACTIVE_FAIL_FALLBACK_TASKS to " |
| << FLAGS_MAX_ACTIVE_FAIL_FALLBACK_TASKS; |
| if (FLAGS_ALLOWED_MAX_ACTIVE_FAIL_FALLBACK_DURATION < 0) { |
| // Prefer to show network failure to reaching max active fail |
| // fallback. If fail fallback is caused by network error, it is also |
| // counted as active fail fallbacks but people can easily understand |
| // the reason by seeing network failure. |
| FLAGS_ALLOWED_MAX_ACTIVE_FAIL_FALLBACK_DURATION = |
| FLAGS_ALLOWED_NETWORK_ERROR_DURATION + 10; |
| LOG(INFO) << "override " |
| << "FLAGS_ALLOWED_MAX_ACTIVE_FAIL_FALLBACK_DURATION_IN_SEC " |
| << "to " << FLAGS_ALLOWED_MAX_ACTIVE_FAIL_FALLBACK_DURATION |
| << " secs"; |
| } |
| } |
| } |
| http_options_.proxy_host_name = FLAGS_PROXY_HOST; |
| http_options_.proxy_port = FLAGS_PROXY_PORT; |
| HttpClient::Options http_options = http_options_; |
| InitHttpClientOptions(&http_options); |
| http_options.network_error_margin = network_error_margin; |
| if (FLAGS_NETWORK_ERROR_THRESHOLD_PERCENT >= 0 && |
| FLAGS_NETWORK_ERROR_THRESHOLD_PERCENT < 100) { |
| http_options.network_error_threshold_percent = |
| FLAGS_NETWORK_ERROR_THRESHOLD_PERCENT; |
| } |
| LOG_IF(ERROR, FLAGS_NETWORK_ERROR_THRESHOLD_PERCENT >= 100) |
| << "GOMA_NETWORK_ERROR_THRESHOLD_PERCENT must be less than 100: " |
| << FLAGS_NETWORK_ERROR_THRESHOLD_PERCENT; |
| if (FLAGS_BACKEND_SOFT_STICKINESS) { |
| string cookie; |
| if (FLAGS_BACKEND_SOFT_STICKINESS_REFRESH) { |
| cookie = GetRandomAlphanumeric(64); |
| } else { |
| ComputeDataHashKey(service_.username() + "@" + service_.nodename(), |
| &cookie); |
| } |
| http_options.cookie = "GomaClient=" + cookie; |
| } |
| std::unique_ptr<HttpClient> client(new HttpClient( |
| HttpClient::NewSocketFactoryFromOptions(http_options), |
| HttpClient::NewTLSEngineFactoryFromOptions(http_options), |
| http_options, wm)); |
| CHECK_GE(FLAGS_MAX_SUBPROCS, FLAGS_MAX_SUBPROCS_LOW); |
| CHECK_GE(FLAGS_MAX_SUBPROCS, FLAGS_MAX_SUBPROCS_HEAVY); |
| CHECK_GE(FLAGS_BURST_MAX_SUBPROCS, FLAGS_BURST_MAX_SUBPROCS_LOW); |
| CHECK_GE(FLAGS_BURST_MAX_SUBPROCS, FLAGS_BURST_MAX_SUBPROCS_HEAVY); |
| std::unique_ptr<SubProcessOptionSetter> option_setter( |
| new SubProcessOptionSetter( |
| FLAGS_MAX_SUBPROCS, |
| FLAGS_MAX_SUBPROCS_LOW, |
| FLAGS_MAX_SUBPROCS_HEAVY, |
| FLAGS_BURST_MAX_SUBPROCS, |
| FLAGS_BURST_MAX_SUBPROCS_LOW, |
| FLAGS_BURST_MAX_SUBPROCS_HEAVY)); |
| client->SetMonitor( |
| absl::make_unique<NetworkErrorMonitor>(option_setter.get())); |
| service_.SetSubProcessOptionSetter(std::move(option_setter)); |
| service_.SetMaxCompilerDisabledTasks(FLAGS_MAX_COMPILER_DISABLED_TASKS); |
| service_.SetHttpClient(std::move(client)); |
| |
| HttpRPC::Options http_rpc_options; |
| InitHttpRPCOptions(&http_rpc_options); |
| service_.SetHttpRPC( |
| absl::make_unique<HttpRPC>(service_.http_client(), http_rpc_options)); |
| |
| service_.SetExecServiceClient( |
| absl::make_unique<ExecServiceClient>(service_.http_rpc(), "/e")); |
| |
| MultiHttpRPC::Options multi_store_options; |
| multi_store_options.max_req_in_call = FLAGS_MULTI_STORE_IN_CALL; |
| multi_store_options.req_size_threshold_in_call = |
| FLAGS_MULTI_STORE_THRESHOLD_SIZE_IN_CALL; |
| multi_store_options.check_interval_ms = FLAGS_MULTI_STORE_PENDING_MS; |
| service_.SetMultiFileStore(absl::make_unique<MultiFileStore>( |
| |
| service_.http_rpc(), "/s", multi_store_options, wm)); |
| service_.SetFileServiceHttpClient(absl::make_unique<FileServiceHttpClient>( |
| |
| service_.http_rpc(), "/s", "/l", service_.multi_file_store())); |
| if (FLAGS_PROVIDE_INFO) |
| service_.SetLogServiceClient(absl::make_unique<LogServiceClient>( |
| |
| service_.http_rpc(), "/sl", FLAGS_NUM_LOG_IN_SAVE_LOG, |
| FLAGS_LOG_PENDING_MS, wm)); |
| ArFileReader::Register(); |
| JarFileReader::Register(); |
| service_.StartIncludeProcessorWorkers(FLAGS_INCLUDE_PROCESSOR_THREADS); |
| service_.SetNeedToSendContent(FLAGS_COMPILER_PROXY_STORE_FILE); |
| service_.SetNewFileThreshold(FLAGS_COMPILER_PROXY_NEW_FILE_THRESHOLD); |
| service_.SetEnableGchHack(FLAGS_ENABLE_GCH_HACK); |
| service_.SetUseRelativePathsInArgv(FLAGS_USE_RELATIVE_PATHS_IN_ARGV); |
| service_.SetCommandCheckLevel(FLAGS_COMMAND_CHECK_LEVEL); |
| if (FLAGS_HERMETIC == "off") { |
| service_.SetHermetic(false); |
| } else if (FLAGS_HERMETIC == "fallback") { |
| service_.SetHermetic(true); |
| service_.SetHermeticFallback(true); |
| } else if (FLAGS_HERMETIC == "error") { |
| service_.SetHermetic(true); |
| service_.SetHermeticFallback(false); |
| } else { |
| LOG(FATAL) << "Unknown hermetic mode: " << FLAGS_HERMETIC |
| << " should be one of \"off\", \"fallback\" or \"error\""; |
| } |
| service_.SetDontKillSubprocess(FLAGS_DONT_KILL_SUBPROCESS); |
| service_.SetMaxSubProcsPending(FLAGS_MAX_SUBPROCS_PENDING); |
| service_.SetLocalRunPreference(FLAGS_LOCAL_RUN_PREFERENCE); |
| service_.SetLocalRunForFailedInput(FLAGS_LOCAL_RUN_FOR_FAILED_INPUT); |
| service_.SetLocalRunDelayMsec(FLAGS_LOCAL_RUN_DELAY_MSEC); |
| service_.SetMaxSumOutputSize( |
| FLAGS_MAX_SUM_OUTPUT_SIZE_IN_MB * 1024 * 1024); |
| service_.SetStoreLocalRunOutput(FLAGS_STORE_LOCAL_RUN_OUTPUT); |
| service_.SetEnableRemoteLink(FLAGS_ENABLE_REMOTE_LINK); |
| service_.SetShouldFailForUnsupportedCompilerFlag( |
| FLAGS_FAIL_FOR_UNSUPPORTED_COMPILER_FLAGS); |
| service_.SetTmpDir(tmpdir_); |
| service_.SetAllowedNetworkErrorDuration( |
| FLAGS_ALLOWED_NETWORK_ERROR_DURATION); |
| service_.SetMaxActiveFailFallbackTasks( |
| FLAGS_MAX_ACTIVE_FAIL_FALLBACK_TASKS); |
| service_.SetAllowedMaxActiveFailFallbackDuration( |
| FLAGS_ALLOWED_MAX_ACTIVE_FAIL_FALLBACK_DURATION); |
| |
| std::vector<string> timeout_secs_str = ToVector( |
| absl::StrSplit(FLAGS_COMPILER_PROXY_RPC_TIMEOUT_SECS, |
| ',', |
| absl::SkipEmpty())); |
| std::vector<int> timeout_secs; |
| for (const auto& it : timeout_secs_str) |
| timeout_secs.push_back(atoi(it.c_str())); |
| service_.SetTimeoutSecs(timeout_secs); |
| |
| if (FLAGS_LOG_CLEAN_INTERVAL > 0) { |
| log_cleaner_.AddLogBasename(myname_); |
| log_cleaner_.AddLogBasename(myname_ + "-subproc"); |
| log_cleaner_.AddLogBasename("gomacc"); |
| log_cleaner_.AddLogBasename("cc"); |
| log_cleaner_.AddLogBasename("c++"); |
| log_cleaner_.AddLogBasename("gcc"); |
| log_cleaner_.AddLogBasename("g++"); |
| log_cleaner_.AddLogBasename("clang"); |
| log_cleaner_.AddLogBasename("clang++"); |
| log_cleaner_.AddLogBasename("goma_fetch"); |
| |
| std::unique_ptr<PermanentClosure> closure = NewPermanentCallback( |
| this, &CompilerProxyHttpHandler::RunCleanOldLogs); |
| closure->Run(); |
| log_cleaner_closure_id_ = wm->RegisterPeriodicClosure( |
| FROM_HERE, FLAGS_LOG_CLEAN_INTERVAL * 1000, std::move(closure)); |
| } else { |
| LOG(INFO) << "log cleaner disabled"; |
| } |
| |
| if (FLAGS_MEMORY_TRACK_INTERVAL > 0) { |
| memory_tracker_closure_id_ = wm->RegisterPeriodicClosure( |
| FROM_HERE, FLAGS_MEMORY_TRACK_INTERVAL * 1000, |
| NewPermanentCallback(this, |
| &CompilerProxyHttpHandler::RunTrackMemory)); |
| } else { |
| LOG(INFO) << "memory tracker disabled"; |
| } |
| |
| InitialPing(); |
| |
| http_handlers_.insert( |
| std::make_pair("/", |
| &CompilerProxyHttpHandler::HandleStatusRequest)); |
| internal_http_handlers_.insert( |
| std::make_pair("/static/jquery.min.js", |
| &CompilerProxyHttpHandler::HandleJQuery)); |
| internal_http_handlers_.insert( |
| std::make_pair("/static/compiler_proxy_status_script.js", |
| &CompilerProxyHttpHandler::HandleStatusJavaScript)); |
| internal_http_handlers_.insert( |
| std::make_pair("/static/compiler_proxy_contentionz_script.js", |
| &CompilerProxyHttpHandler::HandleContentionzJavaScript)); |
| internal_http_handlers_.insert( |
| std::make_pair("/static/compiler_proxy_status_style.css", |
| &CompilerProxyHttpHandler::HandleStatusCSS)); |
| internal_http_handlers_.insert( |
| std::make_pair("/static/compilerz.js", |
| &CompilerProxyHttpHandler::HandleCompilerzScript)); |
| internal_http_handlers_.insert( |
| std::make_pair("/static/compilerz.css", |
| &CompilerProxyHttpHandler::HandleCompilerzStyle)); |
| internal_http_handlers_.insert( |
| std::make_pair("/api/taskz", |
| &CompilerProxyHttpHandler::HandleTaskRequest)); |
| internal_http_handlers_.insert( |
| std::make_pair("/api/accountz", |
| &CompilerProxyHttpHandler::HandleAccountRequest)); |
| internal_http_handlers_.insert( |
| std::make_pair("/api/compilerz", |
| &CompilerProxyHttpHandler::HandleCompilerJSONRequest)); |
| internal_http_handlers_.insert( |
| std::make_pair("/api/loginz", |
| &CompilerProxyHttpHandler::HandleLoginRequest)); |
| internal_http_handlers_.insert( |
| std::make_pair("/api/authz", |
| &CompilerProxyHttpHandler::HandleAuthRequest)); |
| internal_http_handlers_.insert( |
| std::make_pair("/api/logoutz", |
| &CompilerProxyHttpHandler::HandleLogoutRequest)); |
| http_handlers_.insert( |
| std::make_pair("/statz", |
| &CompilerProxyHttpHandler::HandleStatsRequest)); |
| http_handlers_.insert( |
| std::make_pair("/compilerz", |
| &CompilerProxyHttpHandler::HandleCompilerzRequest)); |
| http_handlers_.insert( |
| std::make_pair("/histogramz", |
| &CompilerProxyHttpHandler::HandleHistogramRequest)); |
| http_handlers_.insert( |
| std::make_pair("/httprpcz", |
| &CompilerProxyHttpHandler::HandleHttpRpcRequest)); |
| http_handlers_.insert( |
| std::make_pair("/threadz", |
| &CompilerProxyHttpHandler::HandleThreadRequest)); |
| http_handlers_.insert( |
| std::make_pair("/contentionz", |
| &CompilerProxyHttpHandler::HandleContentionRequest)); |
| http_handlers_.insert( |
| std::make_pair("/filecachez", |
| &CompilerProxyHttpHandler::HandleFileCacheRequest)); |
| http_handlers_.insert( |
| std::make_pair("/compilerinfoz", |
| &CompilerProxyHttpHandler::HandleCompilerInfoRequest)); |
| http_handlers_.insert( |
| std::make_pair("/includecachez", |
| &CompilerProxyHttpHandler::HandleIncludeCacheRequest)); |
| http_handlers_.insert( |
| std::make_pair("/flagz", |
| &CompilerProxyHttpHandler::HandleFlagRequest)); |
| http_handlers_.insert( |
| std::make_pair("/versionz", |
| &CompilerProxyHttpHandler::HandleVersionRequest)); |
| http_handlers_.insert( |
| std::make_pair("/healthz", |
| &CompilerProxyHttpHandler::HandleHealthRequest)); |
| internal_http_handlers_.insert( |
| std::make_pair("/portz", |
| &CompilerProxyHttpHandler::HandlePortRequest)); |
| http_handlers_.insert( |
| std::make_pair("/logz", |
| &CompilerProxyHttpHandler::HandleLogRequest)); |
| http_handlers_.insert( |
| std::make_pair("/errorz", |
| &CompilerProxyHttpHandler::HandleErrorStatusRequest)); |
| #if HAVE_COUNTERZ |
| http_handlers_.insert( |
| std::make_pair("/counterz", |
| &CompilerProxyHttpHandler::HandleCounterRequest)); |
| #endif |
| #if HAVE_HEAP_PROFILER |
| http_handlers_.insert( |
| std::make_pair("/heapz", |
| &CompilerProxyHttpHandler::HandleHeapRequest)); |
| #endif |
| #if HAVE_CPU_PROFILER |
| http_handlers_.insert( |
| std::make_pair("/profilez", |
| &CompilerProxyHttpHandler::HandleProfileRequest)); |
| #endif |
| } |
| |
| ~CompilerProxyHttpHandler() override { |
| } |
| |
| // TODO: better handling of HTTP errors. |
| // might be ok to retry soon on timeout but might not be |
| // good to retry soon for 4xx or 5xx status code. |
| bool InitialPing() { |
| int http_status_code = -1; |
| time_t ping_end_time = time(nullptr) + FLAGS_PING_TIMEOUT_SEC; |
| int num_retry = 0; |
| int backoff_ms = service_.http_client()->options().min_retry_backoff_ms; |
| while (time(nullptr) < ping_end_time) { |
| HttpRPC::Status status; |
| status.timeout_secs.push_back(FLAGS_PING_RETRY_INTERVAL); |
| status.trace_id = "ping"; |
| http_status_code = service_.http_rpc()->Ping(service_.wm(), |
| "/ping", &status); |
| if ((http_status_code != -1 && http_status_code != 0 && |
| http_status_code != 401 && http_status_code != 408 && |
| http_status_code / 100 != 5) || |
| // Since SocketPool retries connections and it should be natural |
| // to assume that IP address that did not respond well would not |
| // respond well for a while, we can think connection failure |
| // as non-retryable error. |
| !status.connect_success) { |
| LOG(INFO) << "will not retry." |
| << " http_status_code=" << http_status_code |
| << " connect_success=" << status.connect_success |
| << " finished=" << status.finished |
| << " err=" << status.err; |
| break; |
| } |
| // Retry for HTTP status 401 only if OAuth2 is valid. |
| // When OAuth2 is enabled, but not valid (i.e. no refresh token), |
| // it would fail with 401 and no need to retry. |
| // b/68980193 |
| if (http_status_code == 401 && |
| !service_.http_client()->options().oauth2_config.valid()) { |
| LOG(INFO) << "will not retry for auth failure without valid OAuth2." |
| << " http_status_code=" << http_status_code |
| << " connect_success=" << status.connect_success |
| << " finished=" << status.finished |
| << " err=" << status.err; |
| break; |
| } |
| if (http_status_code == 401 || http_status_code / 100 == 5) { |
| // retry after backoff_ms. |
| backoff_ms = HttpClient::BackoffMsec( |
| service_.http_client()->options(), |
| backoff_ms, true); |
| LOG(INFO) << "backoff " << backoff_ms << " msec" |
| << " because of http_status_code=" << http_status_code; |
| PlatformThread::Sleep(backoff_ms); |
| } |
| LOG(ERROR) << "Going to retry ping." |
| << " http_status_code=" << http_status_code |
| << " num_retry=" << num_retry; |
| num_retry++; |
| } |
| if (http_status_code != 200) { |
| LOG(ERROR) << "HTTP error=" << http_status_code |
| << ": Cannot connect to server at " |
| << service_.http_client()->options().RequestURL("/ping") |
| << " num_retry=" << num_retry; |
| if (http_status_code == 401) { |
| // TODO: fix this message for external users. |
| LOG(ERROR) |
| << "Please use OAuth2 to access from non-corp network."; |
| } |
| return false; |
| } |
| return true; |
| } |
| |
| void HandleHttpRequest( |
| ThreadpoolHttpServer::HttpServerRequest* http_server_request) override { |
| const string& path = http_server_request->req_path(); |
| if (service_.compiler_proxy_id_prefix().empty()) { |
| std::ostringstream ss; |
| ss << service_.username() << "@" << service_.nodename() << ":" |
| << http_server_request->server().port() |
| << "/" << service_.start_time() << "/"; |
| if (FLAGS_SEND_USER_INFO) { |
| service_.SetCompilerProxyIdPrefix(ss.str()); |
| } else { |
| string hash; |
| ComputeDataHashKey(ss.str(), &hash); |
| std::ostringstream sss; |
| sss << "anonymous@" << hash << ":8088/" << service_.start_time() << "/"; |
| service_.SetCompilerProxyIdPrefix(sss.str()); |
| } |
| } |
| #ifdef _WIN32 |
| if (path == "/me") { |
| if (!http_server_request->CheckCredential()) { |
| SendErrorMessage(http_server_request, 401, "Unauthorized"); |
| return; |
| } |
| CompileService::MultiRpcController* rpc |
| = new CompileService::MultiRpcController( |
| service_.wm(), http_server_request); |
| MultiExecReq multi_exec; |
| if (!rpc->ParseRequest(&multi_exec)) { |
| delete rpc; |
| SendErrorMessage(http_server_request, 404, "Bad request"); |
| return; |
| } |
| for (int i = 0; i < multi_exec.req_size(); ++i) { |
| if (ShouldTrace()) { |
| VLOG(1) << "Setting Trace on this request"; |
| multi_exec.mutable_req(i)->set_trace(true); |
| } else { |
| multi_exec.mutable_req(i)->set_trace(false); |
| } |
| service_.Exec(rpc->rpc(i), &multi_exec.req(i), rpc->mutable_resp(i), |
| NewCallback( |
| this, |
| &CompilerProxyHttpHandler::ExecDoneInMulti, rpc, i)); |
| } |
| return; |
| } |
| #endif |
| if (path == "/e") { |
| if (!http_server_request->CheckCredential()) { |
| SendErrorMessage(http_server_request, 401, "Unauthorized"); |
| return; |
| } |
| CompileService::RpcController* rpc |
| = new CompileService::RpcController(http_server_request); |
| ExecReq req; |
| if (!rpc->ParseRequest(&req)) { |
| delete rpc; |
| SendErrorMessage(http_server_request, 404, "Bad request"); |
| return; |
| } |
| if (ShouldTrace()) { |
| VLOG(1) << "Setting Trace on this request"; |
| req.set_trace(true); |
| } else { |
| req.set_trace(false); |
| } |
| |
| ExecResp* resp = new ExecResp; |
| // rpc and resp will be deleted in ExecDone. |
| service_.Exec(rpc, &req, resp, |
| devtools_goma::NewCallback( |
| this, &CompilerProxyHttpHandler::ExecDone, rpc, resp)); |
| return; |
| } |
| |
| // Most paths will be accessed by browser, so checked by IsTrusted(). |
| if (http_server_request->IsTrusted()) { |
| HttpHandlerMethod handler = nullptr; |
| std::map<string, HttpHandlerMethod>::const_iterator found = |
| internal_http_handlers_.find(path); |
| if (found != internal_http_handlers_.end()) { |
| handler = found->second; |
| } else if ((found = http_handlers_.find(path)) != http_handlers_.end()) { |
| handler = found->second; |
| // Users are checking the console... This would be a good |
| // timing for flushing logs. |
| devtools_goma::FlushLogFiles(); |
| } |
| if (handler != nullptr) { |
| string response; |
| int responsecode = (this->*handler)(*http_server_request, |
| &response); |
| if (response.empty()) { |
| if (responsecode == 404) { |
| response = "HTTP/1.1 404 Not Found\r\n\r\n"; |
| } else { |
| LOG(FATAL) << "Response is empty and unknown response code: " |
| << responsecode; |
| } |
| } |
| http_server_request->SendReply(response); |
| http_server_request = nullptr; |
| } else if (path == "/quitquitquit") { |
| DumpStatsToInfoLog(); |
| service_.wm()->DebugLog(); |
| DumpHistogramToInfoLog(); |
| DumpIncludeCacheLogToInfoLog(); |
| DumpContentionLogToInfoLog(); |
| DumpStatsProto(); |
| DumpCounterz(); |
| DumpDirectiveOptimizer(); |
| LOG(INFO) << "Dump done."; |
| devtools_goma::FlushLogFiles(); |
| http_server_request->SendReply("HTTP/1.1 200 OK\r\n\r\nquit!"); |
| http_server_request = nullptr; |
| service_.Quit(); |
| } else if (path == "/abortabortabort") { |
| http_server_request->SendReply("HTTP/1.1 200 OK\r\n\r\nquit!"); |
| http_server_request = nullptr; |
| service_.ClearTasks(); |
| exit(1); |
| } else { |
| http_server_request->SendReply("HTTP/1.1 404 Not found\r\n\r\n"); |
| http_server_request = nullptr; |
| } |
| } else { |
| http_server_request->SendReply("HTTP/1.1 404 Not found\r\n\r\n"); |
| http_server_request = nullptr; |
| } |
| } |
| |
| bool shutting_down() override { |
| return service_.quit(); |
| } |
| |
| void FinishHandle(const ThreadpoolHttpServer::Stat& stat) override { |
| service_.histogram()->UpdateThreadpoolHttpServerStat(stat); |
| } |
| |
| void Wait() { |
| if (memory_tracker_closure_id_ != kInvalidPeriodicClosureId) { |
| service_.wm()->UnregisterPeriodicClosure(memory_tracker_closure_id_); |
| memory_tracker_closure_id_ = kInvalidPeriodicClosureId; |
| } |
| if (log_cleaner_closure_id_ != kInvalidPeriodicClosureId) { |
| service_.wm()->UnregisterPeriodicClosure(log_cleaner_closure_id_); |
| log_cleaner_closure_id_ = kInvalidPeriodicClosureId; |
| } |
| service_.Wait(); |
| } |
| |
| // Takes ownership of auto_upadter. |
| void SetAutoUpdater(std::unique_ptr<AutoUpdater> auto_updater) { |
| service_.SetAutoUpdater(std::move(auto_updater)); |
| } |
| |
| // Takes ownership of watchdog. |
| void SetWatchdog(std::unique_ptr<Watchdog> watchdog, |
| const std::vector<string>& goma_ipc_env, |
| ThreadpoolHttpServer* server, |
| int count) { |
| service_.SetWatchdog(std::move(watchdog), goma_ipc_env); |
| service_.WatchdogStart(server, count); |
| } |
| |
| void TrackMemoryOneshot() { |
| TrackMemory(); |
| } |
| |
| private: |
| typedef ThreadpoolHttpServer::HttpServerRequest HttpServerRequest; |
| |
| typedef int (CompilerProxyHttpHandler::*HttpHandlerMethod)( |
| const HttpServerRequest& request, string* response); |
| |
| void OutputOkHeader(const char* content_type, std::ostringstream* ss) { |
| *ss << "HTTP/1.1 200 OK\r\n" |
| << "Content-Type: " << content_type << "\r\n\r\n"; |
| } |
| |
| int Redirect(const string& url, string* response) { |
| std::ostringstream ss; |
| ss << "HTTP/1.1 302 Found\r\n" |
| << "Location: " << url << "\r\n" |
| << "\r\n"; |
| *response = ss.str(); |
| return 302; |
| } |
| |
| int BadRequest(string* response) { |
| *response = "HTTP/1.1 400 Bad Request\r\n\r\n"; |
| return 400; |
| } |
| |
| void OutputOkHeaderAndBody(const char* content_type, |
| absl::string_view content, |
| std::ostringstream* ss) { |
| *ss << "HTTP/1.1 200 OK\r\n" |
| << "Content-Type: " << content_type << "\r\n" |
| << "Content-Length: " << content.size() << "\r\n\r\n" |
| << content; |
| } |
| |
| int HandleStatusRequest(const HttpServerRequest& request, string* response) { |
| return HandleStatusRequestHtml( |
| request, |
| string(compiler_proxy_status_html5_html_start, |
| compiler_proxy_status_html5_html_size), |
| response); |
| } |
| |
| int HandleCompilerzRequest(const HttpServerRequest& request, |
| string* response) { |
| std::ostringstream ss; |
| OutputOkHeaderAndBody("text/html; charset=utf-8", |
| absl::string_view(compilerz_html_html_start, |
| compilerz_html_html_size), |
| &ss); |
| *response = ss.str(); |
| return 200; |
| } |
| |
| int HandleCompilerzScript(const HttpServerRequest& request, |
| string* response) { |
| std::ostringstream ss; |
| OutputOkHeaderAndBody("text/javascript; charset=utf-8", |
| absl::string_view(compilerz_script_js_start, |
| compilerz_script_js_size), |
| &ss); |
| *response = ss.str(); |
| return 200; |
| } |
| |
| int HandleCompilerzStyle(const HttpServerRequest& request, |
| string* response) { |
| std::ostringstream ss; |
| OutputOkHeaderAndBody("text/css; charset=utf-8", |
| absl::string_view(compilerz_style_css_start, |
| compilerz_style_css_size), |
| &ss); |
| *response = ss.str(); |
| return 200; |
| } |
| |
| int HandleJQuery(const HttpServerRequest& request, |
| string* response) { |
| std::ostringstream ss; |
| OutputOkHeaderAndBody("text/javascript; charset=utf-8", |
| absl::string_view(jquery_min_js_start, |
| jquery_min_js_size), |
| &ss); |
| |
| *response = ss.str(); |
| return 200; |
| } |
| |
| |
| int HandleStatusJavaScript(const HttpServerRequest& request, |
| string* response) { |
| std::ostringstream ss; |
| OutputOkHeaderAndBody( |
| "text/javascript; charset=utf-8", |
| absl::string_view(compiler_proxy_status_script_js_start, |
| compiler_proxy_status_script_js_size), |
| &ss); |
| *response = ss.str(); |
| return 200; |
| } |
| |
| int HandleContentionzJavaScript(const HttpServerRequest& request, |
| string* response) { |
| std::ostringstream ss; |
| OutputOkHeaderAndBody("text/javascript; charset=utf-8", |
| absl::string_view( |
| compiler_proxy_contentionz_script_js_start, |
| compiler_proxy_contentionz_script_js_size), |
| &ss); |
| *response = ss.str(); |
| return 200; |
| } |
| |
| int HandleStatusCSS(const HttpServerRequest& request, |
| string* response) { |
| std::ostringstream ss; |
| OutputOkHeaderAndBody( |
| "text/css; charset=utf-8", |
| absl::string_view(compiler_proxy_status_style_css_start, |
| compiler_proxy_status_style_css_size), |
| &ss); |
| *response = ss.str(); |
| return 200; |
| } |
| |
| // Helper function for HandleStatusRequest() and HandleStatusRequestOld(). |
| int HandleStatusRequestHtml(const HttpServerRequest& request, |
| const string& original_status, string* response) { |
| std::ostringstream endpoints; |
| GetEndpoints(&endpoints); |
| std::ostringstream global_info; |
| GetGlobalInfo(request, &global_info); |
| string status = absl::StrReplaceAll( |
| original_status, |
| {{ "{{ENDPOINTS}}", endpoints.str() }, |
| { "{{GLOBAL_INFO}}", global_info.str() }, |
| } |
| ); |
| |
| std::ostringstream ss; |
| ss << "HTTP/1.1 200 OK\r\n"; |
| ss << "Content-Type: text/html; charset=utf-8\r\n"; |
| ss << "Content-Length: " << status.size() << "\r\n"; |
| ss << "\r\n"; |
| |
| ss << status; |
| *response = ss.str(); |
| return 200; |
| } |
| |
| void GetEndpoints(std::ostringstream* ss) { |
| for (const auto& iter : http_handlers_) { |
| if (absl::StartsWith(iter.first, "/api/")) |
| continue; |
| *ss << "<a href='" << iter.first << "'>" << iter.first << "</a>"; |
| *ss << " "; |
| } |
| } |
| |
| void GetGlobalInfo(const HttpServerRequest& request, std::ostringstream* ss) { |
| static const char kBr[] = "<br>"; |
| |
| *ss << "<table width=100%>"; |
| *ss << "<tr>"; |
| |
| *ss << "<td>"; |
| *ss << "CompilerProxyIdPrefix: " << service_.compiler_proxy_id_prefix() |
| << kBr; |
| |
| char ctime_buf[30]; |
| const time_t start_time = service_.start_time(); |
| #ifndef _WIN32 |
| ctime_r(&start_time, ctime_buf); |
| #else |
| ctime_s(ctime_buf, 30, &start_time); |
| #endif |
| int uptime = static_cast<int>(time(nullptr) - start_time); |
| int upsec = uptime % 60; |
| int upmin = (uptime / 60) % 60; |
| int uphour = (uptime / 60 / 24); |
| *ss << "Started: " << ctime_buf << " -- up " |
| << uphour << " hr " << upmin << " min "<< upsec << " sec" << kBr; |
| |
| *ss << "Built on " << kBuiltTimeString << kBr; |
| |
| *ss << "Built at " << kBuiltUserNameString << '@' |
| << kBuiltHostNameString << ':' << kBuiltDirectoryString << kBr; |
| |
| *ss << "Built from changelist " << kBuiltRevisionString << kBr; |
| #ifndef NDEBUG |
| *ss << "WARNING: DEBUG BINARY -- Performance may suffer" << kBr; |
| #endif |
| #ifdef ADDRESS_SANITIZER |
| *ss << "WARNING: ASAN BINARY -- Performance may suffer" << kBr; |
| #endif |
| |
| *ss << "PID is " << Getpid() << kBr; |
| |
| *ss << "</td>"; |
| |
| *ss << "<td align=right valign=top>"; |
| |
| *ss << "Running on " |
| << service_.username() << "@" << service_.nodename() << ":" |
| << request.server().port(); |
| if (!request.server().un_socket_name().empty()) { |
| *ss << " + " << request.server().un_socket_name(); |
| } |
| *ss << kBr; |
| |
| *ss << "Running at " << GetCurrentDirNameOrDie() << kBr; |
| |
| // TODO: Process size from /proc/self/stat for linux. |
| |
| // TODO: Links to /proc. |
| |
| *ss << "Log files: " |
| << "<a href=\"/logz?INFO\">INFO</a> " |
| << "<a href=\"/logz?WARNING\">WARNING</a> " |
| << "<a href=\"/logz?ERROR\">ERROR</a>" << kBr; |
| #ifndef _WIN32 |
| *ss << "Log files(subproc): " |
| << "<a href=\"/logz?subproc-INFO\">INFO</a> " |
| << "<a href=\"/logz?subproc-WARNING\">WARNING</a> " |
| << "<a href=\"/logz?subproc-ERROR\">ERROR</a>" << kBr; |
| #endif |
| |
| |
| *ss << "</td>"; |
| |
| *ss << "</tr>"; |
| *ss << "</table>"; |
| } |
| |
| int HandleTaskRequest(const HttpServerRequest& request, string* response) { |
| std::ostringstream ss; |
| if (request.method() != "POST") { |
| // Check for cross-site script inclusion (XSSI). |
| const string content = |
| "unacceptable http method:" + request.method() + "\r\n"; |
| ss << "HTTP/1.1 405 Method Not Allowed\r\n"; |
| ss << "Content-Type: text/plain\r\n"; |
| ss << "Content-Length: " << content.size() << "\r\n"; |
| ss << "\r\n"; |
| ss << content; |
| *response = ss.str(); |
| return 405; |
| } |
| if (!FLAGS_API_TASKZ_FILE_FOR_TEST.empty()) { |
| string content; |
| CHECK(ReadFileToString(FLAGS_API_TASKZ_FILE_FOR_TEST, &content)) |
| << FLAGS_API_TASKZ_FILE_FOR_TEST; |
| OutputOkHeaderAndBody("application/json", content, &ss); |
| *response = ss.str(); |
| return 200; |
| } |
| const string& query = request.query(); |
| std::map<string, string> params = ParseQuery(query); |
| auto p = params.find("id"); |
| if (p != params.end()) { |
| const string& task_id_str = p->second; |
| int task_id = atoi(task_id_str.c_str()); |
| |
| if (params["dump"] == "req") { |
| if (!service_.DumpTaskRequest(task_id)) { |
| ss << "HTTP/1.1 404 Not found\r\n"; |
| ss << "\r\n"; |
| *response = ss.str(); |
| return 404; |
| } |
| OutputOkHeader("text/plain", &ss); |
| *response = ss.str(); |
| return 200; |
| } |
| |
| string json; |
| if (!service_.DumpTask(task_id, &json)) { |
| ss << "HTTP/1.1 404 Not found\r\n"; |
| ss << "\r\n"; |
| *response = ss.str(); |
| return 404; |
| } |
| OutputOkHeaderAndBody("application/json", json, &ss); |
| *response = ss.str(); |
| return 200; |
| } |
| long long after = 0; |
| p = params.find("after"); |
| if (p != params.end()) { |
| const string& after_str = p->second; |
| #ifndef _WIN32 |
| after = strtoll(after_str.c_str(), nullptr, 10); |
| #else |
| after = _atoi64(after_str.c_str()); |
| #endif |
| } |
| OutputOkHeader("application/json", &ss); |
| Json::Value json; |
| service_.DumpToJson(&json, after); |
| ss << json; |
| *response = ss.str(); |
| return 200; |
| } |
| |
| int HandleAccountRequest(const HttpServerRequest& /* req */, |
| string* response) { |
| std::ostringstream ss; |
| OutputOkHeader("application/json", &ss); |
| ss << "{"; |
| ss << "\"status\": " |
| << EscapeString(service_.http_client()->GetHealthStatusMessage()); |
| const string& account = service_.http_client()->GetAccount(); |
| if (!account.empty()) { |
| ss << ", \"account\": " << EscapeString(account); |
| } |
| OAuth2Config config; |
| service_.http_client()->GetOAuth2Config(&config); |
| if (config.enabled()) { |
| if (config.refresh_token.empty()) { |
| ss << ", \"text\": \"login\"" |
| << ", \"href\": \"/api/loginz\""; |
| } else if (account.empty()) { |
| // even if refresh_token exists, account is empty. |
| // maybe, bad oauth2 setup. |
| ss << ", \"text\": \"bad oauth2 config - login\"" |
| << ", \"href\": \"/api/loginz\""; |
| } |
| // TODO: add logout. |
| } else { |
| LOG(WARNING) << "oauth2 config disabled"; |
| } |
| ss << "}"; |
| *response = ss.str(); |
| return 200; |
| } |
| |
| int HandleLoginRequest(const HttpServerRequest& request, |
| string* response) { |
| OAuth2Config config; |
| service_.http_client()->GetOAuth2Config(&config); |
| if (config.valid()) { |
| const string& account = service_.http_client()->GetAccount(); |
| if (!account.empty()) { |
| // already login. |
| return BadRequest(response); |
| } |
| // bad oauth2 config? |
| } |
| // TODO: limit access? |
| LOG(INFO) << "start login"; |
| DefaultOAuth2Config(&config); |
| SaveOAuth2Config(FLAGS_OAUTH2_CONFIG_FILE, config); |
| service_.http_client()->SetOAuth2Config(config); |
| string login_state; |
| string redirect_uri; |
| NewLoginState(request.server().port(), &login_state, &redirect_uri); |
| std::ostringstream url; |
| url << config.auth_uri << "?scope=" << config.scope |
| << "&redirect_uri=" << redirect_uri |
| << "&client_id=" << config.client_id |
| << "&state=" << login_state |
| << "&response_type=code"; |
| return Redirect(url.str(), response); |
| } |
| |
| int HandleAuthRequest(const HttpServerRequest& request, |
| string* response) { |
| const string& query = request.query(); |
| std::map<string, string> params = ParseQuery(query); |
| if (!CheckLoginState(params["state"])) { |
| LOG(WARNING) << "login state mismatch:" << params["state"]; |
| return BadRequest(response); |
| } |
| const string& code = params["code"]; |
| if (code.empty()) { |
| LOG(WARNING) << "missing code:" << query; |
| return BadRequest(response); |
| } |
| LOG(INFO) << "got auth code"; |
| OAuth2Config config; |
| service_.http_client()->GetOAuth2Config(&config); |
| if (config.valid()) { |
| const string& account = service_.http_client()->GetAccount(); |
| if (!account.empty()) { |
| // already login. |
| return BadRequest(response); |
| } |
| } |
| config.refresh_token = |
| ExchangeOAuth2RefreshToken(service_.wm(), |
| http_options_, |
| config, |
| code, GetRedirectURI()); |
| if (config.refresh_token.empty()) { |
| LOG(WARNING) << "failed to get refresh token"; |
| return BadRequest(response); |
| } |
| LOG(INFO) << "got refresh token"; |
| SaveOAuth2Config(FLAGS_OAUTH2_CONFIG_FILE, config); |
| service_.http_client()->SetOAuth2Config(config); |
| if (InitialPing()) { |
| LOG(INFO) << "Login as " << service_.http_client()->GetAccount(); |
| } |
| return Redirect("/", response); |
| } |
| |
| int HandleLogoutRequest(const HttpServerRequest& /* req */, |
| string* response) { |
| // TODO: limit access only for the authenticated user. |
| *response = "HTTP/1.1 501 Not Implemented\r\n\r\n"; |
| return 501; |
| #if 0 |
| std::ostringstream ss; |
| OAuth2Config config; |
| if (!service_.http_client()->GetOAuth2Config(&config)) { |
| return BadRequest(response); |
| } |
| config.refresh_token = ""; |
| service_.http_client()->SetOAuth2Config(config); |
| SaveOAuth2Config(FLAGS_OAUTH2_CONFIG_FILE, config); |
| LOG(INFO) << "logout"; |
| return Redirect("/", response); |
| #endif |
| } |
| |
| int HandleStatsRequest(const HttpServerRequest& request, |
| string* response) { |
| bool emit_json = false; |
| for (const auto& s : absl::StrSplit(request.query(), '&')) { |
| if (s == "format=json") { |
| emit_json = true; |
| break; |
| } |
| } |
| |
| std::ostringstream ss; |
| if (emit_json) { |
| OutputOkHeader("text/json", &ss); |
| string json_string; |
| service_.DumpStatsJson(&json_string, CompileService::kHumanReadable); |
| ss << json_string; |
| } else { |
| OutputOkHeader("text/plain", &ss); |
| service_.DumpStats(&ss); |
| } |
| |
| *response = ss.str(); |
| return 200; |
| } |
| |
| int HandleHistogramRequest(const HttpServerRequest& request, |
| string* response) { |
| const string& query = request.query(); |
| bool reset = strstr(query.c_str(), "reset") != nullptr; |
| std::ostringstream ss; |
| OutputOkHeader("text/plain", &ss); |
| service_.histogram()->DumpString(&ss); |
| if (reset) { |
| service_.histogram()->Reset(); |
| ss << "Reset done\n"; |
| } |
| *response = ss.str(); |
| return 200; |
| } |
| |
| int HandleHttpRpcRequest(const HttpServerRequest& /* request */, |
| string* response) { |
| std::ostringstream ss; |
| OutputOkHeader("text/plain", &ss); |
| ss << "[http configuration]\n\n" << service_.http_client()->DebugString(); |
| ss << "\n\n"; |
| ss << "[http rpc]\n\n" << service_.http_rpc()->DebugString(); |
| ss << "\n\n"; |
| ss << "[multi store]\n\n" |
| << service_.file_service()->multi_file_store()->DebugString(); |
| *response = ss.str(); |
| return 200; |
| } |
| |
| int HandleThreadRequest(const HttpServerRequest& /* request */, |
| string* response) { |
| std::ostringstream ss; |
| OutputOkHeader("text/plain", &ss); |
| ss << "[worker threads]\n\n" << service_.wm()->DebugString(); |
| ss << "[subprocess]\n\n" |
| << SubProcessControllerClient::Get()->DebugString(); |
| *response = ss.str(); |
| return 200; |
| } |
| |
| int HandleContentionRequest(const HttpServerRequest& request, |
| string* response) { |
| std::ostringstream ss; |
| |
| if (g_auto_lock_stats) { |
| std::unordered_set<string> skip_name = { |
| "descriptor_poller::PollEvents", |
| "worker_thread::NextClosure", |
| }; |
| |
| for (const auto& s : absl::StrSplit(request.query(), '&')) { |
| if (s == "detailed=1") { |
| skip_name.clear(); |
| break; |
| } |
| } |
| |
| OutputOkHeader("text/html", &ss); |
| g_auto_lock_stats->Report(&ss, skip_name); |
| } else { |
| OutputOkHeader("text/plain", &ss); |
| #ifdef NO_AUTOLOCK_STAT |
| ss << "disabled (built with NO_AUTOLOCK_STAT)"; |
| #else |
| ss << "disabled. to turn on contentionz, GOMA_ENABLE_CONTENTIONZ=true"; |
| #endif |
| } |
| *response = ss.str(); |
| return 200; |
| } |
| |
| int HandleFileCacheRequest(const HttpServerRequest& /* request */, |
| string* response) { |
| std::ostringstream ss; |
| OutputOkHeader("text/plain", &ss); |
| ss << "[file hash cache]\n\n" << service_.file_hash_cache()->DebugString(); |
| *response = ss.str(); |
| return 200; |
| } |
| |
| int HandleCompilerInfoRequest(const HttpServerRequest& /* request */, |
| string* response) { |
| std::ostringstream ss; |
| OutputOkHeader("text/plain", &ss); |
| service_.DumpCompilerInfo(&ss); |
| *response = ss.str(); |
| return 200; |
| } |
| |
| int HandleCompilerJSONRequest(const HttpServerRequest& /* request */, |
| string* response) { |
| std::ostringstream ss; |
| OutputOkHeader("application/json", &ss); |
| |
| Json::Value json; |
| CompilerInfoCache::instance()->DumpCompilersJSON(&json); |
| ss << json.toStyledString() << std::endl; |
| *response = ss.str(); |
| |
| return 200; |
| } |
| |
| int HandleIncludeCacheRequest(const HttpServerRequest& /* request */, |
| string* response) { |
| std::ostringstream ss; |
| OutputOkHeader("text/plain", &ss); |
| IncludeCache::DumpAll(&ss); |
| *response = ss.str(); |
| return 200; |
| } |
| |
| int HandleFlagRequest(const HttpServerRequest& /* request */, |
| string* response) { |
| std::ostringstream ss; |
| OutputOkHeader("text/plain", &ss); |
| DumpEnvFlag(&ss); |
| *response = ss.str(); |
| return 200; |
| } |
| |
| int HandleVersionRequest(const HttpServerRequest& /* request */, |
| string* response) { |
| std::ostringstream ss; |
| OutputOkHeader("text/plain", &ss); |
| ss << kBuiltRevisionString; |
| *response = ss.str(); |
| return 200; |
| } |
| |
| int HandleHealthRequest(const HttpServerRequest& request, |
| string* response) { |
| const string& query = request.query(); |
| const string health_status = |
| service_.http_client()->GetHealthStatusMessage(); |
| *response = "HTTP/1.1 200 OK\r\n\r\n" + health_status; |
| if (!setting_.empty()) { |
| *response += "\nsetting=" + setting_; |
| } |
| LOG(INFO) << "I am healthy:" << health_status |
| << " to pid:" << request.peer_pid() |
| << " query:" << query; |
| // gomacc checkhealth use ?pid=<pid> as query. |
| // note that: build_nexe.py also checks /healthz. |
| if (request.peer_pid() != 0 || !query.empty()) { |
| service_.wm()->DebugLog(); |
| } |
| return 200; |
| } |
| |
| int HandlePortRequest(const HttpServerRequest& request, |
| string* response) { |
| LOG(INFO) << "handle portz port=" << request.server().port(); |
| HttpPortResponse resp; |
| resp.set_port(request.server().port()); |
| string serialized_resp; |
| resp.SerializeToString(&serialized_resp); |
| |
| std::ostringstream oss; |
| oss << "HTTP/1.1 200 OK\r\n" |
| << "Content-Type: binary/x-protocol-buffer\r\n" |
| << "Content-Length: " << serialized_resp.size() << "\r\n\r\n" |
| << serialized_resp; |
| *response = oss.str(); |
| return 200; |
| } |
| |
| int HandleLogRequest(const HttpServerRequest& request, |
| string* response) { |
| std::ostringstream oss; |
| const string& log_request = request.query(); |
| if (log_request.empty()) { |
| string content = ("<a href=\"?INFO\">INFO</a> /" |
| "<a href=\"?WARNING\">WARNING</a> /" |
| "<a href=\"?ERROR\">ERROR</a>" |
| #ifndef _WIN32 |
| "<br />" |
| "<a href=\"?subproc-INFO\">subproc-INFO</a> /" |
| "<a href=\"?subproc-WARNING\">subproc-WARNING</a> /" |
| "<a href=\"?subproc-ERROR\">subproc-ERROR</a>" |
| #endif |
| "<br />"); |
| oss << "HTTP/1.1 200 OK\r\n" |
| << "Content-Type: text/html\r\n" |
| << "Content-Length: " << content.size() << "\r\n\r\n" |
| << content; |
| } else { |
| const std::vector<string>& log_dirs = google::GetLoggingDirectories(); |
| if (log_dirs.empty()) { |
| LOG(ERROR) << "No logging directories"; |
| return 404; |
| } |
| string log_suffix; |
| string log_type = log_request; |
| if (log_request.find("subproc-") == 0) { |
| log_suffix = "-subproc"; |
| log_type = log_request.substr(strlen("subproc-")); |
| } |
| if (log_type != "INFO" && log_type != "WARNING" && |
| log_type != "ERROR" && log_type != "FATAL") { |
| LOG(WARNING) << "Unknown log type: " << log_type; |
| return 404; |
| } |
| string log_filename = |
| file::JoinPath(log_dirs[0], myname_ + log_suffix + "." + log_type); |
| #ifdef _WIN32 |
| const string& original_log = FindLogFile(log_dirs[0], myname_, log_type); |
| // Workaround GLOG not opening file in share read. |
| if (!CopyFileA(original_log.c_str(), log_filename.c_str(), FALSE)) { |
| log_filename = original_log; // Can't copy, let's just try share read. |
| } |
| #endif |
| string log; |
| if (!ReadFileToString(log_filename.c_str(), &log)) { |
| return 404; |
| } |
| oss << "HTTP/1.1 200 OK\r\n" |
| << "Content-Type: text/plain\r\n" |
| << "Content-Length: " << log.size() << "\r\n\r\n" |
| << log; |
| } |
| |
| *response = oss.str(); |
| return 200; |
| } |
| |
| int HandleErrorStatusRequest(const HttpServerRequest&, string* response) { |
| std::ostringstream ss; |
| OutputOkHeader("application/json", &ss); |
| service_.DumpErrorStatus(&ss); |
| *response = ss.str(); |
| return 200; |
| } |
| |
| #ifdef HAVE_COUNTERZ |
| int HandleCounterRequest(const HttpServerRequest&, string* response) { |
| // TODO: implement better view using javascript if necessary. |
| std::ostringstream ss; |
| OutputOkHeader("application/json", &ss); |
| Json::Value json; |
| if (Counterz::Instance() != nullptr) { |
| Counterz::Instance()->DumpToJson(&json); |
| } else { |
| LOG(ERROR) << "counterz is used before Init()."; |
| json = "counterz is used before Init()."; |
| } |
| |
| ss << json.toStyledString() << std::endl; |
| *response = ss.str(); |
| return 200; |
| } |
| #endif |
| |
| #ifdef _WIN32 |
| void ExecDoneInMulti(CompileService::MultiRpcController* rpc, int i) { |
| if (rpc->ExecDone(i)) { |
| std::unique_ptr<CompileService::MultiRpcController> rpc_autodeleter(rpc); |
| rpc->SendReply(); |
| } |
| } |
| #endif |
| |
| void ExecDone(CompileService::RpcController* rpc, ExecResp* resp) { |
| std::unique_ptr<CompileService::RpcController> rpc_autodeleter(rpc); |
| std::unique_ptr<ExecResp> resp_autodeleter(resp); |
| |
| rpc->SendReply(*resp); |
| } |
| |
| void SendErrorMessage( |
| ThreadpoolHttpServer::HttpServerRequest* http_server_request, |
| int response_code, const string& status_message) { |
| std::ostringstream http_response_message; |
| http_response_message |
| << "HTTP/1.1 " << response_code << " " << status_message << "\r\n\r\n"; |
| http_server_request->SendReply(http_response_message.str()); |
| } |
| |
| void RunCleanOldLogs() { |
| if (FLAGS_LOG_CLEAN_INTERVAL <= 0) { |
| LOG(WARNING) << "log clean interval <= 0, " |
| << "but attempted cleaning old logs"; |
| return; |
| } |
| // Switch from alarm worker to normal worker. |
| service_.wm()->RunClosure( |
| FROM_HERE, |
| NewCallback( |
| this, &CompilerProxyHttpHandler::CleanOldLogs), |
| WorkerThreadManager::PRIORITY_LOW); |
| } |
| |
| void CleanOldLogs() { |
| if (FLAGS_LOG_CLEAN_INTERVAL <= 0) |
| return; |
| time_t now = time(nullptr); |
| log_cleaner_.CleanOldLogs(now - FLAGS_LOG_CLEAN_INTERVAL); |
| } |
| |
| void RunTrackMemory() { |
| if (FLAGS_MEMORY_TRACK_INTERVAL <= 0) { |
| LOG(WARNING) << "memory track interval <= 0, " |
| << "but attempted tracking memory"; |
| return; |
| } |
| |
| // Switch from alarm worker to normal worker. |
| service_.wm()->RunClosure( |
| FROM_HERE, |
| NewCallback( |
| this, &CompilerProxyHttpHandler::TrackMemory), |
| WorkerThreadManager::PRIORITY_LOW); |
| } |
| |
| void TrackMemory() { |
| int64_t memory_byte = GetConsumingMemoryOfCurrentProcess(); |
| int64_t warning_threshold = |
| static_cast<int64_t>(FLAGS_MEMORY_WARNING_THRESHOLD_IN_MB) * |
| 1024 * 1024; |
| if (memory_byte >= warning_threshold) { |
| LOG(WARNING) << "memory tracking: consuming memory = " |
| << memory_byte << " bytes, which is higher than " |
| << "warning threshold " |
| << warning_threshold << " bytes"; |
| } else { |
| LOG(INFO) << "memory tracking: consuming memory = " |
| << memory_byte << " bytes"; |
| } |
| |
| if (service_.log_service()) { |
| MemoryUsageLog memory_usage_log; |
| memory_usage_log.set_compiler_proxy_start_time(service_.start_time()); |
| memory_usage_log.set_compiler_proxy_user_agent(kUserAgentString); |
| if (FLAGS_SEND_USER_INFO) { |
| memory_usage_log.set_username(service_.username()); |
| memory_usage_log.set_nodename(service_.nodename()); |
| } |
| |
| time_t current_time; |
| time(¤t_time); |
| memory_usage_log.set_memory(memory_byte); |
| memory_usage_log.set_time(current_time); |
| |
| service_.log_service()->SaveMemoryUsageLog(memory_usage_log); |
| } |
| } |
| |
| void DumpStatsToInfoLog() { |
| // TODO: Remove this after diagnose_goma_log.py and |
| // diagnose_goma_log_server reads json format stats. |
| { |
| std::ostringstream ss; |
| service_.DumpStats(&ss); |
| LOG(INFO) << "Dumping stats...\n" |
| << ss.str(); |
| } |
| |
| // Also dump json format. Using FastWriter for compaction. |
| { |
| std::string json_string; |
| service_.DumpStatsJson(&json_string, |
| CompileService::kFastHumanUnreadable); |
| LOG(INFO) << "Dumping json stats...\n" |
| << json_string; |
| } |
| } |
| |
| void DumpHistogramToInfoLog() { |
| std::ostringstream ss; |
| service_.histogram()->DumpString(&ss); |
| |
| LOG(INFO) << "Dumping histogram...\n" |
| << ss.str(); |
| } |
| |
| void DumpIncludeCacheLogToInfoLog() { |
| std::ostringstream ss; |
| IncludeCache::DumpAll(&ss); |
| |
| LOG(INFO) << "Dumping include cache...\n" |
| << ss.str(); |
| } |
| |
| void DumpContentionLogToInfoLog() { |
| std::ostringstream ss; |
| g_auto_lock_stats->TextReport(&ss); |
| LOG(INFO) << "Dumping contention...\n" |
| << ss.str(); |
| } |
| |
| void DumpStatsProto() { |
| if (FLAGS_DUMP_STATS_FILE.empty()) |
| return; |
| |
| service_.DumpStatsToFile(FLAGS_DUMP_STATS_FILE); |
| } |
| |
| void DumpCounterz() { |
| #ifdef HAVE_COUNTERZ |
| if (FLAGS_DUMP_COUNTERZ_FILE.empty()) |
| return; |
| |
| Counterz::Dump(FLAGS_DUMP_COUNTERZ_FILE); |
| #endif // HAVE_COUNTERZ |
| } |
| |
| void DumpDirectiveOptimizer() { |
| std::ostringstream ss; |
| CppDirectiveOptimizer::DumpStats(&ss); |
| LOG(INFO) << "Dumping directive optimizer...\n" |
| << ss.str(); |
| } |
| |
| #if HAVE_HEAP_PROFILER |
| int HandleHeapRequest(const HttpServerRequest& request, |
| string* response) { |
| *response = "HTTP/1.1 200 OK\r\n\r\n"; |
| if (IsHeapProfilerRunning()) { |
| HeapProfilerDump("requested by /heapz"); |
| HeapProfilerStop(); |
| *response += "heap profiler stopped. see " + |
| compiler_proxy_heap_profile_file_ + ".*.heap"; |
| } else { |
| HeapProfilerStart(compiler_proxy_heap_profile_file_.c_str()); |
| *response += "heap profiler starts."; |
| } |
| return 200; |
| } |
| #endif |
| #if HAVE_CPU_PROFILER |
| int HandleProfileRequest(const HttpServerRequest& request, |
| string* response) { |
| *response = "HTTP/1.1 200 OK\r\n\r\n"; |
| if (cpu_profiling_) { |
| ProfilerStop(); |
| cpu_profiling_ = false; |
| *response += "cpu profiler stopped. see " + |
| compiler_proxy_cpu_profile_file_; |
| } else { |
| ProfilerStart(compiler_proxy_cpu_profile_file_.c_str()); |
| cpu_profiling_ = true; |
| *response += "cpu profiler starts."; |
| } |
| return 200; |
| } |
| #endif |
| |
| void NewLoginState(int port, string* login_state, string* redirect_uri) { |
| *login_state = GetRandomAlphanumeric(32); |
| std::ostringstream ss; |
| ss << "http://localhost:" << port << "/api/authz"; |
| *redirect_uri = ss.str(); |
| AUTOLOCK(lock, &login_state_mu_); |
| oauth2_login_state_ = *login_state; |
| oauth2_redirect_uri_ = *redirect_uri; |
| } |
| |
| bool CheckLoginState(const string& state) const { |
| AUTOLOCK(lock, &login_state_mu_); |
| return oauth2_login_state_ == state; |
| } |
| |
| string GetRedirectURI() const { |
| AUTOLOCK(lock, &login_state_mu_); |
| return oauth2_redirect_uri_; |
| } |
| |
| bool ShouldTrace() { |
| if (FLAGS_RPC_TRACE_PERIOD < 1) { |
| return false; |
| } |
| AUTOLOCK(lock, &rpc_sent_count_mu_); |
| return rpc_sent_count_++ % FLAGS_RPC_TRACE_PERIOD == 0; |
| } |
| |
| const string myname_; |
| const string setting_; |
| CompileService service_; |
| LogCleaner log_cleaner_; |
| PeriodicClosureId log_cleaner_closure_id_; |
| PeriodicClosureId memory_tracker_closure_id_; |
| mutable Lock rpc_sent_count_mu_; |
| uint64_t rpc_sent_count_ GUARDED_BY(rpc_sent_count_mu_); |
| |
| std::map<string, HttpHandlerMethod> http_handlers_; |
| std::map<string, HttpHandlerMethod> internal_http_handlers_; |
| |
| const string tmpdir_; |
| |
| #if HAVE_HEAP_PROFILER |
| const string compiler_proxy_heap_profile_file_; |
| #endif |
| #if HAVE_CPU_PROFILER |
| const string compiler_proxy_cpu_profile_file_; |
| bool cpu_profiling_; |
| #endif |
| |
| // Default http_options_ for any http clients in compiler_proxy |
| // such as oauth2 etc. |
| HttpClient::Options http_options_; |
| |
| mutable Lock login_state_mu_; |
| string oauth2_login_state_; |
| string oauth2_redirect_uri_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CompilerProxyHttpHandler); |
| }; |
| |
| } // namespace devtools_goma |
| |
| #ifndef _WIN32 |
| bool CheckFileOwnedByMyself(const string& filename, uid_t uid) { |
| struct stat st; |
| if (stat(filename.c_str(), &st) == -1) |
| return true; |
| if (st.st_uid == uid) |
| return true; |
| |
| std::cerr << "GOMA: compiler_proxy:" |
| << " other user (" << st.st_uid << ") owns " << filename |
| << ", so you (" << uid << ") can not run compiler_proxy. " |
| << std::endl; |
| std::cerr << "GOMA: remove " << filename << std::endl; |
| return false; |
| } |
| |
| ScopedFd LockMyself(const string& filename, int port) { |
| // Open myself and lock it during execution. |
| std::ostringstream filename_buf; |
| filename_buf << filename << "." << port; |
| string lock_filename = filename_buf.str(); |
| if (!CheckFileOwnedByMyself(lock_filename.c_str(), getuid())) { |
| exit(1); |
| } |
| ScopedFd fd(open(lock_filename.c_str(), O_RDONLY|O_CREAT, S_IRUSR)); |
| if (!fd.valid()) { |
| std::cerr << "GOMA: compiler_proxy: " |
| << "failed to open lock file:" << lock_filename << std::endl; |
| exit(1); |
| } |
| int ret = flock(fd.fd(), LOCK_EX | LOCK_NB); |
| if (ret == -1 && errno == EWOULDBLOCK) { |
| std::cerr << "GOMA: compiler_proxy: " |
| << "there is already someone else with lock" << std::endl; |
| exit(1); |
| } |
| return fd; |
| } |
| #endif |
| |
| void InitResourceLimits(int* nfile) { |
| #ifndef _WIN32 |
| struct rlimit lim; |
| PCHECK(getrlimit(RLIMIT_NOFILE, &lim) == 0); |
| *nfile = static_cast<int>(lim.rlim_cur); |
| const rlim_t prev = lim.rlim_cur; |
| rlim_t open_max = static_cast<rlim_t>(sysconf(_SC_OPEN_MAX)); |
| #ifdef OPEN_MAX |
| open_max = std::max(open_max, static_cast<rlim_t>(OPEN_MAX)); |
| #endif |
| open_max = std::max(open_max, lim.rlim_cur); |
| #ifdef __MACH__ |
| // Choose smaller size from sysctl. (b/9548636) |
| int mib[2] = {CTL_KERN, -1}; |
| static const int kSecondMibs[] = {KERN_MAXFILES, KERN_MAXFILESPERPROC}; |
| for (const auto& it : kSecondMibs) { |
| rlim_t tmp; |
| size_t length = sizeof(tmp); |
| mib[1] = it; |
| PCHECK(sysctl(mib, 2, &tmp, &length, nullptr, 0) == 0) << it; |
| open_max = std::min(tmp, open_max); |
| } |
| // setrlimit(3) will fail with EINVAL if launchctl sets smaller limit, |
| // which default is 256. b/11596636 |
| #endif |
| lim.rlim_cur = std::min(open_max, lim.rlim_max); |
| if (setrlimit(RLIMIT_NOFILE, &lim) != 0) { |
| // we might get EPERM or EINVAL if we try to increase RLIMIT_NOFILE above |
| // the current kernel maxium. |
| PLOG(ERROR) << "setrlimit(RLIMIT_NOFILE, &lim) != 0" |
| << " rlim_cur:" << lim.rlim_cur |
| << " rlim_max:" << lim.rlim_max |
| << " rlim_cur would remain " << prev; |
| lim.rlim_cur = prev; |
| } else { |
| LOG(INFO) << "setrlimit RLIMIT_NOFILE " << prev << " -> " << lim.rlim_cur; |
| } |
| *nfile = static_cast<int>(lim.rlim_cur); |
| #else |
| *nfile = FLAGS_COMPILER_PROXY_MAX_SOCKETS; |
| #endif |
| } |
| |
| void InitTrustedIps(devtools_goma::TrustedIpsManager* trustedipsmanager) { |
| for (auto&& ip : absl::StrSplit(FLAGS_COMPILER_PROXY_TRUSTED_IPS, |
| ',', |
| absl::SkipEmpty())) { |
| trustedipsmanager->AddAllow(string(ip)); |
| } |
| } |
| |
| namespace devtools_goma { |
| |
| void DepsCacheInit() { |
| string cache_filename; |
| if (!FLAGS_DEPS_CACHE_FILE.empty()) { |
| cache_filename = file::JoinPathRespectAbsolute(GetCacheDirectory(), |
| FLAGS_DEPS_CACHE_FILE); |
| } |
| |
| DepsCache::Init(cache_filename, |
| FLAGS_DEPS_CACHE_IDENTIFIER_ALIVE_DURATION, |
| FLAGS_DEPS_CACHE_TABLE_THRESHOLD, |
| FLAGS_DEPS_CACHE_MAX_PROTO_SIZE_IN_MB); |
| } |
| |
| void CompilerInfoCacheInit() { |
| CompilerInfoCache::Init(GetCacheDirectory(), FLAGS_COMPILER_INFO_CACHE_FILE, |
| FLAGS_COMPILER_INFO_CACHE_HOLDING_TIME_SEC); |
| } |
| |
| } // namespace devtools_goma |
| |
| int main(int argc, char* argv[], const char* envp[]) { |
| devtools_goma::Init(argc, argv, envp); |
| |
| #if HAVE_COUNTERZ |
| if (FLAGS_ENABLE_COUNTERZ) { |
| devtools_goma::Counterz::Init(); |
| } |
| #endif |
| |
| if (FLAGS_ENABLE_GLOBAL_FILE_STAT_CACHE || |
| FLAGS_ENABLE_GLOBAL_FILE_ID_CACHE) { |
| devtools_goma::GlobalFileStatCache::Init(); |
| } |
| |
| const string tmpdir = FLAGS_TMP_DIR; |
| #ifndef _WIN32 |
| const string compiler_proxy_addr = file::JoinPathRespectAbsolute( |
| tmpdir, |
| FLAGS_COMPILER_PROXY_SOCKET_NAME); |
| |
| if (!CheckFileOwnedByMyself(compiler_proxy_addr, getuid())) { |
| exit(1); |
| } |
| |
| const string lock_filename = file::JoinPathRespectAbsolute( |
| tmpdir, |
| FLAGS_COMPILER_PROXY_LOCK_FILENAME); |
| ScopedFd lockfd( |
| LockMyself(lock_filename, |
| FLAGS_COMPILER_PROXY_PORT)); |
| if (FLAGS_COMPILER_PROXY_DAEMON_MODE) { |
| int fd[2]; |
| PCHECK(pipe(fd) == 0); |
| pid_t pid; |
| if ((pid = fork())) { |
| PCHECK(pid > 0); |
| // Get pid from daemonized process |
| close(fd[1]); |
| pid_t server_pid; |
| PCHECK(read(fd[0], &server_pid, sizeof(server_pid)) == |
| sizeof(server_pid)); |
| std::cout << server_pid << std::endl; |
| exit(0); |
| } |
| close(fd[0]); |
| std::set<int> preserve_fds; |
| preserve_fds.insert(lockfd.fd()); |
| Daemonize( |
| file::JoinPathRespectAbsolute(tmpdir, |
| FLAGS_COMPILER_PROXY_DAEMON_STDERR), |
| fd[1], |
| preserve_fds); |
| } |
| |
| // Initialize rand. |
| srand(static_cast<unsigned int>(time(nullptr))); |
| |
| // Do not die with a SIGHUP and SIGPIPE. |
| signal(SIGHUP, SIG_IGN); |
| signal(SIGPIPE, SIG_IGN); |
| #else |
| const string compiler_proxy_addr = FLAGS_COMPILER_PROXY_SOCKET_NAME; |
| WinsockHelper wsa; |
| devtools_goma::ScopedFd lock_fd; |
| std::ostringstream filename_buf; |
| filename_buf << "Global\\" << FLAGS_COMPILER_PROXY_LOCK_FILENAME << "." |
| << FLAGS_COMPILER_PROXY_PORT; |
| string lock_filename = filename_buf.str(); |
| |
| lock_fd.reset(CreateEventA(nullptr, TRUE, FALSE, lock_filename.c_str())); |
| DWORD last_error = GetLastError(); |
| if (last_error == ERROR_ALREADY_EXISTS) { |
| std::cerr << "GOMA: compiler proxy: already existed" << std::endl; |
| exit(1); |
| } |
| if (!lock_fd.valid()) { |
| LOG(ERROR) << "Cannot acquire global named object: " << last_error; |
| exit(1); |
| } |
| |
| #ifdef NDEBUG |
| // Sets error mode to SEM_FAILCRITICALERRORS and SEM_NOGPFAULTERRORBOX |
| // to prevent from popping up message box on error. |
| // We don't use CREATE_DEFAULT_ERROR_MODE for dwCreationFlags in |
| // CreateProcess function. |
| // http://msdn.microsoft.com/en-us/library/windows/desktop/ms680621(v=vs.85).aspx |
| UINT old_error_mode = SetErrorMode( |
| SEM_FAILCRITICALERRORS|SEM_NOGPFAULTERRORBOX); |
| LOG(INFO) << "Set error mode from " << old_error_mode |
| << " to " << GetErrorMode(); |
| #endif |
| #endif |
| devtools_goma::SubProcessController::Options subproc_options; |
| subproc_options.max_subprocs = FLAGS_MAX_SUBPROCS; |
| subproc_options.max_subprocs_low_priority = FLAGS_MAX_SUBPROCS_LOW; |
| subproc_options.max_subprocs_heavy_weight = FLAGS_MAX_SUBPROCS_HEAVY; |
| subproc_options.dont_kill_subprocess = FLAGS_DONT_KILL_SUBPROCESS; |
| if (!FLAGS_DONT_KILL_COMMANDS.empty()) { |
| for (auto&& cmd_view : absl::StrSplit(FLAGS_DONT_KILL_COMMANDS, |
| ',', |
| absl::SkipEmpty())) { |
| string cmd(cmd_view); |
| #ifdef _WIN32 |
| absl::AsciiStrToLower(&cmd); |
| #endif |
| subproc_options.dont_kill_commands.insert(cmd); |
| } |
| } |
| devtools_goma::SubProcessController::Initialize(argv[0], subproc_options); |
| |
| devtools_goma::InitLogging(argv[0]); |
| if (FLAGS_COMPILER_PROXY_ENABLE_CRASH_DUMP) { |
| devtools_goma::InitCrashReporter(devtools_goma::GetCrashDumpDirectory()); |
| LOG(INFO) << "breakpad is enabled"; |
| } |
| |
| std::unique_ptr<devtools_goma::AutoUpdater> auto_updater; |
| if (FLAGS_ENABLE_AUTO_UPDATE) { |
| auto_updater = |
| absl::make_unique<devtools_goma::AutoUpdater>(FLAGS_CTL_SCRIPT_NAME); |
| if (auto_updater->my_version() > 0) { |
| LOG(INFO) << "goma version:" << auto_updater->my_version(); |
| } |
| auto_updater->SetEnv(envp); |
| } else { |
| LOG(INFO) << "auto updater is disabled"; |
| } |
| |
| int max_nfile = 0; |
| InitResourceLimits(&max_nfile); |
| CHECK_GT(max_nfile, 0); |
| #if defined(USE_EPOLL) || defined(USE_KQUEUE) || defined(_WIN32) |
| int max_num_sockets = max_nfile; |
| #else |
| // UNIX select doesn't accept descriptors greater than FD_SETSIZE. |
| int max_num_sockets = std::min<int>(max_nfile, FD_SETSIZE); |
| #endif |
| LOG(INFO) << "max_num_sockets=" << max_num_sockets |
| << " max_nfile=" << max_nfile; |
| |
| devtools_goma::WorkerThreadManager wm; |
| wm.Start(FLAGS_COMPILER_PROXY_THREADS); |
| |
| devtools_goma::SubProcessControllerClient::Initialize(&wm, tmpdir); |
| |
| devtools_goma::InstallReadCommandOutputFunc( |
| &devtools_goma::SubProcessTask::ReadCommandOutput); |
| |
| devtools_goma::IncludeFileFinder::Init(FLAGS_ENABLE_GCH_HACK); |
| |
| devtools_goma::IncludeCache::Init( |
| FLAGS_MAX_INCLUDE_CACHE_SIZE, !FLAGS_DEPS_CACHE_FILE.empty()); |
| devtools_goma::ListDirCache::Init(FLAGS_MAX_LIST_DIR_CACHE_ENTRY_NUM); |
| |
| std::unique_ptr<devtools_goma::WorkerThreadRunner> init_deps_cache( |
| new devtools_goma::WorkerThreadRunner( |
| &wm, FROM_HERE, |
| devtools_goma::NewCallback(devtools_goma::DepsCacheInit))); |
| std::unique_ptr<devtools_goma::WorkerThreadRunner> init_compiler_info_cache( |
| new devtools_goma::WorkerThreadRunner( |
| &wm, FROM_HERE, |
| devtools_goma::NewCallback(devtools_goma::CompilerInfoCacheInit))); |
| |
| devtools_goma::TrustedIpsManager trustedipsmanager; |
| InitTrustedIps(&trustedipsmanager); |
| |
| string setting; |
| if (!FLAGS_SETTINGS_SERVER.empty()) { |
| setting = ApplySettings(FLAGS_SETTINGS_SERVER, FLAGS_ASSERT_SETTINGS, &wm); |
| } |
| std::unique_ptr<devtools_goma::CompilerProxyHttpHandler> handler( |
| new devtools_goma::CompilerProxyHttpHandler( |
| string(file::Basename(argv[0])), setting, tmpdir, &wm)); |
| |
| ThreadpoolHttpServer server(FLAGS_COMPILER_PROXY_LISTEN_ADDR, |
| FLAGS_COMPILER_PROXY_PORT, |
| FLAGS_COMPILER_PROXY_NUM_FIND_PORTS, |
| &wm, |
| FLAGS_COMPILER_PROXY_HTTP_THREADS, |
| handler.get(), |
| max_num_sockets); |
| server.SetMonitor(handler.get()); |
| server.SetTrustedIpsManager(&trustedipsmanager); |
| CHECK(!compiler_proxy_addr.empty()) |
| << "broken compiler_proxy_addr configuration. " |
| << "set GOMA_COMPILER_PROXY_SOCKET_NAME" |
| << " for compiler_proxy ipc addr"; |
| server.StartIPC(compiler_proxy_addr, |
| FLAGS_COMPILER_PROXY_THREADS, |
| FLAGS_MAX_OVERCOMMIT_INCOMING_SOCKETS); |
| // TCP serves only status pages, no limit. |
| if (auto_updater) { |
| auto_updater->Start(&server, FLAGS_AUTO_UPDATE_IDLE_COUNT); |
| handler->SetAutoUpdater(std::move(auto_updater)); |
| } |
| if (FLAGS_WATCHDOG_TIMER > 0) { |
| std::unique_ptr<devtools_goma::Watchdog> watchdog( |
| new devtools_goma::Watchdog); |
| std::vector<string> env; |
| env.push_back("GOMA_COMPILER_PROXY_SOCKET_NAME=" + compiler_proxy_addr); |
| env.push_back("PATH=" + devtools_goma::GetEnv("PATH")); |
| env.push_back("PATHEXT=" + devtools_goma::GetEnv("PATHEXT")); |
| env.push_back("USER=" + devtools_goma::GetUsername()); |
| env.push_back("GOMA_TMP_DIR=" + FLAGS_TMP_DIR); |
| handler->SetWatchdog(std::move(watchdog), env, |
| &server, FLAGS_WATCHDOG_TIMER); |
| } |
| |
| devtools_goma::LocalOutputCache::Init( |
| FLAGS_LOCAL_OUTPUT_CACHE_DIR, |
| &wm, |
| FLAGS_LOCAL_OUTPUT_CACHE_MAX_CACHE_AMOUNT_IN_MB, |
| FLAGS_LOCAL_OUTPUT_CACHE_THRESHOLD_CACHE_AMOUNT_IN_MB, |
| FLAGS_LOCAL_OUTPUT_CACHE_MAX_ITEMS, |
| FLAGS_LOCAL_OUTPUT_CACHE_THRESHOLD_ITEMS); |
| |
| init_deps_cache.reset(); |
| init_compiler_info_cache.reset(); |
| // Show memory just before server loop to understand how much memory is |
| // used for initialization. |
| handler->TrackMemoryOneshot(); |
| |
| LOG(INFO) << "server loop start"; |
| if (server.Loop() != 0) { |
| LOG(ERROR) << "Server failed"; |
| exit(1); |
| } |
| LOG(INFO) << "server loop end"; |
| devtools_goma::FlushLogFiles(); |
| server.StopIPC(); |
| #ifndef _WIN32 |
| flock(lockfd.fd(), LOCK_UN); |
| lockfd.reset(-1); |
| #else |
| lock_fd.Close(); |
| #endif |
| LOG(INFO) << "unlock compiler_proxy"; |
| devtools_goma::FlushLogFiles(); |
| devtools_goma::SubProcessControllerClient::Get()->Quit(); |
| devtools_goma::LocalOutputCache::Quit(); |
| server.Wait(); |
| handler->Wait(); |
| devtools_goma::CompilerInfoCache::Quit(); |
| devtools_goma::DepsCache::Quit(); |
| devtools_goma::IncludeCache::Quit(); |
| devtools_goma::ListDirCache::Quit(); |
| devtools_goma::SubProcessControllerClient::Get()->Shutdown(); |
| |
| handler.reset(); |
| wm.Finish(); |
| #ifndef _WIN32 |
| // compiler_proxy only creates subprocess controller server as child process, |
| // so waits for the status of it; |
| int status; |
| PCHECK(wait(&status) > 0); |
| LOG(INFO) << "wait:" << status; |
| #endif |
| |
| if (FLAGS_ENABLE_GLOBAL_FILE_STAT_CACHE || |
| FLAGS_ENABLE_GLOBAL_FILE_ID_CACHE) { |
| devtools_goma::GlobalFileStatCache::Quit(); |
| } |
| |
| #if HAVE_COUNTERZ |
| if (FLAGS_ENABLE_COUNTERZ) { |
| devtools_goma::Counterz::Quit(); |
| } |
| #endif |
| |
| return 0; |
| } |