• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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 package com.android.server.healthconnect;
18 
19 import android.annotation.NonNull;
20 import android.util.Log;
21 
22 import com.android.internal.annotations.GuardedBy;
23 
24 import java.util.Map;
25 import java.util.Queue;
26 import java.util.concurrent.ConcurrentSkipListMap;
27 import java.util.concurrent.LinkedBlockingQueue;
28 
29 /**
30  * A scheduler class to run the tasks in a Round Robin fashion based on client package names.
31  *
32  * @hide
33  */
34 public final class HealthConnectRoundRobinScheduler {
35     private static final String TAG = "HealthConnectScheduler";
36     private final ConcurrentSkipListMap<Integer, Queue<Runnable>> mTasks =
37             new ConcurrentSkipListMap<>();
38     private final Object mLock = new Object();
39 
40     @GuardedBy("mLock")
41     private boolean mPauseScheduler;
42 
43     @GuardedBy("mLock")
44     private Integer mLastKeyUsed;
45 
resume()46     void resume() {
47         synchronized (mLock) {
48             mPauseScheduler = false;
49         }
50     }
51 
addTask(int uid, Runnable task)52     void addTask(int uid, Runnable task) {
53         synchronized (mLock) {
54             // If the scheduler is currently paused (this can happen if the platform is doing a user
55             // switch), ignore this request. This most likely means that we won't be able to deliver
56             // the result back anyway.
57             if (mPauseScheduler) {
58                 Log.e(TAG, "Unable to schedule task for uid: " + uid);
59                 return;
60             }
61 
62             mTasks.putIfAbsent(uid, new LinkedBlockingQueue<>());
63             mTasks.get(uid).add(task);
64         }
65     }
66 
67     @NonNull
getNextTask()68     Runnable getNextTask() {
69         synchronized (mLock) {
70             if (mLastKeyUsed == null) {
71                 mLastKeyUsed = mTasks.firstKey();
72             }
73 
74             Map.Entry<Integer, Queue<Runnable>> entry = mTasks.higherEntry(mLastKeyUsed);
75             while (entry != null && entry.getValue().isEmpty()) {
76                 mTasks.remove(entry.getKey());
77                 entry = mTasks.higherEntry(entry.getKey());
78             }
79 
80             if (entry == null) {
81                 // Reached the end, no tasks found. Reset to first entry and try again.
82                 entry = mTasks.firstEntry();
83                 while (entry != null && entry.getValue().isEmpty()) {
84                     mTasks.remove(entry.getKey());
85                     entry = mTasks.higherEntry(entry.getKey());
86                 }
87             }
88 
89             if (entry != null) {
90                 mLastKeyUsed = entry.getKey();
91                 return entry.getValue().poll();
92             }
93 
94             throw new InternalError("Task scheduled but none found");
95         }
96     }
97 
killTasksAndPauseScheduler()98     void killTasksAndPauseScheduler() {
99         synchronized (mLock) {
100             mPauseScheduler = true;
101             mTasks.clear();
102         }
103     }
104 }
105