1 /* 2 * Copyright (C) 2023 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.providers.media.photopicker.sync; 18 19 import android.annotation.IntDef; 20 import android.util.Log; 21 22 import androidx.annotation.NonNull; 23 24 import com.android.internal.annotations.VisibleForTesting; 25 import com.android.providers.media.photopicker.util.exceptions.UnableToAcquireLockException; 26 27 import java.lang.annotation.Retention; 28 import java.lang.annotation.RetentionPolicy; 29 import java.util.concurrent.TimeUnit; 30 import java.util.concurrent.locks.ReentrantLock; 31 32 /** 33 * Manages Java locks acquired during the sync process to ensure that the cloud sync is thread safe. 34 */ 35 public class PickerSyncLockManager { 36 private static final String TAG = PickerSyncLockManager.class.getSimpleName(); 37 private static final Integer LOCK_ACQUIRE_TIMEOUT_MINS = 4; 38 private static final TimeUnit LOCK_ACQUIRE_TIMEOUT_UNIT = TimeUnit.MINUTES; 39 40 @IntDef(value = {CLOUD_SYNC_LOCK, CLOUD_ALBUM_SYNC_LOCK, CLOUD_PROVIDER_LOCK, DB_CLOUD_LOCK}) 41 @Retention(RetentionPolicy.SOURCE) 42 public @interface LockType {} 43 public static final int CLOUD_SYNC_LOCK = 0; 44 public static final int CLOUD_ALBUM_SYNC_LOCK = 1; 45 public static final int CLOUD_PROVIDER_LOCK = 2; 46 public static final int DB_CLOUD_LOCK = 3; 47 48 private final CloseableReentrantLock mCloudSyncLock = 49 new CloseableReentrantLock("CLOUD_SYNC_LOCK"); 50 private final CloseableReentrantLock mCloudAlbumSyncLock = 51 new CloseableReentrantLock("CLOUD_ALBUM_SYNC_LOCK"); 52 private final CloseableReentrantLock mCloudProviderLock = 53 new CloseableReentrantLock("CLOUD_PROVIDER_LOCK"); 54 private final CloseableReentrantLock mDbCloudLock = 55 new CloseableReentrantLock("DB_CLOUD_LOCK"); 56 57 /** 58 * Try to acquire lock with a default timeout after running some validations. 59 */ tryLock(@ockType int lockType)60 public CloseableReentrantLock tryLock(@LockType int lockType) 61 throws UnableToAcquireLockException { 62 return tryLock(lockType, LOCK_ACQUIRE_TIMEOUT_MINS, LOCK_ACQUIRE_TIMEOUT_UNIT); 63 } 64 65 /** 66 * Try to acquire lock with the provided timeout after running some validations. 67 */ tryLock(@ockType int lockType, long timeout, TimeUnit unit)68 public CloseableReentrantLock tryLock(@LockType int lockType, long timeout, TimeUnit unit) 69 throws UnableToAcquireLockException { 70 return tryLock(getLock(lockType), timeout, unit); 71 } 72 73 /** 74 * Try to acquire the given lock with the provided timeout after running some validations. 75 */ 76 @VisibleForTesting tryLock(@onNull CloseableReentrantLock lock, long timeout, TimeUnit unit)77 public CloseableReentrantLock tryLock(@NonNull CloseableReentrantLock lock, 78 long timeout, TimeUnit unit) throws UnableToAcquireLockException { 79 Log.d(TAG, "Trying to acquire lock " + lock + " with timeout."); 80 validateLockOrder(lock); 81 return lock.lockWithTimeout(timeout, unit); 82 } 83 84 /** 85 * Try to acquire the lock after running some validations. 86 */ lock(@ockType int lockType)87 public CloseableReentrantLock lock(@LockType int lockType) { 88 final CloseableReentrantLock reentrantLock = getLock(lockType); 89 Log.d(TAG, "Trying to acquire lock " + reentrantLock); 90 validateLockOrder(reentrantLock); 91 reentrantLock.lock(); 92 return reentrantLock; 93 } 94 95 /** 96 * Return the {@link CloseableReentrantLock} corresponding to the given {@link LockType}. 97 * Throws a {@link RuntimeException} if the lock is not recognized. 98 */ 99 @VisibleForTesting getLock(@ockType int lockType)100 public CloseableReentrantLock getLock(@LockType int lockType) { 101 switch (lockType) { 102 case CLOUD_SYNC_LOCK: 103 return mCloudSyncLock; 104 case CLOUD_ALBUM_SYNC_LOCK: 105 return mCloudAlbumSyncLock; 106 case CLOUD_PROVIDER_LOCK: 107 return mCloudProviderLock; 108 case DB_CLOUD_LOCK: 109 return mDbCloudLock; 110 default: 111 throw new RuntimeException("Unrecognizable lock type " + lockType); 112 } 113 } 114 validateLockOrder(@onNull ReentrantLock lockToBeAcquired)115 private void validateLockOrder(@NonNull ReentrantLock lockToBeAcquired) { 116 if (lockToBeAcquired.equals(mCloudSyncLock)) { 117 validateLockOrder(lockToBeAcquired, mCloudAlbumSyncLock); 118 validateLockOrder(lockToBeAcquired, mCloudProviderLock); 119 validateLockOrder(lockToBeAcquired, mDbCloudLock); 120 } else if (lockToBeAcquired.equals(mCloudAlbumSyncLock)) { 121 validateLockOrder(lockToBeAcquired, mCloudSyncLock); 122 validateLockOrder(lockToBeAcquired, mCloudProviderLock); 123 validateLockOrder(lockToBeAcquired, mDbCloudLock); 124 } else if (lockToBeAcquired.equals(mCloudProviderLock)) { 125 validateLockOrder(lockToBeAcquired, mDbCloudLock); 126 } 127 } 128 validateLockOrder(@onNull ReentrantLock lockToBeAcquired, @NonNull ReentrantLock lockThatShouldNotBeHeld)129 private void validateLockOrder(@NonNull ReentrantLock lockToBeAcquired, 130 @NonNull ReentrantLock lockThatShouldNotBeHeld) { 131 if (lockThatShouldNotBeHeld.isHeldByCurrentThread()) { 132 Log.e(TAG, String.format("Lock {%s} should not be held before acquiring lock {%s}" 133 + " This could lead to a deadlock.", 134 lockThatShouldNotBeHeld, lockToBeAcquired)); 135 } 136 } 137 } 138