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.adservices.service.measurement.util; 18 19 import static com.android.adservices.service.measurement.util.JobLockHolder.Type.AGGREGATE_REPORTING; 20 import static com.android.adservices.service.measurement.util.JobLockHolder.Type.ASYNC_REGISTRATION_PROCESSING; 21 import static com.android.adservices.service.measurement.util.JobLockHolder.Type.ATTRIBUTION_PROCESSING; 22 import static com.android.adservices.service.measurement.util.JobLockHolder.Type.DEBUG_REPORTING; 23 import static com.android.adservices.service.measurement.util.JobLockHolder.Type.EVENT_REPORTING; 24 import static com.android.adservices.service.measurement.util.JobLockHolder.Type.VERBOSE_DEBUG_REPORTING; 25 26 import android.annotation.Nullable; 27 28 import com.android.adservices.LoggerFactory; 29 import com.android.adservices.LoggerFactory.Logger; 30 31 import com.google.common.annotations.VisibleForTesting; 32 33 import java.util.Map; 34 import java.util.Objects; 35 import java.util.concurrent.locks.ReentrantLock; 36 import java.util.function.Supplier; 37 38 /** 39 * Holds the lock to be used by the background jobs. The locks will be used by multiple jobs, 40 * fallback jobs, and similar functions that could perform the same action. However, only one of 41 * these functions should be able to process at a time. This is to prevent conflicts and ensure that 42 * the system runs smoothly. 43 */ 44 public final class JobLockHolder { 45 public enum Type { 46 AGGREGATE_REPORTING, 47 ASYNC_REGISTRATION_PROCESSING, 48 ATTRIBUTION_PROCESSING, 49 DEBUG_REPORTING, 50 EVENT_REPORTING, 51 VERBOSE_DEBUG_REPORTING 52 } 53 54 private static final Map<Type, JobLockHolder> INSTANCES = 55 Map.of( 56 AGGREGATE_REPORTING, new JobLockHolder(AGGREGATE_REPORTING), 57 ASYNC_REGISTRATION_PROCESSING, new JobLockHolder(ASYNC_REGISTRATION_PROCESSING), 58 ATTRIBUTION_PROCESSING, new JobLockHolder(ATTRIBUTION_PROCESSING), 59 DEBUG_REPORTING, new JobLockHolder(DEBUG_REPORTING), 60 EVENT_REPORTING, new JobLockHolder(EVENT_REPORTING), 61 VERBOSE_DEBUG_REPORTING, new JobLockHolder(VERBOSE_DEBUG_REPORTING)); 62 63 private final Type mType; 64 65 /* Holds the lock that will be given per instance */ 66 private final ReentrantLock mLock; 67 JobLockHolder(Type type)68 private JobLockHolder(Type type) { 69 this(type, new ReentrantLock()); 70 } 71 72 @VisibleForTesting JobLockHolder(Type type, ReentrantLock lock)73 JobLockHolder(Type type, ReentrantLock lock) { 74 mType = type; 75 mLock = lock; 76 } 77 78 /** 79 * Retrieves an instance that has already been created based on its type. 80 * 81 * @param type of lock to be shared by similar tasks 82 * @return lock instance 83 */ getInstance(Type type)84 public static JobLockHolder getInstance(Type type) { 85 return INSTANCES.get(type); 86 } 87 88 /** 89 * Runs the given runnable after acquiring the lock. 90 * 91 * @param tag name of the caller (used for logging purposes) 92 * @param runnable what to run 93 */ runWithLock(String tag, Runnable runnable)94 public void runWithLock(String tag, Runnable runnable) { 95 Objects.requireNonNull(tag, "tag cannot be null"); 96 Objects.requireNonNull(runnable, "runnable cannot be null"); 97 98 Logger logger = LoggerFactory.getMeasurementLogger(); 99 logger.v("%s.runWithLock(%s) started", tag, mType); 100 101 if (mLock.tryLock()) { 102 try { 103 runnable.run(); 104 } finally { 105 mLock.unlock(); 106 } 107 return; 108 } 109 110 logger.e("%s.runWithLock(%s) failed to acquire lock", tag, mType); 111 } 112 113 /** 114 * Calls the given "callable" after acquiring the lock. 115 * 116 * @param tag name of the caller (used for logging purposes) 117 * @param callable what to call (i.e, the value returned by {@code get()}. 118 * @param lockFailureResult what to return if the lock could not be acquired 119 * @return result of callable, or {@code failureResult} if the lock could not be acquired. 120 */ callWithLock(String tag, Supplier<T> callable, @Nullable T lockFailureResult)121 public <T> T callWithLock(String tag, Supplier<T> callable, @Nullable T lockFailureResult) { 122 Objects.requireNonNull(tag, "tag cannot be null"); 123 Objects.requireNonNull(callable, "callable cannot be null"); 124 125 Logger logger = LoggerFactory.getMeasurementLogger(); 126 logger.v("%s.callWithLock(%s) started", tag, mType); 127 128 if (mLock.tryLock()) { 129 try { 130 return callable.get(); 131 } finally { 132 mLock.unlock(); 133 } 134 } 135 136 logger.e( 137 "%s.callWithLock(%s) failed to acquire lock; returning %s", 138 tag, mType, lockFailureResult); 139 return lockFailureResult; 140 } 141 142 @Override toString()143 public String toString() { 144 return "JobLockHolder[mType=" + mType + ", mLock=" + mLock + "]"; 145 } 146 } 147