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