1 /*
<lambda>null2  * Copyright (C) 2019 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 @file:JvmName("ConcurrentUtils")
18 
19 package com.android.testutils
20 
21 import java.util.concurrent.CountDownLatch
22 import java.util.concurrent.ExecutorService
23 import java.util.concurrent.TimeUnit
24 import java.util.function.Consumer
25 import kotlin.system.measureTimeMillis
26 import kotlin.test.assertFalse
27 import kotlin.test.assertTrue
28 
29 // For Java usage
30 fun durationOf(fn: Runnable) = measureTimeMillis { fn.run() }
31 
awaitnull32 fun CountDownLatch.await(timeoutMs: Long): Boolean = await(timeoutMs, TimeUnit.MILLISECONDS)
33 
34 /**
35  * Quit resources provided as a list by a supplier.
36  *
37  * The supplier may return more resources as the process progresses, for example while interrupting
38  * threads and waiting for them to finish they may spawn more threads, so this implements a
39  * [maxRetryCount] which, in this case, would be the maximum length of the thread chain that can be
40  * terminated.
41  */
42 fun <T> quitResources(
43     maxRetryCount: Int,
44     supplier: () -> List<T>,
45     terminator: Consumer<T>
46 ) {
47     // Run it multiple times since new threads might be generated in a thread
48     // that is about to be terminated
49     for (retryCount in 0 until maxRetryCount) {
50         val resourcesToBeCleared = supplier()
51         if (resourcesToBeCleared.isEmpty()) return
52         for (resource in resourcesToBeCleared) {
53             terminator.accept(resource)
54         }
55     }
56     assertEmpty(supplier())
57 }
58 
59 /**
60  * Implementation of [quitResources] to interrupt and wait for [ExecutorService]s to finish.
61  */
62 @JvmOverloads
quitExecutorServicesnull63 fun quitExecutorServices(
64     maxRetryCount: Int,
65     interrupt: Boolean = true,
66     timeoutMs: Long = 10_000L,
67     supplier: () -> List<ExecutorService>
68 ) {
69     quitResources(maxRetryCount, supplier) { ecs ->
70         if (interrupt) {
71             ecs.shutdownNow()
72         }
73         assertTrue(ecs.awaitTermination(timeoutMs, TimeUnit.MILLISECONDS),
74             "ExecutorServices did not terminate within timeout")
75     }
76 }
77 
78 /**
79  * Implementation of [quitResources] to interrupt and wait for [Thread]s to finish.
80  */
81 @JvmOverloads
quitThreadsnull82 fun quitThreads(
83     maxRetryCount: Int,
84     interrupt: Boolean = true,
85     timeoutMs: Long = 10_000L,
86     supplier: () -> List<Thread>
87 ) {
88     quitResources(maxRetryCount, supplier) { th ->
89         if (interrupt) {
90             th.interrupt()
91         }
92         th.join(timeoutMs)
93         assertFalse(th.isAlive, "Threads did not terminate within timeout.")
94     }
95 }
96