Skip to content

"MEDIUM: FCM topic subscription blocks synchronously in request loop, causing API latency" #15

Description

@pyrahermesagent

Severity: MEDIUM

File: ApiApp/views.py
Lines: 64-69

Description

FCMTokenUpdateView calls messaging.subscribe_to_topic() synchronously inside a request loop. Each FCM topic subscription is an HTTP call to Google Cloud, and these calls are made sequentially without any timeout or error recovery.

Current code:

topics = ["global", device.type]
for topic in topics:
    try:
        messaging.subscribe_to_topic([device.registration_id], topic)
    except Exception as e:
        logger.debug(f"Failed to subscribe device {device.id} to topic {topic}: {e}")

Problems:

  1. Blocking I/O: Each subscribe_to_topic() call makes an HTTP request to Google Cloud FCM. If the network is slow or Google Cloud is under load, the request hangs.
  2. No timeout: The HTTP call has no explicit timeout, so a slow response can block the request indefinitely.
  3. Request latency: The user experience is degraded because the API response waits for all topic subscriptions to complete before returning.
  4. Error swallowing: Failures are only logged at debug level — no visibility into subscription failures in production.

Impact

  • API latency: Every FCM token update request is delayed by network round-trips to Google Cloud
  • User experience: Mobile apps wait longer for token registration confirmation
  • Silent failures: Topic subscription errors are invisible in production logging

Root Cause

Synchronous FCM API calls inside the request handler without timeout, async offloading, or error monitoring.

Fix

Move topic subscriptions to a background task (Celery/Django Q) with proper timeout and error handling:

# Offload to background task
from django_q.tasks import async_task

async_task("ApiApp.tasks.subscribe_device_to_topics", device.id, topics)

# Or use a simple async approach with timeout
import asyncio

async def subscribe_topics(device, topics):
    for topic in topics:
        try:
            await asyncio.wait_for(
                messaging.subscribe_to_topic([device.registration_id], topic),
                timeout=5.0
            )
        except Exception as e:
            logger.error(f"Failed to subscribe device {device.id} to topic {topic}: {e}")

@valentynhol please review.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions