blob: 9e3497f92dc0fb83ed851a2ed75c5056447b7c66 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.timezonedetector.location;
import android.annotation.DurationMillisLong;
import android.annotation.NonNull;
import com.android.internal.util.Preconditions;
import java.util.concurrent.Callable;
/**
* A class that can be used to enforce / indicate a set of components that need to share threading
* behavior such as a shared lock object and a common thread, with async execution support.
*
* <p>It is <em>not</em> essential that the object returned by {@link #getLockObject()} is only used
* when executing on the domain's thread, but users should be careful to avoid deadlocks when
* multiple locks / threads are in use. Generally sticking to a single thread / lock is safest.
*/
abstract class ThreadingDomain {
@NonNull private final Object mLockObject;
ThreadingDomain() {
mLockObject = new Object();
}
/**
* Returns the common lock object for this threading domain that can be used for synchronized ()
* blocks. The lock is unique to this threading domain.
*/
@NonNull
Object getLockObject() {
return mLockObject;
}
/**
* Returns the Thread associated with this threading domain.
*/
@NonNull
abstract Thread getThread();
/**
* Asserts the currently executing thread is the one associated with this threading domain.
* Generally useful for documenting expectations in the code. By asserting a single thread is
* being used within a set of components, a lot of races can be avoided.
*/
void assertCurrentThread() {
Preconditions.checkState(Thread.currentThread() == getThread());
}
/**
* Asserts the currently executing thread is not the one associated with this threading domain.
* Generally useful for documenting expectations in the code and avoiding deadlocks.
*/
void assertNotCurrentThread() {
Preconditions.checkState(Thread.currentThread() != getThread());
}
/**
* Execute the supplied runnable on the threading domain's thread.
*/
abstract void post(@NonNull Runnable runnable);
/**
* Executes the supplied runnable and waits for up to the duration specified for it to be
* executed. This is only intended for use by test and/or shell command code as it consumes
* multiple threads and could lead to deadlocks.
*
* <p>An {@link IllegalStateException} will be thrown if calling this method would cause a
* deadlock, e.g. if it is called using the threading domain's own thread.
*/
final void postAndWait(@NonNull Runnable runnable, @DurationMillisLong long durationMillis) {
try {
postAndWait(() -> {
runnable.run();
return null;
}, durationMillis);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Executes the supplied callable and waits for up to the duration specified for it to be
* executed. This is only intended for use by test and/or shell command code as it consumes
* multiple threads and could lead to deadlocks.
*
* <p>An {@link IllegalStateException} will be thrown if calling this method would cause a
* deadlock, e.g. if it is called using the threading domain's own thread.
*/
abstract <V> V postAndWait(
@NonNull Callable<V> callable, @DurationMillisLong long durationMillis)
throws Exception;
/**
* Execute the supplied runnable on the threading domain's thread with a delay.
*/
abstract void postDelayed(@NonNull Runnable runnable, @DurationMillisLong long delayMillis);
abstract void postDelayed(Runnable r, Object token, @DurationMillisLong long delayMillis);
abstract void removeQueuedRunnables(Object token);
/**
* Creates a new {@link SingleRunnableQueue} that can be used to ensure that (at most) a
* single runnable for a given purpose is ever queued. Create new ones for different purposes.
*/
SingleRunnableQueue createSingleRunnableQueue() {
return new SingleRunnableQueue();
}
/**
* A class that allows up to one {@link Runnable} to be queued, i.e. calling {@link
* #runDelayed(Runnable, long)} will cancel the execution of any previously queued runnable. All
* methods must be called from the {@link ThreadingDomain}'s thread.
*/
final class SingleRunnableQueue {
private boolean mIsQueued;
@DurationMillisLong
private long mDelayMillis;
/**
* Posts the supplied {@link Runnable} asynchronously and delayed on the threading domain
* handler thread, cancelling any queued but not-yet-executed {@link Runnable} previously
* added by this. This method must be called from the threading domain's thread.
*/
void runDelayed(Runnable r, @DurationMillisLong long delayMillis) {
cancel();
mIsQueued = true;
mDelayMillis = delayMillis;
ThreadingDomain.this.postDelayed(() -> {
mIsQueued = false;
mDelayMillis = -2;
r.run();
}, this, delayMillis);
}
/**
* Returns {@code true} if there is an item current queued. This method must be called from
* the threading domain's thread.
*/
boolean hasQueued() {
assertCurrentThread();
return mIsQueued;
}
/**
* Returns the delay in milliseconds for the currently queued item. Throws {@link
* IllegalStateException} if nothing is currently queued, see {@link #hasQueued()}.
* This method must be called from the threading domain's thread.
*/
@DurationMillisLong
long getQueuedDelayMillis() {
assertCurrentThread();
if (!mIsQueued) {
throw new IllegalStateException("No item queued");
}
return mDelayMillis;
}
/**
* Cancels any queued but not-yet-executed {@link Runnable} previously added by this.
* This method must be called from the threading domain's thread.
*/
public void cancel() {
assertCurrentThread();
if (mIsQueued) {
removeQueuedRunnables(this);
}
mIsQueued = false;
mDelayMillis = -1;
}
}
}