1 /* 2 * Copyright (C) 2020 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 android.car.watchdog; 18 19 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; 20 21 import android.annotation.CallbackExecutor; 22 import android.annotation.IntDef; 23 import android.annotation.NonNull; 24 import android.annotation.RequiresPermission; 25 import android.annotation.SystemApi; 26 import android.car.Car; 27 import android.car.CarManagerBase; 28 import android.os.Handler; 29 import android.os.IBinder; 30 import android.os.Looper; 31 import android.os.RemoteException; 32 import android.util.Log; 33 34 import com.android.internal.annotations.GuardedBy; 35 import com.android.internal.util.Preconditions; 36 37 import java.lang.annotation.ElementType; 38 import java.lang.annotation.Retention; 39 import java.lang.annotation.RetentionPolicy; 40 import java.lang.annotation.Target; 41 import java.lang.ref.WeakReference; 42 import java.util.concurrent.Executor; 43 44 /** 45 * Provides APIs and interfaces for client health checking. 46 * 47 * @hide 48 */ 49 @SystemApi 50 public final class CarWatchdogManager extends CarManagerBase { 51 52 private static final String TAG = CarWatchdogManager.class.getSimpleName(); 53 private static final boolean DEBUG = false; // STOPSHIP if true 54 private static final int INVALID_SESSION_ID = -1; 55 private static final int NUMBER_OF_CONDITIONS_TO_BE_MET = 2; 56 // Message ID representing main thread activeness checking. 57 private static final int WHAT_CHECK_MAIN_THREAD = 1; 58 59 /** Timeout for services which should be responsive. The length is 3,000 milliseconds. */ 60 public static final int TIMEOUT_CRITICAL = 0; 61 62 /** Timeout for services which are relatively responsive. The length is 5,000 milliseconds. */ 63 public static final int TIMEOUT_MODERATE = 1; 64 65 /** Timeout for all other services. The length is 10,000 milliseconds. */ 66 public static final int TIMEOUT_NORMAL = 2; 67 68 /** @hide */ 69 @Retention(RetentionPolicy.SOURCE) 70 @IntDef(prefix = "TIMEOUT_", value = { 71 TIMEOUT_CRITICAL, 72 TIMEOUT_MODERATE, 73 TIMEOUT_NORMAL, 74 }) 75 @Target({ElementType.TYPE_USE}) 76 public @interface TimeoutLengthEnum {} 77 78 private final ICarWatchdogService mService; 79 private final ICarWatchdogClientImpl mClientImpl; 80 private final Handler mMainHandler = new Handler(Looper.getMainLooper()); 81 82 private final Object mLock = new Object(); 83 @GuardedBy("mLock") 84 private CarWatchdogClientCallback mRegisteredClient; 85 @GuardedBy("mLock") 86 private Executor mCallbackExecutor; 87 @GuardedBy("mLock") 88 private SessionInfo mSession = new SessionInfo(INVALID_SESSION_ID, INVALID_SESSION_ID); 89 @GuardedBy("mLock") 90 private int mRemainingConditions; 91 92 /** 93 * CarWatchdogClientCallback is implemented by the clients which want to be health-checked by 94 * car watchdog server. Every time onCheckHealthStatus is called, they are expected to 95 * respond by calling {@link CarWatchdogManager.tellClientAlive} within timeout. If they don't 96 * respond, car watchdog server reports the current state and kills them. 97 * 98 * <p>Before car watchdog server kills the client, it calls onPrepareProcessTermination to allow 99 * them to prepare the termination. They will be killed in 1 second. 100 */ 101 public abstract static class CarWatchdogClientCallback { 102 /** 103 * Car watchdog server pings the client to check if it is alive. 104 * 105 * <p>The callback method is called at the Executor which is specifed in {@link 106 * #registerClient}. 107 * 108 * @param sessionId Unique id to distinguish each health checking. 109 * @param timeout Time duration within which the client should respond. 110 * 111 * @return whether the response is immediately acknowledged. If {@code true}, car watchdog 112 * server considers that the response is acknowledged already. If {@code false}, 113 * the client should call {@link CarWatchdogManager.tellClientAlive} later to tell 114 * that it is alive. 115 */ onCheckHealthStatus(int sessionId, @TimeoutLengthEnum int timeout)116 public boolean onCheckHealthStatus(int sessionId, @TimeoutLengthEnum int timeout) { 117 return false; 118 } 119 120 /** 121 * Car watchdog server notifies the client that it will be terminated in 1 second. 122 * 123 * <p>The callback method is called at the Executor which is specifed in {@link 124 * #registerClient}. 125 */ onPrepareProcessTermination()126 public void onPrepareProcessTermination() {} 127 } 128 129 /** @hide */ CarWatchdogManager(Car car, IBinder service)130 public CarWatchdogManager(Car car, IBinder service) { 131 super(car); 132 mService = ICarWatchdogService.Stub.asInterface(service); 133 mClientImpl = new ICarWatchdogClientImpl(this); 134 } 135 136 /** 137 * Registers the car watchdog clients to {@link CarWatchdogManager}. 138 * 139 * <p>It is allowed to register a client from any thread, but only one client can be 140 * registered. If two or more clients are needed, create a new {@link Car} and register a client 141 * to it. 142 * 143 * @param client Watchdog client implementing {@link CarWatchdogClientCallback} interface. 144 * @param timeout The time duration within which the client desires to respond. The actual 145 * timeout is decided by watchdog server. 146 * @throws IllegalStateException if at least one client is already registered. 147 */ 148 @RequiresPermission(Car.PERMISSION_USE_CAR_WATCHDOG) registerClient(@onNull @allbackExecutor Executor executor, @NonNull CarWatchdogClientCallback client, @TimeoutLengthEnum int timeout)149 public void registerClient(@NonNull @CallbackExecutor Executor executor, 150 @NonNull CarWatchdogClientCallback client, @TimeoutLengthEnum int timeout) { 151 synchronized (mLock) { 152 if (mRegisteredClient == client) { 153 return; 154 } 155 if (mRegisteredClient != null) { 156 throw new IllegalStateException( 157 "Cannot register the client. Only one client can be registered."); 158 } 159 mRegisteredClient = client; 160 mCallbackExecutor = executor; 161 } 162 try { 163 mService.registerClient(mClientImpl, timeout); 164 if (DEBUG) { 165 Log.d(TAG, "Car watchdog client is successfully registered"); 166 } 167 } catch (RemoteException e) { 168 synchronized (mLock) { 169 mRegisteredClient = null; 170 } 171 handleRemoteExceptionFromCarService(e); 172 } 173 } 174 175 /** 176 * Unregisters the car watchdog client from {@link CarWatchdogManager}. 177 * 178 * @param client Watchdog client implementing {@link CarWatchdogClientCallback} interface. 179 */ 180 @RequiresPermission(Car.PERMISSION_USE_CAR_WATCHDOG) unregisterClient(@onNull CarWatchdogClientCallback client)181 public void unregisterClient(@NonNull CarWatchdogClientCallback client) { 182 synchronized (mLock) { 183 if (mRegisteredClient != client) { 184 Log.w(TAG, "Cannot unregister the client. It has not been registered."); 185 return; 186 } 187 mRegisteredClient = null; 188 mCallbackExecutor = null; 189 } 190 try { 191 mService.unregisterClient(mClientImpl); 192 if (DEBUG) { 193 Log.d(TAG, "Car watchdog client is successfully unregistered"); 194 } 195 } catch (RemoteException e) { 196 handleRemoteExceptionFromCarService(e); 197 } 198 } 199 200 /** 201 * Tells {@link CarWatchdogManager} that the client is alive. 202 * 203 * @param client Watchdog client implementing {@link CarWatchdogClientCallback} interface. 204 * @param sessionId Session id given by {@link CarWatchdogManager}. 205 * @throws IllegalStateException if {@code client} is not registered. 206 * @throws IllegalArgumentException if {@code session Id} is not correct. 207 */ 208 @RequiresPermission(Car.PERMISSION_USE_CAR_WATCHDOG) tellClientAlive(@onNull CarWatchdogClientCallback client, int sessionId)209 public void tellClientAlive(@NonNull CarWatchdogClientCallback client, int sessionId) { 210 boolean shouldReport; 211 synchronized (mLock) { 212 if (mRegisteredClient != client) { 213 throw new IllegalStateException( 214 "Cannot report client status. The client has not been registered."); 215 } 216 Preconditions.checkArgument(sessionId != -1 && mSession.currentId == sessionId, 217 "Cannot report client status. " 218 + "The given session id doesn't match the current one."); 219 if (mSession.lastReportedId == sessionId) { 220 Log.w(TAG, "The given session id is already reported."); 221 return; 222 } 223 mSession.lastReportedId = sessionId; 224 mRemainingConditions--; 225 shouldReport = checkConditionLocked(); 226 } 227 if (shouldReport) { 228 reportToService(sessionId); 229 } 230 } 231 232 /** @hide */ 233 @Override onCarDisconnected()234 public void onCarDisconnected() { 235 // nothing to do 236 } 237 checkClientStatus(int sessionId, int timeout)238 private void checkClientStatus(int sessionId, int timeout) { 239 CarWatchdogClientCallback client; 240 Executor executor; 241 mMainHandler.removeMessages(WHAT_CHECK_MAIN_THREAD); 242 synchronized (mLock) { 243 if (mRegisteredClient == null) { 244 Log.w(TAG, "Cannot check client status. The client has not been registered."); 245 return; 246 } 247 mSession.currentId = sessionId; 248 client = mRegisteredClient; 249 executor = mCallbackExecutor; 250 mRemainingConditions = NUMBER_OF_CONDITIONS_TO_BE_MET; 251 } 252 // For a car watchdog client to be active, 1) its main thread is active and 2) the client 253 // responds within timeout. When each condition is met, the remaining task counter is 254 // decreased. If the remaining task counter is zero, the client is considered active. 255 mMainHandler.sendMessage(obtainMessage(CarWatchdogManager::checkMainThread, this) 256 .setWhat(WHAT_CHECK_MAIN_THREAD)); 257 // Call the client callback to check if the client is active. 258 executor.execute(() -> { 259 boolean checkDone = client.onCheckHealthStatus(sessionId, timeout); 260 if (checkDone) { 261 boolean shouldReport; 262 synchronized (mLock) { 263 if (mSession.lastReportedId == sessionId) { 264 return; 265 } 266 mSession.lastReportedId = sessionId; 267 mRemainingConditions--; 268 shouldReport = checkConditionLocked(); 269 } 270 if (shouldReport) { 271 reportToService(sessionId); 272 } 273 } 274 }); 275 } 276 checkMainThread()277 private void checkMainThread() { 278 int sessionId; 279 boolean shouldReport; 280 synchronized (mLock) { 281 mRemainingConditions--; 282 sessionId = mSession.currentId; 283 shouldReport = checkConditionLocked(); 284 } 285 if (shouldReport) { 286 reportToService(sessionId); 287 } 288 } 289 checkConditionLocked()290 private boolean checkConditionLocked() { 291 if (mRemainingConditions < 0) { 292 Log.wtf(TAG, "Remaining condition is less than zero: should not happen"); 293 } 294 return mRemainingConditions == 0; 295 } 296 reportToService(int sessionId)297 private void reportToService(int sessionId) { 298 try { 299 mService.tellClientAlive(mClientImpl, sessionId); 300 } catch (RemoteException e) { 301 handleRemoteExceptionFromCarService(e); 302 } 303 } 304 notifyProcessTermination()305 private void notifyProcessTermination() { 306 CarWatchdogClientCallback client; 307 Executor executor; 308 synchronized (mLock) { 309 if (mRegisteredClient == null) { 310 Log.w(TAG, "Cannot notify the client. The client has not been registered."); 311 return; 312 } 313 client = mRegisteredClient; 314 executor = mCallbackExecutor; 315 } 316 executor.execute(() -> client.onPrepareProcessTermination()); 317 } 318 319 /** @hide */ 320 private static final class ICarWatchdogClientImpl extends ICarWatchdogServiceCallback.Stub { 321 private final WeakReference<CarWatchdogManager> mManager; 322 ICarWatchdogClientImpl(CarWatchdogManager manager)323 private ICarWatchdogClientImpl(CarWatchdogManager manager) { 324 mManager = new WeakReference<>(manager); 325 } 326 327 @Override onCheckHealthStatus(int sessionId, int timeout)328 public void onCheckHealthStatus(int sessionId, int timeout) { 329 CarWatchdogManager manager = mManager.get(); 330 if (manager != null) { 331 manager.checkClientStatus(sessionId, timeout); 332 } 333 } 334 335 @Override onPrepareProcessTermination()336 public void onPrepareProcessTermination() { 337 CarWatchdogManager manager = mManager.get(); 338 if (manager != null) { 339 manager.notifyProcessTermination(); 340 } 341 } 342 } 343 344 private final class SessionInfo { 345 public int currentId; 346 public int lastReportedId; 347 SessionInfo(int currentId, int lastReportedId)348 SessionInfo(int currentId, int lastReportedId) { 349 this.currentId = currentId; 350 this.lastReportedId = lastReportedId; 351 } 352 } 353 } 354