1 /* <lambda>null2 * Copyright (C) 2024 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 package com.google.jetpackcamera.core.common 17 18 import android.util.Log 19 import kotlinx.atomicfu.atomic 20 import kotlinx.atomicfu.loop 21 22 /** 23 * A thread-safe, lock-free class for managing the lifecycle of an object using ref-counting. 24 * 25 * The object that is ref-counted can be initialized late using [initialize]. 26 * 27 * @param[debugRefCounts] whether to print debug log statements. 28 * @param[onRelease] a block that will be invoked once when the ref-count reaches 0. 29 */ 30 class RefCounted<T : Any>( 31 private val debugRefCounts: Boolean = false, 32 private val onRelease: (T) -> Unit 33 ) { 34 private val refCounted = atomic(uninitialized<T>()) 35 36 /** 37 * Initializes the ref-count managed object with the object being managed. 38 * 39 * This also initializes the implicit ref-count to 1. 40 * 41 * All calls to this function must be paired with [release] to ensure the initial implicit 42 * ref count is decremented and the `onRelease` callback can be called. 43 * 44 */ 45 fun initialize(newValue: T) { 46 val initialVal = Pair(newValue, 1) 47 check(refCounted.compareAndSet(uninitialized(), initialVal)) { 48 "Ref-count managed object has already been initialized." 49 } 50 51 if (debugRefCounts) { 52 Log.d( 53 TAG, 54 "RefCounted@${"%x".format(hashCode())}<${newValue::class.simpleName}> " + 55 "initialized: [refCount: 1, value: $newValue]", 56 Throwable() 57 ) 58 } 59 } 60 61 /** 62 * Retrieves the underlying managed object, increasing the ref-count by 1. 63 * 64 * This increases the ref-count if the object has not already been released. If the object has 65 * been released, `null` is returned. 66 * 67 * All calls to this function must be paired with [release], unless `null` is returned. 68 */ 69 fun acquire(): T? { 70 check(refCounted.value != uninitialized<T>()) { 71 "Ref-count managed object has not yet been initialized. Unable to acquire." 72 } 73 74 refCounted.loop { old -> 75 if (old == released<T>()) { 76 if (debugRefCounts) { 77 Log.d( 78 TAG, 79 "RefCounted@${"%x".format(hashCode())}.acquire() failure: " + 80 "[refCount: 0]", 81 Throwable() 82 ) 83 } 84 return null 85 } 86 87 val (value, oldCount) = old 88 val new = Pair(value, oldCount + 1) 89 if (refCounted.compareAndSet(old, new)) { 90 if (debugRefCounts) { 91 Log.d( 92 TAG, 93 "RefCounted@${"%x".format(hashCode())}<${value::class.simpleName}>" + 94 ".acquire() success: [refCount: ${oldCount + 1}, value: $value]", 95 Throwable() 96 ) 97 } 98 return value 99 } 100 } 101 } 102 103 /** 104 * Decrements the ref-count by 1 after a call to [acquire] or [initialize]. 105 * 106 * This should always be called once for each [initialize] call and once for each [acquire] 107 * call that does not return `null`. 108 */ 109 fun release() { 110 check(refCounted.value != uninitialized<T>()) { 111 "Ref-count managed object has not yet been initialized. Unable to release." 112 } 113 114 refCounted.loop { old -> 115 check(old != released<T>()) { 116 "Release called more times than initialize + acquire." 117 } 118 119 val (value, oldCount) = old 120 val new = if (oldCount == 1) released() else Pair(value, oldCount - 1) 121 if (refCounted.compareAndSet(old, new)) { 122 if (new == released<T>()) { 123 if (debugRefCounts) { 124 Log.d( 125 TAG, 126 "RefCounted@${"%x".format(hashCode())}<${value::class.simpleName}>" + 127 ".release() (last ref): [refCount: 0, value: $value]", 128 Throwable() 129 ) 130 } 131 onRelease(value) 132 } else { 133 if (debugRefCounts) { 134 Log.d( 135 TAG, 136 "RefCounted@${"%x".format(hashCode())}<${value::class.simpleName}>" + 137 ".release(): [refCount: ${oldCount - 1}, value: $value]", 138 Throwable() 139 ) 140 } 141 } 142 return 143 } 144 } 145 } 146 147 companion object { 148 private const val TAG = "RefCounted" 149 private val UNINITIALIZED = Pair(Unit, -1) 150 private val RELEASED = Pair(Unit, 0) 151 152 @Suppress("UNCHECKED_CAST") 153 private fun <T> uninitialized(): Pair<T, Int> { 154 return UNINITIALIZED as Pair<T, Int> 155 } 156 157 @Suppress("UNCHECKED_CAST") 158 private fun <T> released(): Pair<T, Int> { 159 return RELEASED as Pair<T, Int> 160 } 161 } 162 } 163