firebase-ios-sdk: Realtime Database stays offline and doesn't reconnect after sleeping

[REQUIRED] Step 1: Describe your environment

  • Xcode version: 13.2.1
  • Firebase SDK version: 8.15.0
  • Installation method: CocoaPods
  • Firebase Component: Database
  • Target platform(s): macOS

[REQUIRED] Step 2: Describe the problem

Steps to reproduce: Have the mac going to sleep. After waking up, sometimes (with really low frequency), the Realtime Database client would stay offline indefinitely, even if network conditions are good.

We have an app that uses Realtime Database for implementing Presence in Firestore. https://firebase.google.com/docs/firestore/solutions/presence. If we have the app running and the mac goes to sleep, sometimes, after waking up, the Realtime Database client doesn’t reconnect.

We have strong evidence that database.reference(withPath: ".info/connected").observe reported a false value while the mac was sleeping but it never reported true again, even after waking up and having good network conditions.

If we use any operation while Realtime database is offline, like statusReference.setValue to write to the database, the operation never runs the completion callback.

Firestore is able to reconnect properly without problems but Realtime Database client is not working properly.

We saw one case that recovered by itself after a some hours (12 hours after disconnected, 2 hours after the mac woke up). But as we need the Realtime Database to implement Presence, that delay is not acceptable.

Restarting the app makes it work again.

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Comments: 19 (4 by maintainers)

Most upvoted comments

Hi, this issue is more widespread than just macos, it definitely happens on iOS as well in ionic cordova apps (environment info below).

A couple observations and research items of note that may help:

  • iOS suspends the app within ~25 seconds of being backgrounded
  • The Firebase realtime database connection survives about 60 seconds, during this period resuming the app the realtime db will keep working like nothing happened.
  • To observe a ‘frozen’ / ‘dead’ realtime connection, wait at least 90 seconds, I was using 4 mins to know it was dead.
  • Firestore will happily resume, debug level logs show it detecting inactive threads and then reconnecting
  • Realtime detects the app resuming but does not reconnect! No new events, no debug logs saying threads are dead.
  • Using ‘goOnline’ when the app detects a resume does not work
  • Using all kinds of methods of unsubscribing the old angular fire and deleting the conn and starting again on resume does not work, the realtime db still knows of the connection but it’s a tunnel to no where. - However if you use goOnline on resume AND use ‘goOffline’ when the app detects being backgrounded it works! Happily reconnects all prior threads/connections

It’s quite a bit of effort to setup a new ‘minimal’ repo, ionic, angularfire and a subscription to a listval with detection events on resume/background. If someone else has that handy then go for it, I hope this post helps someone else so they don’t spend days searching and experimenting!

Note: using modular angularfire.

ENVIRONMENT

Angular:
Angular CLI: 13.3.10
Node: 14.20.0
Package Manager: yarn 1.22.15
OS: darwin x64

Angular: 13.3.12
... animations, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router, service-worker

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1303.6
@angular-devkit/build-angular   13.3.10
@angular-devkit/core            13.3.10
@angular-devkit/schematics      13.3.10
@angular/cli                    13.3.10
@angular/fire                   7.5.0
@schematics/angular             13.3.10
rxjs                            6.6.7
typescript                      4.6.2
webpack                         5.70.0


Ionic:

   Ionic CLI                     : 6.20.3 (/Users/markterrill/.config/yarn/global/node_modules/@ionic/cli)
   Ionic Framework               : @ionic/angular 6.2.4
   @angular-devkit/build-angular : 13.3.10
   @angular-devkit/schematics    : 13.3.10
   @angular/cli                  : 13.3.10
   @ionic/angular-toolkit        : 3.1.1

Capacitor:

   Capacitor CLI      : 4.5.0
   @capacitor/android : 4.5.0
   @capacitor/core    : 4.1.0
   @capacitor/ios     : 4.5.0

Cordova:

   Cordova CLI       : not installed
   Cordova Platforms : not available
   Cordova Plugins   : not available

Utility:

   cordova-res (update available: 0.15.4) : 0.15.3
   native-run                             : 1.7.1

System:

   Android SDK Tools : 26.1.1 (/Users/markterrill/Library/Android/sdk)
   NodeJS            : v14.20.0 (/Users/markterrill/.nvm/versions/node/v14.20.0/bin/node)
   npm               : 6.14.17
   OS                : macOS Monterey
   Xcode             : Xcode 14.2 Build version 14C18

Hello, I had a similar problem while using Firebase web modular API with Capacitor on iOS. Each time a tried a write operation on the database (with set()), and a network change occurred meanwhile, the database API got stuck forever. My listener to “.info/connected” was never called. I think a found a workaround:

import { Injectable } from '@angular/core';
import { getApp } from 'firebase/app';
import { getDatabase, goOffline, goOnline, onValue, ref } from 'firebase/database';
import { Network } from '@capacitor/network';


@Injectable({
  providedIn: 'root'
})
export class DatabaseService {

  public connectedToDatabase: boolean;
  
  private app = getApp();

  private database = getDatabase(this.app, "...firebasedatabase.app/");

  constructor() {
    // Get network status changes
    // Network status changes when switching between wifi, cellular and offline
    Network.addListener('networkStatusChange', status => {
      console.log('Network status changed', status);
      if (status.connected == true) {
        // We need to go offline first, because realtime database can get stuck when switching between wifi and cellular
        goOffline(this.database);
        goOnline(this.database);
      }
      if (status.connected == false) {
        goOffline(this.database);
      }
    });

    // Now the callback on connectedRef is triggered at each network change
    // And write operations like (set()) don't get stuck anymore
    const connectedRef = ref(this.database, ".info/connected");
    onValue(connectedRef, (snap) => {
      if (snap.val() === true) {
        this.connectedToDatabase = true;
      } else {
        this.connectedToDatabase = false;
      }
    });
  }
}

In my case, when the app goes offline then online the listener to “.info/connected” is never called again. This means when the app reconnects to WiFi again, my app is not able to update its online status again in the realtime database.

this cost me a lot of negative reviews on my app, I guess we need to remove rtdb. I tried @markterrill solution listening the lifecycle of the app but it doesn’t work, rtdb never recovers and it doesn’t show an error or something in debug window.

Thanks, it sounds like the right way to fix this is to just retry on socket connection failures, respecting the NSWorkspace state. I’ll take a crack at implementing this when I have the bandwidth.