Set verbosity on cancel requests when --worker_verbose is set.

When --worker_verbose is enabled, regular work requests have their verbosity set to 10. Previously, cancellation requests did not set verbosity, making it difficult to enable verbose logging for debugging worker cancel requests.

This change sets the verbosity level to 10 on cancellation work requests whenever --worker_verbose is enabled, resolving Bazel issue #25803.

PiperOrigin-RevId: 971830073
Change-Id: Ib9e8c903c6efe4191ee094a56d35bc5d3fd9fd59
diff --git a/src/main/java/com/google/devtools/build/lib/worker/WorkerSpawnRunner.java b/src/main/java/com/google/devtools/build/lib/worker/WorkerSpawnRunner.java
index 869db85..03b4343 100644
--- a/src/main/java/com/google/devtools/build/lib/worker/WorkerSpawnRunner.java
+++ b/src/main/java/com/google/devtools/build/lib/worker/WorkerSpawnRunner.java
@@ -17,6 +17,7 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 import static java.nio.charset.StandardCharsets.UTF_8;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Stopwatch;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.hash.HashCode;
@@ -86,7 +87,7 @@
    * The verbosity level implied by `--worker_verbose`. This value allows for manually setting some
    * only-slightly-verbose levels.
    */
-  private static final int VERBOSE_LEVEL = 10;
+  static final int VERBOSE_LEVEL = 10;
 
   /**
    * The next work request ID to use. This field is static so we don't reuse work request IDs across
@@ -290,6 +291,16 @@
     return requestBuilder.build();
   }
 
+  @VisibleForTesting
+  WorkRequest createCancelRequest(int requestId) {
+    WorkRequest.Builder cancelRequestBuilder =
+        WorkRequest.newBuilder().setRequestId(requestId).setCancel(true);
+    if (workerOptions.getWorkerVerbose()) {
+      cancelRequestBuilder.setVerbosity(VERBOSE_LEVEL);
+    }
+    return cancelRequestBuilder.build();
+  }
+
   /**
    * Recursively expands arguments by replacing @filename args with the contents of the referenced
    * files. The @ itself can be escaped with @@. This deliberately does not expand --flagfile= style
@@ -633,11 +644,7 @@
               Worker w = worker;
               try {
                 if (canCancel) {
-                  WorkRequest cancelRequest =
-                      WorkRequest.newBuilder()
-                          .setRequestId(request.getRequestId())
-                          .setCancel(true)
-                          .build();
+                  WorkRequest cancelRequest = createCancelRequest(request.getRequestId());
                   w.putRequest(cancelRequest);
                 }
                 w.getResponse(request.getRequestId());
diff --git a/src/test/java/com/google/devtools/build/lib/worker/WorkerSpawnRunnerTest.java b/src/test/java/com/google/devtools/build/lib/worker/WorkerSpawnRunnerTest.java
index 8e95ab6..154c3ae 100644
--- a/src/test/java/com/google/devtools/build/lib/worker/WorkerSpawnRunnerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/worker/WorkerSpawnRunnerTest.java
@@ -68,6 +68,7 @@
 import com.google.devtools.build.lib.worker.WorkerProtocol.WorkResponse;
 import com.google.devtools.common.options.Options;
 import java.io.IOException;
+import java.time.Duration;
 import java.util.Optional;
 import java.util.concurrent.Semaphore;
 import org.junit.Before;
@@ -279,6 +280,90 @@
   }
 
   @Test
+  public void testCreateCancelRequest_default() throws Exception {
+    WorkerOptions workerOptions = Options.getDefaults(WorkerOptions.class);
+    WorkerSpawnRunner runner = createWorkerSpawnRunner(workerOptions);
+    WorkRequest request = runner.createCancelRequest(1);
+    assertThat(request).isEqualTo(WorkRequest.newBuilder().setRequestId(1).setCancel(true).build());
+  }
+
+  @Test
+  public void testCreateCancelRequest_verbose() throws Exception {
+    WorkerOptions workerOptions = Options.getDefaults(WorkerOptions.class);
+    workerOptions.setWorkerVerbose(true);
+    WorkerSpawnRunner runner = createWorkerSpawnRunner(workerOptions);
+    WorkRequest request = runner.createCancelRequest(1);
+    assertThat(request)
+        .isEqualTo(
+            WorkRequest.newBuilder()
+                .setRequestId(1)
+                .setCancel(true)
+                .setVerbosity(WorkerSpawnRunner.VERBOSE_LEVEL)
+                .build());
+  }
+
+  @Test
+  public void testExecInWorker_sendsCancelMessageOnInterrupt_verbose() throws Exception {
+    WorkerOptions workerOptions = Options.getDefaults(WorkerOptions.class);
+    workerOptions.setWorkerCancellation(true);
+    workerOptions.setWorkerVerbose(true);
+    workerOptions.setWorkerSandboxing(ImmutableList.of(Maps.immutableEntry("", true)));
+    when(spawn.getExecutionInfo())
+        .thenReturn(ImmutableMap.of(ExecutionRequirements.SUPPORTS_WORKER_CANCELLATION, "1"));
+    when(worker.isSandboxed()).thenReturn(true);
+    WorkerSpawnRunner runner = createWorkerSpawnRunner(workerOptions);
+    WorkerKey key = createWorkerKey(fs, "mnem", false);
+    Path logFile = fs.getPath("/worker.log");
+    Semaphore secondResponseRequested = new Semaphore(0);
+    // Fake that the getting the regular response gets interrupted and we then answer the cancel.
+    when(worker.getResponse(anyInt()))
+        .thenThrow(new InterruptedException())
+        .thenAnswer(
+            invocation -> {
+              secondResponseRequested.release();
+              return WorkResponse.newBuilder()
+                  .setRequestId(invocation.getArgument(0))
+                  .setWasCancelled(true)
+                  .build();
+            });
+    assertThrows(
+        InterruptedException.class,
+        () ->
+            runner.execInWorker(
+                spawn,
+                key,
+                context,
+                new SandboxInputs(ImmutableMap.of(), ImmutableMap.of(), ImmutableMap.of()),
+                SandboxOutputs.create(ImmutableSet.of(), ImmutableSet.of()),
+                ImmutableList.of(),
+                inputFileCache,
+                spawnMetrics));
+    secondResponseRequested.acquire();
+    assertThat(logFile.exists()).isFalse();
+    verify(context).report(SpawnExecutingEvent.create("worker"));
+    ArgumentCaptor<WorkRequest> argumentCaptor = ArgumentCaptor.forClass(WorkRequest.class);
+    verify(worker, times(2)).putRequest(argumentCaptor.capture());
+    assertThat(argumentCaptor.getAllValues().get(0))
+        .isEqualTo(
+            WorkRequest.newBuilder()
+                .setRequestId(0)
+                .setVerbosity(WorkerSpawnRunner.VERBOSE_LEVEL)
+                .build());
+    assertThat(argumentCaptor.getAllValues().get(1))
+        .isEqualTo(
+            WorkRequest.newBuilder()
+                .setRequestId(0)
+                .setCancel(true)
+                .setVerbosity(WorkerSpawnRunner.VERBOSE_LEVEL)
+                .build());
+    // Wait until thread produced by WorkerSpawnRunner.finishWorkAsync is finshed and returned
+    // resources via resourceHandle.
+    Thread.sleep(Duration.ofMillis(50));
+    verify(resourceHandle).close();
+    verify(resourceHandle, never()).invalidateAndClose(any());
+  }
+
+  @Test
   public void testExecInWorker_unsandboxedDiesOnInterrupt() throws Exception {
     WorkerOptions workerOptions = Options.getDefaults(WorkerOptions.class);
     workerOptions.setWorkerCancellation(true);