firebase / firebase/firebase-functions-python

`firestore_fn` builds a new `firestore_v1.Client` (and calls `google.auth.default()`) on every event delivery

未關閉
#309 2 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視
bug reproduced
主要語言
Python
星號
167
分支
34
PR 合併指標
30 天內沒有已合併 PR

描述

**Version:** firebase-functions 0.5.0 (same code on `main` / 0.6.0), Python 3.14, Cloud Run functions gen2, google-auth 2.53.0

**What happens**

`firebase_functions/firestore_fn.py::_firestore_endpoint_handler` does, per delivery:

```python
if _DEFAULT_APP_NAME not in _apps:
initialize_app()
app = get_app()
firestore_client = _firestore_v1.Client(project=app.project_id, database=event_database)
```

`google.cloud.firestore_v1.Client(...)` with no `credentials=` calls `google.auth.default()`,
which on Cloud Run pings the metadata server (`google.auth.compute_engine._metadata.ping`).
That ping only retries on `TransportError`; any non-`200 + Metadata-Flavor: Google` answer
returns `False` immediately, and gen2's sandbox has no DMI fallback, so the call raises
`DefaultCredentialsError` — before the user's handler runs and before any user-level error
handling can see it.

We observed this on a **warm** instance that had been serving for three hours and had served
nine events in the previous thirty seconds: four concurrent deliveries at
`2026-08-28T21:40:13Z` all died in the framework with

```
File "firebase_functions/firestore_fn.py", line 145, in _firestore_endpoint_handler
firestore_client = _firestore_v1.Client(project=app.project_id, database=event_database)
...
File "google/auth/_default.py", line 748, in default
raise exceptions.DefaultCredentialsError(_CLOUD_SDK_MISSING_CREDENTIALS)
```

while `firebase_admin` in the same process already held perfectly good cached credentials.
Thirty seconds later the same instance logged a TLS EOF talking to an unrelated host, so the
trigger was a brief network hiccup on that instance — but the per-event credential lookup is
what turned it into four dropped events (the trigger had no retry policy).

**Why it matters**

- The client is only used to decode the snapshot (`_firestore_helpers.decode_dict(...,
firestore_client)` and `DocumentReference`); it never talks to Firestore for that. A
credential lookup per event is pure overhead and a per-event failure point.
- The failure happens before user code, so `retry` on the trigger is the only mitigation
available to users.

**Suggested fix**

Reuse the admin app's client (`firebase_admin.firestore.client(app, database_id=...)`) or
cache one `firestore_v1.Client` per `(project, database)` at module level, so
`google.auth.default()` runs once per process instead of once per event. Passing
`credentials=app.credential.get_credential()` into the `Client` constructor would also avoid
the per-event lookup.

**Related gap (worth a second issue or the same one)**

`FirestoreOptions._endpoint` hardcodes `retry=False` and `FirestoreOptions` does not accept a
`retry` keyword (`TypeError: FirestoreOptions.__init__() got an unexpected keyword argument
'retry'`), even though `EventHandlerOptions.retry` exists and the Node SDK exposes `retry` on
Firestore triggers. The only way to get an Eventarc retry policy from Python today is to
mutate `fn.__firebase_endpoint__.eventTrigger["retry"]` after decoration.

**Repro**

Any Firestore trigger; make the metadata server return a non-200 for one request (or block
`169.254.169.254` briefly) on a warm instance and deliver an event. The handler never runs.

貢獻指南

開啟貢獻指南

研究方向

從 firebase_functions/firestore_fn.py::_firestore_endpoint_handler 開始,追蹤 firestore_client 如何傳遞給 firestore_helpers.decode_dict。比較建議的 admin-app 用戶端、按資料庫劃分的快取和明確憑證方案,然後確認快照解碼在每次傳遞時不必查找憑證仍能正常運作。如果包含重試策略缺口,請另外檢查 FirestoreOptions._endpoint。

由索引模型根據 Issue 內容生成。

評估

技術堆疊
google-cloud, python
領域
backend, databases
Issue 類型
缺陷
難度
4/5
預估耗時
3-5 天
活躍度
活躍
描述清晰度
基本清楚
新手友好度
48/100

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。