1 /* <lambda>null2 * Copyright 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 17 package com.android.photopicker.util 18 19 import android.util.Log 20 import kotlinx.coroutines.CoroutineDispatcher 21 import kotlinx.coroutines.CoroutineScope 22 import kotlinx.coroutines.Deferred 23 import kotlinx.coroutines.Dispatchers 24 import kotlinx.coroutines.async 25 import kotlinx.coroutines.coroutineScope 26 import kotlinx.coroutines.withTimeout 27 28 private const val TAG = "MapOfDeferredWithTimeout" 29 30 /** 31 * A Suspend function that accepts a (key -> lambda) map and a timeout. 32 * 33 * Each lambda runs in parallel using [async] and a (key -> [Deferred] result) map is returned by 34 * this method. 35 * 36 * If any [async] task runs for longer than the provided timeout, it will automatically be marked as 37 * cancelled and the result will be set to null. However, the task should periodically check if it 38 * was cancelled and terminate processing on its own. This is because coroutine cancellation is 39 * cooperative. 40 * 41 * If any async task throws an error, it will be swallowed and the result will be set to null. 42 */ 43 suspend fun <A, B> mapOfDeferredWithTimeout( 44 inputMap: Map<A, suspend (B) -> Any?>, 45 input: B, 46 timeoutMillis: Long, 47 backgroundScope: CoroutineScope, 48 dispatcher: CoroutineDispatcher = Dispatchers.IO, 49 ): Map<A, Deferred<Any?>> = coroutineScope { 50 inputMap 51 .map<A, suspend (B) -> Any?, Pair<A, Deferred<Any?>>> { (key, block) -> 52 key to 53 async { 54 try { 55 withTimeout(timeoutMillis) { 56 // Coroutine cancellations are cooperative. This means that if the block 57 // running inside [withTimeout] is non-cooperative, the timeout will 58 // not be enforced. In order to ensure that the timeout is enforced, 59 // wrap the call in an [async] block which is cancellation aware. 60 Log.d(TAG, "Fetching result for : $key") 61 val result = backgroundScope.async(dispatcher) { block(input) }.await() 62 Log.d(TAG, "Finished fetching result for : $key val: $result") 63 result 64 } 65 } catch (e: RuntimeException) { 66 Log.e(TAG, "An error occurred in fetching result for key: $key", e) 67 null 68 } 69 } 70 } 71 .toMap() 72 } 73