• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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