Merge "SoundPool: Fix WeakRef lifetime" into tm-dev am: 62e9478875
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/17225466 Change-Id: I9e9cd27f634e474104505681946ba3ea2cbbc7a9 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -181,6 +181,116 @@ inline auto setSoundPool(
|
|||||||
return getSoundPoolManager().set(env, thiz, soundPool);
|
return getSoundPoolManager().set(env, thiz, soundPool);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ConcurrentHashMap is a locked hash map
|
||||||
|
*
|
||||||
|
* As from the name, this class is thread_safe.
|
||||||
|
*
|
||||||
|
* The type V must have 3 properties in the current implementation.
|
||||||
|
* 1) A V{} default constructor which represents a nullValue.
|
||||||
|
* 2) V::operator bool() const efficient detection of such a nullValue.
|
||||||
|
* 3) V must be copyable.
|
||||||
|
*
|
||||||
|
* Note: The Key cannot be a Java LocalRef, as those change between JNI calls.
|
||||||
|
* The Key could be the raw native object pointer if one wanted to associate
|
||||||
|
* extra data with a native object.
|
||||||
|
*
|
||||||
|
* Using set() with a nullValue V results in erasing the key entry.
|
||||||
|
* A nullValue V is returned by get() if there is no underlying entry.
|
||||||
|
*
|
||||||
|
* Design notes:
|
||||||
|
* 1) For objects of type V that do not naturally have a "nullValue",
|
||||||
|
* wrapping VOpt = std::optional<V> or VShared = std::shared<V> is recommended.
|
||||||
|
*
|
||||||
|
* 2) An overload for an explicit equality comparable nullValue such as
|
||||||
|
* get(..., const V& nullValue) or set(..., const V& nullValue)
|
||||||
|
* is omitted. An alternative is to pass a fixed nullValue into a special
|
||||||
|
* constructor (omitted) for equality comparisons and return value.
|
||||||
|
*
|
||||||
|
* 3) This ConcurrentHashMap currently allows only one thread at a time.
|
||||||
|
* It is not optimized for heavy multi-threaded use.
|
||||||
|
*/
|
||||||
|
template <typename K, typename V>
|
||||||
|
class ConcurrentHashMap
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
// Sets the value and returns the old one.
|
||||||
|
//
|
||||||
|
// If the old value doesn't exist, then nullValue V is returned.
|
||||||
|
// If the new value is false by operator bool(), the internal value is destroyed.
|
||||||
|
// Note: The old value is returned so if V is a smart pointer, it can be held
|
||||||
|
// by the caller to be deleted outside of any external lock.
|
||||||
|
|
||||||
|
V set(const K& key, const V& value) {
|
||||||
|
std::lock_guard lg(mLock);
|
||||||
|
auto it = mMap.find(key);
|
||||||
|
if (it == mMap.end()) {
|
||||||
|
if (value) {
|
||||||
|
mMap[key] = value;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
V oldValue = std::move(it->second);
|
||||||
|
if (value) {
|
||||||
|
it->second = value;
|
||||||
|
} else {
|
||||||
|
mMap.erase(it);
|
||||||
|
}
|
||||||
|
return oldValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieves the associated object, returns nullValue V if not available.
|
||||||
|
V get(const K& key) const {
|
||||||
|
std::lock_guard lg(mLock);
|
||||||
|
auto it = mMap.find(key);
|
||||||
|
return it != mMap.end() ? it->second : V{};
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
mutable std::mutex mLock;
|
||||||
|
std::unordered_map<K, V> mMap GUARDED_BY(mLock);
|
||||||
|
};
|
||||||
|
|
||||||
|
// *jobject is needed to fit the jobject into a std::shared_ptr.
|
||||||
|
// This is the Android type _jobject, but we derive this type as JObjectValue.
|
||||||
|
using JObjectValue = std::remove_pointer_t<jobject>; // _jobject
|
||||||
|
|
||||||
|
// Check that jobject is really a pointer to JObjectValue.
|
||||||
|
// The JNI contract is that jobject is NULL comparable,
|
||||||
|
// so jobject is pointer equivalent; we check here to be sure.
|
||||||
|
// Note std::remove_ptr_t<NonPointerType> == NonPointerType.
|
||||||
|
static_assert(std::is_same_v<JObjectValue*, jobject>);
|
||||||
|
|
||||||
|
// We store the ancillary data associated with a SoundPool object in a concurrent
|
||||||
|
// hash map indexed on the SoundPool native object pointer.
|
||||||
|
auto& getSoundPoolJavaRefManager() {
|
||||||
|
static ConcurrentHashMap<SoundPool *, std::shared_ptr<JObjectValue>> concurrentHashMap;
|
||||||
|
return concurrentHashMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// make_shared_globalref_from_localref() creates a sharable Java global
|
||||||
|
// reference from a Java local reference. The equivalent type is
|
||||||
|
// std::shared_ptr<_jobject> (where _jobject is JObjectValue,
|
||||||
|
// and _jobject * is jobject),
|
||||||
|
// and the jobject may be retrieved by .get() or pointer dereference.
|
||||||
|
// This encapsulation gives the benefit of std::shared_ptr
|
||||||
|
// ref counting, weak_ptr, etc.
|
||||||
|
//
|
||||||
|
// The Java global reference should be stable between JNI calls. It is a limited
|
||||||
|
// quantity so sparingly use global references.
|
||||||
|
//
|
||||||
|
// The Android JNI implementation is described here:
|
||||||
|
// https://developer.android.com/training/articles/perf-jni
|
||||||
|
// https://android-developers.googleblog.com/2011/11/jni-local-reference-changes-in-ics.html
|
||||||
|
//
|
||||||
|
inline auto make_shared_globalref_from_localref(JNIEnv *env, jobject localRef) {
|
||||||
|
return std::shared_ptr<JObjectValue>(
|
||||||
|
env->NewGlobalRef(localRef),
|
||||||
|
[](JObjectValue* object) { // cannot cache env as don't know which thread we're on.
|
||||||
|
if (object != nullptr) AndroidRuntime::getJNIEnv()->DeleteGlobalRef(object);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
static const char* const kAudioAttributesClassPathName = "android/media/AudioAttributes";
|
static const char* const kAudioAttributesClassPathName = "android/media/AudioAttributes";
|
||||||
@@ -321,10 +431,22 @@ android_media_SoundPool_setRate(JNIEnv *env, jobject thiz, jint channelID,
|
|||||||
static void android_media_callback(SoundPoolEvent event, SoundPool* soundPool, void* user)
|
static void android_media_callback(SoundPoolEvent event, SoundPool* soundPool, void* user)
|
||||||
{
|
{
|
||||||
ALOGV("callback: (%d, %d, %d, %p, %p)", event.mMsg, event.mArg1, event.mArg2, soundPool, user);
|
ALOGV("callback: (%d, %d, %d, %p, %p)", event.mMsg, event.mArg1, event.mArg2, soundPool, user);
|
||||||
|
auto weakRef = getSoundPoolJavaRefManager().get(soundPool); // shared_ptr to WeakRef
|
||||||
|
if (weakRef == nullptr) {
|
||||||
|
ALOGD("%s: no weak ref, object released, ignoring callback", __func__);
|
||||||
|
return;
|
||||||
|
}
|
||||||
JNIEnv *env = AndroidRuntime::getJNIEnv();
|
JNIEnv *env = AndroidRuntime::getJNIEnv();
|
||||||
env->CallStaticVoidMethod(
|
env->CallStaticVoidMethod(
|
||||||
fields.mSoundPoolClass, fields.mPostEvent, user, event.mMsg, event.mArg1, event.mArg2,
|
fields.mSoundPoolClass, fields.mPostEvent,
|
||||||
|
weakRef.get(), event.mMsg, event.mArg1, event.mArg2,
|
||||||
nullptr /* object */);
|
nullptr /* object */);
|
||||||
|
|
||||||
|
if (env->ExceptionCheck() != JNI_FALSE) {
|
||||||
|
ALOGE("%s: Uncaught exception returned from Java callback", __func__);
|
||||||
|
env->ExceptionDescribe();
|
||||||
|
env->ExceptionClear(); // Just clear it, hopefully all is ok.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static jint
|
static jint
|
||||||
@@ -353,13 +475,15 @@ android_media_SoundPool_native_setup(JNIEnv *env, jobject thiz, jobject weakRef,
|
|||||||
ALOGV("android_media_SoundPool_native_setup");
|
ALOGV("android_media_SoundPool_native_setup");
|
||||||
ScopedUtfChars opPackageNameStr(env, opPackageName);
|
ScopedUtfChars opPackageNameStr(env, opPackageName);
|
||||||
auto soundPool = std::make_shared<SoundPool>(maxChannels, paa, opPackageNameStr.c_str());
|
auto soundPool = std::make_shared<SoundPool>(maxChannels, paa, opPackageNameStr.c_str());
|
||||||
|
soundPool->setCallback(android_media_callback, nullptr /* user */);
|
||||||
// set callback with weak reference
|
|
||||||
jobject globalWeakRef = env->NewGlobalRef(weakRef);
|
|
||||||
soundPool->setCallback(android_media_callback, globalWeakRef);
|
|
||||||
|
|
||||||
// register with SoundPoolManager.
|
// register with SoundPoolManager.
|
||||||
|
|
||||||
auto oldSoundPool = setSoundPool(env, thiz, soundPool);
|
auto oldSoundPool = setSoundPool(env, thiz, soundPool);
|
||||||
|
// register Java SoundPool WeakRef using native SoundPool * as the key, for the callback.
|
||||||
|
auto oldSoundPoolJavaRef = getSoundPoolJavaRefManager().set(
|
||||||
|
soundPool.get(), make_shared_globalref_from_localref(env, weakRef));
|
||||||
|
|
||||||
ALOGW_IF(oldSoundPool != nullptr, "%s: Aliased SoundPool object %p",
|
ALOGW_IF(oldSoundPool != nullptr, "%s: Aliased SoundPool object %p",
|
||||||
__func__, oldSoundPool.get());
|
__func__, oldSoundPool.get());
|
||||||
|
|
||||||
@@ -375,19 +499,12 @@ android_media_SoundPool_release(JNIEnv *env, jobject thiz)
|
|||||||
ALOGV("android_media_SoundPool_release");
|
ALOGV("android_media_SoundPool_release");
|
||||||
|
|
||||||
// Remove us from SoundPoolManager.
|
// Remove us from SoundPoolManager.
|
||||||
auto oldSoundPool = setSoundPool(env, thiz, nullptr);
|
|
||||||
|
|
||||||
// Caution: Deleting the weakRef is not race free from invoking
|
auto oldSoundPool = setSoundPool(env, thiz, nullptr);
|
||||||
// the Java callback because we may not have the last remaining
|
|
||||||
// reference to the SoundPool object - another method could still
|
|
||||||
// be in progress.
|
|
||||||
if (oldSoundPool != nullptr) {
|
if (oldSoundPool != nullptr) {
|
||||||
// release weak reference and clear callback
|
// Note: setting the weak ref is thread safe in case there is a callback
|
||||||
auto weakRef = (jobject) oldSoundPool->getUserData();
|
// simultaneously occurring.
|
||||||
oldSoundPool->setCallback(nullptr /* callback */, nullptr /* user */);
|
auto oldSoundPoolJavaRef = getSoundPoolJavaRefManager().set(oldSoundPool.get(), nullptr);
|
||||||
if (weakRef != nullptr) {
|
|
||||||
env->DeleteGlobalRef(weakRef);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// destructor to oldSoundPool should occur at exit.
|
// destructor to oldSoundPool should occur at exit.
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user