• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 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 package com.example.tracing.demo.experiments
17 
18 import android.os.Trace
19 import com.android.app.tracing.coroutines.createCoroutineTracingContext
20 import com.android.app.tracing.coroutines.flow.flowName
21 import com.example.tracing.demo.FixedThread1
22 import javax.inject.Inject
23 import javax.inject.Singleton
24 import kotlin.random.Random
25 import kotlinx.coroutines.CoroutineDispatcher
26 import kotlinx.coroutines.CoroutineScope
27 import kotlinx.coroutines.flow.SharingStarted
28 import kotlinx.coroutines.flow.flow
29 import kotlinx.coroutines.flow.shareIn
30 import kotlinx.coroutines.job
31 
32 @Singleton
33 class LeakySharedFlow
34 @Inject
35 constructor(@FixedThread1 private var handlerDispatcher: CoroutineDispatcher) : TracedExperiment() {
36 
37     override val description: String = "Create a shared flow that cannot be cancelled by the caller"
38 
<lambda>null39     private val counter = flow {
40         var n = 0
41         while (true) {
42             emit(n++)
43             forceSuspend(timeMillis = 5)
44         }
45     }
46 
runExperimentnull47     override suspend fun runExperiment() {
48         val cookie = Random.nextInt()
49         Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, TRACK_NAME, "leaky-flow", cookie)
50         // BAD - does not follow structured concurrency. This creates a new job each time it is
51         // called. There is no way to cancel the shared flow because the parent does not know about
52         // it
53         val leakedScope =
54             CoroutineScope(
55                 handlerDispatcher +
56                     createCoroutineTracingContext(
57                         "leaky-flow-scope",
58                         walkStackForDefaultNames = true,
59                     )
60             )
61         counter
62             .flowName("leakySharedFlow")
63             .shareIn(leakedScope, SharingStarted.Eagerly, replay = 10)
64 
65         leakedScope.coroutineContext.job.invokeOnCompletion {
66             Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, TRACK_NAME, cookie)
67         }
68     }
69 }
70