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