Merge "Resolve race condition by guaranteeing that onDestroyed is first on the right queue" into sc-mainline-prod
diff --git a/service/java/com/android/server/wifi/HalDeviceManager.java b/service/java/com/android/server/wifi/HalDeviceManager.java
index b664c1f..e87c38d 100644
--- a/service/java/com/android/server/wifi/HalDeviceManager.java
+++ b/service/java/com/android/server/wifi/HalDeviceManager.java
@@ -57,6 +57,7 @@
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -2367,6 +2368,11 @@
return false;
}
+ // dispatch listeners on other threads to prevent race conditions in case the HAL is
+ // blocking and they get notification about destruction from HAL before cleaning up
+ // status.
+ dispatchDestroyedListeners(name, type, true);
+
WifiStatus status = null;
try {
switch (type) {
@@ -2391,7 +2397,7 @@
}
// dispatch listeners no matter what status
- dispatchDestroyedListeners(name, type);
+ dispatchDestroyedListeners(name, type, false);
if (validateRttController) {
// Try to update the RttController
updateRttControllerWhenInterfaceChanges();
@@ -2407,10 +2413,13 @@
}
// dispatch all destroyed listeners registered for the specified interface AND remove the
- // cache entry
- private void dispatchDestroyedListeners(String name, int type) {
+ // cache entries for the called listeners
+ // onlyOnOtherThreads = true: only call listeners on other threads
+ // onlyOnOtherThreads = false: call all listeners
+ private void dispatchDestroyedListeners(String name, int type, boolean onlyOnOtherThreads) {
if (VDBG) Log.d(TAG, "dispatchDestroyedListeners: iface(name)=" + name);
+ List<InterfaceDestroyedListenerProxy> triggerList = new ArrayList<>();
synchronized (mLock) {
InterfaceCacheEntry entry = mInterfaceInfoCache.get(Pair.create(name, type));
if (entry == null) {
@@ -2418,11 +2427,22 @@
return;
}
- for (InterfaceDestroyedListenerProxy listener : entry.destroyedListeners) {
- listener.trigger();
+ Iterator<InterfaceDestroyedListenerProxy> iterator =
+ entry.destroyedListeners.iterator();
+ while (iterator.hasNext()) {
+ InterfaceDestroyedListenerProxy listener = iterator.next();
+ if (!onlyOnOtherThreads || !listener.requestedToRunInCurrentThread()) {
+ triggerList.add(listener);
+ iterator.remove();
+ }
}
- entry.destroyedListeners.clear(); // for insurance (though cache entry is removed)
- mInterfaceInfoCache.remove(Pair.create(name, type));
+ if (!onlyOnOtherThreads) { // leave entry until final call to *all* callbacks
+ mInterfaceInfoCache.remove(Pair.create(name, type));
+ }
+ }
+
+ for (InterfaceDestroyedListenerProxy listener : triggerList) {
+ listener.trigger();
}
}
@@ -2462,44 +2482,34 @@
return mListener.hashCode();
}
- void trigger() {
- if (mHandler != null) {
- // TODO(b/199792691): The thread check is needed to preserve the existing
- // assumptions of synchronous execution of the "onDestroyed" callback as much as
- // possible. This is needed to prevent regressions caused by posting to the handler
- // thread changing the code execution order.
- // When all wifi services (ie. WifiAware, WifiP2p) get moved to the wifi handler
- // thread, remove this thread check and the Handler#post() and simply always
- // invoke the callback directly.
- long currentTid = mWifiInjector.getCurrentThreadId();
- long handlerTid = mHandler.getLooper().getThread().getId();
- if (currentTid == handlerTid) {
- // Already running on the same handler thread. Trigger listener synchronously.
- action();
- } else {
- // Current thread is not the thread the listener should be invoked on.
- // Post action to the intended thread.
- mHandler.post(() -> {
- action();
- });
- }
- } else {
- action();
- }
+ public boolean requestedToRunInCurrentThread() {
+ if (mHandler == null) return true;
+ long currentTid = mWifiInjector.getCurrentThreadId();
+ long handlerTid = mHandler.getLooper().getThread().getId();
+ return currentTid == handlerTid;
}
- void triggerWithArg(boolean arg) {
- if (mHandler != null) {
- mHandler.post(() -> {
- actionWithArg(arg);
- });
+ void trigger() {
+ // TODO(b/199792691): The thread check is needed to preserve the existing
+ // assumptions of synchronous execution of the "onDestroyed" callback as much as
+ // possible. This is needed to prevent regressions caused by posting to the handler
+ // thread changing the code execution order.
+ // When all wifi services (ie. WifiAware, WifiP2p) get moved to the wifi handler
+ // thread, remove this thread check and the Handler#post() and simply always
+ // invoke the callback directly.
+ if (requestedToRunInCurrentThread()) {
+ // Already running on the same handler thread. Trigger listener synchronously.
+ action();
} else {
- actionWithArg(arg);
+ // Current thread is not the thread the listener should be invoked on.
+ // Post action to the intended thread.
+ mHandler.postAtFrontOfQueue(() -> {
+ action();
+ });
}
}
protected void action() {}
- protected void actionWithArg(boolean arg) {}
ListenerProxy(LISTENER listener, Handler handler, String tag) {
mListener = listener;
diff --git a/service/java/com/android/server/wifi/WifiNative.java b/service/java/com/android/server/wifi/WifiNative.java
index b07101c..33746bc 100644
--- a/service/java/com/android/server/wifi/WifiNative.java
+++ b/service/java/com/android/server/wifi/WifiNative.java
@@ -133,6 +133,7 @@
mSupplicantStaIfaceHal.enableVerboseLogging(mVerboseLoggingEnabled);
mHostapdHal.enableVerboseLogging(mVerboseLoggingEnabled);
mWifiVendorHal.enableVerboseLogging(mVerboseLoggingEnabled);
+ mIfaceMgr.enableVerboseLogging(mVerboseLoggingEnabled);
}
/**
@@ -300,9 +301,17 @@
private int mNextId;
/** Map of the id to the iface structure */
private HashMap<Integer, Iface> mIfaces = new HashMap<>();
+ private boolean mVerboseLoggingEnabled = false;
+
+ public void enableVerboseLogging(boolean enable) {
+ mVerboseLoggingEnabled = enable;
+ }
/** Allocate a new iface for the given type */
- private Iface allocateIface(@Iface.IfaceType int type) {
+ private Iface allocateIface(@Iface.IfaceType int type) {
+ if (mVerboseLoggingEnabled) {
+ Log.d(TAG, "IfaceManager#allocateIface: type=" + type + ", pre-map=" + mIfaces);
+ }
Iface iface = new Iface(mNextId, type);
mIfaces.put(mNextId, iface);
mNextId++;
@@ -311,6 +320,9 @@
/** Remove the iface using the provided id */
private Iface removeIface(int id) {
+ if (mVerboseLoggingEnabled) {
+ Log.d(TAG, "IfaceManager#removeIface: id=" + id + ", pre-map=" + mIfaces);
+ }
return mIfaces.remove(id);
}
@@ -387,6 +399,10 @@
/** Removes the existing iface that does not match the provided id. */
public Iface removeExistingIface(int newIfaceId) {
+ if (mVerboseLoggingEnabled) {
+ Log.d(TAG, "IfaceManager#removeExistingIface: newIfaceId=" + newIfaceId
+ + ", pre-map=" + mIfaces);
+ }
Iface removedIface = null;
// The number of ifaces in the database could be 1 existing & 1 new at the max.
if (mIfaces.size() > 2) {
@@ -402,6 +418,11 @@
}
return removedIface;
}
+
+ @Override
+ public String toString() {
+ return mIfaces.toString();
+ }
}
private class NormalScanEventCallback implements WifiNl80211Manager.ScanEventCallback {
@@ -829,6 +850,11 @@
// This is invoked from the main system_server thread. Post to our handler.
mHandler.post(() -> {
synchronized (mLock) {
+ if (mVerboseLoggingEnabled) {
+ Log.d(TAG, "interfaceLinkStateChanged: ifaceName=" + ifaceName
+ + ", mInterfaceId = " + mInterfaceId
+ + ", mIfaceMgr=" + mIfaceMgr.toString());
+ }
final Iface ifaceWithId = mIfaceMgr.getIface(mInterfaceId);
if (ifaceWithId == null) {
if (mVerboseLoggingEnabled) {
@@ -1073,7 +1099,9 @@
* @param listener StatusListener listener object.
*/
public void registerStatusListener(@NonNull StatusListener listener) {
- mStatusListeners.add(listener);
+ synchronized (mLock) {
+ mStatusListeners.add(listener);
+ }
}
/**
diff --git a/service/tests/wifitests/src/com/android/server/wifi/HalDeviceManagerTest.java b/service/tests/wifitests/src/com/android/server/wifi/HalDeviceManagerTest.java
index c7127ce..f62fc2b 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/HalDeviceManagerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/HalDeviceManagerTest.java
@@ -985,7 +985,7 @@
// Verify a runnable is posted because current thread is different than the intended thread
// for running "onDestroyed"
- verify(staIfaceOnDestroyedHandler).post(lambdaCaptor.capture());
+ verify(staIfaceOnDestroyedHandler).postAtFrontOfQueue(lambdaCaptor.capture());
// Verify onDestroyed is only run after the posted runnable is dispatched
verify(staIdl, never()).onDestroyed("wlan0");
@@ -1012,7 +1012,7 @@
simulateStartAndStopWifi(staIdl, staIfaceOnDestroyedHandler);
// Verify a runnable is never posted
- verify(staIfaceOnDestroyedHandler, never()).post(any());
+ verify(staIfaceOnDestroyedHandler, never()).postAtFrontOfQueue(any());
// Verify onDestroyed is triggered directly
verify(staIdl).onDestroyed("wlan0");
}