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