| /* | |
| * Copyright (C) 2010 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. | |
| */ | |
| #include "sles_allinclusive.h" | |
| // Use this macro to validate a pthread_t before passing it into pthread_gettid_np. | |
| // One of the common reasons for deadlock is trying to lock a mutex for an object | |
| // which has been destroyed (which does memset to 0x00 or 0x55 as the final step). | |
| // To avoid crashing with a SIGSEGV right before we're about to log a deadlock warning, | |
| // we check that the pthread_t is probably valid. Note that it is theoretically | |
| // possible for something to look like a valid pthread_t but not actually be valid. | |
| // So we might still crash, but only in the case where a deadlock was imminent anyway. | |
| #define LIKELY_VALID(ptr) (((ptr) != (pthread_t) 0) && ((((size_t) (ptr)) & 3) == 0)) | |
| /** \brief Exclusively lock an object */ | |
| #ifdef USE_DEBUG | |
| void init_time_spec(timespec* ts, long delta) { | |
| clock_gettime(CLOCK_REALTIME, ts); | |
| ts->tv_nsec += delta; | |
| if (ts->tv_nsec >= 1000000000L) { | |
| ts->tv_sec++; | |
| ts->tv_nsec -= 1000000000L; | |
| } | |
| } | |
| void object_lock_exclusive_(IObject *thiz, const char *file, int line) | |
| { | |
| int ok; | |
| ok = pthread_mutex_trylock(&thiz->mMutex); | |
| if (0 != ok) { | |
| // not android_atomic_acquire_load because we don't care about relative load/load ordering | |
| int32_t oldGeneration = thiz->mGeneration; | |
| // wait up to a total of 250 ms | |
| static const long nanoBackoffs[] = { | |
| 10 * 1000000, 20 * 1000000, 30 * 1000000, 40 * 1000000, 50 * 1000000, 100 * 1000000}; | |
| unsigned i = 0; | |
| timespec ts; | |
| memset(&ts, 0, sizeof(timespec)); | |
| for (;;) { | |
| init_time_spec(&ts, nanoBackoffs[i]); | |
| ok = pthread_mutex_timedlock(&thiz->mMutex, &ts); | |
| if (0 == ok) { | |
| break; | |
| } | |
| if (EBUSY == ok) { | |
| // this is the expected return value for timeout, and will be handled below | |
| } else if (EDEADLK == ok) { | |
| // we don't use the kind of mutex that can return this error, but just in case | |
| SL_LOGE("%s:%d: recursive lock detected", file, line); | |
| } else { | |
| // some other return value | |
| SL_LOGE("%s:%d: pthread_mutex_lock_timeout_np returned %d", file, line, ok); | |
| } | |
| // is anyone else making forward progress? | |
| int32_t newGeneration = thiz->mGeneration; | |
| if (newGeneration != oldGeneration) { | |
| // if we ever see forward progress then lock without timeout (more efficient) | |
| goto forward_progress; | |
| } | |
| // no, then continue trying to lock but with increasing timeouts | |
| if (++i >= (sizeof(nanoBackoffs) / sizeof(nanoBackoffs[0]))) { | |
| // the extra block avoids a C++ compiler error about goto past initialization | |
| { | |
| pthread_t me = pthread_self(); | |
| pthread_t owner = thiz->mOwner; | |
| // unlikely, but this could result in a memory fault if owner is corrupt | |
| pid_t ownerTid = LIKELY_VALID(owner) ? pthread_gettid_np(owner) : -1; | |
| SL_LOGW("%s:%d: pthread %p (tid %d) sees object %p was locked by pthread %p" | |
| " (tid %d) at %s:%d\n", file, line, *(void **)&me, gettid(), thiz, | |
| *(void **)&owner, ownerTid, thiz->mFile, thiz->mLine); | |
| } | |
| forward_progress: | |
| // attempt one more time without timeout; maybe this time we will be successful | |
| ok = pthread_mutex_lock(&thiz->mMutex); | |
| assert(0 == ok); | |
| break; | |
| } | |
| } | |
| } | |
| // here if mutex was successfully locked | |
| pthread_t zero; | |
| memset(&zero, 0, sizeof(pthread_t)); | |
| if (0 != memcmp(&zero, &thiz->mOwner, sizeof(pthread_t))) { | |
| pthread_t me = pthread_self(); | |
| pthread_t owner = thiz->mOwner; | |
| pid_t ownerTid = LIKELY_VALID(owner) ? pthread_gettid_np(owner) : -1; | |
| if (pthread_equal(pthread_self(), owner)) { | |
| SL_LOGE("%s:%d: pthread %p (tid %d) sees object %p was recursively locked by pthread" | |
| " %p (tid %d) at %s:%d\n", file, line, *(void **)&me, gettid(), thiz, | |
| *(void **)&owner, ownerTid, thiz->mFile, thiz->mLine); | |
| } else { | |
| SL_LOGE("%s:%d: pthread %p (tid %d) sees object %p was left unlocked in unexpected" | |
| " state by pthread %p (tid %d) at %s:%d\n", file, line, *(void **)&me, gettid(), | |
| thiz, *(void **)&owner, ownerTid, thiz->mFile, thiz->mLine); | |
| } | |
| assert(false); | |
| } | |
| thiz->mOwner = pthread_self(); | |
| thiz->mFile = file; | |
| thiz->mLine = line; | |
| // not android_atomic_inc because we are already holding a mutex | |
| // use explicit add as mGeneration is a volatile for which ++v and v += are deprecated. | |
| thiz->mGeneration = thiz->mGeneration + 1; | |
| } | |
| #else | |
| void object_lock_exclusive(IObject *thiz) | |
| { | |
| int ok; | |
| ok = pthread_mutex_lock(&thiz->mMutex); | |
| assert(0 == ok); | |
| } | |
| #endif | |
| /** \brief Exclusively unlock an object and do not report any updates */ | |
| #ifdef USE_DEBUG | |
| void object_unlock_exclusive_(IObject *thiz, const char *file, int line) | |
| { | |
| assert(pthread_equal(pthread_self(), thiz->mOwner)); | |
| assert(NULL != thiz->mFile); | |
| assert(0 != thiz->mLine); | |
| memset(&thiz->mOwner, 0, sizeof(pthread_t)); | |
| thiz->mFile = file; | |
| thiz->mLine = line; | |
| int ok; | |
| ok = pthread_mutex_unlock(&thiz->mMutex); | |
| assert(0 == ok); | |
| } | |
| #else | |
| void object_unlock_exclusive(IObject *thiz) | |
| { | |
| int ok; | |
| ok = pthread_mutex_unlock(&thiz->mMutex); | |
| assert(0 == ok); | |
| } | |
| #endif | |
| /** \brief Exclusively unlock an object and report updates to the specified bit-mask of | |
| * attributes | |
| */ | |
| #ifdef USE_DEBUG | |
| void object_unlock_exclusive_attributes_(IObject *thiz, unsigned attributes, | |
| const char *file, int line) | |
| #else | |
| void object_unlock_exclusive_attributes(IObject *thiz, unsigned attributes) | |
| #endif | |
| { | |
| #ifdef USE_DEBUG | |
| assert(pthread_equal(pthread_self(), thiz->mOwner)); | |
| assert(NULL != thiz->mFile); | |
| assert(0 != thiz->mLine); | |
| #endif | |
| int ok; | |
| // make SL object IDs be contiguous with XA object IDs | |
| SLuint32 objectID = IObjectToObjectID(thiz); | |
| SLuint32 index = objectID; | |
| if ((XA_OBJECTID_ENGINE <= index) && (index <= XA_OBJECTID_CAMERADEVICE)) { | |
| ; | |
| } else if ((SL_OBJECTID_ENGINE <= index) && (index <= SL_OBJECTID_METADATAEXTRACTOR)) { | |
| index -= SL_OBJECTID_ENGINE - XA_OBJECTID_CAMERADEVICE - 1; | |
| } else { | |
| assert(false); | |
| index = 0; | |
| } | |
| // first synchronously handle updates to attributes here, while object is still locked. | |
| // This appears to be a loop, but actually typically runs through the loop only once. | |
| unsigned asynchronous = attributes; | |
| while (attributes) { | |
| // this sequence is carefully crafted to be O(1); tread carefully when making changes | |
| unsigned bit = ctz(attributes); | |
| // ATTR_INDEX_MAX == next bit position after the last attribute | |
| assert(ATTR_INDEX_MAX > bit); | |
| // compute the entry in the handler table using object ID and bit number | |
| AttributeHandler handler = handlerTable[index][bit]; | |
| if (NULL != handler) { | |
| asynchronous &= ~(*handler)(thiz); | |
| } | |
| attributes &= ~(1 << bit); | |
| } | |
| // any remaining attributes are handled asynchronously in the sync thread | |
| if (asynchronous) { | |
| unsigned oldAttributesMask = thiz->mAttributesMask; | |
| thiz->mAttributesMask = oldAttributesMask | asynchronous; | |
| if (oldAttributesMask) { | |
| asynchronous = ATTR_NONE; | |
| } | |
| } | |
| #ifdef ANDROID | |
| // FIXME hack to safely handle a post-unlock PrefetchStatus callback and/or AudioTrack::start() | |
| slPrefetchCallback prefetchCallback = NULL; | |
| void *prefetchContext = NULL; | |
| SLuint32 prefetchEvents = SL_PREFETCHEVENT_NONE; | |
| android::sp<android::AudioTrack> audioTrack; | |
| if (SL_OBJECTID_AUDIOPLAYER == objectID) { | |
| CAudioPlayer *ap = (CAudioPlayer *) thiz; | |
| prefetchCallback = ap->mPrefetchStatus.mDeferredPrefetchCallback; | |
| prefetchContext = ap->mPrefetchStatus.mDeferredPrefetchContext; | |
| prefetchEvents = ap->mPrefetchStatus.mDeferredPrefetchEvents; | |
| ap->mPrefetchStatus.mDeferredPrefetchCallback = NULL; | |
| // clearing these next two fields is not required, but avoids stale data during debugging | |
| ap->mPrefetchStatus.mDeferredPrefetchContext = NULL; | |
| ap->mPrefetchStatus.mDeferredPrefetchEvents = SL_PREFETCHEVENT_NONE; | |
| if (ap->mDeferredStart) { | |
| audioTrack = ap->mTrackPlayer->getAudioTrack(); | |
| ap->mDeferredStart = false; | |
| } | |
| } | |
| #endif | |
| #ifdef USE_DEBUG | |
| memset(&thiz->mOwner, 0, sizeof(pthread_t)); | |
| thiz->mFile = file; | |
| thiz->mLine = line; | |
| #endif | |
| ok = pthread_mutex_unlock(&thiz->mMutex); | |
| assert(0 == ok); | |
| #ifdef ANDROID | |
| // FIXME call the prefetch status callback while not holding the mutex on AudioPlayer | |
| if (NULL != prefetchCallback) { | |
| // note these are synchronous by the application's thread as it is about to return from API | |
| assert(prefetchEvents != SL_PREFETCHEVENT_NONE); | |
| CAudioPlayer *ap = (CAudioPlayer *) thiz; | |
| // spec requires separate callbacks for each event | |
| if (SL_PREFETCHEVENT_STATUSCHANGE & prefetchEvents) { | |
| (*prefetchCallback)(&ap->mPrefetchStatus.mItf, prefetchContext, | |
| SL_PREFETCHEVENT_STATUSCHANGE); | |
| } | |
| if (SL_PREFETCHEVENT_FILLLEVELCHANGE & prefetchEvents) { | |
| (*prefetchCallback)(&ap->mPrefetchStatus.mItf, prefetchContext, | |
| SL_PREFETCHEVENT_FILLLEVELCHANGE); | |
| } | |
| } | |
| // call AudioTrack::start() while not holding the mutex on AudioPlayer | |
| if (audioTrack != 0) { | |
| audioTrack->start(); | |
| audioTrack.clear(); | |
| } | |
| #endif | |
| // first update to this interface since previous sync | |
| if (ATTR_NONE != asynchronous) { | |
| unsigned id = thiz->mInstanceID; | |
| if (0 != id) { | |
| --id; | |
| assert(MAX_INSTANCE > id); | |
| IEngine *thisEngine = &thiz->mEngine->mEngine; | |
| // FIXME atomic or here | |
| interface_lock_exclusive(thisEngine); | |
| thisEngine->mChangedMask |= 1 << id; | |
| interface_unlock_exclusive(thisEngine); | |
| } | |
| } | |
| } | |
| /** \brief Wait on the condition variable associated with the object; see pthread_cond_wait */ | |
| #ifdef USE_DEBUG | |
| void object_cond_wait_(IObject *thiz, const char *file, int line) | |
| { | |
| // note that this will unlock the mutex, so we have to clear the owner | |
| assert(pthread_equal(pthread_self(), thiz->mOwner)); | |
| assert(NULL != thiz->mFile); | |
| assert(0 != thiz->mLine); | |
| memset(&thiz->mOwner, 0, sizeof(pthread_t)); | |
| thiz->mFile = file; | |
| thiz->mLine = line; | |
| // alas we don't know the new owner's identity | |
| int ok; | |
| ok = pthread_cond_wait(&thiz->mCond, &thiz->mMutex); | |
| assert(0 == ok); | |
| // restore my ownership | |
| thiz->mOwner = pthread_self(); | |
| thiz->mFile = file; | |
| thiz->mLine = line; | |
| } | |
| #else | |
| void object_cond_wait(IObject *thiz) | |
| { | |
| int ok; | |
| ok = pthread_cond_wait(&thiz->mCond, &thiz->mMutex); | |
| assert(0 == ok); | |
| } | |
| #endif | |
| /** \brief Signal the condition variable associated with the object; see pthread_cond_signal */ | |
| void object_cond_signal(IObject *thiz) | |
| { | |
| int ok; | |
| ok = pthread_cond_signal(&thiz->mCond); | |
| assert(0 == ok); | |
| } | |
| /** \brief Broadcast the condition variable associated with the object; | |
| * see pthread_cond_broadcast | |
| */ | |
| void object_cond_broadcast(IObject *thiz) | |
| { | |
| int ok; | |
| ok = pthread_cond_broadcast(&thiz->mCond); | |
| assert(0 == ok); | |
| } |