diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..23f9bc4 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,78 @@ +name: "CodeQL" + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + schedule: + # Run weekly on Sundays at 04:00 UTC to catch new CVEs in dependencies. + - cron: '0 4 * * 0' + +jobs: + analyze: + name: Analyze (C/C++) + runs-on: ubuntu-24.04 + permissions: + security-events: write # Required to upload SARIF results. + contents: read # Required to checkout code. + actions: read # Required for private repos. + + strategy: + fail-fast: false + matrix: + # CodeQL supports: cpp, csharp, go, java, javascript, python, ruby, swift. + language: [ 'c-cpp' ] + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # ── Initialize CodeQL ──────────────────────────────────────────── + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + # Use the extended query suite for deeper analysis (security + quality). + queries: security-extended + + # ── Toolchain Setup ────────────────────────────────────────────── + # CodeQL for C/C++ needs to observe the build. We must set up the + # same LLVM 19 + Bazel toolchain used by the main CI pipeline so + # that the CodeQL tracer can instrument the compilation. + - name: Set up LLVM 19 + run: | + sudo apt-get update + sudo apt-get install -y wget gnupg ca-certificates + sudo mkdir -p /usr/share/keyrings + wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/llvm-archive-keyring.gpg + echo "deb [signed-by=/usr/share/keyrings/llvm-archive-keyring.gpg] http://apt.llvm.org/noble/ llvm-toolchain-noble-19 main" | sudo tee /etc/apt/sources.list.d/llvm.list + sudo apt-get update + sudo apt-get install -y clang-19 lld-19 libclang-rt-19-dev + sudo ln -sf /usr/bin/clang-19 /usr/bin/clang + sudo ln -sf /usr/bin/clang++-19 /usr/bin/clang++ + sudo ln -sf /usr/bin/lld-19 /usr/bin/lld + + - name: Cache Bazel + uses: actions/cache@v4 + with: + path: | + ~/.cache/bazel + key: ${{ runner.os }}-codeql-bazel-${{ hashFiles('**/MODULE.bazel', '**/WORKSPACE', '**/WORKSPACE.bazel') }} + restore-keys: | + ${{ runner.os }}-codeql-bazel- + + # ── Build (observed by CodeQL tracer) ──────────────────────────── + # Use the CI bazelrc for resource-constrained runners. + # Build all targets so CodeQL sees the full source graph. + - name: Bazel Build (CodeQL traced) + run: | + bazel --bazelrc=.github/workflows/ci.bazelrc build \ + --verbose_failures \ + //... + + # ── Upload Results ─────────────────────────────────────────────── + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{ matrix.language }}" diff --git a/.github/workflows/linux_ci.yml b/.github/workflows/linux_ci.yml index ccf2f74..a2ad570 100644 --- a/.github/workflows/linux_ci.yml +++ b/.github/workflows/linux_ci.yml @@ -40,24 +40,70 @@ jobs: restore-keys: | ${{ runner.os }}-bazel- + # ── Environment Verification ───────────────────────────────────── + # Runs the full dcodex-setup.sh --test to verify the environment is + # correctly configured and all sanitizer suites pass. This catches + # setup-level issues (missing deps, broken symlinks) that individual + # test steps wouldn't detect. + - name: "🔧 Environment Verification (dcodex-setup.sh --test)" + run: | + chmod +x ./dcodex-setup.sh + sudo REPO_DIR="$GITHUB_WORKSPACE" \ + BAZEL_JOBS=4 \ + BAZEL_MEM_MB=2048 \ + SKIP_APT=0 \ + ./dcodex-setup.sh --test + env: + DEBIAN_FRONTEND: noninteractive + + # The dcodex-setup.sh runs as root (sudo), creating .bazel/output_base + # and /tmp/bazel-test-logs (via .bazelrc --test_tmpdir) owned by root. + # Subsequent Bazel steps run as the runner user and need read/write access. + - name: Fix root-owned directory permissions + run: | + sudo chown -R "$(id -u):$(id -g)" .bazel + sudo chown -R "$(id -u):$(id -g)" /tmp/bazel-test-logs 2>/dev/null || true + + # ── Fine-grained Test Steps ────────────────────────────────────── + # These provide per-sanitizer visibility in the GitHub Actions UI. + # The dcodex-setup.sh step above is the single source of truth; + # these are redundant-but-visible steps for PR review ergonomics. + - name: Bazel Build run: | - bazel --bazelrc=.github/workflows/ci.bazelrc build //... + bazel --bazelrc=.github/workflows/ci.bazelrc build \ + --verbose_failures \ + //... - name: Bazel Test (Standard) run: | - bazel --bazelrc=.github/workflows/ci.bazelrc test //... + bazel --bazelrc=.github/workflows/ci.bazelrc test \ + --verbose_failures \ + //... + + - name: Bazel Test (ASan + UBSan) + run: | + bazel --bazelrc=.github/workflows/ci.bazelrc test \ + --config=asan \ + --verbose_failures \ + --jobs=2 \ + //src/engine:sandbox_test \ + //src/engine:warm_worker_pool_test \ + //src/engine:dynamic_worker_coordinator_test \ + //src/engine:tsan_checker - - name: Bazel Test (TSan) + - name: Bazel Test (TSan — concurrency tests) run: | bazel --bazelrc=.github/workflows/ci.bazelrc test --config=tsan \ - //... \ + --verbose_failures \ --jobs=2 \ - --test_tag_filters=-no-sandbox-tsan + --test_tag_filters=-no-sandbox-tsan \ + //... - - name: Bazel Test (TSan - Sandbox, constrained) + - name: Bazel Test (TSan — Sandbox, constrained) run: | bazel --bazelrc=.github/workflows/ci.bazelrc test --config=tsan \ + --verbose_failures \ //src/engine:sandbox_test \ --jobs=1 \ --runs_per_test=1 \ @@ -95,11 +141,6 @@ jobs: exit 1 fi - - name: Bazel Test (ASan) - run: | - bazel --bazelrc=.github/workflows/ci.bazelrc test --config=asan //... \ - --jobs=2 - # MSan is intentionally excluded from CI. It requires ALL linked libraries # (including libstdc++) to be compiled with MSan instrumentation. The system # libstdc++ on GitHub runners is not instrumented, producing false positives @@ -111,4 +152,6 @@ jobs: uses: actions/upload-artifact@v4 with: name: bazel-test-logs - path: bazel-testlogs/ + path: | + bazel-testlogs/ + /tmp/dcodex-test-*.log diff --git a/dcodex-setup.sh b/dcodex-setup.sh index 3599a60..ceb9d5c 100755 --- a/dcodex-setup.sh +++ b/dcodex-setup.sh @@ -273,11 +273,12 @@ if [[ ! -f "${REPO_DIR}/bin/grpcurl" ]]; then chmod +x "${REPO_DIR}/bin/grpcurl" fi -info "Upgrading pip..." -python -m pip install --quiet --upgrade pip - info "Installing grpcio-tools and client requirements..." -python -m pip install --quiet \ +# --break-system-packages: Ubuntu 24.04+ marks system Python as EXTERNALLY-MANAGED. +# Since we're running as root in CI/Docker, venvs are unnecessary overhead. +# We skip `pip install --upgrade pip` because the apt-managed pip cannot be +# upgraded via pip itself (RECORD file not found). +python -m pip install --quiet --break-system-packages --ignore-installed \ grpcio-tools \ -r "${REPO_DIR}/python_client/requirements.txt" @@ -348,46 +349,195 @@ timer # ───────────────────────────────────────────────────────────────────────────── step "6/7 Tests" -if [[ "$MODE" == "test" ]]; then - info "Running sandbox integration tests..." - TEST_START=$(date +%s) +# Common Bazel test flags for diagnostics — always verbose. +BAZEL_TEST_COMMON=( + --verbose_failures + --sandbox_debug + --test_output=all + --test_env=HOME=/tmp +) - set +e - bazel "${BAZEL_JVM_FLAGS[@]}" test \ - --jobs="${BAZEL_JOBS}" \ - --config=asan \ - //... \ - --test_env=HOME=/tmp \ - 2>&1 | tee /tmp/dcodex-test-asan.log - TEST_STATUS_ASAN=$? - set -e +# The specific test targets to run under sanitizers. +# We do NOT use //... because: +# 1. It picks up tests tagged for exclusion under certain sanitizers. +# 2. One test failure aborts the entire invocation (all others = NO STATUS). +# 3. MSan requires all linked libraries to be instrumented — system libstdc++ is not. +ENGINE_TESTS=( + "//src/engine:sandbox_test" + "//src/engine:warm_worker_pool_test" + "//src/engine:dynamic_worker_coordinator_test" + "//src/engine:tsan_checker" +) - set +e - bazel "${BAZEL_JVM_FLAGS[@]}" test \ - --jobs="${BAZEL_JOBS}" \ - --config=msan \ - //... \ - --test_env=HOME=/tmp \ - 2>&1 | tee /tmp/dcodex-test-msan.log - TEST_STATUS_MSAN=$? - set -e +# ── Helper: dump test logs on failure ───────────────────────────────────── +# Bazel buries the actual test output inside bazel-testlogs//test.log. +# This function finds all test.log files and prints their content so failures +# are diagnosable without manually navigating the output tree. +dump_test_logs() { + local config_name="$1" + local log_dir="${REPO_DIR}/bazel-testlogs" + + # The bazel-testlogs convenience symlink is the primary source. + # Fall back to the output_base path used when --output_base is set, + # and finally to the .bazel/testlogs path from dcodex-setup config. + if [[ ! -d "$log_dir" ]]; then + # Search for testlogs under the output_base tree (CI layout). + local output_base_logs + output_base_logs=$(find "${REPO_DIR}/.bazel" -type d -name "testlogs" 2>/dev/null | head -n1) + if [[ -n "$output_base_logs" ]]; then + log_dir="$output_base_logs" + fi + fi + echo "" + echo -e "${RED}${BOLD}━━━ ${config_name} FAILURE — Test Log Dump ━━━${NC}" + + local found_logs=0 + local find_args=("$log_dir" -name "test.log") + # Only apply -newer filter if the timestamp file exists. + if [[ -f /tmp/dcodex-test-ts-"$config_name" ]]; then + find_args+=(-newer /tmp/dcodex-test-ts-"$config_name") + fi + find_args+=(-print0) + + while IFS= read -r -d '' logfile; do + found_logs=1 + local rel_path="${logfile#"${log_dir}/"}" + echo -e "\n${YELLOW}── ${rel_path} ──${NC}" + # Print last 200 lines to avoid flooding the terminal with 10k-line logs. + tail -n 200 "$logfile" + echo -e "${YELLOW}── end ${rel_path} ──${NC}" + done < <(find "${find_args[@]}" 2>/dev/null) + + if [[ $found_logs -eq 0 ]]; then + echo -e "${YELLOW} (no test.log files found in ${log_dir})${NC}" + echo -e "${YELLOW} Check the build output above for compiler errors.${NC}" + fi + echo "" +} + +# ── Helper: run one sanitizer suite ─────────────────────────────────────── +# Runs a specific set of targets under a given sanitizer config, captures +# the exit status, and dumps logs on failure. +run_sanitizer_suite() { + local config_name="$1" + shift + local targets=("$@") + + info "Running ${config_name} tests: ${targets[*]}" + + # Timestamp file for dump_test_logs() to find only fresh logs. + touch /tmp/dcodex-test-ts-"$config_name" + set +e bazel "${BAZEL_JVM_FLAGS[@]}" test \ --jobs="${BAZEL_JOBS}" \ - --config=tsan \ - //... \ - --test_env=HOME=/tmp \ - 2>&1 | tee /tmp/dcodex-test-tsan.log - TEST_STATUS_TSAN=$? + --config="$config_name" \ + "${BAZEL_TEST_COMMON[@]}" \ + "${targets[@]}" \ + 2>&1 | tee /tmp/dcodex-test-"$config_name".log + local status=$? set -e + + if [[ $status -ne 0 ]]; then + dump_test_logs "$config_name" + else + ok "${config_name} tests passed" + fi + + return $status +} + +if [[ "$MODE" == "test" ]]; then + info "Running sanitizer test suites..." + TEST_START=$(date +%s) + + TEST_STATUS_ASAN=0 + TEST_STATUS_MSAN=0 + TEST_STATUS_TSAN=0 + + # ── ASan + UBSan ────────────────────────────────────────────────────── + step "6a/7 ASan + UBSan Tests" + run_sanitizer_suite asan "${ENGINE_TESTS[@]}" || TEST_STATUS_ASAN=$? + + # ── MSan ────────────────────────────────────────────────────────────── + step "6b/7 MSan Tests (SKIPPED)" + warn "MSan requires ALL linked libraries (including system libstdc++) to be" + warn " compiled with -fsanitize=memory. The system libstdc++ is NOT instrumented," + warn " causing false positives in googletest before any DCodeX code runs." + warn " → Matching CI decision: MSan is excluded from automated testing." + warn " → To run MSan, build a custom toolchain with instrumented libc++." + warn " → See: https://clang.llvm.org/docs/MemorySanitizer.html#handling-external-code" + info "MSan skipped (set RUN_MSAN=1 to force-run with suppressions)" + + if [[ "${RUN_MSAN:-0}" == "1" ]]; then + warn "RUN_MSAN=1 — running MSan anyway (expect false positives)..." + MSAN_TARGETS=( + "//src/engine:warm_worker_pool_test" + "//src/engine:dynamic_worker_coordinator_test" + "//src/engine:tsan_checker" + ) + run_sanitizer_suite msan "${MSAN_TARGETS[@]}" || TEST_STATUS_MSAN=$? + fi + + # ── TSan ────────────────────────────────────────────────────────────── + step "6c/7 TSan Tests" + + # TSan: run non-sandbox tests first (matching CI tag filter). + TSAN_TARGETS=( + "//src/engine:warm_worker_pool_test" + "//src/engine:dynamic_worker_coordinator_test" + "//src/engine:tsan_checker" + ) + run_sanitizer_suite tsan "${TSAN_TARGETS[@]}" || TEST_STATUS_TSAN=$? + + # TSan: sandbox_test runs separately with constrained resources. + # The sandbox_test forks clang++ which has high memory overhead under TSan. + if [[ $TEST_STATUS_TSAN -eq 0 ]]; then + info "Running sandbox_test under TSan (constrained: 1 job, 1 run)..." + touch /tmp/dcodex-test-ts-tsan-sandbox + set +e + bazel "${BAZEL_JVM_FLAGS[@]}" test \ + --config=tsan \ + "${BAZEL_TEST_COMMON[@]}" \ + --jobs=1 \ + --runs_per_test=1 \ + --local_test_jobs=1 \ + //src/engine:sandbox_test \ + 2>&1 | tee -a /tmp/dcodex-test-tsan.log + TSAN_SANDBOX_STATUS=$? + set -e + + if [[ $TSAN_SANDBOX_STATUS -ne 0 ]]; then + dump_test_logs "tsan-sandbox" + TEST_STATUS_TSAN=$TSAN_SANDBOX_STATUS + else + ok "TSan sandbox_test passed" + fi + else + warn "Skipping TSan sandbox_test — earlier TSan tests failed" + fi TEST_END=$(date +%s) + + # ── Summary ────────────────────────────────────────────────────────── + echo "" + echo -e "${BOLD}${CYAN}━━━ Test Summary ━━━${NC}" + echo -e " ASan + UBSan: $(if [[ $TEST_STATUS_ASAN -eq 0 ]]; then echo -e "${GREEN}PASS${NC}"; else echo -e "${RED}FAIL (exit $TEST_STATUS_ASAN)${NC}"; fi)" + echo -e " MSan: $(if [[ "${RUN_MSAN:-0}" == "1" ]]; then if [[ $TEST_STATUS_MSAN -eq 0 ]]; then echo -e "${GREEN}PASS${NC}"; else echo -e "${RED}FAIL (exit $TEST_STATUS_MSAN)${NC}"; fi; else echo -e "${YELLOW}SKIPPED${NC}"; fi)" + echo -e " TSan: $(if [[ $TEST_STATUS_TSAN -eq 0 ]]; then echo -e "${GREEN}PASS${NC}"; else echo -e "${RED}FAIL (exit $TEST_STATUS_TSAN)${NC}"; fi)" + echo -e " Duration: $(( TEST_END - TEST_START ))s" + echo -e " Logs: /tmp/dcodex-test-{asan,tsan}.log" + echo "" - if [[ $TEST_STATUS_ASAN -eq 0 && $TEST_STATUS_MSAN -eq 0 && $TEST_STATUS_TSAN -eq 0 ]]; then - ok "All tests passed in $(( TEST_END - TEST_START ))s" + if [[ $TEST_STATUS_ASAN -eq 0 && $TEST_STATUS_TSAN -eq 0 ]]; then + ok "All active test suites passed in $(( TEST_END - TEST_START ))s" else - die "Tests FAILED (asan: ${TEST_STATUS_ASAN}, msan: ${TEST_STATUS_MSAN}, tsan: ${TEST_STATUS_TSAN}) — see /tmp/dcodex-test-*.log" + FAILED_SUITES="" + [[ $TEST_STATUS_ASAN -ne 0 ]] && FAILED_SUITES+="asan " + [[ $TEST_STATUS_MSAN -ne 0 ]] && FAILED_SUITES+="msan " + [[ $TEST_STATUS_TSAN -ne 0 ]] && FAILED_SUITES+="tsan " + die "Tests FAILED: ${FAILED_SUITES}— see diagnostic output above and /tmp/dcodex-test-*.log" fi else info "Skipping tests (pass --test to run them)" diff --git a/python_client/requirements.txt b/python_client/requirements.txt index d2655dd..e22b5b5 100755 --- a/python_client/requirements.txt +++ b/python_client/requirements.txt @@ -1,3 +1,3 @@ -grpcio==1.78.1 -typing-extensions==4.12.2 +grpcio==1.80.0 +typing-extensions>=4.14.1 pytest>=8.0 diff --git a/src/engine/dynamic_worker_coordinator.cpp b/src/engine/dynamic_worker_coordinator.cpp index fed7d4f..7fe82a0 100644 --- a/src/engine/dynamic_worker_coordinator.cpp +++ b/src/engine/dynamic_worker_coordinator.cpp @@ -385,14 +385,27 @@ void DynamicWorkerCoordinator::Worker::Run() { current_task->StartExecution(); current_task->PumpWrites(); - // Transition to RECYCLING. compare_exchange prevents overwriting kShutdown - // if NotifyShutdown() was called during task execution. - state_.store(WorkerState::kRecycling, std::memory_order_release); + // Transition to RECYCLING via compare_exchange to prevent overwriting + // kShutdown if NotifyShutdown() was called during task execution. + // Without this, the worker could clobber kShutdown with kRecycling, + // then transition to kIdle and block forever in cv_.Wait() — a deadlock + // because Shutdown() already called NotifyShutdown() and won't again. + WorkerState expected_busy = WorkerState::kBusy; + if (!state_.compare_exchange_strong(expected_busy, WorkerState::kRecycling, + std::memory_order_acq_rel)) { + // State was changed to kShutdown during task execution. Exit now. + break; + } DoRecycle(); WorkerState expected = WorkerState::kRecycling; - state_.compare_exchange_strong(expected, WorkerState::kIdle, - std::memory_order_acq_rel); + if (!state_.compare_exchange_strong(expected, WorkerState::kIdle, + std::memory_order_acq_rel)) { + // State was changed to kShutdown during recycling. Do NOT scan the + // pool queue — we must exit the worker loop to avoid dequeuing a + // request that we'll never execute (which causes caller hangs). + break; + } // After recycling, scan the pool queue for a waiting request. // Lock order: pool->mutex_ is taken here, worker->mutex_ is taken diff --git a/src/engine/dynamic_worker_coordinator_test.cc b/src/engine/dynamic_worker_coordinator_test.cc index bdb176c..f32196a 100644 --- a/src/engine/dynamic_worker_coordinator_test.cc +++ b/src/engine/dynamic_worker_coordinator_test.cc @@ -205,20 +205,22 @@ TEST(DynamicWorkerCoordinatorTest, LeaseTimeout) { DynamicWorkerCoordinator::Options opts; opts.min_workers = 1; opts.max_workers = 1; - opts.recycle_duration = absl::Seconds(60); // Keep worker busy. + // Use a short recycle so shutdown doesn't block for minutes. + // The gate in task1 keeps the worker busy, not the recycle duration. + opts.recycle_duration = absl::Milliseconds(200); opts.balance_period = absl::Seconds(60); opts.lease_timeout = absl::Milliseconds(100); DynamicWorkerCoordinator coordinator(opts); coordinator.Start(); - // Occupy the sole worker indefinitely. + // Occupy the sole worker via the gate — it blocks in StartExecution. std::atomic c1{0}; absl::Notification gate, done1; auto task1 = std::make_shared(&c1, &gate, &done1); auto lease1 = coordinator.LeaseWorker(LanguageId::kCpp, task1); ASSERT_TRUE(lease1.ok()); - // Second request must time out. + // Second request must time out (worker is blocked on gate). std::atomic c2{0}; absl::Notification done2; auto task2 = std::make_shared(&c2, nullptr, &done2); @@ -228,7 +230,7 @@ TEST(DynamicWorkerCoordinatorTest, LeaseTimeout) { EXPECT_EQ(lease2.status().code(), absl::StatusCode::kDeadlineExceeded) << lease2.status(); - // Clean up. + // Clean up: release the gate so task1 can finish, then shut down. gate.Notify(); done1.WaitForNotification(); coordinator.ReleaseWorker(*lease1); diff --git a/src/engine/warm_worker_pool.cpp b/src/engine/warm_worker_pool.cpp index a594a66..b1999e5 100755 --- a/src/engine/warm_worker_pool.cpp +++ b/src/engine/warm_worker_pool.cpp @@ -35,18 +35,32 @@ void WarmWorkerPool::Start() { } void WarmWorkerPool::Shutdown() { - if (shutting_down_.exchange(true)) { + if (shutting_down_.exchange(true, std::memory_order_acq_rel)) { return; } - // No need to lock mutex_ just to notify workers, - // as the workers_ vector is constant after Start(). - for (const auto& worker : workers_) { + + // Collect raw worker pointers under the lock so we don't race with + // AcquireWorker() which iterates workers_ under the same mutex. + std::vector workers_to_stop; + { + absl::MutexLock lock(&mutex_); + workers_to_stop.reserve(workers_.size()); + for (const auto& worker : workers_) { + workers_to_stop.push_back(worker.get()); + } + } + + // Signal all workers to stop (outside the pool lock to avoid deadlock + // with workers that might re-acquire the pool mutex during their loop). + for (auto* worker : workers_to_stop) { worker->Notify(); } - // Wait for all workers to finish. - for (const auto& worker : workers_) { + // Wait for all worker threads to finish. + for (auto* worker : workers_to_stop) { worker->Join(); } + + // Now safe to clear — no worker threads are running. { absl::MutexLock lock(&mutex_); active_tasks_.clear(); @@ -55,18 +69,18 @@ void WarmWorkerPool::Shutdown() { } absl::Status WarmWorkerPool::AcquireWorker(std::shared_ptr task) { - if (shutting_down_.load(std::memory_order_relaxed)) { + if (shutting_down_.load(std::memory_order_acquire)) { return absl::FailedPreconditionError("Worker pool is shutting down"); } // Quick check before locking. - if (idle_workers_.load(std::memory_order_relaxed) <= 0) { + if (idle_workers_.load(std::memory_order_acquire) <= 0) { return absl::ResourceExhaustedError("No idle workers available"); } absl::MutexLock lock(&mutex_); // Re-check after locking. - if (shutting_down_.load(std::memory_order_relaxed)) { + if (shutting_down_.load(std::memory_order_acquire)) { return absl::FailedPreconditionError("Worker pool is shutting down"); } @@ -107,13 +121,13 @@ bool WarmWorkerPool::Worker::TryAssign(std::shared_ptr task) { } void WarmWorkerPool::Worker::Notify() { - stopping_.store(true, std::memory_order_relaxed); + stopping_.store(true, std::memory_order_release); absl::MutexLock lock(&mutex_); cv_.Signal(); } void WarmWorkerPool::Worker::Join() { - stopping_.store(true, std::memory_order_relaxed); + stopping_.store(true, std::memory_order_release); { absl::MutexLock lock(&mutex_); cv_.Signal(); @@ -128,10 +142,10 @@ void WarmWorkerPool::Worker::Run() { std::shared_ptr task; { absl::MutexLock lock(&mutex_); - while (task_ == nullptr && !stopping_.load(std::memory_order_relaxed)) { + while (task_ == nullptr && !stopping_.load(std::memory_order_acquire)) { cv_.Wait(&mutex_); } - if (stopping_.load(std::memory_order_relaxed) && task_ == nullptr) { + if (stopping_.load(std::memory_order_acquire) && task_ == nullptr) { break; } task = std::move(task_);