1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.appsearch.util; 18 19 import android.annotation.NonNull; 20 import android.app.appsearch.AppSearchEnvironmentFactory; 21 import android.os.UserHandle; 22 import android.util.ArrayMap; 23 24 import com.android.internal.annotations.GuardedBy; 25 import com.android.server.appsearch.AppSearchRateLimitConfig; 26 import com.android.server.appsearch.FrameworkServiceAppSearchConfig; 27 import com.android.server.appsearch.ServiceAppSearchConfig; 28 29 import java.util.Map; 30 import java.util.Objects; 31 import java.util.concurrent.Executor; 32 import java.util.concurrent.ExecutorService; 33 import java.util.concurrent.LinkedBlockingQueue; 34 import java.util.concurrent.TimeUnit; 35 36 /** 37 * Manages executors within AppSearch. 38 * 39 * <p>This class is thread-safe. 40 * 41 * @hide 42 */ 43 public class ExecutorManager { 44 private final ServiceAppSearchConfig mAppSearchConfig; 45 46 /** 47 * A map of per-user executors for queued work. These can be started or shut down via this 48 * class's public API. 49 */ 50 @GuardedBy("mPerUserExecutorsLocked") 51 private final Map<UserHandle, ExecutorService> mPerUserExecutorsLocked = new ArrayMap<>(); 52 53 /** 54 * Creates a new {@link ExecutorService} with default settings for use in AppSearch. 55 * 56 * <p>The default settings are to use as many threads as there are CPUs. The core pool size is 1 57 * if cached executors should be used, or also the CPU number if fixed executors should be used. 58 */ 59 @NonNull createDefaultExecutorService()60 public static ExecutorService createDefaultExecutorService() { 61 boolean useFixedExecutorService = 62 FrameworkServiceAppSearchConfig.getUseFixedExecutorService(); 63 int corePoolSize = useFixedExecutorService ? Runtime.getRuntime().availableProcessors() : 1; 64 long keepAliveTime = useFixedExecutorService ? 0L : 60L; 65 66 return AppSearchEnvironmentFactory.getEnvironmentInstance() 67 .createExecutorService( 68 /* corePoolSize= */ corePoolSize, 69 /* maxConcurrency= */ Runtime.getRuntime().availableProcessors(), 70 /* keepAliveTime= */ keepAliveTime, 71 /* unit= */ TimeUnit.SECONDS, 72 /* workQueue= */ new LinkedBlockingQueue<>(), 73 /* priority= */ 0); // priority is unused. 74 } 75 ExecutorManager(@onNull ServiceAppSearchConfig appSearchConfig)76 public ExecutorManager(@NonNull ServiceAppSearchConfig appSearchConfig) { 77 mAppSearchConfig = Objects.requireNonNull(appSearchConfig); 78 } 79 80 /** 81 * Gets the executor service for the given user, creating it if it does not exist. 82 * 83 * <p>If AppSearch rate limiting is enabled, the input rate Limit config will be non-null, and 84 * the returned executor will be a RateLimitedExecutor instance. 85 * 86 * <p>You are responsible for making sure not to call this for locked users. The executor will 87 * be created without problems but most operations on locked users will fail. 88 */ 89 @NonNull getOrCreateUserExecutor(@onNull UserHandle userHandle)90 public Executor getOrCreateUserExecutor(@NonNull UserHandle userHandle) { 91 Objects.requireNonNull(userHandle); 92 synchronized (mPerUserExecutorsLocked) { 93 if (mAppSearchConfig.getCachedRateLimitEnabled()) { 94 return getOrCreateUserRateLimitedExecutorLocked( 95 userHandle, mAppSearchConfig.getCachedRateLimitConfig()); 96 } else { 97 return getOrCreateUserExecutorLocked(userHandle); 98 } 99 } 100 } 101 102 @GuardedBy("mPerUserExecutorsLocked") 103 @NonNull getOrCreateUserExecutorLocked(@onNull UserHandle userHandle)104 private Executor getOrCreateUserExecutorLocked(@NonNull UserHandle userHandle) { 105 Objects.requireNonNull(userHandle); 106 ExecutorService executor = mPerUserExecutorsLocked.get(userHandle); 107 if (executor == null) { 108 executor = ExecutorManager.createDefaultExecutorService(); 109 mPerUserExecutorsLocked.put(userHandle, executor); 110 } else if (executor instanceof RateLimitedExecutor) { 111 executor = ((RateLimitedExecutor) executor).getExecutor(); 112 } 113 return executor; 114 } 115 116 @GuardedBy("mPerUserExecutorsLocked") 117 @NonNull getOrCreateUserRateLimitedExecutorLocked( @onNull UserHandle userHandle, @NonNull AppSearchRateLimitConfig rateLimitConfig)118 private Executor getOrCreateUserRateLimitedExecutorLocked( 119 @NonNull UserHandle userHandle, @NonNull AppSearchRateLimitConfig rateLimitConfig) { 120 Objects.requireNonNull(userHandle); 121 Objects.requireNonNull(rateLimitConfig); 122 ExecutorService executor = mPerUserExecutorsLocked.get(userHandle); 123 if (executor instanceof RateLimitedExecutor) { 124 ((RateLimitedExecutor) executor).setRateLimitConfig(rateLimitConfig); 125 } else { 126 executor = 127 new RateLimitedExecutor( 128 ExecutorManager.createDefaultExecutorService(), rateLimitConfig); 129 mPerUserExecutorsLocked.put(userHandle, executor); 130 } 131 return executor; 132 } 133 134 /** 135 * Gracefully shuts down the executor for the given user if there is one, waiting up to 30 136 * seconds for jobs to finish. 137 */ shutDownAndRemoveUserExecutor(@onNull UserHandle userHandle)138 public void shutDownAndRemoveUserExecutor(@NonNull UserHandle userHandle) 139 throws InterruptedException { 140 Objects.requireNonNull(userHandle); 141 ExecutorService executor; 142 synchronized (mPerUserExecutorsLocked) { 143 executor = mPerUserExecutorsLocked.remove(userHandle); 144 } 145 if (executor != null) { 146 executor.shutdown(); 147 // Wait a little bit to finish outstanding requests. It's important not to call 148 // shutdownNow because nothing would pass a final result to the caller, leading to 149 // hangs. If we are interrupted or the timeout elapses, just move on to closing the 150 // user instance, meaning pending tasks may crash when AppSearchImpl closes under 151 // them. 152 executor.awaitTermination(30, TimeUnit.SECONDS); 153 } 154 } 155 } 156