MEDIUM: Fix mixed async/sync API endpoints with fire-and-forget patterns #40

Open
opened 2026-02-20 14:49:48 +02:00 by Koko210 · 0 comments
Owner

Problem

bot/api.py mixes async def and def endpoint handlers inconsistently. Several sync endpoints use globals.client.loop.create_task() to schedule async work but return a success response immediately, with no error feedback to the caller.

Example Pattern (problematic)

@app.post('/some-action')
def some_action():  # sync endpoint
    task = globals.client.loop.create_task(do_async_work())  # fire and forget
    return {'status': 'ok'}  # returns immediately, errors are silently lost

Problems with this pattern:

  1. The caller receives 'ok' even if the async work fails
  2. Exceptions in the created task are silently swallowed (no await, no callback)
  3. The task variable is assigned but never used — if garbage collected before completion, the task may be cancelled
  4. No way for the dashboard/caller to know if the operation actually succeeded

Known Affected Endpoints

  • delete_conversation
  • delete_all_conversations
  • Various DM-related actions
  • Some server management operations

Proposed Solution

Option A: Make endpoints properly async (preferred)

@app.post('/some-action')
async def some_action():  # async endpoint
    try:
        await do_async_work()
        return {'status': 'ok'}
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

Option B: Background tasks with status tracking

For long-running operations, use FastAPI BackgroundTasks with a job status endpoint:
from fastapi import BackgroundTasks

@app.post('/some-action')
async def some_action(background_tasks: BackgroundTasks):
    job_id = create_job_id()
    background_tasks.add_task(do_async_work, job_id)
    return {'status': 'accepted', 'job_id': job_id}

@app.get('/jobs/{job_id}')
async def get_job_status(job_id: str):
    return get_job(job_id)

Option C: At minimum, add error callbacks

task = globals.client.loop.create_task(do_async_work())
task.add_done_callback(lambda t: log_error(t.exception()) if t.exception() else None)

Impact

  • Risk: Low (making sync endpoints async is safe in FastAPI)
  • Effort: Medium (audit all fire-and-forget patterns)
  • Benefit: Proper error propagation, debuggability, reliable dashboard feedback

Files Affected

  • bot/api.py (all sync endpoints using create_task)
## Problem bot/api.py mixes async def and def endpoint handlers inconsistently. Several sync endpoints use globals.client.loop.create_task() to schedule async work but return a success response immediately, with no error feedback to the caller. ### Example Pattern (problematic) @app.post('/some-action') def some_action(): # sync endpoint task = globals.client.loop.create_task(do_async_work()) # fire and forget return {'status': 'ok'} # returns immediately, errors are silently lost Problems with this pattern: 1. The caller receives 'ok' even if the async work fails 2. Exceptions in the created task are silently swallowed (no await, no callback) 3. The task variable is assigned but never used — if garbage collected before completion, the task may be cancelled 4. No way for the dashboard/caller to know if the operation actually succeeded ### Known Affected Endpoints - delete_conversation - delete_all_conversations - Various DM-related actions - Some server management operations ## Proposed Solution ### Option A: Make endpoints properly async (preferred) @app.post('/some-action') async def some_action(): # async endpoint try: await do_async_work() return {'status': 'ok'} except Exception as e: raise HTTPException(status_code=500, detail=str(e)) ### Option B: Background tasks with status tracking For long-running operations, use FastAPI BackgroundTasks with a job status endpoint: from fastapi import BackgroundTasks @app.post('/some-action') async def some_action(background_tasks: BackgroundTasks): job_id = create_job_id() background_tasks.add_task(do_async_work, job_id) return {'status': 'accepted', 'job_id': job_id} @app.get('/jobs/{job_id}') async def get_job_status(job_id: str): return get_job(job_id) ### Option C: At minimum, add error callbacks task = globals.client.loop.create_task(do_async_work()) task.add_done_callback(lambda t: log_error(t.exception()) if t.exception() else None) ## Impact - Risk: Low (making sync endpoints async is safe in FastAPI) - Effort: Medium (audit all fire-and-forget patterns) - Benefit: Proper error propagation, debuggability, reliable dashboard feedback ## Files Affected - bot/api.py (all sync endpoints using create_task)
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Koko210/miku-discord#40