File: | var/lib/jenkins/workspace/firefox-scan-build/storage/mozStorageAsyncStatementExecution.cpp |
Warning: | line 236, column 7 Value stored to 'busyRetry' is never read |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- |
2 | * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : |
3 | * This Source Code Form is subject to the terms of the Mozilla Public |
4 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | |
7 | #include "sqlite3.h" |
8 | |
9 | #include "mozIStorageStatementCallback.h" |
10 | #include "mozStorageBindingParams.h" |
11 | #include "mozStorageHelper.h" |
12 | #include "mozStorageResultSet.h" |
13 | #include "mozStorageRow.h" |
14 | #include "mozStorageConnection.h" |
15 | #include "mozStorageError.h" |
16 | #include "mozStoragePrivateHelpers.h" |
17 | #include "mozStorageStatementData.h" |
18 | #include "mozStorageAsyncStatementExecution.h" |
19 | |
20 | #include "mozilla/DebugOnly.h" |
21 | #include "mozilla/Telemetry.h" |
22 | |
23 | #ifndef MOZ_STORAGE_SORTWARNING_SQL_DUMP |
24 | # include "mozilla/Logging.h" |
25 | extern mozilla::LazyLogModule gStorageLog; |
26 | #endif |
27 | |
28 | namespace mozilla { |
29 | namespace storage { |
30 | |
31 | /** |
32 | * The following constants help batch rows into result sets. |
33 | * MAX_MILLISECONDS_BETWEEN_RESULTS was chosen because any user-based task that |
34 | * takes less than 200 milliseconds is considered to feel instantaneous to end |
35 | * users. MAX_ROWS_PER_RESULT was arbitrarily chosen to reduce the number of |
36 | * dispatches to calling thread, while also providing reasonably-sized sets of |
37 | * data for consumers. Both of these constants are used because we assume that |
38 | * consumers are trying to avoid blocking their execution thread for long |
39 | * periods of time, and dispatching many small events to the calling thread will |
40 | * end up blocking it. |
41 | */ |
42 | #define MAX_MILLISECONDS_BETWEEN_RESULTS75 75 |
43 | #define MAX_ROWS_PER_RESULT15 15 |
44 | |
45 | //////////////////////////////////////////////////////////////////////////////// |
46 | //// AsyncExecuteStatements |
47 | |
48 | /* static */ |
49 | nsresult AsyncExecuteStatements::execute( |
50 | StatementDataArray&& aStatements, Connection* aConnection, |
51 | sqlite3* aNativeConnection, mozIStorageStatementCallback* aCallback, |
52 | mozIStoragePendingStatement** _stmt) { |
53 | // Create our event to run in the background |
54 | RefPtr<AsyncExecuteStatements> event = new AsyncExecuteStatements( |
55 | std::move(aStatements), aConnection, aNativeConnection, aCallback); |
56 | NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY)do { if ((__builtin_expect(!!(!(event)), 0))) { NS_DebugBreak (NS_DEBUG_WARNING, "NS_ENSURE_TRUE(" "event" ") failed", nullptr , "/var/lib/jenkins/workspace/firefox-scan-build/storage/mozStorageAsyncStatementExecution.cpp" , 56); return NS_ERROR_OUT_OF_MEMORY; } } while (false); |
57 | |
58 | // Dispatch it to the background |
59 | nsIEventTarget* target = aConnection->getAsyncExecutionTarget(); |
60 | |
61 | // If we don't have a valid target, this is a bug somewhere else. In the past, |
62 | // this assert found cases where a Run method would schedule a new statement |
63 | // without checking if asyncClose had been called. The caller must prevent |
64 | // that from happening or, if the work is not critical, just avoid creating |
65 | // the new statement during shutdown. See bug 718449 for an example. |
66 | MOZ_ASSERT(target)do { static_assert( mozilla::detail::AssertionConditionType< decltype(target)>::isValid, "invalid assertion condition") ; if ((__builtin_expect(!!(!(!!(target))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("target", "/var/lib/jenkins/workspace/firefox-scan-build/storage/mozStorageAsyncStatementExecution.cpp" , 66); AnnotateMozCrashReason("MOZ_ASSERT" "(" "target" ")"); do { *((volatile int*)__null) = 66; __attribute__((nomerge)) ::abort(); } while (false); } } while (false); |
67 | if (!target) { |
68 | return NS_ERROR_NOT_AVAILABLE; |
69 | } |
70 | |
71 | nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMALnsIEventTarget::DISPATCH_NORMAL); |
72 | NS_ENSURE_SUCCESS(rv, rv)do { nsresult __rv = rv; if (((bool)(__builtin_expect(!!(NS_FAILED_impl (__rv)), 0)))) { const char* name = mozilla::GetStaticErrorName (__rv); mozilla::SmprintfPointer msg = mozilla::Smprintf( "NS_ENSURE_SUCCESS(%s, %s) failed with " "result 0x%" "X" "%s%s%s", "rv", "rv", static_cast<uint32_t >(__rv), name ? " (" : "", name ? name : "", name ? ")" : "" ); NS_DebugBreak(NS_DEBUG_WARNING, msg.get(), nullptr, "/var/lib/jenkins/workspace/firefox-scan-build/storage/mozStorageAsyncStatementExecution.cpp" , 72); return rv; } } while (false); |
73 | |
74 | // Return it as the pending statement object and track it. |
75 | event.forget(_stmt); |
76 | return NS_OK; |
77 | } |
78 | |
79 | AsyncExecuteStatements::AsyncExecuteStatements( |
80 | StatementDataArray&& aStatements, Connection* aConnection, |
81 | sqlite3* aNativeConnection, mozIStorageStatementCallback* aCallback) |
82 | : Runnable("AsyncExecuteStatements"), |
83 | mStatements(std::move(aStatements)), |
84 | mConnection(aConnection), |
85 | mNativeConnection(aNativeConnection), |
86 | mHasTransaction(false), |
87 | mCallback(aCallback), |
88 | mCallingThread(::do_GetCurrentThread()), |
89 | mMaxWait( |
90 | TimeDuration::FromMilliseconds(MAX_MILLISECONDS_BETWEEN_RESULTS75)), |
91 | mIntervalStart(TimeStamp::Now()), |
92 | mState(PENDING), |
93 | mCancelRequested(false), |
94 | mMutex(aConnection->sharedAsyncExecutionMutex), |
95 | mDBMutex(aConnection->sharedDBMutex) { |
96 | NS_ASSERTION(mStatements.Length(), "We weren't given any statements!")do { if (!(mStatements.Length())) { NS_DebugBreak(NS_DEBUG_ASSERTION , "We weren't given any statements!", "mStatements.Length()", "/var/lib/jenkins/workspace/firefox-scan-build/storage/mozStorageAsyncStatementExecution.cpp" , 96); MOZ_PretendNoReturn(); } } while (0); |
97 | } |
98 | |
99 | AsyncExecuteStatements::~AsyncExecuteStatements() { |
100 | MOZ_ASSERT(!mCallback, "Never called the Completion callback!")do { static_assert( mozilla::detail::AssertionConditionType< decltype(!mCallback)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(!mCallback))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("!mCallback" " (" "Never called the Completion callback!" ")", "/var/lib/jenkins/workspace/firefox-scan-build/storage/mozStorageAsyncStatementExecution.cpp" , 100); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!mCallback" ") (" "Never called the Completion callback!" ")"); do { *((volatile int*)__null) = 100; __attribute__((nomerge)) ::abort(); } while (false); } } while (false); |
101 | MOZ_ASSERT(!mHasTransaction, "There should be no transaction at this point")do { static_assert( mozilla::detail::AssertionConditionType< decltype(!mHasTransaction)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(!mHasTransaction))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("!mHasTransaction" " (" "There should be no transaction at this point" ")", "/var/lib/jenkins/workspace/firefox-scan-build/storage/mozStorageAsyncStatementExecution.cpp" , 101); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!mHasTransaction" ") (" "There should be no transaction at this point" ")"); do { *((volatile int*)__null) = 101; __attribute__((nomerge)) :: abort(); } while (false); } } while (false); |
102 | if (mCallback) { |
103 | NS_ProxyRelease("AsyncExecuteStatements::mCallback", mCallingThread, |
104 | mCallback.forget()); |
105 | } |
106 | } |
107 | |
108 | bool AsyncExecuteStatements::shouldNotify() { |
109 | #ifdef DEBUG1 |
110 | mMutex.AssertNotCurrentThreadOwns(); |
111 | |
112 | bool onCallingThread = false; |
113 | (void)mCallingThread->IsOnCurrentThread(&onCallingThread); |
114 | NS_ASSERTION(onCallingThread, "runEvent not running on the calling thread!")do { if (!(onCallingThread)) { NS_DebugBreak(NS_DEBUG_ASSERTION , "runEvent not running on the calling thread!", "onCallingThread" , "/var/lib/jenkins/workspace/firefox-scan-build/storage/mozStorageAsyncStatementExecution.cpp" , 114); MOZ_PretendNoReturn(); } } while (0); |
115 | #endif |
116 | |
117 | // We do not need to acquire mMutex here because it can only ever be written |
118 | // to on the calling thread, and the only thread that can call us is the |
119 | // calling thread, so we know that our access is serialized. |
120 | return !mCancelRequested; |
121 | } |
122 | |
123 | bool AsyncExecuteStatements::bindExecuteAndProcessStatement( |
124 | StatementData& aData, bool aLastStatement) { |
125 | mMutex.AssertNotCurrentThreadOwns(); |
126 | |
127 | sqlite3_stmt* aStatement = nullptr; |
128 | // This cannot fail; we are only called if it's available. |
129 | Unused << aData.getSqliteStatement(&aStatement); |
130 | MOZ_DIAGNOSTIC_ASSERT(do { static_assert( mozilla::detail::AssertionConditionType< decltype(aStatement)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aStatement))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aStatement" " (" "bindExecuteAndProcessStatement called without an initialized statement" ")", "/var/lib/jenkins/workspace/firefox-scan-build/storage/mozStorageAsyncStatementExecution.cpp" , 132); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aStatement" ") (" "bindExecuteAndProcessStatement called without an initialized statement" ")"); do { *((volatile int*)__null) = 132; __attribute__((nomerge )) ::abort(); } while (false); } } while (false) |
131 | aStatement,do { static_assert( mozilla::detail::AssertionConditionType< decltype(aStatement)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aStatement))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aStatement" " (" "bindExecuteAndProcessStatement called without an initialized statement" ")", "/var/lib/jenkins/workspace/firefox-scan-build/storage/mozStorageAsyncStatementExecution.cpp" , 132); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aStatement" ") (" "bindExecuteAndProcessStatement called without an initialized statement" ")"); do { *((volatile int*)__null) = 132; __attribute__((nomerge )) ::abort(); } while (false); } } while (false) |
132 | "bindExecuteAndProcessStatement called without an initialized statement")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aStatement)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aStatement))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aStatement" " (" "bindExecuteAndProcessStatement called without an initialized statement" ")", "/var/lib/jenkins/workspace/firefox-scan-build/storage/mozStorageAsyncStatementExecution.cpp" , 132); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aStatement" ") (" "bindExecuteAndProcessStatement called without an initialized statement" ")"); do { *((volatile int*)__null) = 132; __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
133 | BindingParamsArray* paramsArray(aData); |
134 | |
135 | // Iterate through all of our parameters, bind them, and execute. |
136 | bool continueProcessing = true; |
137 | BindingParamsArray::iterator itr = paramsArray->begin(); |
138 | BindingParamsArray::iterator end = paramsArray->end(); |
139 | while (itr != end && continueProcessing) { |
140 | // Bind the data to our statement. |
141 | nsCOMPtr<IStorageBindingParamsInternal> bindingInternal = |
142 | do_QueryInterface(*itr); |
143 | nsCOMPtr<mozIStorageError> error = bindingInternal->bind(aStatement); |
144 | if (error) { |
145 | // Set our error state. |
146 | mState = ERROR; |
147 | |
148 | // And notify. |
149 | (void)notifyError(error); |
150 | return false; |
151 | } |
152 | |
153 | // Advance our iterator, execute, and then process the statement. |
154 | itr++; |
155 | bool lastStatement = aLastStatement && itr == end; |
156 | continueProcessing = executeAndProcessStatement(aData, lastStatement); |
157 | |
158 | // Always reset our statement. |
159 | (void)::sqlite3_reset(aStatement); |
160 | } |
161 | |
162 | return continueProcessing; |
163 | } |
164 | |
165 | bool AsyncExecuteStatements::executeAndProcessStatement(StatementData& aData, |
166 | bool aLastStatement) { |
167 | mMutex.AssertNotCurrentThreadOwns(); |
168 | |
169 | sqlite3_stmt* aStatement = nullptr; |
170 | // This cannot fail; we are only called if it's available. |
171 | Unused << aData.getSqliteStatement(&aStatement); |
172 | MOZ_DIAGNOSTIC_ASSERT(do { static_assert( mozilla::detail::AssertionConditionType< decltype(aStatement)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aStatement))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aStatement" " (" "executeAndProcessStatement called without an initialized statement" ")", "/var/lib/jenkins/workspace/firefox-scan-build/storage/mozStorageAsyncStatementExecution.cpp" , 174); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aStatement" ") (" "executeAndProcessStatement called without an initialized statement" ")"); do { *((volatile int*)__null) = 174; __attribute__((nomerge )) ::abort(); } while (false); } } while (false) |
173 | aStatement,do { static_assert( mozilla::detail::AssertionConditionType< decltype(aStatement)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aStatement))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aStatement" " (" "executeAndProcessStatement called without an initialized statement" ")", "/var/lib/jenkins/workspace/firefox-scan-build/storage/mozStorageAsyncStatementExecution.cpp" , 174); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aStatement" ") (" "executeAndProcessStatement called without an initialized statement" ")"); do { *((volatile int*)__null) = 174; __attribute__((nomerge )) ::abort(); } while (false); } } while (false) |
174 | "executeAndProcessStatement called without an initialized statement")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aStatement)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aStatement))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aStatement" " (" "executeAndProcessStatement called without an initialized statement" ")", "/var/lib/jenkins/workspace/firefox-scan-build/storage/mozStorageAsyncStatementExecution.cpp" , 174); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aStatement" ") (" "executeAndProcessStatement called without an initialized statement" ")"); do { *((volatile int*)__null) = 174; __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
175 | |
176 | // Execute our statement |
177 | bool hasResults; |
178 | do { |
179 | hasResults = executeStatement(aData); |
180 | |
181 | // If we had an error, bail. |
182 | if (mState == ERROR || mState == CANCELED) return false; |
183 | |
184 | // If we have been canceled, there is no point in going on... |
185 | { |
186 | MutexAutoLock lockedScope(mMutex); |
187 | if (mCancelRequested) { |
188 | mState = CANCELED; |
189 | return false; |
190 | } |
191 | } |
192 | |
193 | // Build our result set and notify if we got anything back and have a |
194 | // callback to notify. |
195 | if (mCallback && hasResults && |
196 | NS_FAILED(buildAndNotifyResults(aStatement))((bool)(__builtin_expect(!!(NS_FAILED_impl(buildAndNotifyResults (aStatement))), 0)))) { |
197 | // We had an error notifying, so we notify on error and stop processing. |
198 | mState = ERROR; |
199 | |
200 | // Notify, and stop processing statements. |
201 | (void)notifyError(mozIStorageError::ERROR, |
202 | "An error occurred while notifying about results"); |
203 | |
204 | return false; |
205 | } |
206 | } while (hasResults); |
207 | |
208 | #ifndef MOZ_STORAGE_SORTWARNING_SQL_DUMP |
209 | if (MOZ_LOG_TEST(gStorageLog, LogLevel::Warning)(__builtin_expect(!!(mozilla::detail::log_test(gStorageLog, LogLevel ::Warning)), 0))) |
210 | #endif |
211 | { |
212 | // Check to make sure that this statement was smart about what it did. |
213 | checkAndLogStatementPerformance(aStatement); |
214 | } |
215 | |
216 | // If we are done, we need to set our state accordingly while we still hold |
217 | // our mutex. We would have already returned if we were canceled or had |
218 | // an error at this point. |
219 | if (aLastStatement) mState = COMPLETED; |
220 | |
221 | return true; |
222 | } |
223 | |
224 | bool AsyncExecuteStatements::executeStatement(StatementData& aData) { |
225 | mMutex.AssertNotCurrentThreadOwns(); |
226 | |
227 | sqlite3_stmt* aStatement = nullptr; |
228 | // This cannot fail; we are only called if it's available. |
229 | Unused << aData.getSqliteStatement(&aStatement); |
230 | MOZ_DIAGNOSTIC_ASSERT(do { static_assert( mozilla::detail::AssertionConditionType< decltype(aStatement)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aStatement))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aStatement" " (" "executeStatement called without an initialized statement" ")", "/var/lib/jenkins/workspace/firefox-scan-build/storage/mozStorageAsyncStatementExecution.cpp" , 231); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aStatement" ") (" "executeStatement called without an initialized statement" ")"); do { *((volatile int*)__null) = 231; __attribute__((nomerge )) ::abort(); } while (false); } } while (false) |
231 | aStatement, "executeStatement called without an initialized statement")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aStatement)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aStatement))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aStatement" " (" "executeStatement called without an initialized statement" ")", "/var/lib/jenkins/workspace/firefox-scan-build/storage/mozStorageAsyncStatementExecution.cpp" , 231); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aStatement" ") (" "executeStatement called without an initialized statement" ")"); do { *((volatile int*)__null) = 231; __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
232 | |
233 | bool busyRetry = false; |
234 | while (true) { |
235 | if (busyRetry) { |
236 | busyRetry = false; |
Value stored to 'busyRetry' is never read | |
237 | |
238 | // Yield, and try again |
239 | Unused << PR_Sleep(PR_INTERVAL_NO_WAIT0UL); |
240 | |
241 | // Check for cancellation before retrying |
242 | { |
243 | MutexAutoLock lockedScope(mMutex); |
244 | if (mCancelRequested) { |
245 | mState = CANCELED; |
246 | return false; |
247 | } |
248 | } |
249 | } |
250 | |
251 | // lock the sqlite mutex so sqlite3_errmsg cannot change |
252 | SQLiteMutexAutoLock lockedScope(mDBMutex); |
253 | |
254 | int rc = mConnection->stepStatement(mNativeConnection, aStatement); |
255 | |
256 | // Some errors are not fatal, and we can handle them and continue. |
257 | if (rc == SQLITE_BUSY5) { |
258 | ::sqlite3_reset(aStatement); |
259 | busyRetry = true; |
260 | continue; |
261 | } |
262 | |
263 | aData.MaybeRecordQueryStatus(rc); |
264 | |
265 | // Stop if we have no more results. |
266 | if (rc == SQLITE_DONE101) { |
267 | return false; |
268 | } |
269 | |
270 | // If we got results, we can return now. |
271 | if (rc == SQLITE_ROW100) { |
272 | return true; |
273 | } |
274 | |
275 | if (rc == SQLITE_INTERRUPT9) { |
276 | mState = CANCELED; |
277 | return false; |
278 | } |
279 | |
280 | // Set an error state. |
281 | mState = ERROR; |
282 | |
283 | // Construct the error message before giving up the mutex (which we cannot |
284 | // hold during the call to notifyError). |
285 | nsCOMPtr<mozIStorageError> errorObj( |
286 | new Error(rc, ::sqlite3_errmsg(mNativeConnection))); |
287 | // We cannot hold the DB mutex while calling notifyError. |
288 | SQLiteMutexAutoUnlock unlockedScope(mDBMutex); |
289 | (void)notifyError(errorObj); |
290 | |
291 | // Finally, indicate that we should stop processing. |
292 | return false; |
293 | } |
294 | } |
295 | |
296 | nsresult AsyncExecuteStatements::buildAndNotifyResults( |
297 | sqlite3_stmt* aStatement) { |
298 | NS_ASSERTION(mCallback, "Trying to dispatch results without a callback!")do { if (!(mCallback)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Trying to dispatch results without a callback!" , "mCallback", "/var/lib/jenkins/workspace/firefox-scan-build/storage/mozStorageAsyncStatementExecution.cpp" , 298); MOZ_PretendNoReturn(); } } while (0); |
299 | mMutex.AssertNotCurrentThreadOwns(); |
300 | |
301 | // Build result object if we need it. |
302 | if (!mResultSet) mResultSet = new ResultSet(); |
303 | NS_ENSURE_TRUE(mResultSet, NS_ERROR_OUT_OF_MEMORY)do { if ((__builtin_expect(!!(!(mResultSet)), 0))) { NS_DebugBreak (NS_DEBUG_WARNING, "NS_ENSURE_TRUE(" "mResultSet" ") failed", nullptr, "/var/lib/jenkins/workspace/firefox-scan-build/storage/mozStorageAsyncStatementExecution.cpp" , 303); return NS_ERROR_OUT_OF_MEMORY; } } while (false); |
304 | |
305 | RefPtr<Row> row(new Row()); |
306 | NS_ENSURE_TRUE(row, NS_ERROR_OUT_OF_MEMORY)do { if ((__builtin_expect(!!(!(row)), 0))) { NS_DebugBreak(NS_DEBUG_WARNING , "NS_ENSURE_TRUE(" "row" ") failed", nullptr, "/var/lib/jenkins/workspace/firefox-scan-build/storage/mozStorageAsyncStatementExecution.cpp" , 306); return NS_ERROR_OUT_OF_MEMORY; } } while (false); |
307 | |
308 | nsresult rv = row->initialize(aStatement); |
309 | NS_ENSURE_SUCCESS(rv, rv)do { nsresult __rv = rv; if (((bool)(__builtin_expect(!!(NS_FAILED_impl (__rv)), 0)))) { const char* name = mozilla::GetStaticErrorName (__rv); mozilla::SmprintfPointer msg = mozilla::Smprintf( "NS_ENSURE_SUCCESS(%s, %s) failed with " "result 0x%" "X" "%s%s%s", "rv", "rv", static_cast<uint32_t >(__rv), name ? " (" : "", name ? name : "", name ? ")" : "" ); NS_DebugBreak(NS_DEBUG_WARNING, msg.get(), nullptr, "/var/lib/jenkins/workspace/firefox-scan-build/storage/mozStorageAsyncStatementExecution.cpp" , 309); return rv; } } while (false); |
310 | |
311 | rv = mResultSet->add(row); |
312 | NS_ENSURE_SUCCESS(rv, rv)do { nsresult __rv = rv; if (((bool)(__builtin_expect(!!(NS_FAILED_impl (__rv)), 0)))) { const char* name = mozilla::GetStaticErrorName (__rv); mozilla::SmprintfPointer msg = mozilla::Smprintf( "NS_ENSURE_SUCCESS(%s, %s) failed with " "result 0x%" "X" "%s%s%s", "rv", "rv", static_cast<uint32_t >(__rv), name ? " (" : "", name ? name : "", name ? ")" : "" ); NS_DebugBreak(NS_DEBUG_WARNING, msg.get(), nullptr, "/var/lib/jenkins/workspace/firefox-scan-build/storage/mozStorageAsyncStatementExecution.cpp" , 312); return rv; } } while (false); |
313 | |
314 | // If we have hit our maximum number of allowed results, or if we have hit |
315 | // the maximum amount of time we want to wait for results, notify the |
316 | // calling thread about it. |
317 | TimeStamp now = TimeStamp::Now(); |
318 | TimeDuration delta = now - mIntervalStart; |
319 | if (mResultSet->rows() >= MAX_ROWS_PER_RESULT15 || delta > mMaxWait) { |
320 | // Notify the caller |
321 | rv = notifyResults(); |
322 | if (NS_FAILED(rv)((bool)(__builtin_expect(!!(NS_FAILED_impl(rv)), 0)))) return NS_OK; // we'll try again with the next result |
323 | |
324 | // Reset our start time |
325 | mIntervalStart = now; |
326 | } |
327 | |
328 | return NS_OK; |
329 | } |
330 | |
331 | nsresult AsyncExecuteStatements::notifyComplete() { |
332 | mMutex.AssertNotCurrentThreadOwns(); |
333 | NS_ASSERTION(mState != PENDING,do { if (!(mState != PENDING)) { NS_DebugBreak(NS_DEBUG_ASSERTION , "Still in a pending state when calling Complete!", "mState != PENDING" , "/var/lib/jenkins/workspace/firefox-scan-build/storage/mozStorageAsyncStatementExecution.cpp" , 334); MOZ_PretendNoReturn(); } } while (0) |
334 | "Still in a pending state when calling Complete!")do { if (!(mState != PENDING)) { NS_DebugBreak(NS_DEBUG_ASSERTION , "Still in a pending state when calling Complete!", "mState != PENDING" , "/var/lib/jenkins/workspace/firefox-scan-build/storage/mozStorageAsyncStatementExecution.cpp" , 334); MOZ_PretendNoReturn(); } } while (0); |
335 | |
336 | // Reset our statements before we try to commit or rollback. If we are |
337 | // canceling and have statements that think they have pending work, the |
338 | // rollback will fail. |
339 | for (uint32_t i = 0; i < mStatements.Length(); i++) mStatements[i].reset(); |
340 | |
341 | // Release references to the statement data as soon as possible. If this |
342 | // is the last reference, statements will be finalized immediately on the |
343 | // async thread, hence avoiding several bounces between threads and possible |
344 | // race conditions with AsyncClose(). |
345 | mStatements.Clear(); |
346 | |
347 | // Handle our transaction, if we have one |
348 | if (mHasTransaction) { |
349 | SQLiteMutexAutoLock lockedScope(mDBMutex); |
350 | if (mState == COMPLETED) { |
351 | nsresult rv = mConnection->commitTransactionInternal(lockedScope, |
352 | mNativeConnection); |
353 | if (NS_FAILED(rv)((bool)(__builtin_expect(!!(NS_FAILED_impl(rv)), 0)))) { |
354 | mState = ERROR; |
355 | // We cannot hold the DB mutex while calling notifyError. |
356 | SQLiteMutexAutoUnlock unlockedScope(mDBMutex); |
357 | (void)notifyError(mozIStorageError::ERROR, |
358 | "Transaction failed to commit"); |
359 | } |
360 | } else { |
361 | DebugOnly<nsresult> rv = mConnection->rollbackTransactionInternal( |
362 | lockedScope, mNativeConnection); |
363 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Transaction failed to rollback")do { if (!(((bool)(__builtin_expect(!!(!NS_FAILED_impl(rv)), 1 ))))) { NS_DebugBreak(NS_DEBUG_WARNING, "Transaction failed to rollback" , "NS_SUCCEEDED(rv)", "/var/lib/jenkins/workspace/firefox-scan-build/storage/mozStorageAsyncStatementExecution.cpp" , 363); } } while (false); |
364 | } |
365 | mHasTransaction = false; |
366 | } |
367 | |
368 | // This will take ownership of mCallback and make sure its destruction will |
369 | // happen on the owner thread. |
370 | Unused << mCallingThread->Dispatch( |
371 | NewRunnableMethod("AsyncExecuteStatements::notifyCompleteOnCallingThread", |
372 | this, |
373 | &AsyncExecuteStatements::notifyCompleteOnCallingThread), |
374 | NS_DISPATCH_NORMALnsIEventTarget::DISPATCH_NORMAL); |
375 | |
376 | return NS_OK; |
377 | } |
378 | |
379 | nsresult AsyncExecuteStatements::notifyCompleteOnCallingThread() { |
380 | MOZ_ASSERT(mCallingThread->IsOnCurrentThread())do { static_assert( mozilla::detail::AssertionConditionType< decltype(mCallingThread->IsOnCurrentThread())>::isValid , "invalid assertion condition"); if ((__builtin_expect(!!(!( !!(mCallingThread->IsOnCurrentThread()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("mCallingThread->IsOnCurrentThread()" , "/var/lib/jenkins/workspace/firefox-scan-build/storage/mozStorageAsyncStatementExecution.cpp" , 380); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mCallingThread->IsOnCurrentThread()" ")"); do { *((volatile int*)__null) = 380; __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
381 | // Take ownership of mCallback and responsibility for freeing it when we |
382 | // release it. Any notifyResultsOnCallingThread and |
383 | // notifyErrorOnCallingThread calls on the stack spinning the event loop have |
384 | // guaranteed their safety by creating their own strong reference before |
385 | // invoking the callback. |
386 | nsCOMPtr<mozIStorageStatementCallback> callback = std::move(mCallback); |
387 | if (callback) { |
388 | Unused << callback->HandleCompletion(mState); |
389 | } |
390 | return NS_OK; |
391 | } |
392 | |
393 | nsresult AsyncExecuteStatements::notifyError(int32_t aErrorCode, |
394 | const char* aMessage) { |
395 | mMutex.AssertNotCurrentThreadOwns(); |
396 | mDBMutex.assertNotCurrentThreadOwns(); |
397 | |
398 | if (!mCallback) return NS_OK; |
399 | |
400 | nsCOMPtr<mozIStorageError> errorObj(new Error(aErrorCode, aMessage)); |
401 | NS_ENSURE_TRUE(errorObj, NS_ERROR_OUT_OF_MEMORY)do { if ((__builtin_expect(!!(!(errorObj)), 0))) { NS_DebugBreak (NS_DEBUG_WARNING, "NS_ENSURE_TRUE(" "errorObj" ") failed", nullptr , "/var/lib/jenkins/workspace/firefox-scan-build/storage/mozStorageAsyncStatementExecution.cpp" , 401); return NS_ERROR_OUT_OF_MEMORY; } } while (false); |
402 | |
403 | return notifyError(errorObj); |
404 | } |
405 | |
406 | nsresult AsyncExecuteStatements::notifyError(mozIStorageError* aError) { |
407 | mMutex.AssertNotCurrentThreadOwns(); |
408 | mDBMutex.assertNotCurrentThreadOwns(); |
409 | |
410 | if (!mCallback) return NS_OK; |
411 | |
412 | Unused << mCallingThread->Dispatch( |
413 | NewRunnableMethod<nsCOMPtr<mozIStorageError>>( |
414 | "AsyncExecuteStatements::notifyErrorOnCallingThread", this, |
415 | &AsyncExecuteStatements::notifyErrorOnCallingThread, aError), |
416 | NS_DISPATCH_NORMALnsIEventTarget::DISPATCH_NORMAL); |
417 | |
418 | return NS_OK; |
419 | } |
420 | |
421 | nsresult AsyncExecuteStatements::notifyErrorOnCallingThread( |
422 | mozIStorageError* aError) { |
423 | MOZ_ASSERT(mCallingThread->IsOnCurrentThread())do { static_assert( mozilla::detail::AssertionConditionType< decltype(mCallingThread->IsOnCurrentThread())>::isValid , "invalid assertion condition"); if ((__builtin_expect(!!(!( !!(mCallingThread->IsOnCurrentThread()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("mCallingThread->IsOnCurrentThread()" , "/var/lib/jenkins/workspace/firefox-scan-build/storage/mozStorageAsyncStatementExecution.cpp" , 423); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mCallingThread->IsOnCurrentThread()" ")"); do { *((volatile int*)__null) = 423; __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
424 | // Acquire our own strong reference so that if the callback spins a nested |
425 | // event loop and notifyCompleteOnCallingThread is executed, forgetting |
426 | // mCallback, we still have a valid/strong reference that won't be freed until |
427 | // we exit. |
428 | nsCOMPtr<mozIStorageStatementCallback> callback = mCallback; |
429 | if (shouldNotify() && callback) { |
430 | Unused << callback->HandleError(aError); |
431 | } |
432 | return NS_OK; |
433 | } |
434 | |
435 | nsresult AsyncExecuteStatements::notifyResults() { |
436 | mMutex.AssertNotCurrentThreadOwns(); |
437 | MOZ_ASSERT(mCallback, "notifyResults called without a callback!")do { static_assert( mozilla::detail::AssertionConditionType< decltype(mCallback)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(mCallback))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("mCallback" " (" "notifyResults called without a callback!" ")", "/var/lib/jenkins/workspace/firefox-scan-build/storage/mozStorageAsyncStatementExecution.cpp" , 437); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mCallback" ") (" "notifyResults called without a callback!" ")"); do { *((volatile int*)__null) = 437; __attribute__((nomerge)) ::abort(); } while (false); } } while (false); |
438 | |
439 | // This takes ownership of mResultSet, a new one will be generated in |
440 | // buildAndNotifyResults() when further results will arrive. |
441 | Unused << mCallingThread->Dispatch( |
442 | NewRunnableMethod<RefPtr<ResultSet>>( |
443 | "AsyncExecuteStatements::notifyResultsOnCallingThread", this, |
444 | &AsyncExecuteStatements::notifyResultsOnCallingThread, |
445 | mResultSet.forget()), |
446 | NS_DISPATCH_NORMALnsIEventTarget::DISPATCH_NORMAL); |
447 | |
448 | return NS_OK; |
449 | } |
450 | |
451 | nsresult AsyncExecuteStatements::notifyResultsOnCallingThread( |
452 | ResultSet* aResultSet) { |
453 | MOZ_ASSERT(mCallingThread->IsOnCurrentThread())do { static_assert( mozilla::detail::AssertionConditionType< decltype(mCallingThread->IsOnCurrentThread())>::isValid , "invalid assertion condition"); if ((__builtin_expect(!!(!( !!(mCallingThread->IsOnCurrentThread()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("mCallingThread->IsOnCurrentThread()" , "/var/lib/jenkins/workspace/firefox-scan-build/storage/mozStorageAsyncStatementExecution.cpp" , 453); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mCallingThread->IsOnCurrentThread()" ")"); do { *((volatile int*)__null) = 453; __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
454 | // Acquire our own strong reference so that if the callback spins a nested |
455 | // event loop and notifyCompleteOnCallingThread is executed, forgetting |
456 | // mCallback, we still have a valid/strong reference that won't be freed until |
457 | // we exit. |
458 | nsCOMPtr<mozIStorageStatementCallback> callback = mCallback; |
459 | if (shouldNotify() && callback) { |
460 | Unused << callback->HandleResult(aResultSet); |
461 | } |
462 | return NS_OK; |
463 | } |
464 | |
465 | NS_IMPL_ISUPPORTS_INHERITED(AsyncExecuteStatements, Runnable,nsresult AsyncExecuteStatements::QueryInterface(const nsIID& aIID, void** aInstancePtr) { do { if (!(aInstancePtr)) { NS_DebugBreak (NS_DEBUG_ASSERTION, "QueryInterface requires a non-NULL destination!" , "aInstancePtr", "/var/lib/jenkins/workspace/firefox-scan-build/storage/mozStorageAsyncStatementExecution.cpp" , 466); MOZ_PretendNoReturn(); } } while (0); nsresult rv = NS_ERROR_FAILURE ; static_assert(1 > 0, "Need more arguments to NS_INTERFACE_TABLE_INHERITED" ); static const QITableEntry table[] = { {&mozilla::detail ::kImplementedIID<AsyncExecuteStatements, mozIStoragePendingStatement >, int32_t( reinterpret_cast<char*>(static_cast<mozIStoragePendingStatement *>((AsyncExecuteStatements*)0x1000)) - reinterpret_cast< char*>((AsyncExecuteStatements*)0x1000))}, { nullptr, 0 } } ; static_assert((sizeof(table) / sizeof(table[0])) > 1, "need at least 1 interface" ); rv = NS_TableDrivenQI(static_cast<void*>(this), aIID , aInstancePtr, table); if (((bool)(__builtin_expect(!!(!NS_FAILED_impl (rv)), 1)))) return rv; return Runnable::QueryInterface(aIID, aInstancePtr); } MozExternalRefCountType AsyncExecuteStatements ::AddRef(void) { static_assert(!std::is_destructible_v<AsyncExecuteStatements >, "Reference-counted class " "AsyncExecuteStatements" " should not have a public destructor. " "Make this class's destructor non-public"); nsrefcnt r = Runnable ::AddRef(); if constexpr (::mozilla::detail::ShouldLogInheritedRefcnt <AsyncExecuteStatements>) { NS_LogAddRef((this), (r), ( "AsyncExecuteStatements"), (uint32_t)(sizeof(*this))); } return r; } MozExternalRefCountType AsyncExecuteStatements::Release (void) { nsrefcnt r = Runnable::Release(); if constexpr (::mozilla ::detail::ShouldLogInheritedRefcnt<AsyncExecuteStatements> ) { NS_LogRelease((this), (r), ("AsyncExecuteStatements")); } return r; } |
466 | mozIStoragePendingStatement)nsresult AsyncExecuteStatements::QueryInterface(const nsIID& aIID, void** aInstancePtr) { do { if (!(aInstancePtr)) { NS_DebugBreak (NS_DEBUG_ASSERTION, "QueryInterface requires a non-NULL destination!" , "aInstancePtr", "/var/lib/jenkins/workspace/firefox-scan-build/storage/mozStorageAsyncStatementExecution.cpp" , 466); MOZ_PretendNoReturn(); } } while (0); nsresult rv = NS_ERROR_FAILURE ; static_assert(1 > 0, "Need more arguments to NS_INTERFACE_TABLE_INHERITED" ); static const QITableEntry table[] = { {&mozilla::detail ::kImplementedIID<AsyncExecuteStatements, mozIStoragePendingStatement >, int32_t( reinterpret_cast<char*>(static_cast<mozIStoragePendingStatement *>((AsyncExecuteStatements*)0x1000)) - reinterpret_cast< char*>((AsyncExecuteStatements*)0x1000))}, { nullptr, 0 } } ; static_assert((sizeof(table) / sizeof(table[0])) > 1, "need at least 1 interface" ); rv = NS_TableDrivenQI(static_cast<void*>(this), aIID , aInstancePtr, table); if (((bool)(__builtin_expect(!!(!NS_FAILED_impl (rv)), 1)))) return rv; return Runnable::QueryInterface(aIID, aInstancePtr); } MozExternalRefCountType AsyncExecuteStatements ::AddRef(void) { static_assert(!std::is_destructible_v<AsyncExecuteStatements >, "Reference-counted class " "AsyncExecuteStatements" " should not have a public destructor. " "Make this class's destructor non-public"); nsrefcnt r = Runnable ::AddRef(); if constexpr (::mozilla::detail::ShouldLogInheritedRefcnt <AsyncExecuteStatements>) { NS_LogAddRef((this), (r), ( "AsyncExecuteStatements"), (uint32_t)(sizeof(*this))); } return r; } MozExternalRefCountType AsyncExecuteStatements::Release (void) { nsrefcnt r = Runnable::Release(); if constexpr (::mozilla ::detail::ShouldLogInheritedRefcnt<AsyncExecuteStatements> ) { NS_LogRelease((this), (r), ("AsyncExecuteStatements")); } return r; } |
467 | |
468 | bool AsyncExecuteStatements::statementsNeedTransaction() { |
469 | // If there is more than one write statement, run in a transaction. |
470 | // Additionally, if we have only one statement but it needs a transaction, due |
471 | // to multiple BindingParams, we will wrap it in one. |
472 | for (uint32_t i = 0, transactionsCount = 0; i < mStatements.Length(); ++i) { |
473 | transactionsCount += mStatements[i].needsTransaction(); |
474 | if (transactionsCount > 1) { |
475 | return true; |
476 | } |
477 | } |
478 | return false; |
479 | } |
480 | |
481 | //////////////////////////////////////////////////////////////////////////////// |
482 | //// mozIStoragePendingStatement |
483 | |
484 | NS_IMETHODIMPnsresult |
485 | AsyncExecuteStatements::Cancel() { |
486 | #ifdef DEBUG1 |
487 | bool onCallingThread = false; |
488 | (void)mCallingThread->IsOnCurrentThread(&onCallingThread); |
489 | NS_ASSERTION(onCallingThread, "Not canceling from the calling thread!")do { if (!(onCallingThread)) { NS_DebugBreak(NS_DEBUG_ASSERTION , "Not canceling from the calling thread!", "onCallingThread" , "/var/lib/jenkins/workspace/firefox-scan-build/storage/mozStorageAsyncStatementExecution.cpp" , 489); MOZ_PretendNoReturn(); } } while (0); |
490 | #endif |
491 | |
492 | // If we have already canceled, we have an error, but always indicate that |
493 | // we are trying to cancel. |
494 | NS_ENSURE_FALSE(mCancelRequested, NS_ERROR_UNEXPECTED)do { if ((__builtin_expect(!!(!(!(mCancelRequested))), 0))) { NS_DebugBreak(NS_DEBUG_WARNING, "NS_ENSURE_TRUE(" "!(mCancelRequested)" ") failed", nullptr, "/var/lib/jenkins/workspace/firefox-scan-build/storage/mozStorageAsyncStatementExecution.cpp" , 494); return NS_ERROR_UNEXPECTED; } } while (false); |
495 | |
496 | { |
497 | MutexAutoLock lockedScope(mMutex); |
498 | |
499 | // We need to indicate that we want to try and cancel now. |
500 | mCancelRequested = true; |
501 | } |
502 | |
503 | return NS_OK; |
504 | } |
505 | |
506 | //////////////////////////////////////////////////////////////////////////////// |
507 | //// nsIRunnable |
508 | |
509 | NS_IMETHODIMPnsresult |
510 | AsyncExecuteStatements::Run() { |
511 | MOZ_ASSERT(mConnection->isConnectionReadyOnThisThread())do { static_assert( mozilla::detail::AssertionConditionType< decltype(mConnection->isConnectionReadyOnThisThread())> ::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(mConnection->isConnectionReadyOnThisThread()))), 0 ))) { do { } while (false); MOZ_ReportAssertionFailure("mConnection->isConnectionReadyOnThisThread()" , "/var/lib/jenkins/workspace/firefox-scan-build/storage/mozStorageAsyncStatementExecution.cpp" , 511); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mConnection->isConnectionReadyOnThisThread()" ")"); do { *((volatile int*)__null) = 511; __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
512 | |
513 | // Do not run if we have been canceled. |
514 | { |
515 | MutexAutoLock lockedScope(mMutex); |
516 | if (mCancelRequested) mState = CANCELED; |
517 | } |
518 | if (mState == CANCELED) return notifyComplete(); |
519 | |
520 | if (statementsNeedTransaction()) { |
521 | SQLiteMutexAutoLock lockedScope(mDBMutex); |
522 | if (!mConnection->transactionInProgress(lockedScope)) { |
523 | if (NS_SUCCEEDED(mConnection->beginTransactionInternal(((bool)(__builtin_expect(!!(!NS_FAILED_impl(mConnection->beginTransactionInternal ( lockedScope, mNativeConnection, mozIStorageConnection::TRANSACTION_IMMEDIATE ))), 1))) |
524 | lockedScope, mNativeConnection,((bool)(__builtin_expect(!!(!NS_FAILED_impl(mConnection->beginTransactionInternal ( lockedScope, mNativeConnection, mozIStorageConnection::TRANSACTION_IMMEDIATE ))), 1))) |
525 | mozIStorageConnection::TRANSACTION_IMMEDIATE))((bool)(__builtin_expect(!!(!NS_FAILED_impl(mConnection->beginTransactionInternal ( lockedScope, mNativeConnection, mozIStorageConnection::TRANSACTION_IMMEDIATE ))), 1)))) { |
526 | mHasTransaction = true; |
527 | } |
528 | #ifdef DEBUG1 |
529 | else { |
530 | NS_WARNING("Unable to create a transaction for async execution.")NS_DebugBreak(NS_DEBUG_WARNING, "Unable to create a transaction for async execution." , nullptr, "/var/lib/jenkins/workspace/firefox-scan-build/storage/mozStorageAsyncStatementExecution.cpp" , 530); |
531 | } |
532 | #endif |
533 | } |
534 | } |
535 | |
536 | // Execute each statement, giving the callback results if it returns any. |
537 | for (uint32_t i = 0; i < mStatements.Length(); i++) { |
538 | bool finished = (i == (mStatements.Length() - 1)); |
539 | |
540 | sqlite3_stmt* stmt; |
541 | { // lock the sqlite mutex so sqlite3_errmsg cannot change |
542 | SQLiteMutexAutoLock lockedScope(mDBMutex); |
543 | |
544 | int rc = mStatements[i].getSqliteStatement(&stmt); |
545 | if (rc != SQLITE_OK0) { |
546 | // Set our error state. |
547 | mState = ERROR; |
548 | |
549 | // Build the error object; can't call notifyError with the lock held |
550 | nsCOMPtr<mozIStorageError> errorObj( |
551 | new Error(rc, ::sqlite3_errmsg(mNativeConnection))); |
552 | { |
553 | // We cannot hold the DB mutex and call notifyError. |
554 | SQLiteMutexAutoUnlock unlockedScope(mDBMutex); |
555 | (void)notifyError(errorObj); |
556 | } |
557 | break; |
558 | } |
559 | } |
560 | |
561 | // If we have parameters to bind, bind them, execute, and process. |
562 | if (mStatements[i].hasParametersToBeBound()) { |
563 | if (!bindExecuteAndProcessStatement(mStatements[i], finished)) break; |
564 | } |
565 | // Otherwise, just execute and process the statement. |
566 | else if (!executeAndProcessStatement(mStatements[i], finished)) { |
567 | break; |
568 | } |
569 | } |
570 | |
571 | // If we still have results that we haven't notified about, take care of |
572 | // them now. |
573 | if (mResultSet) (void)notifyResults(); |
574 | |
575 | // Notify about completion |
576 | return notifyComplete(); |
577 | } |
578 | |
579 | } // namespace storage |
580 | } // namespace mozilla |