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