1 /* <lambda>null2 * Copyright 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 androidx.hardware 18 19 import android.util.Log 20 import androidx.graphics.utils.HandlerThreadExecutor 21 import java.io.File 22 import java.lang.NumberFormatException 23 import java.util.concurrent.TimeUnit 24 import java.util.concurrent.atomic.AtomicBoolean 25 import kotlin.collections.removeLast as removeLastKt 26 27 /** 28 * Class to monitor open file descriptors and clean up those with fences that have already signalled 29 */ 30 internal class FileDescriptorMonitor( 31 executor: HandlerThreadExecutor? = null, 32 private val scheduleMillis: Long = MONITOR_DELAY, 33 manageExecutor: Boolean = executor != null 34 ) { 35 36 private val mIsMonitoring = AtomicBoolean(false) 37 private val mProcFd = File("/proc/self/fd/") 38 private val mCleanupCompleteCallbacks = ArrayList<() -> Unit>() 39 40 private val mIsManagingHandlerThread = AtomicBoolean(false) 41 private var mExecutor: HandlerThreadExecutor 42 43 private data class FdSignalPair(val fd: Int, val signalTime: Long) 44 45 private val pendingFileDescriptors = ArrayList<FdSignalPair>() 46 47 init { 48 mExecutor = executor ?: HandlerThreadExecutor("fdcleanup") 49 mIsManagingHandlerThread.set(manageExecutor) 50 } 51 52 private fun closePendingFileDescriptors() { 53 pendingFileDescriptors.sortByDescending { fdSignalTimePair -> fdSignalTimePair.signalTime } 54 while (pendingFileDescriptors.size > MAX_FD) { 55 val fdSignalPair = pendingFileDescriptors.removeLastKt() 56 try { 57 val fd = fdSignalPair.fd 58 // Re-query the signal time in case the fd was re-used 59 val signalTime = SyncFenceBindings.nGetSignalTime(fd) 60 val diff = signalTime.signalTimeDiffMillis() 61 if (diff > SIGNAL_TIME_DELTA_MILLIS) { 62 SyncFenceBindings.nForceClose(fd) 63 } 64 } catch (_: Throwable) { 65 // Just in case the owner actually does close the fd 66 } 67 } 68 } 69 70 private fun Long.signalTimeDiffMillis(): Long { 71 val now = System.nanoTime() 72 val signalled = 73 this != SyncFenceCompat.SIGNAL_TIME_INVALID && 74 this != SyncFenceCompat.SIGNAL_TIME_PENDING 75 return if (signalled && now > this) { 76 TimeUnit.NANOSECONDS.toMillis(now - this) 77 } else { 78 -1 79 } 80 } 81 82 private val mCleanupRunnable = Runnable { 83 mProcFd.listFiles()?.let { files -> 84 for (file in files) { 85 try { 86 val fd = Integer.parseInt(file.name) 87 val signalTime = SyncFenceBindings.nGetSignalTime(fd) 88 val diff = signalTime.signalTimeDiffMillis() 89 if (diff > SIGNAL_TIME_DELTA_MILLIS) { 90 // Store the signal time as it can potentially change in the middle of 91 // executing the sorting algorithm and can throw exceptions 92 pendingFileDescriptors.add(FdSignalPair(fd, signalTime)) 93 if (pendingFileDescriptors.size > MAX_FD) { 94 closePendingFileDescriptors() 95 } 96 } 97 } catch (formatException: NumberFormatException) { 98 Log.w(TAG, "Unable to parse fd value from name ${file.name}") 99 } 100 } 101 } 102 103 if (mIsMonitoring.get()) { 104 scheduleCleanupTask() 105 } else { 106 teardownExecutorIfNecessary() 107 } 108 invokeCleanupCallbacks() 109 } 110 111 private fun invokeCleanupCallbacks() { 112 synchronized(mCleanupCompleteCallbacks) { 113 for (callback in mCleanupCompleteCallbacks) { 114 callback.invoke() 115 } 116 mCleanupCompleteCallbacks.clear() 117 } 118 } 119 120 private fun scheduleCleanupTask() { 121 if (mExecutor.isRunning) { 122 mExecutor.postDelayed(mCleanupRunnable, scheduleMillis) 123 } 124 } 125 126 fun addCleanupCallback(callback: () -> Unit) { 127 synchronized(mCleanupCompleteCallbacks) { mCleanupCompleteCallbacks.add(callback) } 128 } 129 130 /** 131 * Starts periodic cleaning of file descriptors if it has not already been started previously 132 */ 133 fun startMonitoring() { 134 if (!mIsMonitoring.get()) { 135 initExecutorIfNecessary() 136 scheduleCleanupTask() 137 mIsMonitoring.set(true) 138 } 139 } 140 141 private fun initExecutorIfNecessary() { 142 if (mIsManagingHandlerThread.get() && !mExecutor.isRunning) { 143 mExecutor = HandlerThreadExecutor("fdcleanup") 144 } 145 } 146 147 private fun teardownExecutorIfNecessary() { 148 if (mIsManagingHandlerThread.get() && mExecutor.isRunning) { 149 mExecutor.quit() 150 } 151 } 152 153 /** 154 * Stop scheduling of the periodic clean up of file descriptors 155 * 156 * @param cancelPending Cancels any pending request to clean up contents. If false, the last 157 * pending request to clean up content will still be scheduled but no more will be afterwards. 158 */ 159 fun stopMonitoring(cancelPending: Boolean = false) { 160 if (mIsMonitoring.get()) { 161 if (cancelPending) { 162 mExecutor.removeCallbacks(mCleanupRunnable) 163 teardownExecutorIfNecessary() 164 } 165 mIsMonitoring.set(false) 166 } 167 } 168 169 /** 170 * Returns true if [startMonitoring] has been invoked without a corresponding call to 171 * [stopMonitoring] 172 */ 173 val isMonitoring: Boolean 174 get() = mIsMonitoring.get() 175 176 companion object { 177 const val TAG = "FileDescriptorMonitor" 178 179 /** Delta in which if a fence has signalled it should be removed */ 180 const val SIGNAL_TIME_DELTA_MILLIS = 3000 181 182 const val MONITOR_DELAY = 1000L 183 184 const val MAX_FD = 100 185 } 186 } 187