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