xref: /aosp_15_r20/external/cronet/components/cronet/android/java/src/org/chromium/net/impl/JavaUrlRequestUtils.java (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2019 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.net.impl;
6 
7 import androidx.annotation.IntDef;
8 
9 import org.chromium.net.InlineExecutionProhibitedException;
10 
11 import java.lang.annotation.Retention;
12 import java.lang.annotation.RetentionPolicy;
13 import java.util.concurrent.Executor;
14 
15 /**
16  * Utilities for Java-based UrlRequest implementations.
17  * {@hide}
18  */
19 public final class JavaUrlRequestUtils {
20     /**
21      * State interface for keeping track of the internal state of a {@link UrlRequest}.
22      *
23      *               /- AWAITING_FOLLOW_REDIRECT <- REDIRECT_RECEIVED <-\     /- READING <--\
24      *               |                                                  |     |             |
25      *               V                                                  /     V             /
26      * NOT_STARTED -> STARTED -----------------------------------------------> AWAITING_READ -------
27      * --> COMPLETE
28      *
29      *
30      */
31     @IntDef({
32         State.NOT_STARTED,
33         State.STARTED,
34         State.REDIRECT_RECEIVED,
35         State.AWAITING_FOLLOW_REDIRECT,
36         State.AWAITING_READ,
37         State.READING,
38         State.ERROR,
39         State.COMPLETE,
40         State.CANCELLED
41     })
42     @Retention(RetentionPolicy.SOURCE)
43     public @interface State {
44         int NOT_STARTED = 0;
45         int STARTED = 1;
46         int REDIRECT_RECEIVED = 2;
47         int AWAITING_FOLLOW_REDIRECT = 3;
48         int AWAITING_READ = 4;
49         int READING = 5;
50         int ERROR = 6;
51         int COMPLETE = 7;
52         int CANCELLED = 8;
53     }
54 
55     /**
56      *  Interface used to run commands that could throw an exception. Specifically useful for
57      *  calling {@link UrlRequest.Callback}s on a user-supplied executor.
58      */
59     public interface CheckedRunnable {
run()60         void run() throws Exception;
61     }
62 
63     /** Executor that detects and throws if its mDelegate runs a submitted runnable inline. */
64     public static final class DirectPreventingExecutor implements Executor {
65         private final Executor mDelegate;
66 
67         /**
68          * Constructs an {@link DirectPreventingExecutor} that executes {@link runnable}s on the
69          * provided {@link Executor}.
70          *
71          * @param delegate the {@link Executor} used to run {@link Runnable}s
72          */
DirectPreventingExecutor(Executor delegate)73         public DirectPreventingExecutor(Executor delegate) {
74             this.mDelegate = delegate;
75         }
76 
77         /**
78          * Executes a {@link Runnable} on this {@link Executor} and throws an exception if it is
79          * being run on the same thread as the calling thread.
80          *
81          * @param command the {@link Runnable} to attempt to run
82          */
83         @Override
execute(Runnable command)84         public void execute(Runnable command) {
85             Thread currentThread = Thread.currentThread();
86             InlineCheckingRunnable runnable = new InlineCheckingRunnable(command, currentThread);
87             mDelegate.execute(runnable);
88             // This next read doesn't require synchronization; only the current thread could have
89             // written to runnable.mExecutedInline.
90             if (runnable.mExecutedInline != null) {
91                 throw runnable.mExecutedInline;
92             } else {
93                 // It's possible that this method is being called on an executor, and the runnable
94                 // that was just queued will run on this thread after the current runnable returns.
95                 // By nulling out the mCallingThread field, the InlineCheckingRunnable's current
96                 // thread comparison will not fire.
97                 //
98                 // Java reference assignment is always atomic (no tearing, even on 64-bit VMs, see
99                 // JLS 17.7), but other threads aren't guaranteed to ever see updates without
100                 // something like locking, volatile, or AtomicReferences. We're ok in
101                 // this instance, since this write only needs to be seen in the case that
102                 // InlineCheckingRunnable.run() runs on the same thread as this execute() method.
103                 runnable.mCallingThread = null;
104             }
105         }
106 
107         private static final class InlineCheckingRunnable implements Runnable {
108             private final Runnable mCommand;
109             private Thread mCallingThread;
110             private InlineExecutionProhibitedException mExecutedInline;
111 
InlineCheckingRunnable(Runnable command, Thread callingThread)112             private InlineCheckingRunnable(Runnable command, Thread callingThread) {
113                 this.mCommand = command;
114                 this.mCallingThread = callingThread;
115             }
116 
117             @Override
run()118             public void run() {
119                 if (Thread.currentThread() == mCallingThread) {
120                     // Can't throw directly from here, since the delegate executor could catch this
121                     // exception.
122                     mExecutedInline = new InlineExecutionProhibitedException();
123                     return;
124                 }
125                 mCommand.run();
126             }
127         }
128     }
129 }
130