• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.art;
18 
19 import static android.os.IBinder.DeathRecipient;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.os.Build;
24 import android.os.IBinder;
25 import android.os.RemoteException;
26 import android.system.SystemCleaner;
27 import android.util.CloseGuard;
28 
29 import androidx.annotation.RequiresApi;
30 
31 import com.android.internal.annotations.GuardedBy;
32 import com.android.internal.annotations.VisibleForTesting;
33 
34 import java.lang.ref.Cleaner;
35 import java.lang.ref.Reference;
36 import java.util.concurrent.Executors;
37 import java.util.concurrent.ScheduledExecutorService;
38 
39 /**
40  * A helper class that caches a reference to artd, to avoid repetitive calls to `waitForService`,
41  * as the latter is considered expensive.
42  *
43  * @hide
44  */
45 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
46 public class ArtdRefCache {
47     // The 15s timeout is arbitrarily picked.
48     // TODO(jiakaiz): Revisit this based on real CUJs.
49     @VisibleForTesting public static final long CACHE_TIMEOUT_MS = 15_000;
50 
51     // The static field is associated with the class and the class loader that loads it. In the
52     // Pre-reboot Dexopt case, this class is loaded by a separate class loader, so it doesn't share
53     // the same static field with the class outside of the class loader.
54     @GuardedBy("ArtdRefCache.class") @Nullable private static ArtdRefCache sInstance = null;
55 
56     @NonNull private final Injector mInjector;
57     @NonNull private final Debouncer mDebouncer;
58 
59     /**
60      * A lock that guards the <b>reference</b> to artd.
61      *
62      * Warning: This lock does not guard artd itself. Do not hold this lock when calling artd as it
63      * will prevent parallelism.
64      */
65     @NonNull private final Object mLock = new Object();
66 
67     @GuardedBy("mLock") private int mPinCount = 0;
68     @GuardedBy("mLock") @Nullable private IArtd mArtd = null;
69 
ArtdRefCache()70     public ArtdRefCache() {
71         this(new Injector());
72     }
73 
74     @VisibleForTesting
ArtdRefCache(@onNull Injector injector)75     public ArtdRefCache(@NonNull Injector injector) {
76         mInjector = injector;
77         mDebouncer = new Debouncer(CACHE_TIMEOUT_MS, mInjector::createScheduledExecutor);
78     }
79 
80     @NonNull
getInstance()81     public static synchronized ArtdRefCache getInstance() {
82         if (sInstance == null) {
83             sInstance = new ArtdRefCache();
84         }
85         return sInstance;
86     }
87 
88     /**
89      * Returns a reference to artd, from the cache or created on demand.
90      *
91      * If this method is called when there is no pin, it behaves as if a pin is created and
92      * immediately destroyed.
93      */
94     @NonNull
getArtd()95     public IArtd getArtd() {
96         synchronized (mLock) {
97             if (mArtd == null) {
98                 IArtd artd = mInjector.getArtd();
99                 try {
100                     // Force clear the cache when the artd instance is dead.
101                     artd.asBinder().linkToDeath(new CacheDeathRecipient(), 0 /* flags */);
102                     // Cache the instance.
103                     mArtd = artd;
104                 } catch (RemoteException e) {
105                     Utils.logArtdException(e);
106                     // Not expected. Let the caller decide what to do with it.
107                     return artd;
108                 }
109             }
110             delayedDropIfNoPinLocked();
111             return mArtd;
112         }
113     }
114 
115     /**
116      * Resets ArtdRefCache to its initial state. ArtdRefCache is guaranteed to be GC-able after
117      * this call.
118      *
119      * Can only be called when there is no pin.
120      */
reset()121     public void reset() {
122         synchronized (mLock) {
123             if (mPinCount != 0) {
124                 throw new IllegalStateException("Cannot reset ArtdRefCache when there are pins");
125             }
126             mArtd = null;
127             mDebouncer.cancel();
128         }
129     }
130 
131     @GuardedBy("mLock")
delayedDropIfNoPinLocked()132     private void delayedDropIfNoPinLocked() {
133         if (mPinCount == 0) {
134             // During the timeout:
135             // - If there is no more pinning and unpinning, the cache will be dropped.
136             // - If there are pinnings and unpinnings, and `mPinCount` never reaches 0 again,
137             //   `dropIfNoPin` will be run, but it will not drop the cache.
138             // - If there are pinnings and unpinnings, and `mPinCount` reaches 0 again,
139             //   `dropIfNoPin` will be debounced.
140             mDebouncer.maybeRunAsync(this::dropIfNoPin);
141         }
142     }
143 
dropIfNoPin()144     private void dropIfNoPin() {
145         synchronized (mLock) {
146             if (mPinCount == 0) {
147                 mArtd = null;
148             }
149         }
150     }
151 
152     /**
153      * A scope that pins any reference to artd, either an existing one or one created within the
154      * scope. The reference is dropped when there is no more pin within {@link #CACHE_TIMEOUT_MS}.
155      */
156     public class Pin implements AutoCloseable {
157         @NonNull private final CloseGuard mGuard = new CloseGuard();
158         @NonNull private final Cleaner.Cleanable mCleanable;
159 
Pin()160         public Pin() {
161             synchronized (mLock) {
162                 mPinCount++;
163             }
164             mGuard.open("close");
165             mCleanable =
166                     SystemCleaner.cleaner().register(this, new Cleanup(ArtdRefCache.this, mGuard));
167         }
168 
169         @Override
close()170         public void close() {
171             try {
172                 mGuard.close();
173                 mCleanable.clean();
174             } finally {
175                 // This prevents the cleaner from running the cleanup during the execution of
176                 // `close`.
177                 Reference.reachabilityFence(this);
178             }
179         }
180 
181         // Don't use a lambda. See {@link Cleaner} for the reason.
182         static class Cleanup implements Runnable {
183             @NonNull private final ArtdRefCache mRefCache;
184             @NonNull private final CloseGuard mGuard;
185 
Cleanup(@onNull ArtdRefCache refCache, @NonNull CloseGuard guard)186             Cleanup(@NonNull ArtdRefCache refCache, @NonNull CloseGuard guard) {
187                 mRefCache = refCache;
188                 mGuard = guard;
189             }
190 
191             @Override
run()192             public void run() {
193                 mGuard.warnIfOpen();
194                 synchronized (mRefCache.mLock) {
195                     mRefCache.mPinCount--;
196                     Utils.check(mRefCache.mPinCount >= 0);
197                     mRefCache.delayedDropIfNoPinLocked();
198                 }
199             }
200         }
201     }
202 
203     private class CacheDeathRecipient implements DeathRecipient {
204         @Override
binderDied()205         public void binderDied() {
206             // Legacy.
207         }
208 
209         @Override
binderDied(@onNull IBinder who)210         public void binderDied(@NonNull IBinder who) {
211             synchronized (mLock) {
212                 if (mArtd != null && mArtd.asBinder() == who) {
213                     mArtd = null;
214                 }
215             }
216         }
217     }
218 
219     /** Injector pattern for testing purpose. */
220     @VisibleForTesting
221     public static class Injector {
Injector()222         Injector() {
223             // Call the getters for various dependencies, to ensure correct initialization order.
224             GlobalInjector.getInstance().checkArtModuleServiceManager();
225         }
226 
227         @NonNull
getArtd()228         public IArtd getArtd() {
229             return GlobalInjector.getInstance().getArtd();
230         }
231 
232         @NonNull
createScheduledExecutor()233         public ScheduledExecutorService createScheduledExecutor() {
234             return Executors.newScheduledThreadPool(1 /* corePoolSize */);
235         }
236     }
237 }
238