From afd481e33b2ec2ae2a67eef02ba66c888a9f999b Mon Sep 17 00:00:00 2001 From: AustinBenoit Date: Wed, 29 Jul 2026 11:39:56 -0400 Subject: [PATCH 1/8] fix(auth): remove log spam when USE_AUTH_EMULATOR is not set (fixes #1629) --- auth/src/desktop/rpcs/auth_request.cc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/auth/src/desktop/rpcs/auth_request.cc b/auth/src/desktop/rpcs/auth_request.cc index 4d50cf0ddc..c4229242c9 100644 --- a/auth/src/desktop/rpcs/auth_request.cc +++ b/auth/src/desktop/rpcs/auth_request.cc @@ -102,14 +102,11 @@ std::string AuthRequest::GetUrl() { void AuthRequest::CheckEnvEmulator() { if (!env_emulator_url.empty()) { - LogInfo("Environment Emulator Url already set: %s", - env_emulator_url.c_str()); return; } // Use emulator as long as this env variable is set, regardless its value. if (std::getenv("USE_AUTH_EMULATOR") == nullptr) { - LogInfo("USE_AUTH_EMULATOR not set."); return; } env_emulator_url.append(kEmulatorLocalHost); @@ -121,6 +118,7 @@ void AuthRequest::CheckEnvEmulator() { } else { env_emulator_url.append(std::getenv("AUTH_EMULATOR_PORT")); } + LogInfo("Using Auth Emulator: %s", env_emulator_url.c_str()); } } // namespace auth From e9ec2599f4a0da2c8c9ad8ac093e9b4f713e7046 Mon Sep 17 00:00:00 2001 From: AustinBenoit Date: Wed, 29 Jul 2026 11:55:16 -0400 Subject: [PATCH 2/8] refactor(auth): move USE_AUTH_EMULATOR init to AuthImpl and add release note (fixes #1629) --- auth/src/desktop/auth_desktop.cc | 13 +++++++++++ auth/src/desktop/rpcs/auth_request.cc | 33 ++------------------------- auth/src/desktop/rpcs/auth_request.h | 2 -- release_build_files/readme.md | 4 ++++ 4 files changed, 19 insertions(+), 33 deletions(-) diff --git a/auth/src/desktop/auth_desktop.cc b/auth/src/desktop/auth_desktop.cc index 99ae3419bf..0fd0af2c62 100644 --- a/auth/src/desktop/auth_desktop.cc +++ b/auth/src/desktop/auth_desktop.cc @@ -92,6 +92,19 @@ void* CreatePlatformAuth(App* const app) { AuthImpl* const auth = new AuthImpl(); auth->api_key = app->options().api_key(); auth->app_name = app->name(); + + // Check environment variables for emulator if set. + if (std::getenv("USE_AUTH_EMULATOR") != nullptr) { + auth->assigned_emulator_url.append(kEmulatorLocalHost); + auth->assigned_emulator_url.append(":"); + if (std::getenv("AUTH_EMULATOR_PORT") == nullptr) { + auth->assigned_emulator_url.append(kEmulatorPort); + } else { + auth->assigned_emulator_url.append(std::getenv("AUTH_EMULATOR_PORT")); + } + LogInfo("Using Auth Emulator: %s", auth->assigned_emulator_url.c_str()); + } + return auth; } diff --git a/auth/src/desktop/rpcs/auth_request.cc b/auth/src/desktop/rpcs/auth_request.cc index c4229242c9..a59fbb6927 100644 --- a/auth/src/desktop/rpcs/auth_request.cc +++ b/auth/src/desktop/rpcs/auth_request.cc @@ -42,8 +42,6 @@ const char* kHeaderFirebaseLocale = "X-Firebase-Locale"; AuthRequest::AuthRequest(::firebase::App& app, const char* schema, bool deliver_heartbeat) : RequestJson(schema), app(app) { - CheckEnvEmulator(); - if (deliver_heartbeat) { std::shared_ptr heartbeat_controller = app.GetHeartbeatController(); @@ -76,50 +74,23 @@ AuthRequest::AuthRequest(::firebase::App& app, const char* schema, } std::string AuthRequest::GetUrl() { - std::string emulator_url; Auth* auth_ptr = Auth::GetAuth(&app); std::string assigned_emulator_url = static_cast(auth_ptr->auth_data_->auth_impl) ->assigned_emulator_url; - if (assigned_emulator_url.empty()) { - emulator_url = env_emulator_url; - } else { - emulator_url = assigned_emulator_url; - } - if (emulator_url.empty()) { + if (assigned_emulator_url.empty()) { std::string url(kHttps); url += kServerURL; return url; } else { std::string url(kHttp); - url += emulator_url; + url += assigned_emulator_url; url += "/"; url += kServerURL; return url; } } -void AuthRequest::CheckEnvEmulator() { - if (!env_emulator_url.empty()) { - return; - } - - // Use emulator as long as this env variable is set, regardless its value. - if (std::getenv("USE_AUTH_EMULATOR") == nullptr) { - return; - } - env_emulator_url.append(kEmulatorLocalHost); - env_emulator_url.append(":"); - // Use AUTH_EMULATOR_PORT if it is set to non empty string, - // otherwise use the default port. - if (std::getenv("AUTH_EMULATOR_PORT") == nullptr) { - env_emulator_url.append(kEmulatorPort); - } else { - env_emulator_url.append(std::getenv("AUTH_EMULATOR_PORT")); - } - LogInfo("Using Auth Emulator: %s", env_emulator_url.c_str()); -} - } // namespace auth } // namespace firebase diff --git a/auth/src/desktop/rpcs/auth_request.h b/auth/src/desktop/rpcs/auth_request.h index 889e04aea7..fe14e1c96b 100644 --- a/auth/src/desktop/rpcs/auth_request.h +++ b/auth/src/desktop/rpcs/auth_request.h @@ -55,8 +55,6 @@ class AuthRequest std::string GetUrl(); private: - void CheckEnvEmulator(); - std::string env_emulator_url; ::firebase::App& app; }; diff --git a/release_build_files/readme.md b/release_build_files/readme.md index bfb9836459..e777ff922a 100644 --- a/release_build_files/readme.md +++ b/release_build_files/readme.md @@ -613,6 +613,10 @@ workflow use only during the development of your app, not for publicly shipping code. ## Release Notes +### Unreleased +- Changes + - Auth (Desktop): Fixed log spam and high CPU utilization when offline by moving `USE_AUTH_EMULATOR` environment variable check to initialization and eliminating per-request logging (#1629). + ### 13.10.0 - Changes - General (Android): Update to Firebase Android BoM version 34.16.0. From 434662bf38c5471b2b13d97247406b9526d0a085 Mon Sep 17 00:00:00 2001 From: AustinBenoit Date: Wed, 29 Jul 2026 12:00:15 -0400 Subject: [PATCH 3/8] feat(auth): log emulator endpoint when Auth::UseEmulator is called in code (fixes #1629) --- auth/src/desktop/auth_desktop.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/auth/src/desktop/auth_desktop.cc b/auth/src/desktop/auth_desktop.cc index 0fd0af2c62..2049aabddc 100644 --- a/auth/src/desktop/auth_desktop.cc +++ b/auth/src/desktop/auth_desktop.cc @@ -589,6 +589,7 @@ void Auth::UseEmulator(std::string host, uint32_t port) { auth_impl->assigned_emulator_url.append(host); auth_impl->assigned_emulator_url.append(":"); auth_impl->assigned_emulator_url.append(std::to_string(port)); + LogInfo("Using Auth Emulator: %s", auth_impl->assigned_emulator_url.c_str()); } void InitializeTokenRefresher(AuthData* auth_data) { From 112cf7863c58c963464c4fa4dfebe6dd251a2e6f Mon Sep 17 00:00:00 2001 From: AustinBenoit Date: Wed, 29 Jul 2026 12:00:30 -0400 Subject: [PATCH 4/8] docs: rename Unreleased section to Upcoming in release notes --- release_build_files/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release_build_files/readme.md b/release_build_files/readme.md index e777ff922a..eedcc42cca 100644 --- a/release_build_files/readme.md +++ b/release_build_files/readme.md @@ -613,7 +613,7 @@ workflow use only during the development of your app, not for publicly shipping code. ## Release Notes -### Unreleased +### Upcoming - Changes - Auth (Desktop): Fixed log spam and high CPU utilization when offline by moving `USE_AUTH_EMULATOR` environment variable check to initialization and eliminating per-request logging (#1629). From 490587b202f5898e5f7b821e30859878dc5f67a1 Mon Sep 17 00:00:00 2001 From: AustinBenoit Date: Wed, 29 Jul 2026 12:19:06 -0400 Subject: [PATCH 5/8] fix(auth): add 30s retry backoff in IdTokenRefreshThread on network failure to prevent tight CPU spin loop (fixes #1629) --- auth/src/desktop/auth_desktop.cc | 7 +++++-- auth/src/desktop/auth_desktop.h | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/auth/src/desktop/auth_desktop.cc b/auth/src/desktop/auth_desktop.cc index 2049aabddc..7c89c3ba96 100644 --- a/auth/src/desktop/auth_desktop.cc +++ b/auth/src/desktop/auth_desktop.cc @@ -714,8 +714,11 @@ void IdTokenRefreshThread::Initialize(AuthData* auth_data) { // is completed. future_sem.Wait(); - // (We don't actually care about the results of the token request. - // The token listener will handle that.) + // If token refresh failed (e.g. network outage), wait before retrying + // to prevent a 0ms tight retry loop and high CPU usage. + if (future.error() != 0 && !refresh_thread->is_shutting_down()) { + refresh_thread->wakeup_sem_.TimedWait(kMsPerTokenRefreshFailedRetry); + } } else { auth->auth_data_->future_impl.mutex().Release(); refresh_thread->ref_count_mutex_.Release(); diff --git a/auth/src/desktop/auth_desktop.h b/auth/src/desktop/auth_desktop.h index 42309ee0cb..b81ab9f60d 100644 --- a/auth/src/desktop/auth_desktop.h +++ b/auth/src/desktop/auth_desktop.h @@ -179,6 +179,8 @@ struct AuthImpl { const int kMinutesPerTokenRefresh = 58; const int kMsPerTokenRefresh = kMinutesPerTokenRefresh * internal::kMillisecondsPerMinute; +// Retry delay when token refresh fails (e.g., due to network outage). +const int kMsPerTokenRefreshFailedRetry = 30 * 1000; void InitializeUserDataPersist(AuthData* auth_data); void DestroyUserDataPersist(AuthData* auth_data); From f2db7dfe6105cf42e17549efa4a6122ada45ff01 Mon Sep 17 00:00:00 2001 From: AustinBenoit Date: Wed, 29 Jul 2026 12:28:38 -0400 Subject: [PATCH 6/8] feat(auth): add exponential backoff with jitter on token refresh failures (fixes #1629) --- auth/src/desktop/auth_desktop.cc | 20 ++++++++++++++++---- auth/src/desktop/auth_desktop.h | 5 +++-- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/auth/src/desktop/auth_desktop.cc b/auth/src/desktop/auth_desktop.cc index 7c89c3ba96..25bf9755b2 100644 --- a/auth/src/desktop/auth_desktop.cc +++ b/auth/src/desktop/auth_desktop.cc @@ -681,6 +681,8 @@ void IdTokenRefreshThread::Initialize(AuthData* auth_data) { thread_ = firebase::Thread( [](IdTokenRefreshThread* refresh_thread) { Auth* auth = refresh_thread->auth; + int retry_backoff_ms = kMinRetryBackoffMs; + while (!refresh_thread->is_shutting_down()) { // Note: Make sure to always make future_impl.mutex the innermost // lock, to prevent deadlocks! @@ -714,10 +716,20 @@ void IdTokenRefreshThread::Initialize(AuthData* auth_data) { // is completed. future_sem.Wait(); - // If token refresh failed (e.g. network outage), wait before retrying - // to prevent a 0ms tight retry loop and high CPU usage. - if (future.error() != 0 && !refresh_thread->is_shutting_down()) { - refresh_thread->wakeup_sem_.TimedWait(kMsPerTokenRefreshFailedRetry); + if (future.error() != 0) { + // Token refresh failed (e.g. network outage). Wait with exponential backoff + // to prevent tight retry loops and avoid overloading backend servers upon reconnect. + if (!refresh_thread->is_shutting_down()) { + int jitter_ms = rand() % 1000; + int wait_time_ms = retry_backoff_ms + jitter_ms; + // Double backoff for consecutive failures up to max 30 minutes. + retry_backoff_ms = std::min(kMaxRetryBackoffMs, retry_backoff_ms * 2); + refresh_thread->wakeup_sem_.TimedWait(wait_time_ms); + } + continue; + } else { + // Refresh succeeded! Reset backoff to minimum interval. + retry_backoff_ms = kMinRetryBackoffMs; } } else { auth->auth_data_->future_impl.mutex().Release(); diff --git a/auth/src/desktop/auth_desktop.h b/auth/src/desktop/auth_desktop.h index b81ab9f60d..2703f792e1 100644 --- a/auth/src/desktop/auth_desktop.h +++ b/auth/src/desktop/auth_desktop.h @@ -179,8 +179,9 @@ struct AuthImpl { const int kMinutesPerTokenRefresh = 58; const int kMsPerTokenRefresh = kMinutesPerTokenRefresh * internal::kMillisecondsPerMinute; -// Retry delay when token refresh fails (e.g., due to network outage). -const int kMsPerTokenRefreshFailedRetry = 30 * 1000; +// Exponential backoff parameters when token refresh fails (e.g. network outage). +const int kMinRetryBackoffMs = 30 * 1000; // 30 seconds base delay +const int kMaxRetryBackoffMs = 30 * 60 * 1000; // 30 minutes max delay void InitializeUserDataPersist(AuthData* auth_data); void DestroyUserDataPersist(AuthData* auth_data); From f19fc746e9030570a0d2893888efaeb22dbe7d63 Mon Sep 17 00:00:00 2001 From: AustinBenoit Date: Wed, 29 Jul 2026 12:35:47 -0400 Subject: [PATCH 7/8] feat(auth): match Android DefaultTokenRefresher backoff (30s to 16m) without jitter (fixes #1629) --- auth/src/desktop/auth_desktop.cc | 6 ++---- auth/src/desktop/auth_desktop.h | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/auth/src/desktop/auth_desktop.cc b/auth/src/desktop/auth_desktop.cc index 25bf9755b2..165e47d1eb 100644 --- a/auth/src/desktop/auth_desktop.cc +++ b/auth/src/desktop/auth_desktop.cc @@ -719,12 +719,10 @@ void IdTokenRefreshThread::Initialize(AuthData* auth_data) { if (future.error() != 0) { // Token refresh failed (e.g. network outage). Wait with exponential backoff // to prevent tight retry loops and avoid overloading backend servers upon reconnect. + // Matches Android DefaultTokenRefresher (30s initial, doubling up to 16m max). if (!refresh_thread->is_shutting_down()) { - int jitter_ms = rand() % 1000; - int wait_time_ms = retry_backoff_ms + jitter_ms; - // Double backoff for consecutive failures up to max 30 minutes. + refresh_thread->wakeup_sem_.TimedWait(retry_backoff_ms); retry_backoff_ms = std::min(kMaxRetryBackoffMs, retry_backoff_ms * 2); - refresh_thread->wakeup_sem_.TimedWait(wait_time_ms); } continue; } else { diff --git a/auth/src/desktop/auth_desktop.h b/auth/src/desktop/auth_desktop.h index 2703f792e1..7aa5e8a0bb 100644 --- a/auth/src/desktop/auth_desktop.h +++ b/auth/src/desktop/auth_desktop.h @@ -179,9 +179,9 @@ struct AuthImpl { const int kMinutesPerTokenRefresh = 58; const int kMsPerTokenRefresh = kMinutesPerTokenRefresh * internal::kMillisecondsPerMinute; -// Exponential backoff parameters when token refresh fails (e.g. network outage). +// Exponential backoff parameters when token refresh fails (e.g. network outage), matching Android DefaultTokenRefresher. const int kMinRetryBackoffMs = 30 * 1000; // 30 seconds base delay -const int kMaxRetryBackoffMs = 30 * 60 * 1000; // 30 minutes max delay +const int kMaxRetryBackoffMs = 16 * 60 * 1000; // 16 minutes max delay void InitializeUserDataPersist(AuthData* auth_data); void DestroyUserDataPersist(AuthData* auth_data); From d5a0dd4c5caf98798a0e3635e6b80ef0caa767e6 Mon Sep 17 00:00:00 2001 From: AustinBenoit Date: Wed, 29 Jul 2026 12:37:08 -0400 Subject: [PATCH 8/8] fix(auth): add missing #include in auth_desktop.cc --- auth/src/desktop/auth_desktop.cc | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/auth/src/desktop/auth_desktop.cc b/auth/src/desktop/auth_desktop.cc index 165e47d1eb..fe4764de63 100644 --- a/auth/src/desktop/auth_desktop.cc +++ b/auth/src/desktop/auth_desktop.cc @@ -14,6 +14,7 @@ #include "auth/src/desktop/auth_desktop.h" +#include #include #include #include @@ -717,12 +718,14 @@ void IdTokenRefreshThread::Initialize(AuthData* auth_data) { future_sem.Wait(); if (future.error() != 0) { - // Token refresh failed (e.g. network outage). Wait with exponential backoff - // to prevent tight retry loops and avoid overloading backend servers upon reconnect. - // Matches Android DefaultTokenRefresher (30s initial, doubling up to 16m max). + // Token refresh failed (e.g. network outage). Wait with + // exponential backoff to prevent tight retry loops and avoid + // overloading backend servers upon reconnect. Matches Android + // DefaultTokenRefresher (30s initial, doubling up to 16m max). if (!refresh_thread->is_shutting_down()) { refresh_thread->wakeup_sem_.TimedWait(retry_backoff_ms); - retry_backoff_ms = std::min(kMaxRetryBackoffMs, retry_backoff_ms * 2); + retry_backoff_ms = + std::min(kMaxRetryBackoffMs, retry_backoff_ms * 2); } continue; } else {