1 /*
2  * Copyright 2022 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.datastore.core
18 
19 import android.os.ParcelFileDescriptor
20 import java.io.File
21 import java.io.IOException
22 
23 /** Put the JNI methods in a separate class to make them internal to the package. */
24 internal class NativeSharedCounter {
nativeTruncateFilenull25     external fun nativeTruncateFile(fd: Int): Int
26 
27     external fun nativeCreateSharedCounter(fd: Int): Long
28 
29     external fun nativeGetCounterValue(address: Long): Int
30 
31     external fun nativeIncrementAndGetCounterValue(address: Long): Int
32 }
33 
34 /**
35  * An atomic counter implemented by shared memory, which could be used by multi-process DataStore as
36  * an atomic version counter. The underlying JNI library would be pre-compiled and shipped as part
37  * of the `datastore-multiprocess` AAR artifact, users don't need extra steps other than adding it
38  * as dependency.
39  */
40 internal class SharedCounter
41 private constructor(
42     /** The memory address to be mapped. */
43     private val mappedAddress: Long
44 ) {
45 
46     fun getValue(): Int {
47         return nativeSharedCounter.nativeGetCounterValue(mappedAddress)
48     }
49 
50     fun incrementAndGetValue(): Int {
51         return nativeSharedCounter.nativeIncrementAndGetCounterValue(mappedAddress)
52     }
53 
54     companion object Factory {
55         internal val nativeSharedCounter = NativeSharedCounter()
56 
57         fun loadLib() = System.loadLibrary("datastore_shared_counter")
58 
59         private fun createCounterFromFd(pfd: ParcelFileDescriptor): SharedCounter {
60             val nativeFd = pfd.getFd()
61             if (nativeSharedCounter.nativeTruncateFile(nativeFd) != 0) {
62                 throw IOException("Failed to truncate counter file")
63             }
64             val address = nativeSharedCounter.nativeCreateSharedCounter(nativeFd)
65             if (address < 0) {
66                 throw IOException("Failed to mmap counter file")
67             }
68             return SharedCounter(address)
69         }
70 
71         internal fun create(produceFile: () -> File): SharedCounter {
72             val file = produceFile()
73             var pfd: ParcelFileDescriptor? = null
74             try {
75                 pfd =
76                     ParcelFileDescriptor.open(
77                         file,
78                         ParcelFileDescriptor.MODE_READ_WRITE or ParcelFileDescriptor.MODE_CREATE
79                     )
80                 return createCounterFromFd(pfd)
81             } finally {
82                 pfd?.close()
83             }
84         }
85     }
86 }
87