1 /*
2  * Copyright (C) 2010 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer.
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution.
13  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 #include "HRTFDatabaseLoader.h"
30 #include "HRTFDatabase.h"
31 #include "GeckoProfiler.h"
32 #include "nsThreadUtils.h"
33 
34 using namespace mozilla;
35 
36 namespace WebCore {
37 
38 // Singleton
39 nsTHashtable<HRTFDatabaseLoader::LoaderByRateEntry>*
40     HRTFDatabaseLoader::s_loaderMap = nullptr;
41 
sizeOfLoaders(mozilla::MallocSizeOf aMallocSizeOf)42 size_t HRTFDatabaseLoader::sizeOfLoaders(mozilla::MallocSizeOf aMallocSizeOf) {
43   return s_loaderMap ? s_loaderMap->SizeOfIncludingThis(aMallocSizeOf) : 0;
44 }
45 
46 already_AddRefed<HRTFDatabaseLoader>
createAndLoadAsynchronouslyIfNecessary(float sampleRate)47 HRTFDatabaseLoader::createAndLoadAsynchronouslyIfNecessary(float sampleRate) {
48   MOZ_ASSERT(NS_IsMainThread());
49 
50   RefPtr<HRTFDatabaseLoader> loader;
51 
52   if (!s_loaderMap) {
53     s_loaderMap = new nsTHashtable<LoaderByRateEntry>();
54   }
55 
56   LoaderByRateEntry* entry = s_loaderMap->PutEntry(sampleRate);
57   loader = entry->mLoader;
58   if (loader) {  // existing entry
59     MOZ_ASSERT(sampleRate == loader->databaseSampleRate());
60     return loader.forget();
61   }
62 
63   loader = new HRTFDatabaseLoader(sampleRate);
64   entry->mLoader = loader;
65 
66   loader->loadAsynchronously();
67 
68   return loader.forget();
69 }
70 
HRTFDatabaseLoader(float sampleRate)71 HRTFDatabaseLoader::HRTFDatabaseLoader(float sampleRate)
72     : m_refCnt(0),
73       m_threadLock("HRTFDatabaseLoader"),
74       m_databaseLoaderThread(nullptr),
75       m_databaseSampleRate(sampleRate) {
76   MOZ_ASSERT(NS_IsMainThread());
77 }
78 
~HRTFDatabaseLoader()79 HRTFDatabaseLoader::~HRTFDatabaseLoader() {
80   MOZ_ASSERT(NS_IsMainThread());
81 
82   waitForLoaderThreadCompletion();
83   m_hrtfDatabase.reset();
84 
85   if (s_loaderMap) {
86     // Remove ourself from the map.
87     s_loaderMap->RemoveEntry(m_databaseSampleRate);
88     if (s_loaderMap->Count() == 0) {
89       delete s_loaderMap;
90       s_loaderMap = nullptr;
91     }
92   }
93 }
94 
sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const95 size_t HRTFDatabaseLoader::sizeOfIncludingThis(
96     mozilla::MallocSizeOf aMallocSizeOf) const {
97   size_t amount = aMallocSizeOf(this);
98 
99   // NB: Need to make sure we're not competing with the loader thread.
100   const_cast<HRTFDatabaseLoader*>(this)->waitForLoaderThreadCompletion();
101 
102   if (m_hrtfDatabase) {
103     amount += m_hrtfDatabase->sizeOfIncludingThis(aMallocSizeOf);
104   }
105 
106   return amount;
107 }
108 
109 class HRTFDatabaseLoader::ProxyReleaseEvent final : public Runnable {
110  public:
ProxyReleaseEvent(HRTFDatabaseLoader * loader)111   explicit ProxyReleaseEvent(HRTFDatabaseLoader* loader)
112       : mozilla::Runnable("WebCore::HRTFDatabaseLoader::ProxyReleaseEvent"),
113         mLoader(loader) {}
Run()114   NS_IMETHOD Run() override {
115     mLoader->MainThreadRelease();
116     return NS_OK;
117   }
118 
119  private:
120   // Ownership transferred by ProxyRelease
121   HRTFDatabaseLoader* MOZ_OWNING_REF mLoader;
122 };
123 
ProxyRelease()124 void HRTFDatabaseLoader::ProxyRelease() {
125   nsCOMPtr<nsIEventTarget> mainTarget = GetMainThreadEventTarget();
126   if (MOZ_LIKELY(mainTarget)) {
127     RefPtr<ProxyReleaseEvent> event = new ProxyReleaseEvent(this);
128     DebugOnly<nsresult> rv = mainTarget->Dispatch(event, NS_DISPATCH_NORMAL);
129     MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to dispatch release event");
130   } else {
131     // Should be in XPCOM shutdown.
132     MOZ_ASSERT(NS_IsMainThread(), "Main thread is not available for dispatch.");
133     MainThreadRelease();
134   }
135 }
136 
MainThreadRelease()137 void HRTFDatabaseLoader::MainThreadRelease() {
138   MOZ_ASSERT(NS_IsMainThread());
139   int count = --m_refCnt;
140   MOZ_ASSERT(count >= 0, "extra release");
141   NS_LOG_RELEASE(this, count, "HRTFDatabaseLoader");
142   if (count == 0) {
143     // It is safe to delete here as the first reference can only be added
144     // on this (main) thread.
145     delete this;
146   }
147 }
148 
149 // Asynchronously load the database in this thread.
databaseLoaderEntry(void * threadData)150 static void databaseLoaderEntry(void* threadData) {
151   AUTO_PROFILER_REGISTER_THREAD("HRTFDatabaseLdr");
152   NS_SetCurrentThreadName("HRTFDatabaseLdr");
153 
154   HRTFDatabaseLoader* loader =
155       reinterpret_cast<HRTFDatabaseLoader*>(threadData);
156   MOZ_ASSERT(loader);
157   loader->load();
158 }
159 
load()160 void HRTFDatabaseLoader::load() {
161   MOZ_ASSERT(!NS_IsMainThread());
162   MOZ_ASSERT(!m_hrtfDatabase.get(), "Called twice");
163   // Load the default HRTF database.
164   m_hrtfDatabase = HRTFDatabase::create(m_databaseSampleRate);
165   // Notifies the main thread of completion.  See loadAsynchronously().
166   Release();
167 }
168 
loadAsynchronously()169 void HRTFDatabaseLoader::loadAsynchronously() {
170   MOZ_ASSERT(NS_IsMainThread());
171   MOZ_ASSERT(m_refCnt, "Must not be called before a reference is added");
172 
173   // Add a reference so that the destructor won't run and wait for the
174   // loader thread, until load() has completed.
175   AddRef();
176 
177   MutexAutoLock locker(m_threadLock);
178 
179   MOZ_ASSERT(!m_hrtfDatabase.get() && !m_databaseLoaderThread, "Called twice");
180   // Start the asynchronous database loading process.
181   m_databaseLoaderThread = PR_CreateThread(
182       PR_USER_THREAD, databaseLoaderEntry, this, PR_PRIORITY_NORMAL,
183       PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0);
184 }
185 
isLoaded() const186 bool HRTFDatabaseLoader::isLoaded() const { return m_hrtfDatabase.get(); }
187 
waitForLoaderThreadCompletion()188 void HRTFDatabaseLoader::waitForLoaderThreadCompletion() {
189   MutexAutoLock locker(m_threadLock);
190 
191   // waitForThreadCompletion() should not be called twice for the same thread.
192   if (m_databaseLoaderThread) {
193     DebugOnly<PRStatus> status = PR_JoinThread(m_databaseLoaderThread);
194     MOZ_ASSERT(status == PR_SUCCESS, "PR_JoinThread failed");
195   }
196   m_databaseLoaderThread = nullptr;
197 }
198 
shutdown()199 void HRTFDatabaseLoader::shutdown() {
200   MOZ_ASSERT(NS_IsMainThread());
201   if (s_loaderMap) {
202     // Set s_loaderMap to nullptr so that the hashtable is not modified on
203     // reference release during enumeration.
204     nsTHashtable<LoaderByRateEntry>* loaderMap = s_loaderMap;
205     s_loaderMap = nullptr;
206     for (auto iter = loaderMap->Iter(); !iter.Done(); iter.Next()) {
207       iter.Get()->mLoader->waitForLoaderThreadCompletion();
208     }
209     delete loaderMap;
210   }
211 }
212 
213 }  // namespace WebCore
214