blob: 3e4f31963a4f6298feacbf95c23ca919f7f234d0 [file] [log] [blame]
Chong Zhang6d58e4b2020-03-31 09:41:10 -07001/*
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
Chong Zhangacb33502020-04-20 11:04:48 -070017//#define LOG_NDEBUG 0
Chong Zhang6d58e4b2020-03-31 09:41:10 -070018#define LOG_TAG "TranscodingJobScheduler"
19
20#define VALIDATE_STATE 1
21
22#include <inttypes.h>
23#include <media/TranscodingJobScheduler.h>
24#include <utils/Log.h>
25
26#include <utility>
27
28namespace android {
29
Chong Zhang15c192a2020-05-05 16:24:00 -070030static_assert((JobIdType)-1 < 0, "JobIdType should be signed");
31
Chong Zhang7ae4e2f2020-04-17 15:24:34 -070032constexpr static uid_t OFFLINE_UID = -1;
Chong Zhang6d58e4b2020-03-31 09:41:10 -070033
34//static
35String8 TranscodingJobScheduler::jobToString(const JobKeyType& jobKey) {
36 return String8::format("{client:%lld, job:%d}", (long long)jobKey.first, jobKey.second);
37}
38
39TranscodingJobScheduler::TranscodingJobScheduler(
40 const std::shared_ptr<TranscoderInterface>& transcoder,
Chong Zhang7ae4e2f2020-04-17 15:24:34 -070041 const std::shared_ptr<UidPolicyInterface>& uidPolicy)
42 : mTranscoder(transcoder), mUidPolicy(uidPolicy), mCurrentJob(nullptr), mResourceLost(false) {
Chong Zhang6d58e4b2020-03-31 09:41:10 -070043 // Only push empty offline queue initially. Realtime queues are added when requests come in.
Chong Zhang7ae4e2f2020-04-17 15:24:34 -070044 mUidSortedList.push_back(OFFLINE_UID);
45 mOfflineUidIterator = mUidSortedList.begin();
46 mJobQueues.emplace(OFFLINE_UID, JobQueueType());
Chong Zhang6d58e4b2020-03-31 09:41:10 -070047}
48
49TranscodingJobScheduler::~TranscodingJobScheduler() {}
50
51TranscodingJobScheduler::Job* TranscodingJobScheduler::getTopJob_l() {
52 if (mJobMap.empty()) {
53 return nullptr;
54 }
Chong Zhang7ae4e2f2020-04-17 15:24:34 -070055 uid_t topUid = *mUidSortedList.begin();
56 JobKeyType topJobKey = *mJobQueues[topUid].begin();
Chong Zhang6d58e4b2020-03-31 09:41:10 -070057 return &mJobMap[topJobKey];
58}
59
60void TranscodingJobScheduler::updateCurrentJob_l() {
61 Job* topJob = getTopJob_l();
62 Job* curJob = mCurrentJob;
63 ALOGV("updateCurrentJob: topJob is %s, curJob is %s",
64 topJob == nullptr ? "null" : jobToString(topJob->key).c_str(),
65 curJob == nullptr ? "null" : jobToString(curJob->key).c_str());
66
67 // If we found a topJob that should be run, and it's not already running,
68 // take some actions to ensure it's running.
69 if (topJob != nullptr && (topJob != curJob || topJob->state != Job::RUNNING)) {
70 // If another job is currently running, pause it first.
71 if (curJob != nullptr && curJob->state == Job::RUNNING) {
72 mTranscoder->pause(curJob->key.first, curJob->key.second);
73 curJob->state = Job::PAUSED;
74 }
75 // If we are not experiencing resource loss, we can start or resume
76 // the topJob now.
77 if (!mResourceLost) {
78 if (topJob->state == Job::NOT_STARTED) {
Chong Zhang66469272020-06-04 16:51:55 -070079 mTranscoder->start(topJob->key.first, topJob->key.second, topJob->request,
80 topJob->callback.lock());
Chong Zhang6d58e4b2020-03-31 09:41:10 -070081 } else if (topJob->state == Job::PAUSED) {
Chong Zhangb55c5452020-06-26 14:32:12 -070082 mTranscoder->resume(topJob->key.first, topJob->key.second, topJob->request,
83 topJob->callback.lock());
Chong Zhang6d58e4b2020-03-31 09:41:10 -070084 }
85 topJob->state = Job::RUNNING;
86 }
87 }
88 mCurrentJob = topJob;
89}
90
91void TranscodingJobScheduler::removeJob_l(const JobKeyType& jobKey) {
92 ALOGV("%s: job %s", __FUNCTION__, jobToString(jobKey).c_str());
93
94 if (mJobMap.count(jobKey) == 0) {
95 ALOGE("job %s doesn't exist", jobToString(jobKey).c_str());
96 return;
97 }
98
Chong Zhang7ae4e2f2020-04-17 15:24:34 -070099 // Remove job from uid's queue.
100 const uid_t uid = mJobMap[jobKey].uid;
101 JobQueueType& jobQueue = mJobQueues[uid];
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700102 auto it = std::find(jobQueue.begin(), jobQueue.end(), jobKey);
103 if (it == jobQueue.end()) {
Chong Zhang7ae4e2f2020-04-17 15:24:34 -0700104 ALOGE("couldn't find job %s in queue for uid %d", jobToString(jobKey).c_str(), uid);
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700105 return;
106 }
107 jobQueue.erase(it);
108
Chong Zhang7ae4e2f2020-04-17 15:24:34 -0700109 // If this is the last job in a real-time queue, remove this uid's queue.
110 if (uid != OFFLINE_UID && jobQueue.empty()) {
111 mUidSortedList.remove(uid);
112 mJobQueues.erase(uid);
Chong Zhangacb33502020-04-20 11:04:48 -0700113 mUidPolicy->unregisterMonitorUid(uid);
114
115 std::unordered_set<uid_t> topUids = mUidPolicy->getTopUids();
116 moveUidsToTop_l(topUids, false /*preserveTopUid*/);
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700117 }
118
119 // Clear current job.
120 if (mCurrentJob == &mJobMap[jobKey]) {
121 mCurrentJob = nullptr;
122 }
123
124 // Remove job from job map.
125 mJobMap.erase(jobKey);
126}
127
Chong Zhangacb33502020-04-20 11:04:48 -0700128/**
129 * Moves the set of uids to the front of mUidSortedList (which is used to pick
130 * the next job to run).
131 *
132 * This is called when 1) we received a onTopUidsChanged() callbcak from UidPolicy,
133 * or 2) we removed the job queue for a uid because it becomes empty.
134 *
135 * In case of 1), if there are multiple uids in the set, and the current front
136 * uid in mUidSortedList is still in the set, we try to keep that uid at front
137 * so that current job run is not interrupted. (This is not a concern for case 2)
138 * because the queue for a uid was just removed entirely.)
139 */
140void TranscodingJobScheduler::moveUidsToTop_l(const std::unordered_set<uid_t>& uids,
141 bool preserveTopUid) {
142 // If uid set is empty, nothing to do. Do not change the queue status.
143 if (uids.empty()) {
144 return;
145 }
146
147 // Save the current top uid.
148 uid_t curTopUid = *mUidSortedList.begin();
149 bool pushCurTopToFront = false;
150 int32_t numUidsMoved = 0;
151
152 // Go through the sorted uid list once, and move the ones in top set to front.
153 for (auto it = mUidSortedList.begin(); it != mUidSortedList.end();) {
154 uid_t uid = *it;
155
156 if (uid != OFFLINE_UID && uids.count(uid) > 0) {
157 it = mUidSortedList.erase(it);
158
159 // If this is the top we're preserving, don't push it here, push
160 // it after the for-loop.
161 if (uid == curTopUid && preserveTopUid) {
162 pushCurTopToFront = true;
163 } else {
164 mUidSortedList.push_front(uid);
165 }
166
167 // If we found all uids in the set, break out.
168 if (++numUidsMoved == uids.size()) {
169 break;
170 }
171 } else {
172 ++it;
173 }
174 }
175
176 if (pushCurTopToFront) {
177 mUidSortedList.push_front(curTopUid);
178 }
179}
180
Chong Zhang3fa408f2020-04-30 11:04:28 -0700181bool TranscodingJobScheduler::submit(ClientIdType clientId, JobIdType jobId, uid_t uid,
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700182 const TranscodingRequestParcel& request,
183 const std::weak_ptr<ITranscodingClientCallback>& callback) {
184 JobKeyType jobKey = std::make_pair(clientId, jobId);
185
Chong Zhang7ae4e2f2020-04-17 15:24:34 -0700186 ALOGV("%s: job %s, uid %d, prioirty %d", __FUNCTION__, jobToString(jobKey).c_str(), uid,
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700187 (int32_t)request.priority);
188
189 std::scoped_lock lock{mLock};
190
191 if (mJobMap.count(jobKey) > 0) {
192 ALOGE("job %s already exists", jobToString(jobKey).c_str());
193 return false;
194 }
195
196 // TODO(chz): only support offline vs real-time for now. All kUnspecified jobs
197 // go to offline queue.
198 if (request.priority == TranscodingJobPriority::kUnspecified) {
Chong Zhang7ae4e2f2020-04-17 15:24:34 -0700199 uid = OFFLINE_UID;
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700200 }
201
202 // Add job to job map.
203 mJobMap[jobKey].key = jobKey;
Chong Zhang7ae4e2f2020-04-17 15:24:34 -0700204 mJobMap[jobKey].uid = uid;
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700205 mJobMap[jobKey].state = Job::NOT_STARTED;
206 mJobMap[jobKey].request = request;
207 mJobMap[jobKey].callback = callback;
208
209 // If it's an offline job, the queue was already added in constructor.
Chong Zhang7ae4e2f2020-04-17 15:24:34 -0700210 // If it's a real-time jobs, check if a queue is already present for the uid,
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700211 // and add a new queue if needed.
Chong Zhang7ae4e2f2020-04-17 15:24:34 -0700212 if (uid != OFFLINE_UID) {
213 if (mJobQueues.count(uid) == 0) {
Chong Zhangacb33502020-04-20 11:04:48 -0700214 mUidPolicy->registerMonitorUid(uid);
Chong Zhang7ae4e2f2020-04-17 15:24:34 -0700215 if (mUidPolicy->isUidOnTop(uid)) {
216 mUidSortedList.push_front(uid);
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700217 } else {
218 // Shouldn't be submitting real-time requests from non-top app,
219 // put it in front of the offline queue.
Chong Zhang7ae4e2f2020-04-17 15:24:34 -0700220 mUidSortedList.insert(mOfflineUidIterator, uid);
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700221 }
Chong Zhang7ae4e2f2020-04-17 15:24:34 -0700222 } else if (uid != *mUidSortedList.begin()) {
223 if (mUidPolicy->isUidOnTop(uid)) {
224 mUidSortedList.remove(uid);
225 mUidSortedList.push_front(uid);
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700226 }
227 }
228 }
Chong Zhang7ae4e2f2020-04-17 15:24:34 -0700229 // Append this job to the uid's queue.
230 mJobQueues[uid].push_back(jobKey);
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700231
232 updateCurrentJob_l();
233
234 validateState_l();
235 return true;
236}
237
Chong Zhang3fa408f2020-04-30 11:04:28 -0700238bool TranscodingJobScheduler::cancel(ClientIdType clientId, JobIdType jobId) {
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700239 JobKeyType jobKey = std::make_pair(clientId, jobId);
240
241 ALOGV("%s: job %s", __FUNCTION__, jobToString(jobKey).c_str());
242
Chong Zhang15c192a2020-05-05 16:24:00 -0700243 std::list<JobKeyType> jobsToRemove;
244
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700245 std::scoped_lock lock{mLock};
246
Chong Zhang15c192a2020-05-05 16:24:00 -0700247 if (jobId < 0) {
248 for (auto it = mJobMap.begin(); it != mJobMap.end(); ++it) {
249 if (it->first.first == clientId && it->second.uid != OFFLINE_UID) {
250 jobsToRemove.push_back(it->first);
251 }
252 }
253 } else {
254 if (mJobMap.count(jobKey) == 0) {
255 ALOGE("job %s doesn't exist", jobToString(jobKey).c_str());
256 return false;
257 }
258 jobsToRemove.push_back(jobKey);
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700259 }
260
Chong Zhang15c192a2020-05-05 16:24:00 -0700261 for (auto it = jobsToRemove.begin(); it != jobsToRemove.end(); ++it) {
Chong Zhang00feca22020-05-08 15:02:06 -0700262 // If the job has ever been started, stop it now.
263 // Note that stop() is needed even if the job is currently paused. This instructs
264 // the transcoder to discard any states for the job, otherwise the states may
265 // never be discarded.
266 if (mJobMap[*it].state != Job::NOT_STARTED) {
267 mTranscoder->stop(it->first, it->second);
Chong Zhang15c192a2020-05-05 16:24:00 -0700268 }
269
270 // Remove the job.
271 removeJob_l(*it);
272 }
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700273
274 // Start next job.
275 updateCurrentJob_l();
276
277 validateState_l();
278 return true;
279}
280
Chong Zhang3fa408f2020-04-30 11:04:28 -0700281bool TranscodingJobScheduler::getJob(ClientIdType clientId, JobIdType jobId,
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700282 TranscodingRequestParcel* request) {
283 JobKeyType jobKey = std::make_pair(clientId, jobId);
284
285 std::scoped_lock lock{mLock};
286
287 if (mJobMap.count(jobKey) == 0) {
288 ALOGE("job %s doesn't exist", jobToString(jobKey).c_str());
289 return false;
290 }
291
292 *(TranscodingRequest*)request = mJobMap[jobKey].request;
293 return true;
294}
295
Chong Zhangde60f062020-06-11 17:05:10 -0700296void TranscodingJobScheduler::notifyClient(ClientIdType clientId, JobIdType jobId,
297 const char* reason,
298 std::function<void(const JobKeyType&)> func) {
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700299 JobKeyType jobKey = std::make_pair(clientId, jobId);
300
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700301 std::scoped_lock lock{mLock};
302
303 if (mJobMap.count(jobKey) == 0) {
Chong Zhangde60f062020-06-11 17:05:10 -0700304 ALOGW("%s: ignoring %s for job %s that doesn't exist", __FUNCTION__, reason,
305 jobToString(jobKey).c_str());
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700306 return;
307 }
308
309 // Only ignore if job was never started. In particular, propagate the status
310 // to client if the job is paused. Transcoder could have posted finish when
311 // we're pausing it, and the finish arrived after we changed current job.
312 if (mJobMap[jobKey].state == Job::NOT_STARTED) {
Chong Zhangde60f062020-06-11 17:05:10 -0700313 ALOGW("%s: ignoring %s for job %s that was never started", __FUNCTION__, reason,
314 jobToString(jobKey).c_str());
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700315 return;
316 }
317
Chong Zhangde60f062020-06-11 17:05:10 -0700318 ALOGV("%s: job %s %s", __FUNCTION__, jobToString(jobKey).c_str(), reason);
319 func(jobKey);
320}
321
322void TranscodingJobScheduler::onStarted(ClientIdType clientId, JobIdType jobId) {
323 notifyClient(clientId, jobId, "started", [=](const JobKeyType& jobKey) {
324 auto callback = mJobMap[jobKey].callback.lock();
325 if (callback != nullptr) {
326 callback->onTranscodingStarted(jobId);
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700327 }
Chong Zhangde60f062020-06-11 17:05:10 -0700328 });
329}
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700330
Chong Zhangde60f062020-06-11 17:05:10 -0700331void TranscodingJobScheduler::onPaused(ClientIdType clientId, JobIdType jobId) {
332 notifyClient(clientId, jobId, "paused", [=](const JobKeyType& jobKey) {
333 auto callback = mJobMap[jobKey].callback.lock();
334 if (callback != nullptr) {
335 callback->onTranscodingPaused(jobId);
336 }
337 });
338}
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700339
Chong Zhangde60f062020-06-11 17:05:10 -0700340void TranscodingJobScheduler::onResumed(ClientIdType clientId, JobIdType jobId) {
341 notifyClient(clientId, jobId, "resumed", [=](const JobKeyType& jobKey) {
342 auto callback = mJobMap[jobKey].callback.lock();
343 if (callback != nullptr) {
344 callback->onTranscodingResumed(jobId);
345 }
346 });
347}
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700348
Chong Zhangde60f062020-06-11 17:05:10 -0700349void TranscodingJobScheduler::onFinish(ClientIdType clientId, JobIdType jobId) {
350 notifyClient(clientId, jobId, "finish", [=](const JobKeyType& jobKey) {
351 {
352 auto clientCallback = mJobMap[jobKey].callback.lock();
353 if (clientCallback != nullptr) {
Chong Zhang66469272020-06-04 16:51:55 -0700354 clientCallback->onTranscodingFinished(
hkuang0bd73742020-06-24 12:57:09 -0700355 jobId, TranscodingResultParcel({jobId, -1 /*actualBitrateBps*/,
356 std::nullopt /*jobStats*/}));
Chong Zhangde60f062020-06-11 17:05:10 -0700357 }
358 }
359
360 // Remove the job.
361 removeJob_l(jobKey);
362
363 // Start next job.
364 updateCurrentJob_l();
365
366 validateState_l();
367 });
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700368}
369
Chong Zhang3fa408f2020-04-30 11:04:28 -0700370void TranscodingJobScheduler::onError(ClientIdType clientId, JobIdType jobId,
371 TranscodingErrorCode err) {
Chong Zhangde60f062020-06-11 17:05:10 -0700372 notifyClient(clientId, jobId, "error", [=](const JobKeyType& jobKey) {
373 {
374 auto clientCallback = mJobMap[jobKey].callback.lock();
375 if (clientCallback != nullptr) {
376 clientCallback->onTranscodingFailed(jobId, err);
377 }
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700378 }
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700379
Chong Zhangde60f062020-06-11 17:05:10 -0700380 // Remove the job.
381 removeJob_l(jobKey);
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700382
Chong Zhangde60f062020-06-11 17:05:10 -0700383 // Start next job.
384 updateCurrentJob_l();
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700385
Chong Zhangde60f062020-06-11 17:05:10 -0700386 validateState_l();
387 });
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700388}
389
Chong Zhang3fa408f2020-04-30 11:04:28 -0700390void TranscodingJobScheduler::onProgressUpdate(ClientIdType clientId, JobIdType jobId,
391 int32_t progress) {
Chong Zhangde60f062020-06-11 17:05:10 -0700392 notifyClient(clientId, jobId, "progress", [=](const JobKeyType& jobKey) {
393 auto callback = mJobMap[jobKey].callback.lock();
394 if (callback != nullptr) {
395 callback->onProgressUpdate(jobId, progress);
Chong Zhangacb33502020-04-20 11:04:48 -0700396 }
Chong Zhangde60f062020-06-11 17:05:10 -0700397 });
Chong Zhangacb33502020-04-20 11:04:48 -0700398}
399
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700400void TranscodingJobScheduler::onResourceLost() {
401 ALOGV("%s", __FUNCTION__);
402
403 std::scoped_lock lock{mLock};
404
405 // If we receive a resource loss event, the TranscoderLibrary already paused
406 // the transcoding, so we don't need to call onPaused to notify it to pause.
407 // Only need to update the job state here.
408 if (mCurrentJob != nullptr && mCurrentJob->state == Job::RUNNING) {
409 mCurrentJob->state = Job::PAUSED;
410 }
411 mResourceLost = true;
412
413 validateState_l();
414}
415
Chong Zhangacb33502020-04-20 11:04:48 -0700416void TranscodingJobScheduler::onTopUidsChanged(const std::unordered_set<uid_t>& uids) {
417 if (uids.empty()) {
418 ALOGW("%s: ignoring empty uids", __FUNCTION__);
419 return;
420 }
421
422 std::string uidStr;
423 for (auto it = uids.begin(); it != uids.end(); it++) {
424 if (!uidStr.empty()) {
425 uidStr += ", ";
426 }
427 uidStr += std::to_string(*it);
428 }
429
430 ALOGD("%s: topUids: size %zu, uids: %s", __FUNCTION__, uids.size(), uidStr.c_str());
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700431
432 std::scoped_lock lock{mLock};
433
Chong Zhangacb33502020-04-20 11:04:48 -0700434 moveUidsToTop_l(uids, true /*preserveTopUid*/);
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700435
436 updateCurrentJob_l();
437
438 validateState_l();
439}
440
441void TranscodingJobScheduler::onResourceAvailable() {
442 ALOGV("%s", __FUNCTION__);
443
444 std::scoped_lock lock{mLock};
445
446 mResourceLost = false;
447 updateCurrentJob_l();
448
449 validateState_l();
450}
451
452void TranscodingJobScheduler::validateState_l() {
453#ifdef VALIDATE_STATE
Chong Zhang7ae4e2f2020-04-17 15:24:34 -0700454 LOG_ALWAYS_FATAL_IF(mJobQueues.count(OFFLINE_UID) != 1,
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700455 "mJobQueues offline queue number is not 1");
Chong Zhang7ae4e2f2020-04-17 15:24:34 -0700456 LOG_ALWAYS_FATAL_IF(*mOfflineUidIterator != OFFLINE_UID,
457 "mOfflineUidIterator not pointing to offline uid");
458 LOG_ALWAYS_FATAL_IF(mUidSortedList.size() != mJobQueues.size(),
459 "mUidList and mJobQueues size mismatch");
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700460
461 int32_t totalJobs = 0;
Chong Zhang7ae4e2f2020-04-17 15:24:34 -0700462 for (auto uidIt = mUidSortedList.begin(); uidIt != mUidSortedList.end(); uidIt++) {
463 LOG_ALWAYS_FATAL_IF(mJobQueues.count(*uidIt) != 1, "mJobQueues count for uid %d is not 1",
464 *uidIt);
465 for (auto jobIt = mJobQueues[*uidIt].begin(); jobIt != mJobQueues[*uidIt].end(); jobIt++) {
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700466 LOG_ALWAYS_FATAL_IF(mJobMap.count(*jobIt) != 1, "mJobs count for job %s is not 1",
467 jobToString(*jobIt).c_str());
468 }
469
Chong Zhang7ae4e2f2020-04-17 15:24:34 -0700470 totalJobs += mJobQueues[*uidIt].size();
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700471 }
472 LOG_ALWAYS_FATAL_IF(mJobMap.size() != totalJobs,
Chong Zhang7ae4e2f2020-04-17 15:24:34 -0700473 "mJobs size doesn't match total jobs counted from uid queues");
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700474#endif // VALIDATE_STATE
475}
476
477} // namespace android