1 /* 2 * Copyright 2023 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 androidx.compose.ui 18 19 import androidx.annotation.RestrictTo 20 import kotlin.jvm.JvmInline 21 import kotlinx.coroutines.CoroutineScope 22 import kotlinx.coroutines.Job 23 import kotlinx.coroutines.cancelAndJoin 24 import kotlinx.coroutines.coroutineScope 25 import kotlinx.coroutines.job 26 27 /** 28 * Helper class for coordinating between mutually-exclusive sessions. A session is represented as an 29 * object of type [T] and a coroutine [Job] whose lifetime is tied to the session. 30 * 31 * Only one session can be active at a time. When a new session is started, the old session will be 32 * cancelled and allowed to finish any cancellation tasks (e.g. `finally` blocks) before the new 33 * session's coroutine starts. 34 */ 35 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 36 @InternalComposeUiApi 37 @JvmInline 38 value class SessionMutex<T> 39 private constructor(private val currentSessionHolder: AtomicReference<Session<T>?>) { 40 constructor() : this(AtomicReference(null)) 41 42 /** Returns the current session object. */ 43 val currentSession: T? 44 get() = currentSessionHolder.get()?.value 45 46 /** 47 * Cancels any existing session and then calls [session]. [session] will in turn be cancelled if 48 * this method is called again before it returns. 49 * 50 * @param sessionInitializer Called immediately to create the new session object, before 51 * cancelling the previous session. Receives a [CoroutineScope] that has the same context as 52 * [session] will get. 53 * @param session Called with the return value from [sessionInitializer] after cancelling the 54 * previous session. 55 */ withSessionCancellingPreviousnull56 suspend fun <R> withSessionCancellingPrevious( 57 sessionInitializer: (CoroutineScope) -> T, 58 session: suspend (data: T) -> R 59 ): R = coroutineScope { 60 val newSession = Session(job = coroutineContext.job, value = sessionInitializer(this)) 61 currentSessionHolder.getAndSet(newSession)?.job?.cancelAndJoin() 62 try { 63 return@coroutineScope session(newSession.value) 64 } finally { 65 // If this session is being interrupted by another session, the holder will already have 66 // been changed, so this will fail. If the session is getting cancelled externally or 67 // from within, this will ensure we release the session while no session is active. 68 currentSessionHolder.compareAndSet(newSession, null) 69 } 70 } 71 72 private class Session<T>(val job: Job, val value: T) 73 } 74