• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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