• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download

<lambda>null1 package kotlinx.coroutines.internal
2 
3 import kotlinx.coroutines.CoroutineExceptionHandler
4 import java.io.*
5 import java.net.*
6 import java.util.*
7 import java.util.jar.*
8 import java.util.zip.*
9 
10 /**
11  * Don't use JvmField here to enable R8 optimizations via "assumenosideeffects"
12  */
13 internal val ANDROID_DETECTED = runCatching { Class.forName("android.os.Build") }.isSuccess
14 
15 /**
16  * A simplified version of [ServiceLoader].
17  * FastServiceLoader locates and instantiates all service providers named in configuration
18  * files placed in the resource directory <tt>META-INF/services</tt>.
19  *
20  * The main difference between this class and classic service loader is in skipping
21  * verification JARs. A verification requires reading the whole JAR (and it causes problems and ANRs on Android devices)
22  * and prevents only trivial checksum issues. See #878.
23  *
24  * If any error occurs during loading, it fallbacks to [ServiceLoader], mostly to prevent R8 issues.
25  */
26 internal object FastServiceLoader {
27     private const val PREFIX: String = "META-INF/services/"
28 
29     /**
30      * This method attempts to load [MainDispatcherFactory] in Android-friendly way.
31      *
32      * If we are not on Android, this method fallbacks to a regular service loading,
33      * else we attempt to do `Class.forName` lookup for
34      * `AndroidDispatcherFactory` and `TestMainDispatcherFactory`.
35      * If lookups are successful, we return resultinAg instances because we know that
36      * `MainDispatcherFactory` API is internal and this is the only possible classes of `MainDispatcherFactory` Service on Android.
37      *
38      * Such an intricate dance is required to avoid calls to `ServiceLoader.load` for multiple reasons:
39      * 1) It eliminates disk lookup on potentially slow devices on the Main thread.
40      * 2) Various Android toolchain versions by various vendors don't tend to handle ServiceLoader calls properly.
41      *    Sometimes META-INF is removed from the resulting APK, sometimes class names are mangled, etc.
42      *    While it is not the problem of `kotlinx.coroutines`, it significantly worsens user experience, thus we are workarounding it.
43      *    Examples of such issues are #932, #1072, #1557, #1567
44      *
45      * We also use SL for [CoroutineExceptionHandler], but we do not experience the same problems and CEH is a public API
46      * that may already be injected vis SL, so we are not using the same technique for it.
47      */
loadMainDispatcherFactorynull48     internal fun loadMainDispatcherFactory(): List<MainDispatcherFactory> {
49         val clz = MainDispatcherFactory::class.java
50         if (!ANDROID_DETECTED) {
51             return load(clz, clz.classLoader)
52         }
53 
54         /*
55          * If `ANDROID_DETECTED` is true, it is still possible to have `AndroidDispatcherFactory` missing.
56          * The most notable case of it is firebase-sdk that repackages some Android classes but can be used from an arbitrary
57          * K/JVM application.
58          * See also #3914.
59          */
60         return try {
61             val result = ArrayList<MainDispatcherFactory>(2)
62             val mainFactory = createInstanceOf(clz, "kotlinx.coroutines.android.AndroidDispatcherFactory")
63             if (mainFactory == null) {
64                 // Fallback to regular service loading
65                 return load(clz, clz.classLoader)
66             }
67             result.add(mainFactory)
68             // Also search for test-module factory
69             createInstanceOf(clz, "kotlinx.coroutines.test.internal.TestMainDispatcherFactory")?.apply { result.add(this) }
70             result
71         } catch (_: Throwable) {
72             // Fallback to the regular SL in case of any unexpected exception
73             load(clz, clz.classLoader)
74         }
75     }
76 
77     /*
78      * This method is inline to have a direct Class.forName("string literal") in the byte code to avoid weird interactions with ProGuard/R8.
79      */
80     @Suppress("NOTHING_TO_INLINE")
createInstanceOfnull81     private inline fun createInstanceOf(
82         baseClass: Class<MainDispatcherFactory>,
83         serviceClass: String
84     ): MainDispatcherFactory? {
85         return try {
86             val clz = Class.forName(serviceClass, true, baseClass.classLoader)
87             baseClass.cast(clz.getDeclaredConstructor().newInstance())
88         } catch (_: ClassNotFoundException) { // Do not fail if TestMainDispatcherFactory is not found
89             null
90         }
91     }
92 
loadnull93     private fun <S> load(service: Class<S>, loader: ClassLoader): List<S> {
94         return try {
95             loadProviders(service, loader)
96         } catch (_: Throwable) {
97             // Fallback to default service loader
98             ServiceLoader.load(service, loader).toList()
99         }
100     }
101 
102     // Visible for tests
loadProvidersnull103     internal fun <S> loadProviders(service: Class<S>, loader: ClassLoader): List<S> {
104         val fullServiceName = PREFIX + service.name
105         // Filter out situations when both JAR and regular files are in the classpath (e.g. IDEA)
106         val urls = loader.getResources(fullServiceName)
107         val providers = urls.toList().flatMap { parse(it) }.toSet()
108         require(providers.isNotEmpty()) { "No providers were loaded with FastServiceLoader" }
109         return providers.map { getProviderInstance(it, loader, service) }
110     }
111 
getProviderInstancenull112     private fun <S> getProviderInstance(name: String, loader: ClassLoader, service: Class<S>): S {
113         val clazz = Class.forName(name, false, loader)
114         require(service.isAssignableFrom(clazz)) { "Expected service of class $service, but found $clazz" }
115         return service.cast(clazz.getDeclaredConstructor().newInstance())
116     }
117 
parsenull118     private fun parse(url: URL): List<String> {
119         val path = url.toString()
120         // Fast-path for JARs
121         if (path.startsWith("jar")) {
122             val pathToJar = path.substringAfter("jar:file:").substringBefore('!')
123             val entry = path.substringAfter("!/")
124             // mind the verify = false flag!
125             (JarFile(pathToJar, false)).use { file ->
126                 BufferedReader(InputStreamReader(file.getInputStream(ZipEntry(entry)), "UTF-8")).use { r ->
127                     return parseFile(r)
128                 }
129             }
130         }
131         // Regular path for everything else
132         return BufferedReader(InputStreamReader(url.openStream())).use { reader ->
133             parseFile(reader)
134         }
135     }
136 
137     // JarFile does no implement Closesable on Java 1.6
usenull138     private inline fun <R> JarFile.use(block: (JarFile) -> R): R {
139         var cause: Throwable? = null
140         try {
141             return block(this)
142         } catch (e: Throwable) {
143             cause = e
144             throw e
145         } finally {
146             try {
147                 close()
148             } catch (closeException: Throwable) {
149                 if (cause === null) throw closeException
150                 cause.addSuppressed(closeException)
151                 throw cause
152             }
153         }
154     }
155 
parseFilenull156     private fun parseFile(r: BufferedReader): List<String> {
157         val names = mutableSetOf<String>()
158         while (true) {
159             val line = r.readLine() ?: break
160             val serviceName = line.substringBefore("#").trim()
161             require(serviceName.all { it == '.' || Character.isJavaIdentifierPart(it) }) { "Illegal service provider class name: $serviceName" }
162             if (serviceName.isNotEmpty()) {
163                 names.add(serviceName)
164             }
165         }
166         return names.toList()
167     }
168 }
169