1 /* 2 * Copyright 2022 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 androidx.work.impl 17 18 import androidx.work.impl.model.WorkGenerationalId 19 import androidx.work.impl.model.WorkSpec 20 import androidx.work.impl.model.generationalId 21 22 // Impl note: it is **not** a data class on purpose. 23 // Multiple schedulers can create `StartStopToken`-s for the same workSpecId, and `StartStopToken` 24 // objects should be different. If two schedulers request to start a work with the same 25 // `workSpecId`, Processor dedups those requests. However, if the work is cancelled, both 26 // of the schedules will request `Processor` to stop the work as well. That can lead to tricky race 27 // when one scheduler quickly cancels the work and schedules it again, while another stalls 28 // and tries to cancel the work afterwards. In this situation the freshly scheduled work shouldn't 29 // be cancelled because the second scheduler tries to cancel already cancelled work. 30 // So Processor class relies on StartStopToken-s being different and stores StartStopToken 31 // with the same workSpecId in the set to differentiate between past and future run requests. 32 class StartStopToken(val id: WorkGenerationalId) 33 34 interface StartStopTokens { tokenFornull35 fun tokenFor(id: WorkGenerationalId): StartStopToken 36 37 fun remove(id: WorkGenerationalId): StartStopToken? 38 39 fun remove(workSpecId: String): List<StartStopToken> 40 41 fun contains(id: WorkGenerationalId): Boolean 42 43 fun tokenFor(spec: WorkSpec) = tokenFor(spec.generationalId()) 44 45 fun remove(spec: WorkSpec) = remove(spec.generationalId()) 46 47 companion object { 48 @JvmStatic 49 @JvmOverloads 50 fun create(synchronized: Boolean = true): StartStopTokens { 51 val tokens = StartStopTokensImpl() 52 return if (synchronized) { 53 SynchronizedStartStopTokensImpl(tokens) 54 } else { 55 tokens 56 } 57 } 58 } 59 } 60 61 private class StartStopTokensImpl : StartStopTokens { 62 private val runs = mutableMapOf<WorkGenerationalId, StartStopToken>() 63 tokenFornull64 override fun tokenFor(id: WorkGenerationalId): StartStopToken { 65 return runs.getOrPut(id) { StartStopToken(id) } 66 } 67 removenull68 override fun remove(id: WorkGenerationalId): StartStopToken? { 69 return runs.remove(id) 70 } 71 removenull72 override fun remove(workSpecId: String): List<StartStopToken> { 73 val toRemove = runs.filterKeys { it.workSpecId == workSpecId } 74 toRemove.keys.forEach { runs.remove(it) } 75 return toRemove.values.toList() 76 } 77 containsnull78 override fun contains(id: WorkGenerationalId): Boolean { 79 return runs.contains(id) 80 } 81 } 82 83 private class SynchronizedStartStopTokensImpl(private val delegate: StartStopTokens) : 84 StartStopTokens { 85 86 private val lock = Any() 87 tokenFornull88 override fun tokenFor(id: WorkGenerationalId): StartStopToken { 89 return synchronized(lock) { delegate.tokenFor(id) } 90 } 91 removenull92 override fun remove(id: WorkGenerationalId): StartStopToken? { 93 return synchronized(lock) { delegate.remove(id) } 94 } 95 removenull96 override fun remove(workSpecId: String): List<StartStopToken> { 97 return synchronized(lock) { delegate.remove(workSpecId) } 98 } 99 containsnull100 override fun contains(id: WorkGenerationalId): Boolean { 101 return synchronized(lock) { delegate.contains(id) } 102 } 103 } 104