Skip to content

Commit e35216d

Browse files
authored
Stronger Shutdown Semantics in Database (#90)
* Implementing stronger shutdown semantics for database (specifically RunLoop) * Handling the case where the DB is not fully initialized * Removing test file * Running destroy and onClosed on RunLoop
1 parent fc56292 commit e35216d

File tree

6 files changed

+61
-28
lines changed

6 files changed

+61
-28
lines changed

src/main/java/com/google/firebase/database/FirebaseDatabase.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -358,12 +358,7 @@ void destroy() {
358358
if (destroyed.get()) {
359359
return;
360360
}
361-
362-
if (repo != null) {
363-
RepoManager.interrupt(repo);
364-
repo = null;
365-
}
366-
RepoManager.interrupt(getConfig());
361+
RepoManager.destroy(getConfig());
367362
destroyed.compareAndSet(false, true);
368363
}
369364
}

src/main/java/com/google/firebase/database/connection/PersistentConnectionImpl.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,11 @@ public void onDisconnect(Connection.DisconnectReason reason) {
283283
this.connectionState = ConnectionState.Disconnected;
284284
this.realtime = null;
285285
this.hasOnDisconnects = false;
286-
requestCBHash.clear();
286+
if (inactivityTimer != null) {
287+
logger.debug("cancelling idle time checker");
288+
inactivityTimer.cancel(false);
289+
inactivityTimer = null;
290+
}
287291
cancelSentTransactions();
288292
if (shouldReconnect()) {
289293
long timeSinceLastConnectSucceeded =

src/main/java/com/google/firebase/database/connection/WebsocketConnection.java

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ private WSClient createConnection(
9494
return new WSClientTubesock(ws);
9595
}
9696

97-
public void open() {
97+
void open() {
9898
conn.connect();
9999
connectTimeout =
100100
executorService.schedule(
@@ -126,6 +126,7 @@ public void close() {
126126
}
127127
if (keepAlive != null) {
128128
keepAlive.cancel(true);
129+
keepAlive = null;
129130
}
130131
}
131132

@@ -139,8 +140,8 @@ public void send(Map<String, Object> message) {
139140
conn.send("" + segs.length);
140141
}
141142

142-
for (int i = 0; i < segs.length; ++i) {
143-
conn.send(segs[i]);
143+
for (String seg : segs) {
144+
conn.send(seg);
144145
}
145146
} catch (IOException e) {
146147
logger.error("Failed to serialize message: " + message.toString(), e);
@@ -156,7 +157,6 @@ private void appendFrame(String message) {
156157
try {
157158
frameReader.freeze();
158159
Map<String, Object> decoded = JsonMapper.parseJson(frameReader.toString());
159-
frameReader = null;
160160
if (logger.logsDebug()) {
161161
logger.debug("handleIncomingFrame complete frame: " + decoded);
162162
}
@@ -169,6 +169,8 @@ private void appendFrame(String message) {
169169
logger.error("Error parsing frame (cast error): " + frameReader.toString(), e);
170170
close();
171171
shutdown();
172+
} finally {
173+
frameReader = null;
172174
}
173175
}
174176
}
@@ -221,10 +223,8 @@ private void resetKeepAlive() {
221223
if (logger.logsDebug()) {
222224
logger.debug("Reset keepAlive. Remaining: " + keepAlive.getDelay(TimeUnit.MILLISECONDS));
223225
}
224-
} else {
225-
if (logger.logsDebug()) {
226-
logger.debug("Reset keepAlive");
227-
}
226+
} else if (logger.logsDebug()) {
227+
logger.debug("Reset keepAlive");
228228
}
229229
keepAlive = executorService.schedule(nop(), KEEP_ALIVE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
230230
}
@@ -256,6 +256,7 @@ private void onClosed() {
256256
conn = null;
257257
if (keepAlive != null) {
258258
keepAlive.cancel(false);
259+
keepAlive = null;
259260
}
260261
}
261262

@@ -333,17 +334,17 @@ public void run() {
333334

334335
@Override
335336
public void onClose() {
336-
final String logMessage = "closed";
337-
executorService.execute(
338-
new Runnable() {
339-
@Override
340-
public void run() {
341-
if (logger.logsDebug()) {
342-
logger.debug(logMessage);
343-
}
344-
onClosed();
345-
}
346-
});
337+
if (logger.logsDebug()) {
338+
logger.debug("closed");
339+
}
340+
if (!isClosed) {
341+
executorService.execute(new Runnable() {
342+
@Override
343+
public void run() {
344+
onClosed();
345+
}
346+
});
347+
}
347348
}
348349

349350
@Override

src/main/java/com/google/firebase/database/core/Context.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,12 @@ private void restartServices() {
130130

131131
void stop() {
132132
stopped = true;
133-
eventTarget.shutdown();
134-
runLoop.shutdown();
133+
if (eventTarget != null) {
134+
eventTarget.shutdown();
135+
}
136+
if (runLoop != null) {
137+
runLoop.shutdown();
138+
}
135139
}
136140

137141
protected void assertUnfrozen() {

src/main/java/com/google/firebase/database/core/RepoManager.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ public static Repo createRepo(Context ctx, RepoInfo info, FirebaseDatabase datab
4949
return instance.createLocalRepo(ctx, info, database);
5050
}
5151

52+
public static void destroy(Context ctx) {
53+
try {
54+
instance.destroyInternal(ctx);
55+
} finally {
56+
ctx.stop();
57+
}
58+
}
59+
5260
public static void interrupt(Context ctx) {
5361
instance.interruptInternal(ctx);
5462
}
@@ -134,6 +142,26 @@ public void run() {
134142
}
135143
}
136144

145+
private void destroyInternal(final Context ctx) {
146+
RunLoop runLoop = ctx.getRunLoop();
147+
if (runLoop != null) {
148+
// RunLoop gets initialized before any Repo. Therefore, we can assume that when RunLoop
149+
// is not present, there's nothing to cleanup.
150+
runLoop.scheduleNow(new Runnable() {
151+
@Override
152+
public void run() {
153+
synchronized (repos) {
154+
if (repos.containsKey(ctx)) {
155+
for (Repo repo : repos.get(ctx).values()) {
156+
repo.interrupt();
157+
}
158+
}
159+
}
160+
}
161+
});
162+
}
163+
}
164+
137165
private void resumeInternal(final Context ctx) {
138166
RunLoop runLoop = ctx.getRunLoop();
139167
if (runLoop != null) {

src/main/java/com/google/firebase/internal/RevivingScheduledExecutor.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ public RevivingScheduledExecutor(
9191
INSTANCE_COUNTER.incrementAndGet();
9292
this.initialDelayMs = initialDelayMs;
9393
this.timeoutMs = timeoutMs;
94+
setRemoveOnCancelPolicy(true);
9495
setThreadFactory(
9596
new ThreadFactory() {
9697
@Override

0 commit comments

Comments
 (0)