LCOV - code coverage report
Current view: top level - server/cancellation - RequestStateContext.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 33 33 100.0 %
Date: 2022-11-19 15:00:39 Functions: 13 13 100.0 %

          Line data    Source code
       1             : // Copyright (C) 2021 The Android Open Source Project
       2             : //
       3             : // Licensed under the Apache License, Version 2.0 (the "License");
       4             : // you may not use this file except in compliance with the License.
       5             : // You may obtain a copy of the License at
       6             : //
       7             : // http://www.apache.org/licenses/LICENSE-2.0
       8             : //
       9             : // Unless required by applicable law or agreed to in writing, software
      10             : // distributed under the License is distributed on an "AS IS" BASIS,
      11             : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12             : // See the License for the specific language governing permissions and
      13             : // limitations under the License.
      14             : 
      15             : package com.google.gerrit.server.cancellation;
      16             : 
      17             : import com.google.common.annotations.VisibleForTesting;
      18             : import com.google.common.collect.ImmutableSet;
      19             : import java.util.HashSet;
      20             : import java.util.Set;
      21             : 
      22             : /**
      23             :  * Context that allows to register {@link RequestStateProvider}s.
      24             :  *
      25             :  * <p>The registered {@link RequestStateProvider}s are stored in {@link ThreadLocal} so that they
      26             :  * can be accessed during the request execution (via {@link #getRequestStateProviders()}.
      27             :  *
      28             :  * <p>On {@link #close()} the {@link RequestStateProvider}s that have been registered by this {@code
      29             :  * RequestStateContext} instance are removed from {@link ThreadLocal}.
      30             :  *
      31             :  * <p>Nesting {@code RequestStateContext}s is possible.
      32             :  *
      33             :  * <p>Currently there is no logic to automatically copy the {@link RequestStateContext} to
      34             :  * background threads, but implementing this may be considered in the future. This means that by
      35             :  * default we only support cancellation of the main thread, but not of background threads. That's
      36             :  * fine as all significant work is being done in the main thread.
      37             :  *
      38             :  * <p>{@link com.google.gerrit.server.util.RequestContext} is also a context that is available for
      39             :  * the time of the request, but it is not suitable to manage registrations of {@link
      40             :  * RequestStateProvider}s. Hence {@link RequestStateProvider} registrations are managed by a
      41             :  * separate context, which is this class, {@link RequestStateContext}:
      42             :  *
      43             :  * <ul>
      44             :  *   <li>{@link com.google.gerrit.server.util.RequestContext} is an interface that has many
      45             :  *       implementations and hence cannot manage a {@link ThreadLocal} state.
      46             :  *   <li>{@link com.google.gerrit.server.util.RequestContext} is not an {@link AutoCloseable} and
      47             :  *       hence cannot cleanup any {@link ThreadLocal} state on close (turning it into an {@link
      48             :  *       AutoCloseable} would require a large refactoring).
      49             :  *   <li>Despite the name {@link com.google.gerrit.server.util.RequestContext} is not only used for
      50             :  *       requests scopes but also for other scopes that are not a request (e.g. plugin invocations,
      51             :  *       email sending, manual scopes).
      52             :  *   <li>{@link com.google.gerrit.server.util.RequestContext} is not copied to background and should
      53             :  *       not be, but for {@link RequestStateContext} we may consider doing this in the future.
      54             :  * </ul>
      55             :  */
      56             : public class RequestStateContext implements AutoCloseable {
      57             :   /** The {@link RequestStateProvider}s that have been registered for the thread. */
      58         154 :   private static final ThreadLocal<Set<RequestStateProvider>> threadLocalRequestStateProviders =
      59             :       new ThreadLocal<>();
      60             : 
      61             :   /** Whether currently a non-cancellable operation is being performed. */
      62         154 :   private static final ThreadLocal<Boolean> inNonCancellableOperation = new ThreadLocal<>();
      63             : 
      64             :   /**
      65             :    * Aborts the current request by throwing a {@link RequestCancelledException} if any of the
      66             :    * registered {@link RequestStateProvider}s reports the request as cancelled.
      67             :    *
      68             :    * <p>If an atomic operation is currently being performed, request cancellations are ignored and
      69             :    * the request doesn't get aborted.
      70             :    *
      71             :    * @throws RequestCancelledException thrown if the current request is cancelled and should be
      72             :    *     aborted
      73             :    * @see #startNonCancellableOperation()
      74             :    */
      75             :   public static void abortIfCancelled() throws RequestCancelledException {
      76         154 :     if (inNonCancellableOperation.get() != null && inNonCancellableOperation.get()) {
      77             :       // Do not cancel the request while an atomic operation is being performed.
      78         109 :       return;
      79             :     }
      80             : 
      81         154 :     getRequestStateProviders()
      82         154 :         .forEach(
      83             :             requestStateProvider ->
      84         105 :                 requestStateProvider.checkIfCancelled(
      85             :                     (reason, message) -> {
      86           3 :                       throw new RequestCancelledException(reason, message);
      87             :                     }));
      88         154 :   }
      89             : 
      90             :   /**
      91             :    * Starts a non-cancellable operation.
      92             :    *
      93             :    * <p>If the request was cancelled while the non-cancellable operation was running, it gets
      94             :    * aborted on close of the returned {@link AutoCloseable}.
      95             :    *
      96             :    * @return {@link AutoCloseable} that finishes the non-cancellable operation on close.
      97             :    */
      98             :   public static NonCancellableOperationContext startNonCancellableOperation() {
      99         109 :     if (inNonCancellableOperation.get() != null && inNonCancellableOperation.get()) {
     100             :       // atomic operation is already in progress
     101           1 :       return () -> {};
     102             :     }
     103             : 
     104         109 :     inNonCancellableOperation.set(true);
     105         109 :     return () -> {
     106         109 :       inNonCancellableOperation.remove();
     107         109 :       abortIfCancelled();
     108         109 :     };
     109             :   }
     110             : 
     111             :   /** Returns the {@link RequestStateProvider}s that have been registered for the thread. */
     112             :   @VisibleForTesting
     113             :   static ImmutableSet<RequestStateProvider> getRequestStateProviders() {
     114         154 :     if (threadLocalRequestStateProviders.get() == null) {
     115         154 :       return ImmutableSet.of();
     116             :     }
     117         105 :     return ImmutableSet.copyOf(threadLocalRequestStateProviders.get());
     118             :   }
     119             : 
     120             :   /** Opens a {@code RequestStateContext}. */
     121             :   public static RequestStateContext open() {
     122         105 :     return new RequestStateContext();
     123             :   }
     124             : 
     125             :   /**
     126             :    * The {@link RequestStateProvider}s that have been registered by this {@code
     127             :    * RequestStateContext}.
     128             :    */
     129         105 :   private Set<RequestStateProvider> requestStateProviders = new HashSet<>();
     130             : 
     131         105 :   private RequestStateContext() {}
     132             : 
     133             :   /**
     134             :    * Registers a {@link RequestStateProvider}.
     135             :    *
     136             :    * @param requestStateProvider the {@link RequestStateProvider} that should be registered
     137             :    * @return the {@code RequestStateContext} instance for chaining calls
     138             :    */
     139             :   public RequestStateContext addRequestStateProvider(RequestStateProvider requestStateProvider) {
     140         105 :     if (threadLocalRequestStateProviders.get() == null) {
     141         105 :       threadLocalRequestStateProviders.set(new HashSet<>());
     142             :     }
     143         105 :     if (threadLocalRequestStateProviders.get().add(requestStateProvider)) {
     144         105 :       requestStateProviders.add(requestStateProvider);
     145             :     }
     146         105 :     return this;
     147             :   }
     148             : 
     149             :   /**
     150             :    * Closes this {@code RequestStateContext}.
     151             :    *
     152             :    * <p>Ensures that all {@link RequestStateProvider}s that have been registered by this {@code
     153             :    * RequestStateContext} instance are removed from {@link #threadLocalRequestStateProviders}.
     154             :    *
     155             :    * <p>If no {@link RequestStateProvider}s remain in {@link #threadLocalRequestStateProviders},
     156             :    * {@link #threadLocalRequestStateProviders} is unset.
     157             :    */
     158             :   @Override
     159             :   public void close() {
     160         105 :     if (threadLocalRequestStateProviders.get() != null) {
     161         105 :       requestStateProviders.forEach(
     162             :           requestStateProvider ->
     163         105 :               threadLocalRequestStateProviders.get().remove(requestStateProvider));
     164         105 :       if (threadLocalRequestStateProviders.get().isEmpty()) {
     165         105 :         threadLocalRequestStateProviders.remove();
     166             :       }
     167             :     }
     168         105 :   }
     169             : 
     170             :   /**
     171             :    * Context for running a non-cancellable operation.
     172             :    *
     173             :    * <p>While open, the current request cannot be cancelled.
     174             :    */
     175             :   public interface NonCancellableOperationContext extends AutoCloseable {
     176             :     @Override
     177             :     void close();
     178             :   }
     179             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750