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