Skip to content

Commit be27016

Browse files
committed
fix(fcm): Prevent headers mutation race condition and deduplicate topic error parsing
- Pass a copy of self._fcm_headers in topic management requests to prevent concurrent mutation race conditions in ThreadPoolExecutor. - Extract common error parsing logic from _build_topic_subscription_result_from_requests_error and _build_topic_subscription_result_from_httpx_error into _build_topic_subscription_result.
1 parent c053300 commit be27016

1 file changed

Lines changed: 14 additions & 29 deletions

File tree

firebase_admin/messaging.py

Lines changed: 14 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -658,7 +658,7 @@ def send_request(token: str):
658658
self._client.request(
659659
method,
660660
url=url,
661-
headers=self._fcm_headers,
661+
headers=dict(self._fcm_headers),
662662
json=json_data,
663663
)
664664
return {'success': True}
@@ -702,7 +702,7 @@ async def send_request_async(token: str):
702702
await self._async_client.request(
703703
method,
704704
url=url,
705-
headers=self._fcm_headers,
705+
headers=dict(self._fcm_headers),
706706
json=json_data,
707707
)
708708
return {'success': True}
@@ -754,14 +754,14 @@ def _get_topic_error_code(cls, error_dict: dict, status_code: int) -> str:
754754
}
755755
return status_map.get(status_code, 'UNKNOWN_ERROR')
756756

757-
def _build_topic_subscription_result_from_requests_error(self, error, is_subscribe):
758-
"""Constructs a result dict from a requests error."""
759-
if error.response is not None:
760-
if is_subscribe and error.response.status_code == 409:
757+
def _build_topic_subscription_result(self, response, is_subscribe):
758+
"""Constructs a result dict from a response object."""
759+
if response is not None:
760+
if is_subscribe and response.status_code == 409:
761761
return {'success': True}
762762
error_dict = {}
763763
try:
764-
parsed = error.response.json()
764+
parsed = response.json()
765765
if isinstance(parsed, dict):
766766
error_dict = parsed
767767
except ValueError:
@@ -773,34 +773,19 @@ def _build_topic_subscription_result_from_requests_error(self, error, is_subscri
773773
):
774774
return {'success': True}
775775

776-
error_code = self._get_topic_error_code(error_dict, error.response.status_code)
776+
error_code = self._get_topic_error_code(error_dict, response.status_code)
777777
return {'success': False, 'error': error_code}
778778

779779
return {'success': False, 'error': 'UNKNOWN_ERROR'}
780780

781+
def _build_topic_subscription_result_from_requests_error(self, error, is_subscribe):
782+
"""Constructs a result dict from a requests error."""
783+
return self._build_topic_subscription_result(error.response, is_subscribe)
784+
781785
def _build_topic_subscription_result_from_httpx_error(self, error, is_subscribe):
782786
"""Constructs a result dict from an httpx error."""
783-
if isinstance(error, httpx.HTTPStatusError):
784-
if is_subscribe and error.response.status_code == 409:
785-
return {'success': True}
786-
error_dict = {}
787-
try:
788-
parsed = error.response.json()
789-
if isinstance(parsed, dict):
790-
error_dict = parsed
791-
except ValueError:
792-
pass
793-
794-
error_data = error_dict.get('error')
795-
if is_subscribe and isinstance(error_data, dict) and (
796-
error_data.get('status') == 'ALREADY_EXISTS'
797-
):
798-
return {'success': True}
799-
800-
error_code = self._get_topic_error_code(error_dict, error.response.status_code)
801-
return {'success': False, 'error': error_code}
802-
803-
return {'success': False, 'error': 'UNKNOWN_ERROR'}
787+
response = error.response if isinstance(error, httpx.HTTPStatusError) else None
788+
return self._build_topic_subscription_result(response, is_subscribe)
804789

805790
def _parse_topic_management_results(self, results) -> TopicManagementResponse:
806791
"""Parses individual request results into a TopicManagementResponse."""

0 commit comments

Comments
 (0)