Skip to content

Commit 8dadce6

Browse files
authored
Introducing the ThreadManager API (firebase#74)
* Implemented ThreadManager API for configuring the thread pools and thread factories used by the SDK * Giving all threads unique names; Updated documentation; Using daemons in default thread managers to ensure clean JVM exit * Updated comments and documentation * Adding tests for options * Test cases for basic ThreadManager API * More test cases * Made the executor service private in FirebaseApp; Refactored the tests for clarity * Initializing executor in FirebaseApp constructor. Minor improvements to documentation and tests. * Updated documentation; Renamed submit() to submitCallable() and other minor changes
1 parent 50ab119 commit 8dadce6

30 files changed

Lines changed: 1053 additions & 162 deletions

src/main/java/com/google/firebase/FirebaseApp.java

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,14 @@
3232
import com.google.common.base.Strings;
3333
import com.google.common.collect.ImmutableList;
3434
import com.google.common.io.BaseEncoding;
35+
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
3536
import com.google.firebase.internal.FirebaseAppStore;
36-
import com.google.firebase.internal.FirebaseExecutors;
3737
import com.google.firebase.internal.FirebaseService;
3838
import com.google.firebase.internal.NonNull;
3939
import com.google.firebase.internal.Nullable;
4040

41+
import com.google.firebase.tasks.Task;
42+
import com.google.firebase.tasks.Tasks;
4143
import java.io.IOException;
4244
import java.util.ArrayList;
4345
import java.util.Collections;
@@ -47,9 +49,12 @@
4749
import java.util.Map;
4850
import java.util.Set;
4951
import java.util.concurrent.Callable;
52+
import java.util.concurrent.Future;
5053
import java.util.concurrent.ScheduledFuture;
54+
import java.util.concurrent.ThreadFactory;
5155
import java.util.concurrent.TimeUnit;
5256
import java.util.concurrent.atomic.AtomicBoolean;
57+
5358
import org.slf4j.Logger;
5459
import org.slf4j.LoggerFactory;
5560

@@ -84,9 +89,11 @@ public class FirebaseApp {
8489
private final String name;
8590
private final FirebaseOptions options;
8691
private final TokenRefresher tokenRefresher;
92+
private final ThreadManager threadManager;
8793

8894
private final AtomicBoolean deleted = new AtomicBoolean();
8995
private final Map<String, FirebaseService> services = new HashMap<>();
96+
private final ListeningScheduledExecutorService executor;
9097

9198
/**
9299
* Per application lock for synchronizing all internal FirebaseApp state changes.
@@ -99,6 +106,8 @@ private FirebaseApp(String name, FirebaseOptions options, TokenRefresher.Factory
99106
this.name = name;
100107
this.options = checkNotNull(options);
101108
this.tokenRefresher = checkNotNull(factory).create(this);
109+
this.threadManager = options.getThreadManager();
110+
this.executor = this.threadManager.getListeningExecutor(this);
102111
}
103112

104113
/** Returns a list of all FirebaseApps. */
@@ -242,7 +251,6 @@ private static String normalize(@NonNull String name) {
242251
/** Returns the unique name of this app. */
243252
@NonNull
244253
public String getName() {
245-
checkNotDeleted();
246254
return name;
247255
}
248256

@@ -316,6 +324,9 @@ public void delete() {
316324
}
317325
services.clear();
318326
tokenRefresher.cleanup();
327+
328+
// Clean up and terminate the thread pool
329+
threadManager.releaseExecutor(this, executor);
319330
}
320331

321332
synchronized (appsLock) {
@@ -332,6 +343,21 @@ private void checkNotDeleted() {
332343
checkState(!deleted.get(), "FirebaseApp was deleted %s", this);
333344
}
334345

346+
ThreadFactory getThreadFactory() {
347+
return threadManager.getThreadFactory();
348+
}
349+
350+
// TODO: Return an ApiFuture once Task API is fully removed.
351+
<T> Task<T> submit(Callable<T> command) {
352+
checkNotNull(command);
353+
return Tasks.call(executor, command);
354+
}
355+
356+
<T> ScheduledFuture<T> schedule(Callable<T> command, long delayMillis) {
357+
checkNotNull(command);
358+
return executor.schedule(command, delayMillis, TimeUnit.MILLISECONDS);
359+
}
360+
335361
boolean isDefaultApp() {
336362
return DEFAULT_APP_NAME.equals(getName());
337363
}
@@ -362,12 +388,14 @@ FirebaseService getService(String id) {
362388
*/
363389
static class TokenRefresher implements CredentialsChangedListener {
364390

391+
private final FirebaseApp firebaseApp;
365392
private final GoogleCredentials credentials;
366-
private ScheduledFuture<Void> future;
393+
private Future<Void> future;
367394
private boolean closed;
368395

369-
TokenRefresher(FirebaseApp app) {
370-
this.credentials = app.getOptions().getCredentials();
396+
TokenRefresher(FirebaseApp firebaseApp) {
397+
this.firebaseApp = checkNotNull(firebaseApp);
398+
this.credentials = firebaseApp.getOptions().getCredentials();
371399
this.credentials.addChangeListener(this);
372400
}
373401

@@ -417,9 +445,7 @@ protected void cancelPrevious() {
417445
protected void scheduleNext(Callable<Void> task, long delayMillis) {
418446
logger.debug("Scheduling next token refresh in {} milliseconds", delayMillis);
419447
try {
420-
future =
421-
FirebaseExecutors.DEFAULT_SCHEDULED_EXECUTOR.schedule(
422-
task, delayMillis, TimeUnit.MILLISECONDS);
448+
future = firebaseApp.schedule(task, delayMillis);
423449
} catch (UnsupportedOperationException ignored) {
424450
// Cannot support task scheduling in the current runtime.
425451
}

src/main/java/com/google/firebase/FirebaseOptions.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import com.google.firebase.auth.FirebaseCredentials;
2929
import com.google.firebase.auth.internal.BaseCredential;
3030
import com.google.firebase.auth.internal.FirebaseCredentialsAdapter;
31+
import com.google.firebase.internal.FirebaseExecutors;
3132
import com.google.firebase.internal.NonNull;
3233
import com.google.firebase.internal.Nullable;
3334

@@ -46,6 +47,7 @@ public final class FirebaseOptions {
4647
private final String projectId;
4748
private final HttpTransport httpTransport;
4849
private final JsonFactory jsonFactory;
50+
private final ThreadManager threadManager;
4951

5052
private FirebaseOptions(@NonNull FirebaseOptions.Builder builder) {
5153
this.credentials = checkNotNull(builder.credentials,
@@ -59,6 +61,8 @@ private FirebaseOptions(@NonNull FirebaseOptions.Builder builder) {
5961
"FirebaseOptions must be initialized with a non-null HttpTransport.");
6062
this.jsonFactory = checkNotNull(builder.jsonFactory,
6163
"FirebaseOptions must be initialized with a non-null JsonFactory.");
64+
this.threadManager = checkNotNull(builder.threadManager,
65+
"FirebaseOptions must be initialized with a non-null ThreadManager");
6266
}
6367

6468
/**
@@ -118,7 +122,12 @@ public JsonFactory getJsonFactory() {
118122
return jsonFactory;
119123
}
120124

121-
/**
125+
@NonNull
126+
ThreadManager getThreadManager() {
127+
return threadManager;
128+
}
129+
130+
/**
122131
* Builder for constructing {@link FirebaseOptions}.
123132
*/
124133
public static final class Builder {
@@ -130,6 +139,7 @@ public static final class Builder {
130139
private String projectId;
131140
private HttpTransport httpTransport = Utils.getDefaultTransport();
132141
private JsonFactory jsonFactory = Utils.getDefaultJsonFactory();
142+
private ThreadManager threadManager = FirebaseExecutors.DEFAULT_THREAD_MANAGER;
133143

134144
/** Constructs an empty builder. */
135145
public Builder() {}
@@ -148,6 +158,7 @@ public Builder(FirebaseOptions options) {
148158
projectId = options.projectId;
149159
httpTransport = options.httpTransport;
150160
jsonFactory = options.jsonFactory;
161+
threadManager = options.threadManager;
151162
}
152163

153164
/**
@@ -263,6 +274,18 @@ public Builder setJsonFactory(JsonFactory jsonFactory) {
263274
return this;
264275
}
265276

277+
/**
278+
* Sets the <code>ThreadManager</code> used to initialize thread pools and thread factories
279+
* for Firebase apps.
280+
*
281+
* @param threadManager A <code>ThreadManager</code> instance.
282+
* @return This <code>Builder</code> instance is returned so subsequent calls can be chained.
283+
*/
284+
public Builder setThreadManager(ThreadManager threadManager) {
285+
this.threadManager = threadManager;
286+
return this;
287+
}
288+
266289
/**
267290
* Builds the {@link FirebaseOptions} instance from the previously set options.
268291
*

src/main/java/com/google/firebase/ImplFirebaseTrampolines.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@
2020
import com.google.firebase.internal.FirebaseService;
2121
import com.google.firebase.internal.NonNull;
2222

23+
import com.google.firebase.tasks.Task;
24+
import com.google.firebase.tasks.Tasks;
25+
import java.util.concurrent.Callable;
26+
import java.util.concurrent.Future;
27+
import java.util.concurrent.ScheduledExecutorService;
28+
import java.util.concurrent.ThreadFactory;
29+
2330
/**
2431
* Provides trampolines into package-private APIs used by components of Firebase. Intentionally
2532
* scarily-named to dissuade people from actually trying to use the class and to make it less likely
@@ -61,4 +68,12 @@ public static <T extends FirebaseService> T addService(
6168
app.addService(service);
6269
return service;
6370
}
71+
72+
public static ThreadFactory getThreadFactory(@NonNull FirebaseApp app) {
73+
return app.getThreadFactory();
74+
}
75+
76+
public static <T> Task<T> submitCallable(@NonNull FirebaseApp app, @NonNull Callable<T> command) {
77+
return app.submit(command);
78+
}
6479
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright 2017 Google Inc.
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 com.google.firebase;
18+
19+
import static com.google.common.base.Preconditions.checkNotNull;
20+
21+
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
22+
import com.google.common.util.concurrent.MoreExecutors;
23+
import com.google.firebase.internal.NonNull;
24+
25+
import java.util.concurrent.ScheduledExecutorService;
26+
import java.util.concurrent.ThreadFactory;
27+
28+
/**
29+
* An interface that controls the thread pools and thread factories used by the Admin SDK. Each
30+
* instance of {@link FirebaseApp} uses an implementation of this interface to create and manage
31+
* threads. Multiple app instances may use the same <code>ThreadManager</code> instance.
32+
* Methods in this interface may get invoked multiple times by the same
33+
* app, during its lifetime. Apps may also invoke methods of this interface concurrently, and
34+
* therefore implementations should provide any synchronization necessary.
35+
*/
36+
public abstract class ThreadManager {
37+
38+
@NonNull
39+
final ListeningScheduledExecutorService getListeningExecutor(@NonNull FirebaseApp app) {
40+
ScheduledExecutorService executor = getExecutor(app);
41+
checkNotNull(executor, "ScheduledExecutorService must not be null");
42+
return MoreExecutors.listeningDecorator(executor);
43+
}
44+
45+
/**
46+
* Returns the main thread pool for an app. Implementations may return the same instance of
47+
* <code>ScheduledExecutorService</code> for multiple apps. The returned thread pool is used by
48+
* all components of an app except for the Realtime Database. Database has far stricter and
49+
* complicated threading requirements, and thus initializes its own threads using the
50+
* factory returned by {@link ThreadManager#getThreadFactory()}.
51+
*
52+
* @param app A {@link FirebaseApp} instance.
53+
* @return A non-null {@link ScheduledExecutorService} instance.
54+
*/
55+
@NonNull
56+
protected abstract ScheduledExecutorService getExecutor(@NonNull FirebaseApp app);
57+
58+
/**
59+
* Cleans up the thread pool associated with an app. This method is invoked when an
60+
* app is deleted.
61+
*
62+
* @param app A {@link FirebaseApp} instance.
63+
*/
64+
protected abstract void releaseExecutor(
65+
@NonNull FirebaseApp app, @NonNull ScheduledExecutorService executor);
66+
67+
/**
68+
* Returns the <code>ThreadFactory</code> to be used for creating any additional threads
69+
* required by the SDK. This is used mainly to create the long-lived worker threads for
70+
* the Realtime Database client.
71+
*
72+
* @return A non-null <code>ThreadFactory</code>.
73+
*/
74+
@NonNull
75+
protected abstract ThreadFactory getThreadFactory();
76+
77+
}

0 commit comments

Comments
 (0)