1 /*
2 * Copyright (C) 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 //#define LOG_NDEBUG 0
18 #define LOG_TAG "TranscodingSessionController"
19
20 #define VALIDATE_STATE 1
21
22 #include <android/permission_manager.h>
23 #include <inttypes.h>
24 #include <media/TranscodingSessionController.h>
25 #include <media/TranscodingUidPolicy.h>
26 #include <utils/AndroidThreads.h>
27 #include <utils/Log.h>
28
29 #include <thread>
30 #include <utility>
31
32 namespace android {
33
34 static_assert((SessionIdType)-1 < 0, "SessionIdType should be signed");
35
36 constexpr static uid_t OFFLINE_UID = -1;
37 constexpr static size_t kSessionHistoryMax = 100;
38
39 //static
sessionToString(const SessionKeyType & sessionKey)40 String8 TranscodingSessionController::sessionToString(const SessionKeyType& sessionKey) {
41 return String8::format("{client:%lld, session:%d}", (long long)sessionKey.first,
42 sessionKey.second);
43 }
44
45 //static
sessionStateToString(const Session::State sessionState)46 const char* TranscodingSessionController::sessionStateToString(const Session::State sessionState) {
47 switch (sessionState) {
48 case Session::State::NOT_STARTED:
49 return "NOT_STARTED";
50 case Session::State::RUNNING:
51 return "RUNNING";
52 case Session::State::PAUSED:
53 return "PAUSED";
54 case Session::State::FINISHED:
55 return "FINISHED";
56 case Session::State::CANCELED:
57 return "CANCELED";
58 case Session::State::ERROR:
59 return "ERROR";
60 default:
61 break;
62 }
63 return "(unknown)";
64 }
65
66 ///////////////////////////////////////////////////////////////////////////////
67 struct TranscodingSessionController::Watchdog {
68 Watchdog(TranscodingSessionController* owner, int64_t timeoutUs);
69 ~Watchdog();
70
71 // Starts monitoring the session.
72 void start(const SessionKeyType& key);
73 // Stops monitoring the session.
74 void stop();
75 // Signals that the session is still alive. Must be sent at least every mTimeoutUs.
76 // (Timeout will happen if no ping in mTimeoutUs since the last ping.)
77 void keepAlive();
78
79 private:
80 void threadLoop();
81 void updateTimer_l();
82
83 TranscodingSessionController* mOwner;
84 const int64_t mTimeoutUs;
85 mutable std::mutex mLock;
86 std::condition_variable mCondition GUARDED_BY(mLock);
87 // Whether watchdog is monitoring a session for timeout.
88 bool mActive GUARDED_BY(mLock);
89 // Whether watchdog is aborted and the monitoring thread should exit.
90 bool mAbort GUARDED_BY(mLock);
91 // When watchdog is active, the next timeout time point.
92 std::chrono::steady_clock::time_point mNextTimeoutTime GUARDED_BY(mLock);
93 // When watchdog is active, the session being watched.
94 SessionKeyType mSessionToWatch GUARDED_BY(mLock);
95 std::thread mThread;
96 };
97
Watchdog(TranscodingSessionController * owner,int64_t timeoutUs)98 TranscodingSessionController::Watchdog::Watchdog(TranscodingSessionController* owner,
99 int64_t timeoutUs)
100 : mOwner(owner),
101 mTimeoutUs(timeoutUs),
102 mActive(false),
103 mAbort(false),
104 mThread(&Watchdog::threadLoop, this) {
105 ALOGV("Watchdog CTOR: %p", this);
106 }
107
~Watchdog()108 TranscodingSessionController::Watchdog::~Watchdog() {
109 ALOGV("Watchdog DTOR: %p", this);
110
111 {
112 // Exit the looper thread.
113 std::scoped_lock lock{mLock};
114
115 mAbort = true;
116 mCondition.notify_one();
117 }
118
119 mThread.join();
120 ALOGV("Watchdog DTOR: %p, done.", this);
121 }
122
start(const SessionKeyType & key)123 void TranscodingSessionController::Watchdog::start(const SessionKeyType& key) {
124 std::scoped_lock lock{mLock};
125
126 if (!mActive) {
127 ALOGI("Watchdog start: %s", sessionToString(key).c_str());
128
129 mActive = true;
130 mSessionToWatch = key;
131 updateTimer_l();
132 mCondition.notify_one();
133 }
134 }
135
stop()136 void TranscodingSessionController::Watchdog::stop() {
137 std::scoped_lock lock{mLock};
138
139 if (mActive) {
140 ALOGI("Watchdog stop: %s", sessionToString(mSessionToWatch).c_str());
141
142 mActive = false;
143 mCondition.notify_one();
144 }
145 }
146
keepAlive()147 void TranscodingSessionController::Watchdog::keepAlive() {
148 std::scoped_lock lock{mLock};
149
150 if (mActive) {
151 ALOGI("Watchdog keepAlive: %s", sessionToString(mSessionToWatch).c_str());
152
153 updateTimer_l();
154 mCondition.notify_one();
155 }
156 }
157
158 // updateTimer_l() is only called with lock held.
updateTimer_l()159 void TranscodingSessionController::Watchdog::updateTimer_l() NO_THREAD_SAFETY_ANALYSIS {
160 std::chrono::microseconds timeout(mTimeoutUs);
161 mNextTimeoutTime = std::chrono::steady_clock::now() + timeout;
162 }
163
164 // Unfortunately std::unique_lock is incompatible with -Wthread-safety.
threadLoop()165 void TranscodingSessionController::Watchdog::threadLoop() NO_THREAD_SAFETY_ANALYSIS {
166 androidSetThreadPriority(0 /*tid (0 = current) */, ANDROID_PRIORITY_BACKGROUND);
167 std::unique_lock<std::mutex> lock{mLock};
168
169 while (!mAbort) {
170 if (!mActive) {
171 mCondition.wait(lock);
172 continue;
173 }
174 // Watchdog active, wait till next timeout time.
175 if (mCondition.wait_until(lock, mNextTimeoutTime) == std::cv_status::timeout) {
176 // If timeout happens, report timeout and deactivate watchdog.
177 mActive = false;
178 // Make a copy of session key, as once we unlock, it could be unprotected.
179 SessionKeyType sessionKey = mSessionToWatch;
180
181 ALOGE("Watchdog timeout: %s", sessionToString(sessionKey).c_str());
182
183 lock.unlock();
184 mOwner->onError(sessionKey.first, sessionKey.second,
185 TranscodingErrorCode::kWatchdogTimeout);
186 lock.lock();
187 }
188 }
189 }
190 ///////////////////////////////////////////////////////////////////////////////
191 struct TranscodingSessionController::Pacer {
Pacerandroid::TranscodingSessionController::Pacer192 Pacer(const ControllerConfig& config)
193 : mBurstThresholdMs(config.pacerBurstThresholdMs),
194 mBurstCountQuota(config.pacerBurstCountQuota),
195 mBurstTimeQuotaSec(config.pacerBurstTimeQuotaSeconds) {}
196
197 ~Pacer() = default;
198
199 bool onSessionStarted(uid_t uid, uid_t callingUid);
200 void onSessionCompleted(uid_t uid, std::chrono::microseconds runningTime);
201 void onSessionCancelled(uid_t uid);
202
203 private:
204 // Threshold of time between finish/start below which a back-to-back start is counted.
205 int32_t mBurstThresholdMs;
206 // Maximum allowed back-to-back start count.
207 int32_t mBurstCountQuota;
208 // Maximum allowed back-to-back running time.
209 int32_t mBurstTimeQuotaSec;
210
211 struct UidHistoryEntry {
212 bool sessionActive = false;
213 int32_t burstCount = 0;
214 std::chrono::steady_clock::duration burstDuration{0};
215 std::chrono::steady_clock::time_point lastCompletedTime;
216 };
217 std::map<uid_t, UidHistoryEntry> mUidHistoryMap;
218 std::unordered_set<uid_t> mMtpUids;
219 std::unordered_set<uid_t> mNonMtpUids;
220
221 bool isSubjectToQuota(uid_t uid, uid_t callingUid);
222 };
223
isSubjectToQuota(uid_t uid,uid_t callingUid)224 bool TranscodingSessionController::Pacer::isSubjectToQuota(uid_t uid, uid_t callingUid) {
225 // Submitting with self uid is not limited (which can only happen if it's used as an
226 // app-facing API). MediaProvider usage always submit on behalf of other uids.
227 if (uid == callingUid) {
228 return false;
229 }
230
231 if (mMtpUids.find(uid) != mMtpUids.end()) {
232 return false;
233 }
234
235 if (mNonMtpUids.find(uid) != mNonMtpUids.end()) {
236 return true;
237 }
238
239 // We don't have MTP permission info about this uid yet, check permission and save the result.
240 int32_t result;
241 if (__builtin_available(android __TRANSCODING_MIN_API__, *)) {
242 if (APermissionManager_checkPermission("android.permission.ACCESS_MTP", -1 /*pid*/, uid,
243 &result) == PERMISSION_MANAGER_STATUS_OK &&
244 result == PERMISSION_MANAGER_PERMISSION_GRANTED) {
245 mMtpUids.insert(uid);
246 return false;
247 }
248 }
249
250 mNonMtpUids.insert(uid);
251 return true;
252 }
253
onSessionStarted(uid_t uid,uid_t callingUid)254 bool TranscodingSessionController::Pacer::onSessionStarted(uid_t uid, uid_t callingUid) {
255 if (!isSubjectToQuota(uid, callingUid)) {
256 ALOGI("Pacer::onSessionStarted: uid %d (caling uid: %d): not subject to quota", uid,
257 callingUid);
258 return true;
259 }
260
261 // If uid doesn't exist, only insert the entry and mark session active. Skip quota checking.
262 if (mUidHistoryMap.find(uid) == mUidHistoryMap.end()) {
263 mUidHistoryMap.emplace(uid, UidHistoryEntry{});
264 mUidHistoryMap[uid].sessionActive = true;
265 ALOGV("Pacer::onSessionStarted: uid %d: new", uid);
266 return true;
267 }
268
269 // TODO: if Thermal throttling or resoure lost happened to occurr between this start
270 // and the previous completion, we should deduct the paused time from the elapsed time.
271 // (Individual session's pause time, on the other hand, doesn't need to be deducted
272 // because it doesn't affect the gap between last completion and the start.
273 auto timeSinceLastComplete =
274 std::chrono::steady_clock::now() - mUidHistoryMap[uid].lastCompletedTime;
275 if (mUidHistoryMap[uid].burstCount >= mBurstCountQuota &&
276 mUidHistoryMap[uid].burstDuration >= std::chrono::seconds(mBurstTimeQuotaSec)) {
277 ALOGW("Pacer::onSessionStarted: uid %d: over quota, burst count %d, time %lldms", uid,
278 mUidHistoryMap[uid].burstCount,
279 (long long)mUidHistoryMap[uid].burstDuration.count() / 1000000);
280 return false;
281 }
282
283 // If not over quota, allow the session, and reset as long as this is not too close
284 // to previous completion.
285 if (timeSinceLastComplete > std::chrono::milliseconds(mBurstThresholdMs)) {
286 ALOGV("Pacer::onSessionStarted: uid %d: reset quota", uid);
287 mUidHistoryMap[uid].burstCount = 0;
288 mUidHistoryMap[uid].burstDuration = std::chrono::milliseconds(0);
289 } else {
290 ALOGV("Pacer::onSessionStarted: uid %d: burst count %d, time %lldms", uid,
291 mUidHistoryMap[uid].burstCount,
292 (long long)mUidHistoryMap[uid].burstDuration.count() / 1000000);
293 }
294
295 mUidHistoryMap[uid].sessionActive = true;
296 return true;
297 }
298
onSessionCompleted(uid_t uid,std::chrono::microseconds runningTime)299 void TranscodingSessionController::Pacer::onSessionCompleted(
300 uid_t uid, std::chrono::microseconds runningTime) {
301 // Skip quota update if this uid missed the start. (Could happen if the uid is added via
302 // addClientUid() after the session start.)
303 if (mUidHistoryMap.find(uid) == mUidHistoryMap.end() || !mUidHistoryMap[uid].sessionActive) {
304 ALOGV("Pacer::onSessionCompleted: uid %d: not started", uid);
305 return;
306 }
307 ALOGV("Pacer::onSessionCompleted: uid %d: runningTime %lld", uid, runningTime.count() / 1000);
308 mUidHistoryMap[uid].sessionActive = false;
309 mUidHistoryMap[uid].burstCount++;
310 mUidHistoryMap[uid].burstDuration += runningTime;
311 mUidHistoryMap[uid].lastCompletedTime = std::chrono::steady_clock::now();
312 }
313
onSessionCancelled(uid_t uid)314 void TranscodingSessionController::Pacer::onSessionCancelled(uid_t uid) {
315 if (mUidHistoryMap.find(uid) == mUidHistoryMap.end()) {
316 ALOGV("Pacer::onSessionCancelled: uid %d: not present", uid);
317 return;
318 }
319 // This is only called if a uid is removed from a session (due to it being killed
320 // or the original submitting client was gone but session was kept for offline use).
321 // Since the uid is going to miss the onSessionCompleted(), we can't track this
322 // session, and have to check back at next onSessionStarted().
323 mUidHistoryMap[uid].sessionActive = false;
324 }
325
326 ///////////////////////////////////////////////////////////////////////////////
327
TranscodingSessionController(const TranscoderFactoryType & transcoderFactory,const std::shared_ptr<UidPolicyInterface> & uidPolicy,const std::shared_ptr<ResourcePolicyInterface> & resourcePolicy,const std::shared_ptr<ThermalPolicyInterface> & thermalPolicy,const ControllerConfig * config)328 TranscodingSessionController::TranscodingSessionController(
329 const TranscoderFactoryType& transcoderFactory,
330 const std::shared_ptr<UidPolicyInterface>& uidPolicy,
331 const std::shared_ptr<ResourcePolicyInterface>& resourcePolicy,
332 const std::shared_ptr<ThermalPolicyInterface>& thermalPolicy,
333 const ControllerConfig* config)
334 : mTranscoderFactory(transcoderFactory),
335 mUidPolicy(uidPolicy),
336 mResourcePolicy(resourcePolicy),
337 mThermalPolicy(thermalPolicy),
338 mCurrentSession(nullptr),
339 mResourceLost(false) {
340 // Only push empty offline queue initially. Realtime queues are added when requests come in.
341 mUidSortedList.push_back(OFFLINE_UID);
342 mOfflineUidIterator = mUidSortedList.begin();
343 mSessionQueues.emplace(OFFLINE_UID, SessionQueueType());
344 mUidPackageNames[OFFLINE_UID] = "(offline)";
345 mThermalThrottling = thermalPolicy->getThrottlingStatus();
346 if (config != nullptr) {
347 mConfig = *config;
348 }
349 mPacer.reset(new Pacer(mConfig));
350 ALOGD("@@@ watchdog %lld, burst count %d, burst time %d, burst threshold %d",
351 (long long)mConfig.watchdogTimeoutUs, mConfig.pacerBurstCountQuota,
352 mConfig.pacerBurstTimeQuotaSeconds, mConfig.pacerBurstThresholdMs);
353 }
354
~TranscodingSessionController()355 TranscodingSessionController::~TranscodingSessionController() {}
356
dumpSession_l(const Session & session,String8 & result,bool closedSession)357 void TranscodingSessionController::dumpSession_l(const Session& session, String8& result,
358 bool closedSession) {
359 const size_t SIZE = 256;
360 char buffer[SIZE];
361 const TranscodingRequestParcel& request = session.request;
362 snprintf(buffer, SIZE, " Session: %s, %s, %d%%\n", sessionToString(session.key).c_str(),
363 sessionStateToString(session.getState()), session.lastProgress);
364 result.append(buffer);
365 snprintf(buffer, SIZE, " pkg: %s\n", request.clientPackageName.c_str());
366 result.append(buffer);
367 snprintf(buffer, SIZE, " src: %s\n", request.sourceFilePath.c_str());
368 result.append(buffer);
369 snprintf(buffer, SIZE, " dst: %s\n", request.destinationFilePath.c_str());
370 result.append(buffer);
371
372 if (closedSession) {
373 snprintf(buffer, SIZE,
374 " waiting: %.1fs, running: %.1fs, paused: %.1fs, paused count: %d\n",
375 session.waitingTime.count() / 1000000.0f, session.runningTime.count() / 1000000.0f,
376 session.pausedTime.count() / 1000000.0f, session.pauseCount);
377 result.append(buffer);
378 }
379 }
380
dumpAllSessions(int fd,const Vector<String16> & args __unused)381 void TranscodingSessionController::dumpAllSessions(int fd, const Vector<String16>& args __unused) {
382 String8 result;
383
384 const size_t SIZE = 256;
385 char buffer[SIZE];
386 std::scoped_lock lock{mLock};
387
388 snprintf(buffer, SIZE, "\n========== Dumping live sessions queues =========\n");
389 result.append(buffer);
390 snprintf(buffer, SIZE, " Total num of Sessions: %zu\n", mSessionMap.size());
391 result.append(buffer);
392
393 std::vector<int32_t> uids(mUidSortedList.begin(), mUidSortedList.end());
394
395 for (int32_t i = 0; i < uids.size(); i++) {
396 const uid_t uid = uids[i];
397
398 if (mSessionQueues[uid].empty()) {
399 continue;
400 }
401 snprintf(buffer, SIZE, " uid: %d, pkg: %s\n", uid,
402 mUidPackageNames.count(uid) > 0 ? mUidPackageNames[uid].c_str() : "(unknown)");
403 result.append(buffer);
404 snprintf(buffer, SIZE, " Num of sessions: %zu\n", mSessionQueues[uid].size());
405 result.append(buffer);
406 for (auto& sessionKey : mSessionQueues[uid]) {
407 auto sessionIt = mSessionMap.find(sessionKey);
408 if (sessionIt == mSessionMap.end()) {
409 snprintf(buffer, SIZE, "Failed to look up Session %s \n",
410 sessionToString(sessionKey).c_str());
411 result.append(buffer);
412 continue;
413 }
414 dumpSession_l(sessionIt->second, result);
415 }
416 }
417
418 snprintf(buffer, SIZE, "\n========== Dumping past sessions =========\n");
419 result.append(buffer);
420 for (auto& session : mSessionHistory) {
421 dumpSession_l(session, result, true /*closedSession*/);
422 }
423
424 write(fd, result.string(), result.size());
425 }
426
427 /*
428 * Returns nullptr if there is no session, or we're paused globally (due to resource lost,
429 * thermal throttling, etc.). Otherwise, return the session that should be run next.
430 */
getTopSession_l()431 TranscodingSessionController::Session* TranscodingSessionController::getTopSession_l() {
432 if (mSessionMap.empty()) {
433 return nullptr;
434 }
435
436 // Return nullptr if we're paused globally due to resource lost or thermal throttling.
437 if (((mResourcePolicy != nullptr && mResourceLost) ||
438 (mThermalPolicy != nullptr && mThermalThrottling))) {
439 return nullptr;
440 }
441
442 uid_t topUid = *mUidSortedList.begin();
443 // If the current session is running, and it's in the topUid's queue, let it continue
444 // to run even if it's not the earliest in that uid's queue.
445 // For example, uid(B) is added to a session while it's pending in uid(A)'s queue, then
446 // B is brought to front which caused the session to run, then user switches back to A.
447 if (mCurrentSession != nullptr && mCurrentSession->getState() == Session::RUNNING &&
448 mCurrentSession->allClientUids.count(topUid) > 0) {
449 return mCurrentSession;
450 }
451 SessionKeyType topSessionKey = *mSessionQueues[topUid].begin();
452 return &mSessionMap[topSessionKey];
453 }
454
setSessionState_l(Session * session,Session::State state)455 void TranscodingSessionController::setSessionState_l(Session* session, Session::State state) {
456 bool wasRunning = (session->getState() == Session::RUNNING);
457 session->setState(state);
458 bool isRunning = (session->getState() == Session::RUNNING);
459
460 if (wasRunning == isRunning) {
461 return;
462 }
463
464 // Currently we only have 1 running session, and we always put the previous
465 // session in non-running state before we run the new session, so it's okay
466 // to start/stop the watchdog here. If this assumption changes, we need to
467 // track the number of running sessions and start/stop watchdog based on that.
468 if (isRunning) {
469 mWatchdog->start(session->key);
470 } else {
471 mWatchdog->stop();
472 }
473 }
474
setState(Session::State newState)475 void TranscodingSessionController::Session::setState(Session::State newState) {
476 if (state == newState) {
477 return;
478 }
479 auto nowTime = std::chrono::steady_clock::now();
480 if (state != INVALID) {
481 std::chrono::microseconds elapsedTime =
482 std::chrono::duration_cast<std::chrono::microseconds>(nowTime - stateEnterTime);
483 switch (state) {
484 case PAUSED:
485 pausedTime = pausedTime + elapsedTime;
486 break;
487 case RUNNING:
488 runningTime = runningTime + elapsedTime;
489 break;
490 case NOT_STARTED:
491 waitingTime = waitingTime + elapsedTime;
492 break;
493 default:
494 break;
495 }
496 }
497 if (newState == PAUSED) {
498 pauseCount++;
499 }
500 stateEnterTime = nowTime;
501 state = newState;
502 }
503
updateCurrentSession_l()504 void TranscodingSessionController::updateCurrentSession_l() {
505 Session* curSession = mCurrentSession;
506 Session* topSession = nullptr;
507
508 // Delayed init of transcoder and watchdog.
509 if (mTranscoder == nullptr) {
510 mTranscoder = mTranscoderFactory(shared_from_this());
511 mWatchdog = std::make_shared<Watchdog>(this, mConfig.watchdogTimeoutUs);
512 }
513
514 // If we found a different top session, or the top session's running state is not
515 // correct. Take some actions to ensure it's correct.
516 while ((topSession = getTopSession_l()) != curSession ||
517 (topSession != nullptr && !topSession->isRunning())) {
518 ALOGV("updateCurrentSession_l: topSession is %s, curSession is %s",
519 topSession == nullptr ? "null" : sessionToString(topSession->key).c_str(),
520 curSession == nullptr ? "null" : sessionToString(curSession->key).c_str());
521
522 // If current session is running, pause it first. Note this is needed for either
523 // cases: 1) Top session is changing to another session, or 2) Top session is
524 // changing to null (which means we should be globally paused).
525 if (curSession != nullptr && curSession->getState() == Session::RUNNING) {
526 mTranscoder->pause(curSession->key.first, curSession->key.second);
527 setSessionState_l(curSession, Session::PAUSED);
528 }
529
530 if (topSession == nullptr) {
531 // Nothing more to run (either no session or globally paused).
532 break;
533 }
534
535 // Otherwise, ensure topSession is running.
536 if (topSession->getState() == Session::NOT_STARTED) {
537 // Check if at least one client has quota to start the session.
538 bool keepForClient = false;
539 for (uid_t uid : topSession->allClientUids) {
540 if (mPacer->onSessionStarted(uid, topSession->callingUid)) {
541 keepForClient = true;
542 // DO NOT break here, because book-keeping still needs to happen
543 // for the other uids.
544 }
545 }
546 if (!keepForClient) {
547 // Unfortunately all uids requesting this session are out of quota.
548 // Drop this session and try the next one.
549 {
550 auto clientCallback = mSessionMap[topSession->key].callback.lock();
551 if (clientCallback != nullptr) {
552 clientCallback->onTranscodingFailed(
553 topSession->key.second, TranscodingErrorCode::kDroppedByService);
554 }
555 }
556 removeSession_l(topSession->key, Session::DROPPED_BY_PACER);
557 continue;
558 }
559 mTranscoder->start(topSession->key.first, topSession->key.second, topSession->request,
560 topSession->callingUid, topSession->callback.lock());
561 setSessionState_l(topSession, Session::RUNNING);
562 } else if (topSession->getState() == Session::PAUSED) {
563 mTranscoder->resume(topSession->key.first, topSession->key.second, topSession->request,
564 topSession->callingUid, topSession->callback.lock());
565 setSessionState_l(topSession, Session::RUNNING);
566 }
567 break;
568 }
569 mCurrentSession = topSession;
570 }
571
addUidToSession_l(uid_t clientUid,const SessionKeyType & sessionKey)572 void TranscodingSessionController::addUidToSession_l(uid_t clientUid,
573 const SessionKeyType& sessionKey) {
574 // If it's an offline session, the queue was already added in constructor.
575 // If it's a real-time sessions, check if a queue is already present for the uid,
576 // and add a new queue if needed.
577 if (clientUid != OFFLINE_UID) {
578 if (mSessionQueues.count(clientUid) == 0) {
579 mUidPolicy->registerMonitorUid(clientUid);
580 if (mUidPolicy->isUidOnTop(clientUid)) {
581 mUidSortedList.push_front(clientUid);
582 } else {
583 // Shouldn't be submitting real-time requests from non-top app,
584 // put it in front of the offline queue.
585 mUidSortedList.insert(mOfflineUidIterator, clientUid);
586 }
587 } else if (clientUid != *mUidSortedList.begin()) {
588 if (mUidPolicy->isUidOnTop(clientUid)) {
589 mUidSortedList.remove(clientUid);
590 mUidSortedList.push_front(clientUid);
591 }
592 }
593 }
594 // Append this session to the uid's queue.
595 mSessionQueues[clientUid].push_back(sessionKey);
596 }
597
removeSession_l(const SessionKeyType & sessionKey,Session::State finalState,const std::shared_ptr<std::function<bool (uid_t uid)>> & keepUid)598 void TranscodingSessionController::removeSession_l(
599 const SessionKeyType& sessionKey, Session::State finalState,
600 const std::shared_ptr<std::function<bool(uid_t uid)>>& keepUid) {
601 ALOGV("%s: session %s", __FUNCTION__, sessionToString(sessionKey).c_str());
602
603 if (mSessionMap.count(sessionKey) == 0) {
604 ALOGE("session %s doesn't exist", sessionToString(sessionKey).c_str());
605 return;
606 }
607
608 // Remove session from uid's queue.
609 bool uidQueueRemoved = false;
610 std::unordered_set<uid_t> remainingUids;
611 for (uid_t uid : mSessionMap[sessionKey].allClientUids) {
612 if (keepUid != nullptr) {
613 if ((*keepUid)(uid)) {
614 remainingUids.insert(uid);
615 continue;
616 }
617 // If we have uids to keep, the session is not going to any final
618 // state we can't use onSessionCompleted as the running time will
619 // not be valid. Only notify pacer to stop tracking this session.
620 mPacer->onSessionCancelled(uid);
621 }
622 SessionQueueType& sessionQueue = mSessionQueues[uid];
623 auto it = std::find(sessionQueue.begin(), sessionQueue.end(), sessionKey);
624 if (it == sessionQueue.end()) {
625 ALOGW("couldn't find session %s in queue for uid %d",
626 sessionToString(sessionKey).c_str(), uid);
627 continue;
628 }
629 sessionQueue.erase(it);
630
631 // If this is the last session in a real-time queue, remove this uid's queue.
632 if (uid != OFFLINE_UID && sessionQueue.empty()) {
633 mUidSortedList.remove(uid);
634 mSessionQueues.erase(uid);
635 mUidPolicy->unregisterMonitorUid(uid);
636
637 uidQueueRemoved = true;
638 }
639 }
640
641 if (uidQueueRemoved) {
642 std::unordered_set<uid_t> topUids = mUidPolicy->getTopUids();
643 moveUidsToTop_l(topUids, false /*preserveTopUid*/);
644 }
645
646 if (keepUid != nullptr) {
647 mSessionMap[sessionKey].allClientUids = remainingUids;
648 return;
649 }
650
651 // Clear current session.
652 if (mCurrentSession == &mSessionMap[sessionKey]) {
653 mCurrentSession = nullptr;
654 }
655
656 setSessionState_l(&mSessionMap[sessionKey], finalState);
657
658 // We can use onSessionCompleted() even for CANCELLED, because runningTime is
659 // now updated by setSessionState_l().
660 for (uid_t uid : mSessionMap[sessionKey].allClientUids) {
661 mPacer->onSessionCompleted(uid, mSessionMap[sessionKey].runningTime);
662 }
663
664 mSessionHistory.push_back(mSessionMap[sessionKey]);
665 if (mSessionHistory.size() > kSessionHistoryMax) {
666 mSessionHistory.erase(mSessionHistory.begin());
667 }
668
669 // Remove session from session map.
670 mSessionMap.erase(sessionKey);
671 }
672
673 /**
674 * Moves the set of uids to the front of mUidSortedList (which is used to pick
675 * the next session to run).
676 *
677 * This is called when 1) we received a onTopUidsChanged() callback from UidPolicy,
678 * or 2) we removed the session queue for a uid because it becomes empty.
679 *
680 * In case of 1), if there are multiple uids in the set, and the current front
681 * uid in mUidSortedList is still in the set, we try to keep that uid at front
682 * so that current session run is not interrupted. (This is not a concern for case 2)
683 * because the queue for a uid was just removed entirely.)
684 */
moveUidsToTop_l(const std::unordered_set<uid_t> & uids,bool preserveTopUid)685 void TranscodingSessionController::moveUidsToTop_l(const std::unordered_set<uid_t>& uids,
686 bool preserveTopUid) {
687 // If uid set is empty, nothing to do. Do not change the queue status.
688 if (uids.empty()) {
689 return;
690 }
691
692 // Save the current top uid.
693 uid_t curTopUid = *mUidSortedList.begin();
694 bool pushCurTopToFront = false;
695 int32_t numUidsMoved = 0;
696
697 // Go through the sorted uid list once, and move the ones in top set to front.
698 for (auto it = mUidSortedList.begin(); it != mUidSortedList.end();) {
699 uid_t uid = *it;
700
701 if (uid != OFFLINE_UID && uids.count(uid) > 0) {
702 it = mUidSortedList.erase(it);
703
704 // If this is the top we're preserving, don't push it here, push
705 // it after the for-loop.
706 if (uid == curTopUid && preserveTopUid) {
707 pushCurTopToFront = true;
708 } else {
709 mUidSortedList.push_front(uid);
710 }
711
712 // If we found all uids in the set, break out.
713 if (++numUidsMoved == uids.size()) {
714 break;
715 }
716 } else {
717 ++it;
718 }
719 }
720
721 if (pushCurTopToFront) {
722 mUidSortedList.push_front(curTopUid);
723 }
724 }
725
submit(ClientIdType clientId,SessionIdType sessionId,uid_t callingUid,uid_t clientUid,const TranscodingRequestParcel & request,const std::weak_ptr<ITranscodingClientCallback> & callback)726 bool TranscodingSessionController::submit(
727 ClientIdType clientId, SessionIdType sessionId, uid_t callingUid, uid_t clientUid,
728 const TranscodingRequestParcel& request,
729 const std::weak_ptr<ITranscodingClientCallback>& callback) {
730 SessionKeyType sessionKey = std::make_pair(clientId, sessionId);
731
732 ALOGV("%s: session %s, uid %d, prioirty %d", __FUNCTION__, sessionToString(sessionKey).c_str(),
733 clientUid, (int32_t)request.priority);
734
735 std::scoped_lock lock{mLock};
736
737 if (mSessionMap.count(sessionKey) > 0) {
738 ALOGE("session %s already exists", sessionToString(sessionKey).c_str());
739 return false;
740 }
741
742 // Add the uid package name to the store of package names we already know.
743 if (mUidPackageNames.count(clientUid) == 0) {
744 mUidPackageNames.emplace(clientUid, request.clientPackageName);
745 }
746
747 // TODO(chz): only support offline vs real-time for now. All kUnspecified sessions
748 // go to offline queue.
749 if (request.priority == TranscodingSessionPriority::kUnspecified) {
750 clientUid = OFFLINE_UID;
751 }
752
753 // Add session to session map.
754 mSessionMap[sessionKey].key = sessionKey;
755 mSessionMap[sessionKey].callingUid = callingUid;
756 mSessionMap[sessionKey].allClientUids.insert(clientUid);
757 mSessionMap[sessionKey].request = request;
758 mSessionMap[sessionKey].callback = callback;
759 setSessionState_l(&mSessionMap[sessionKey], Session::NOT_STARTED);
760
761 addUidToSession_l(clientUid, sessionKey);
762
763 updateCurrentSession_l();
764
765 validateState_l();
766 return true;
767 }
768
cancel(ClientIdType clientId,SessionIdType sessionId)769 bool TranscodingSessionController::cancel(ClientIdType clientId, SessionIdType sessionId) {
770 SessionKeyType sessionKey = std::make_pair(clientId, sessionId);
771
772 ALOGV("%s: session %s", __FUNCTION__, sessionToString(sessionKey).c_str());
773
774 std::list<SessionKeyType> sessionsToRemove, sessionsForOffline;
775
776 std::scoped_lock lock{mLock};
777
778 if (sessionId < 0) {
779 for (auto it = mSessionMap.begin(); it != mSessionMap.end(); ++it) {
780 if (it->first.first == clientId) {
781 // If there is offline request, only keep the offline client;
782 // otherwise remove the session.
783 if (it->second.allClientUids.count(OFFLINE_UID) > 0) {
784 sessionsForOffline.push_back(it->first);
785 } else {
786 sessionsToRemove.push_back(it->first);
787 }
788 }
789 }
790 } else {
791 if (mSessionMap.count(sessionKey) == 0) {
792 ALOGE("session %s doesn't exist", sessionToString(sessionKey).c_str());
793 return false;
794 }
795 sessionsToRemove.push_back(sessionKey);
796 }
797
798 for (auto it = sessionsToRemove.begin(); it != sessionsToRemove.end(); ++it) {
799 // If the session has ever been started, stop it now.
800 // Note that stop() is needed even if the session is currently paused. This instructs
801 // the transcoder to discard any states for the session, otherwise the states may
802 // never be discarded.
803 if (mSessionMap[*it].getState() != Session::NOT_STARTED) {
804 mTranscoder->stop(it->first, it->second);
805 }
806
807 // Remove the session.
808 removeSession_l(*it, Session::CANCELED);
809 }
810
811 auto keepUid = std::make_shared<std::function<bool(uid_t)>>(
812 [](uid_t uid) { return uid == OFFLINE_UID; });
813 for (auto it = sessionsForOffline.begin(); it != sessionsForOffline.end(); ++it) {
814 removeSession_l(*it, Session::CANCELED, keepUid);
815 }
816
817 // Start next session.
818 updateCurrentSession_l();
819
820 validateState_l();
821 return true;
822 }
823
addClientUid(ClientIdType clientId,SessionIdType sessionId,uid_t clientUid)824 bool TranscodingSessionController::addClientUid(ClientIdType clientId, SessionIdType sessionId,
825 uid_t clientUid) {
826 SessionKeyType sessionKey = std::make_pair(clientId, sessionId);
827
828 std::scoped_lock lock{mLock};
829
830 if (mSessionMap.count(sessionKey) == 0) {
831 ALOGE("session %s doesn't exist", sessionToString(sessionKey).c_str());
832 return false;
833 }
834
835 if (mSessionMap[sessionKey].allClientUids.count(clientUid) > 0) {
836 ALOGE("session %s already has uid %d", sessionToString(sessionKey).c_str(), clientUid);
837 return false;
838 }
839
840 mSessionMap[sessionKey].allClientUids.insert(clientUid);
841 addUidToSession_l(clientUid, sessionKey);
842
843 updateCurrentSession_l();
844
845 validateState_l();
846 return true;
847 }
848
getClientUids(ClientIdType clientId,SessionIdType sessionId,std::vector<int32_t> * out_clientUids)849 bool TranscodingSessionController::getClientUids(ClientIdType clientId, SessionIdType sessionId,
850 std::vector<int32_t>* out_clientUids) {
851 SessionKeyType sessionKey = std::make_pair(clientId, sessionId);
852
853 std::scoped_lock lock{mLock};
854
855 if (mSessionMap.count(sessionKey) == 0) {
856 ALOGE("session %s doesn't exist", sessionToString(sessionKey).c_str());
857 return false;
858 }
859
860 out_clientUids->clear();
861 for (uid_t uid : mSessionMap[sessionKey].allClientUids) {
862 if (uid != OFFLINE_UID) {
863 out_clientUids->push_back(uid);
864 }
865 }
866 return true;
867 }
868
getSession(ClientIdType clientId,SessionIdType sessionId,TranscodingRequestParcel * request)869 bool TranscodingSessionController::getSession(ClientIdType clientId, SessionIdType sessionId,
870 TranscodingRequestParcel* request) {
871 SessionKeyType sessionKey = std::make_pair(clientId, sessionId);
872
873 std::scoped_lock lock{mLock};
874
875 if (mSessionMap.count(sessionKey) == 0) {
876 ALOGE("session %s doesn't exist", sessionToString(sessionKey).c_str());
877 return false;
878 }
879
880 *(TranscodingRequest*)request = mSessionMap[sessionKey].request;
881 return true;
882 }
883
notifyClient(ClientIdType clientId,SessionIdType sessionId,const char * reason,std::function<void (const SessionKeyType &)> func)884 void TranscodingSessionController::notifyClient(ClientIdType clientId, SessionIdType sessionId,
885 const char* reason,
886 std::function<void(const SessionKeyType&)> func) {
887 SessionKeyType sessionKey = std::make_pair(clientId, sessionId);
888
889 std::scoped_lock lock{mLock};
890
891 if (mSessionMap.count(sessionKey) == 0) {
892 ALOGW("%s: ignoring %s for session %s that doesn't exist", __FUNCTION__, reason,
893 sessionToString(sessionKey).c_str());
894 return;
895 }
896
897 // Only ignore if session was never started. In particular, propagate the status
898 // to client if the session is paused. Transcoder could have posted finish when
899 // we're pausing it, and the finish arrived after we changed current session.
900 if (mSessionMap[sessionKey].getState() == Session::NOT_STARTED) {
901 ALOGW("%s: ignoring %s for session %s that was never started", __FUNCTION__, reason,
902 sessionToString(sessionKey).c_str());
903 return;
904 }
905
906 ALOGV("%s: session %s %s", __FUNCTION__, sessionToString(sessionKey).c_str(), reason);
907 func(sessionKey);
908 }
909
onStarted(ClientIdType clientId,SessionIdType sessionId)910 void TranscodingSessionController::onStarted(ClientIdType clientId, SessionIdType sessionId) {
911 notifyClient(clientId, sessionId, "started", [=](const SessionKeyType& sessionKey) {
912 auto callback = mSessionMap[sessionKey].callback.lock();
913 if (callback != nullptr) {
914 callback->onTranscodingStarted(sessionId);
915 }
916 });
917 }
918
onPaused(ClientIdType clientId,SessionIdType sessionId)919 void TranscodingSessionController::onPaused(ClientIdType clientId, SessionIdType sessionId) {
920 notifyClient(clientId, sessionId, "paused", [=](const SessionKeyType& sessionKey) {
921 auto callback = mSessionMap[sessionKey].callback.lock();
922 if (callback != nullptr) {
923 callback->onTranscodingPaused(sessionId);
924 }
925 });
926 }
927
onResumed(ClientIdType clientId,SessionIdType sessionId)928 void TranscodingSessionController::onResumed(ClientIdType clientId, SessionIdType sessionId) {
929 notifyClient(clientId, sessionId, "resumed", [=](const SessionKeyType& sessionKey) {
930 auto callback = mSessionMap[sessionKey].callback.lock();
931 if (callback != nullptr) {
932 callback->onTranscodingResumed(sessionId);
933 }
934 });
935 }
936
onFinish(ClientIdType clientId,SessionIdType sessionId)937 void TranscodingSessionController::onFinish(ClientIdType clientId, SessionIdType sessionId) {
938 notifyClient(clientId, sessionId, "finish", [=](const SessionKeyType& sessionKey) {
939 {
940 auto clientCallback = mSessionMap[sessionKey].callback.lock();
941 if (clientCallback != nullptr) {
942 clientCallback->onTranscodingFinished(
943 sessionId, TranscodingResultParcel({sessionId, -1 /*actualBitrateBps*/,
944 std::nullopt /*sessionStats*/}));
945 }
946 }
947
948 // Remove the session.
949 removeSession_l(sessionKey, Session::FINISHED);
950
951 // Start next session.
952 updateCurrentSession_l();
953
954 validateState_l();
955 });
956 }
957
onError(ClientIdType clientId,SessionIdType sessionId,TranscodingErrorCode err)958 void TranscodingSessionController::onError(ClientIdType clientId, SessionIdType sessionId,
959 TranscodingErrorCode err) {
960 notifyClient(clientId, sessionId, "error", [=](const SessionKeyType& sessionKey) {
961 if (err == TranscodingErrorCode::kWatchdogTimeout) {
962 // Abandon the transcoder, as its handler thread might be stuck in some call to
963 // MediaTranscoder altogether, and may not be able to handle any new tasks.
964 mTranscoder->stop(clientId, sessionId, true /*abandon*/);
965 // Clear the last ref count before we create new transcoder.
966 mTranscoder = nullptr;
967 mTranscoder = mTranscoderFactory(shared_from_this());
968 }
969
970 {
971 auto clientCallback = mSessionMap[sessionKey].callback.lock();
972 if (clientCallback != nullptr) {
973 clientCallback->onTranscodingFailed(sessionId, err);
974 }
975 }
976
977 // Remove the session.
978 removeSession_l(sessionKey, Session::ERROR);
979
980 // Start next session.
981 updateCurrentSession_l();
982
983 validateState_l();
984 });
985 }
986
onProgressUpdate(ClientIdType clientId,SessionIdType sessionId,int32_t progress)987 void TranscodingSessionController::onProgressUpdate(ClientIdType clientId, SessionIdType sessionId,
988 int32_t progress) {
989 notifyClient(clientId, sessionId, "progress", [=](const SessionKeyType& sessionKey) {
990 auto callback = mSessionMap[sessionKey].callback.lock();
991 if (callback != nullptr) {
992 callback->onProgressUpdate(sessionId, progress);
993 }
994 mSessionMap[sessionKey].lastProgress = progress;
995 });
996 }
997
onHeartBeat(ClientIdType clientId,SessionIdType sessionId)998 void TranscodingSessionController::onHeartBeat(ClientIdType clientId, SessionIdType sessionId) {
999 notifyClient(clientId, sessionId, "heart-beat",
1000 [=](const SessionKeyType& /*sessionKey*/) { mWatchdog->keepAlive(); });
1001 }
1002
onResourceLost(ClientIdType clientId,SessionIdType sessionId)1003 void TranscodingSessionController::onResourceLost(ClientIdType clientId, SessionIdType sessionId) {
1004 ALOGI("%s", __FUNCTION__);
1005
1006 notifyClient(clientId, sessionId, "resource_lost", [=](const SessionKeyType& sessionKey) {
1007 if (mResourceLost) {
1008 return;
1009 }
1010
1011 Session* resourceLostSession = &mSessionMap[sessionKey];
1012 if (resourceLostSession->getState() != Session::RUNNING) {
1013 ALOGW("session %s lost resource but is no longer running",
1014 sessionToString(sessionKey).c_str());
1015 return;
1016 }
1017 // If we receive a resource loss event, the transcoder already paused the transcoding,
1018 // so we don't need to call onPaused() to pause it. However, we still need to notify
1019 // the client and update the session state here.
1020 setSessionState_l(resourceLostSession, Session::PAUSED);
1021 // Notify the client as a paused event.
1022 auto clientCallback = resourceLostSession->callback.lock();
1023 if (clientCallback != nullptr) {
1024 clientCallback->onTranscodingPaused(sessionKey.second);
1025 }
1026 if (mResourcePolicy != nullptr) {
1027 mResourcePolicy->setPidResourceLost(resourceLostSession->request.clientPid);
1028 }
1029 mResourceLost = true;
1030
1031 validateState_l();
1032 });
1033 }
1034
onTopUidsChanged(const std::unordered_set<uid_t> & uids)1035 void TranscodingSessionController::onTopUidsChanged(const std::unordered_set<uid_t>& uids) {
1036 if (uids.empty()) {
1037 ALOGW("%s: ignoring empty uids", __FUNCTION__);
1038 return;
1039 }
1040
1041 std::string uidStr;
1042 for (auto it = uids.begin(); it != uids.end(); it++) {
1043 if (!uidStr.empty()) {
1044 uidStr += ", ";
1045 }
1046 uidStr += std::to_string(*it);
1047 }
1048
1049 ALOGD("%s: topUids: size %zu, uids: %s", __FUNCTION__, uids.size(), uidStr.c_str());
1050
1051 std::scoped_lock lock{mLock};
1052
1053 moveUidsToTop_l(uids, true /*preserveTopUid*/);
1054
1055 updateCurrentSession_l();
1056
1057 validateState_l();
1058 }
1059
onUidGone(uid_t goneUid)1060 void TranscodingSessionController::onUidGone(uid_t goneUid) {
1061 ALOGD("%s: gone uid %u", __FUNCTION__, goneUid);
1062
1063 std::list<SessionKeyType> sessionsToRemove, sessionsForOtherUids;
1064
1065 std::scoped_lock lock{mLock};
1066
1067 for (auto it = mSessionMap.begin(); it != mSessionMap.end(); ++it) {
1068 if (it->second.allClientUids.count(goneUid) > 0) {
1069 // If goneUid is the only uid, remove the session; otherwise, only
1070 // remove the uid from the session.
1071 if (it->second.allClientUids.size() > 1) {
1072 sessionsForOtherUids.push_back(it->first);
1073 } else {
1074 sessionsToRemove.push_back(it->first);
1075 }
1076 }
1077 }
1078
1079 for (auto it = sessionsToRemove.begin(); it != sessionsToRemove.end(); ++it) {
1080 // If the session has ever been started, stop it now.
1081 // Note that stop() is needed even if the session is currently paused. This instructs
1082 // the transcoder to discard any states for the session, otherwise the states may
1083 // never be discarded.
1084 if (mSessionMap[*it].getState() != Session::NOT_STARTED) {
1085 mTranscoder->stop(it->first, it->second);
1086 }
1087
1088 {
1089 auto clientCallback = mSessionMap[*it].callback.lock();
1090 if (clientCallback != nullptr) {
1091 clientCallback->onTranscodingFailed(it->second,
1092 TranscodingErrorCode::kUidGoneCancelled);
1093 }
1094 }
1095
1096 // Remove the session.
1097 removeSession_l(*it, Session::CANCELED);
1098 }
1099
1100 auto keepUid = std::make_shared<std::function<bool(uid_t)>>(
1101 [goneUid](uid_t uid) { return uid != goneUid; });
1102 for (auto it = sessionsForOtherUids.begin(); it != sessionsForOtherUids.end(); ++it) {
1103 removeSession_l(*it, Session::CANCELED, keepUid);
1104 }
1105
1106 // Start next session.
1107 updateCurrentSession_l();
1108
1109 validateState_l();
1110 }
1111
onResourceAvailable()1112 void TranscodingSessionController::onResourceAvailable() {
1113 std::scoped_lock lock{mLock};
1114
1115 if (!mResourceLost) {
1116 return;
1117 }
1118
1119 ALOGI("%s", __FUNCTION__);
1120
1121 mResourceLost = false;
1122 updateCurrentSession_l();
1123
1124 validateState_l();
1125 }
1126
onThrottlingStarted()1127 void TranscodingSessionController::onThrottlingStarted() {
1128 std::scoped_lock lock{mLock};
1129
1130 if (mThermalThrottling) {
1131 return;
1132 }
1133
1134 ALOGI("%s", __FUNCTION__);
1135
1136 mThermalThrottling = true;
1137 updateCurrentSession_l();
1138
1139 validateState_l();
1140 }
1141
onThrottlingStopped()1142 void TranscodingSessionController::onThrottlingStopped() {
1143 std::scoped_lock lock{mLock};
1144
1145 if (!mThermalThrottling) {
1146 return;
1147 }
1148
1149 ALOGI("%s", __FUNCTION__);
1150
1151 mThermalThrottling = false;
1152 updateCurrentSession_l();
1153
1154 validateState_l();
1155 }
1156
validateState_l()1157 void TranscodingSessionController::validateState_l() {
1158 #ifdef VALIDATE_STATE
1159 LOG_ALWAYS_FATAL_IF(mSessionQueues.count(OFFLINE_UID) != 1,
1160 "mSessionQueues offline queue number is not 1");
1161 LOG_ALWAYS_FATAL_IF(*mOfflineUidIterator != OFFLINE_UID,
1162 "mOfflineUidIterator not pointing to offline uid");
1163 LOG_ALWAYS_FATAL_IF(mUidSortedList.size() != mSessionQueues.size(),
1164 "mUidSortedList and mSessionQueues size mismatch, %zu vs %zu",
1165 mUidSortedList.size(), mSessionQueues.size());
1166
1167 int32_t totalSessions = 0;
1168 for (auto uid : mUidSortedList) {
1169 LOG_ALWAYS_FATAL_IF(mSessionQueues.count(uid) != 1,
1170 "mSessionQueues count for uid %d is not 1", uid);
1171 for (auto& sessionKey : mSessionQueues[uid]) {
1172 LOG_ALWAYS_FATAL_IF(mSessionMap.count(sessionKey) != 1,
1173 "mSessions count for session %s is not 1",
1174 sessionToString(sessionKey).c_str());
1175 }
1176
1177 totalSessions += mSessionQueues[uid].size();
1178 }
1179 int32_t totalSessionsAlternative = 0;
1180 for (auto const& s : mSessionMap) {
1181 totalSessionsAlternative += s.second.allClientUids.size();
1182 }
1183 LOG_ALWAYS_FATAL_IF(totalSessions != totalSessionsAlternative,
1184 "session count (including dup) from mSessionQueues doesn't match that from "
1185 "mSessionMap, %d vs %d",
1186 totalSessions, totalSessionsAlternative);
1187 #endif // VALIDATE_STATE
1188 }
1189
1190 } // namespace android
1191