• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2022 Google LLC
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.google.android.libraries.mobiledatadownload.internal.util;
17 
18 import static com.google.common.util.concurrent.Futures.immediateFailedFuture;
19 import static com.google.common.util.concurrent.Futures.immediateVoidFuture;
20 
21 import androidx.annotation.VisibleForTesting;
22 import com.google.android.libraries.mobiledatadownload.internal.logging.LogUtil;
23 import com.google.android.libraries.mobiledatadownload.tracing.PropagatedExecutionSequencer;
24 import com.google.common.base.Optional;
25 import com.google.common.util.concurrent.ListenableFuture;
26 import java.util.HashMap;
27 import java.util.Map;
28 import java.util.concurrent.Executor;
29 
30 /**
31  * Helper class to maintain the state of MDD download futures.
32  *
33  * <p>This follows a limited Map interface and uses {@link ExecutionSequencer} to ensure that all
34  * operations on the map are synchronized.
35  *
36  * <p><b>NOTE:</b> This class is meant to be a container class for download futures and <em>should
37  * not</em> include any download-specific logic. Its sole purpose is to maintain any in-progress
38  * download futures in a synchronized manner. Download-specific logic should be implemented outside
39  * of this class, and can rely on {@link StateChangeCallbacks} to respond to events from this map.
40  */
41 public final class DownloadFutureMap<T> {
42   private static final String TAG = "DownloadFutureMap";
43 
44   // ExecutionSequencer ensures that enqueued futures are executed sequentially (regardless of the
45   // executor used). This allows us to keep critical state changes sequential.
46   private final PropagatedExecutionSequencer futureSerializer =
47       PropagatedExecutionSequencer.create();
48 
49   private final Executor sequentialControlExecutor;
50   private final StateChangeCallbacks callbacks;
51 
52   // Underlying map to store futures -- synchronization of accesses/updates is handled by
53   // ExecutionSequencer.
54   @VisibleForTesting
55   public final Map<String, ListenableFuture<T>> keyToDownloadFutureMap = new HashMap<>();
56 
DownloadFutureMap(Executor sequentialControlExecutor, StateChangeCallbacks callbacks)57   private DownloadFutureMap(Executor sequentialControlExecutor, StateChangeCallbacks callbacks) {
58     this.sequentialControlExecutor = sequentialControlExecutor;
59     this.callbacks = callbacks;
60   }
61 
62   /** Convenience creator when no callbacks should be registered. */
create(Executor sequentialControlExecutor)63   public static <T> DownloadFutureMap<T> create(Executor sequentialControlExecutor) {
64     return create(sequentialControlExecutor, new StateChangeCallbacks() {});
65   }
66 
67   /** Creates a new instance of DownloadFutureMap. */
create( Executor sequentialControlExecutor, StateChangeCallbacks callbacks)68   public static <T> DownloadFutureMap<T> create(
69       Executor sequentialControlExecutor, StateChangeCallbacks callbacks) {
70     return new DownloadFutureMap<T>(sequentialControlExecutor, callbacks);
71   }
72 
73   /** Callback to support custom events based on the state of the map. */
74   public static interface StateChangeCallbacks {
75     /** Respond to the event immediately before a new future is added to the map. */
onAdd(String key, int newSize)76     default void onAdd(String key, int newSize) throws Exception {}
77 
78     /** Respond to the event immediately after a future is removed from the map. */
onRemove(String key, int newSize)79     default void onRemove(String key, int newSize) throws Exception {}
80   }
81 
add(String key, ListenableFuture<T> downloadFuture)82   public ListenableFuture<Void> add(String key, ListenableFuture<T> downloadFuture) {
83     LogUtil.v("%s: submitting request to add in-progress download future with key: %s", TAG, key);
84     return futureSerializer.submitAsync(
85         () -> {
86           try {
87             callbacks.onAdd(key, keyToDownloadFutureMap.size() + 1);
88             keyToDownloadFutureMap.put(key, downloadFuture);
89           } catch (Exception e) {
90             LogUtil.e(e, "%s: Failed to add download future (%s) to map", TAG, key);
91             return immediateFailedFuture(e);
92           }
93           return immediateVoidFuture();
94         },
95         sequentialControlExecutor);
96   }
97 
98   @SuppressWarnings("FutureReturnValueIgnored")
99   public ListenableFuture<Void> remove(String key) {
100     LogUtil.v(
101         "%s: submitting request to remove in-progress download future with key: %s", TAG, key);
102     return futureSerializer.submitAsync(
103         () -> {
104           try {
105             keyToDownloadFutureMap.remove(key);
106             callbacks.onRemove(key, keyToDownloadFutureMap.size());
107           } catch (Exception e) {
108             LogUtil.e(e, "%s: Failed to remove download future (%s) from map", TAG, key);
109             return immediateFailedFuture(e);
110           }
111           return immediateVoidFuture();
112         },
113         sequentialControlExecutor);
114   }
115 
116   public ListenableFuture<Optional<ListenableFuture<T>>> get(String key) {
117     LogUtil.v("%s: submitting request for in-progress download future with key: %s", TAG, key);
118     return futureSerializer.submit(
119         () -> Optional.fromNullable(keyToDownloadFutureMap.get(key)), sequentialControlExecutor);
120   }
121 
122   public ListenableFuture<Boolean> containsKey(String key) {
123     LogUtil.v("%s: submitting check for in-progress download future with key: %s", TAG, key);
124     return futureSerializer.submit(
125         () -> keyToDownloadFutureMap.containsKey(key), sequentialControlExecutor);
126   }
127 }
128