blob: 1b36d4cc418e6049c7feaacc34bcb911eaefac98 [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) {
79 mTranscoder->start(topJob->key.first, topJob->key.second);
80 } else if (topJob->state == Job::PAUSED) {
81 mTranscoder->resume(topJob->key.first, topJob->key.second);
82 }
83 topJob->state = Job::RUNNING;
84 }
85 }
86 mCurrentJob = topJob;
87}
88
89void TranscodingJobScheduler::removeJob_l(const JobKeyType& jobKey) {
90 ALOGV("%s: job %s", __FUNCTION__, jobToString(jobKey).c_str());
91
92 if (mJobMap.count(jobKey) == 0) {
93 ALOGE("job %s doesn't exist", jobToString(jobKey).c_str());
94 return;
95 }
96
Chong Zhang7ae4e2f2020-04-17 15:24:34 -070097 // Remove job from uid's queue.
98 const uid_t uid = mJobMap[jobKey].uid;
99 JobQueueType& jobQueue = mJobQueues[uid];
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700100 auto it = std::find(jobQueue.begin(), jobQueue.end(), jobKey);
101 if (it == jobQueue.end()) {
Chong Zhang7ae4e2f2020-04-17 15:24:34 -0700102 ALOGE("couldn't find job %s in queue for uid %d", jobToString(jobKey).c_str(), uid);
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700103 return;
104 }
105 jobQueue.erase(it);
106
Chong Zhang7ae4e2f2020-04-17 15:24:34 -0700107 // If this is the last job in a real-time queue, remove this uid's queue.
108 if (uid != OFFLINE_UID && jobQueue.empty()) {
109 mUidSortedList.remove(uid);
110 mJobQueues.erase(uid);
Chong Zhangacb33502020-04-20 11:04:48 -0700111 mUidPolicy->unregisterMonitorUid(uid);
112
113 std::unordered_set<uid_t> topUids = mUidPolicy->getTopUids();
114 moveUidsToTop_l(topUids, false /*preserveTopUid*/);
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700115 }
116
117 // Clear current job.
118 if (mCurrentJob == &mJobMap[jobKey]) {
119 mCurrentJob = nullptr;
120 }
121
122 // Remove job from job map.
123 mJobMap.erase(jobKey);
124}
125
Chong Zhangacb33502020-04-20 11:04:48 -0700126/**
127 * Moves the set of uids to the front of mUidSortedList (which is used to pick
128 * the next job to run).
129 *
130 * This is called when 1) we received a onTopUidsChanged() callbcak from UidPolicy,
131 * or 2) we removed the job queue for a uid because it becomes empty.
132 *
133 * In case of 1), if there are multiple uids in the set, and the current front
134 * uid in mUidSortedList is still in the set, we try to keep that uid at front
135 * so that current job run is not interrupted. (This is not a concern for case 2)
136 * because the queue for a uid was just removed entirely.)
137 */
138void TranscodingJobScheduler::moveUidsToTop_l(const std::unordered_set<uid_t>& uids,
139 bool preserveTopUid) {
140 // If uid set is empty, nothing to do. Do not change the queue status.
141 if (uids.empty()) {
142 return;
143 }
144
145 // Save the current top uid.
146 uid_t curTopUid = *mUidSortedList.begin();
147 bool pushCurTopToFront = false;
148 int32_t numUidsMoved = 0;
149
150 // Go through the sorted uid list once, and move the ones in top set to front.
151 for (auto it = mUidSortedList.begin(); it != mUidSortedList.end();) {
152 uid_t uid = *it;
153
154 if (uid != OFFLINE_UID && uids.count(uid) > 0) {
155 it = mUidSortedList.erase(it);
156
157 // If this is the top we're preserving, don't push it here, push
158 // it after the for-loop.
159 if (uid == curTopUid && preserveTopUid) {
160 pushCurTopToFront = true;
161 } else {
162 mUidSortedList.push_front(uid);
163 }
164
165 // If we found all uids in the set, break out.
166 if (++numUidsMoved == uids.size()) {
167 break;
168 }
169 } else {
170 ++it;
171 }
172 }
173
174 if (pushCurTopToFront) {
175 mUidSortedList.push_front(curTopUid);
176 }
177}
178
Chong Zhang3fa408f2020-04-30 11:04:28 -0700179bool TranscodingJobScheduler::submit(ClientIdType clientId, JobIdType jobId, uid_t uid,
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700180 const TranscodingRequestParcel& request,
181 const std::weak_ptr<ITranscodingClientCallback>& callback) {
182 JobKeyType jobKey = std::make_pair(clientId, jobId);
183
Chong Zhang7ae4e2f2020-04-17 15:24:34 -0700184 ALOGV("%s: job %s, uid %d, prioirty %d", __FUNCTION__, jobToString(jobKey).c_str(), uid,
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700185 (int32_t)request.priority);
186
187 std::scoped_lock lock{mLock};
188
189 if (mJobMap.count(jobKey) > 0) {
190 ALOGE("job %s already exists", jobToString(jobKey).c_str());
191 return false;
192 }
193
194 // TODO(chz): only support offline vs real-time for now. All kUnspecified jobs
195 // go to offline queue.
196 if (request.priority == TranscodingJobPriority::kUnspecified) {
Chong Zhang7ae4e2f2020-04-17 15:24:34 -0700197 uid = OFFLINE_UID;
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700198 }
199
200 // Add job to job map.
201 mJobMap[jobKey].key = jobKey;
Chong Zhang7ae4e2f2020-04-17 15:24:34 -0700202 mJobMap[jobKey].uid = uid;
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700203 mJobMap[jobKey].state = Job::NOT_STARTED;
204 mJobMap[jobKey].request = request;
205 mJobMap[jobKey].callback = callback;
206
207 // If it's an offline job, the queue was already added in constructor.
Chong Zhang7ae4e2f2020-04-17 15:24:34 -0700208 // If it's a real-time jobs, check if a queue is already present for the uid,
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700209 // and add a new queue if needed.
Chong Zhang7ae4e2f2020-04-17 15:24:34 -0700210 if (uid != OFFLINE_UID) {
211 if (mJobQueues.count(uid) == 0) {
Chong Zhangacb33502020-04-20 11:04:48 -0700212 mUidPolicy->registerMonitorUid(uid);
Chong Zhang7ae4e2f2020-04-17 15:24:34 -0700213 if (mUidPolicy->isUidOnTop(uid)) {
214 mUidSortedList.push_front(uid);
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700215 } else {
216 // Shouldn't be submitting real-time requests from non-top app,
217 // put it in front of the offline queue.
Chong Zhang7ae4e2f2020-04-17 15:24:34 -0700218 mUidSortedList.insert(mOfflineUidIterator, uid);
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700219 }
Chong Zhang7ae4e2f2020-04-17 15:24:34 -0700220 } else if (uid != *mUidSortedList.begin()) {
221 if (mUidPolicy->isUidOnTop(uid)) {
222 mUidSortedList.remove(uid);
223 mUidSortedList.push_front(uid);
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700224 }
225 }
226 }
Chong Zhang7ae4e2f2020-04-17 15:24:34 -0700227 // Append this job to the uid's queue.
228 mJobQueues[uid].push_back(jobKey);
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700229
230 updateCurrentJob_l();
231
232 validateState_l();
233 return true;
234}
235
Chong Zhang3fa408f2020-04-30 11:04:28 -0700236bool TranscodingJobScheduler::cancel(ClientIdType clientId, JobIdType jobId) {
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700237 JobKeyType jobKey = std::make_pair(clientId, jobId);
238
239 ALOGV("%s: job %s", __FUNCTION__, jobToString(jobKey).c_str());
240
Chong Zhang15c192a2020-05-05 16:24:00 -0700241 std::list<JobKeyType> jobsToRemove;
242
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700243 std::scoped_lock lock{mLock};
244
Chong Zhang15c192a2020-05-05 16:24:00 -0700245 if (jobId < 0) {
246 for (auto it = mJobMap.begin(); it != mJobMap.end(); ++it) {
247 if (it->first.first == clientId && it->second.uid != OFFLINE_UID) {
248 jobsToRemove.push_back(it->first);
249 }
250 }
251 } else {
252 if (mJobMap.count(jobKey) == 0) {
253 ALOGE("job %s doesn't exist", jobToString(jobKey).c_str());
254 return false;
255 }
256 jobsToRemove.push_back(jobKey);
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700257 }
258
Chong Zhang15c192a2020-05-05 16:24:00 -0700259 for (auto it = jobsToRemove.begin(); it != jobsToRemove.end(); ++it) {
260 // If the job is running, pause it first.
261 if (mJobMap[*it].state == Job::RUNNING) {
262 mTranscoder->pause(clientId, jobId);
263 }
264
265 // Remove the job.
266 removeJob_l(*it);
267 }
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700268
269 // Start next job.
270 updateCurrentJob_l();
271
272 validateState_l();
273 return true;
274}
275
Chong Zhang3fa408f2020-04-30 11:04:28 -0700276bool TranscodingJobScheduler::getJob(ClientIdType clientId, JobIdType jobId,
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700277 TranscodingRequestParcel* request) {
278 JobKeyType jobKey = std::make_pair(clientId, jobId);
279
280 std::scoped_lock lock{mLock};
281
282 if (mJobMap.count(jobKey) == 0) {
283 ALOGE("job %s doesn't exist", jobToString(jobKey).c_str());
284 return false;
285 }
286
287 *(TranscodingRequest*)request = mJobMap[jobKey].request;
288 return true;
289}
290
Chong Zhang3fa408f2020-04-30 11:04:28 -0700291void TranscodingJobScheduler::onFinish(ClientIdType clientId, JobIdType jobId) {
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700292 JobKeyType jobKey = std::make_pair(clientId, jobId);
293
294 ALOGV("%s: job %s", __FUNCTION__, jobToString(jobKey).c_str());
295
296 std::scoped_lock lock{mLock};
297
298 if (mJobMap.count(jobKey) == 0) {
Chong Zhangacb33502020-04-20 11:04:48 -0700299 ALOGW("ignoring finish for non-existent job");
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700300 return;
301 }
302
303 // Only ignore if job was never started. In particular, propagate the status
304 // to client if the job is paused. Transcoder could have posted finish when
305 // we're pausing it, and the finish arrived after we changed current job.
306 if (mJobMap[jobKey].state == Job::NOT_STARTED) {
Chong Zhangacb33502020-04-20 11:04:48 -0700307 ALOGW("ignoring finish for job that was never started");
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700308 return;
309 }
310
311 {
312 auto clientCallback = mJobMap[jobKey].callback.lock();
313 if (clientCallback != nullptr) {
314 clientCallback->onTranscodingFinished(jobId, TranscodingResultParcel({jobId, 0}));
315 }
316 }
317
318 // Remove the job.
319 removeJob_l(jobKey);
320
321 // Start next job.
322 updateCurrentJob_l();
323
324 validateState_l();
325}
326
Chong Zhang3fa408f2020-04-30 11:04:28 -0700327void TranscodingJobScheduler::onError(ClientIdType clientId, JobIdType jobId,
328 TranscodingErrorCode err) {
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700329 JobKeyType jobKey = std::make_pair(clientId, jobId);
330
331 ALOGV("%s: job %s, err %d", __FUNCTION__, jobToString(jobKey).c_str(), (int32_t)err);
332
333 std::scoped_lock lock{mLock};
334
335 if (mJobMap.count(jobKey) == 0) {
Chong Zhangacb33502020-04-20 11:04:48 -0700336 ALOGW("ignoring error for non-existent job");
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700337 return;
338 }
339
340 // Only ignore if job was never started. In particular, propagate the status
341 // to client if the job is paused. Transcoder could have posted finish when
342 // we're pausing it, and the finish arrived after we changed current job.
343 if (mJobMap[jobKey].state == Job::NOT_STARTED) {
Chong Zhangacb33502020-04-20 11:04:48 -0700344 ALOGW("ignoring error for job that was never started");
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700345 return;
346 }
347
348 {
349 auto clientCallback = mJobMap[jobKey].callback.lock();
350 if (clientCallback != nullptr) {
351 clientCallback->onTranscodingFailed(jobId, err);
352 }
353 }
354
355 // Remove the job.
356 removeJob_l(jobKey);
357
358 // Start next job.
359 updateCurrentJob_l();
360
361 validateState_l();
362}
363
Chong Zhang3fa408f2020-04-30 11:04:28 -0700364void TranscodingJobScheduler::onProgressUpdate(ClientIdType clientId, JobIdType jobId,
365 int32_t progress) {
Chong Zhangacb33502020-04-20 11:04:48 -0700366 JobKeyType jobKey = std::make_pair(clientId, jobId);
367
368 ALOGV("%s: job %s, progress %d", __FUNCTION__, jobToString(jobKey).c_str(), progress);
369
370 std::scoped_lock lock{mLock};
371
372 if (mJobMap.count(jobKey) == 0) {
373 ALOGW("ignoring progress for non-existent job");
374 return;
375 }
376
377 // Only ignore if job was never started. In particular, propagate the status
378 // to client if the job is paused. Transcoder could have posted finish when
379 // we're pausing it, and the finish arrived after we changed current job.
380 if (mJobMap[jobKey].state == Job::NOT_STARTED) {
381 ALOGW("ignoring progress for job that was never started");
382 return;
383 }
384
385 {
386 auto clientCallback = mJobMap[jobKey].callback.lock();
387 if (clientCallback != nullptr) {
388 clientCallback->onProgressUpdate(jobId, progress);
389 }
390 }
391}
392
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700393void TranscodingJobScheduler::onResourceLost() {
394 ALOGV("%s", __FUNCTION__);
395
396 std::scoped_lock lock{mLock};
397
398 // If we receive a resource loss event, the TranscoderLibrary already paused
399 // the transcoding, so we don't need to call onPaused to notify it to pause.
400 // Only need to update the job state here.
401 if (mCurrentJob != nullptr && mCurrentJob->state == Job::RUNNING) {
402 mCurrentJob->state = Job::PAUSED;
403 }
404 mResourceLost = true;
405
406 validateState_l();
407}
408
Chong Zhangacb33502020-04-20 11:04:48 -0700409void TranscodingJobScheduler::onTopUidsChanged(const std::unordered_set<uid_t>& uids) {
410 if (uids.empty()) {
411 ALOGW("%s: ignoring empty uids", __FUNCTION__);
412 return;
413 }
414
415 std::string uidStr;
416 for (auto it = uids.begin(); it != uids.end(); it++) {
417 if (!uidStr.empty()) {
418 uidStr += ", ";
419 }
420 uidStr += std::to_string(*it);
421 }
422
423 ALOGD("%s: topUids: size %zu, uids: %s", __FUNCTION__, uids.size(), uidStr.c_str());
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700424
425 std::scoped_lock lock{mLock};
426
Chong Zhangacb33502020-04-20 11:04:48 -0700427 moveUidsToTop_l(uids, true /*preserveTopUid*/);
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700428
429 updateCurrentJob_l();
430
431 validateState_l();
432}
433
434void TranscodingJobScheduler::onResourceAvailable() {
435 ALOGV("%s", __FUNCTION__);
436
437 std::scoped_lock lock{mLock};
438
439 mResourceLost = false;
440 updateCurrentJob_l();
441
442 validateState_l();
443}
444
445void TranscodingJobScheduler::validateState_l() {
446#ifdef VALIDATE_STATE
Chong Zhang7ae4e2f2020-04-17 15:24:34 -0700447 LOG_ALWAYS_FATAL_IF(mJobQueues.count(OFFLINE_UID) != 1,
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700448 "mJobQueues offline queue number is not 1");
Chong Zhang7ae4e2f2020-04-17 15:24:34 -0700449 LOG_ALWAYS_FATAL_IF(*mOfflineUidIterator != OFFLINE_UID,
450 "mOfflineUidIterator not pointing to offline uid");
451 LOG_ALWAYS_FATAL_IF(mUidSortedList.size() != mJobQueues.size(),
452 "mUidList and mJobQueues size mismatch");
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700453
454 int32_t totalJobs = 0;
Chong Zhang7ae4e2f2020-04-17 15:24:34 -0700455 for (auto uidIt = mUidSortedList.begin(); uidIt != mUidSortedList.end(); uidIt++) {
456 LOG_ALWAYS_FATAL_IF(mJobQueues.count(*uidIt) != 1, "mJobQueues count for uid %d is not 1",
457 *uidIt);
458 for (auto jobIt = mJobQueues[*uidIt].begin(); jobIt != mJobQueues[*uidIt].end(); jobIt++) {
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700459 LOG_ALWAYS_FATAL_IF(mJobMap.count(*jobIt) != 1, "mJobs count for job %s is not 1",
460 jobToString(*jobIt).c_str());
461 }
462
Chong Zhang7ae4e2f2020-04-17 15:24:34 -0700463 totalJobs += mJobQueues[*uidIt].size();
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700464 }
465 LOG_ALWAYS_FATAL_IF(mJobMap.size() != totalJobs,
Chong Zhang7ae4e2f2020-04-17 15:24:34 -0700466 "mJobs size doesn't match total jobs counted from uid queues");
Chong Zhang6d58e4b2020-03-31 09:41:10 -0700467#endif // VALIDATE_STATE
468}
469
470} // namespace android