#13628·appwrite

Bug Report: Parallel ordinary uploads crash production HTTP workers

Author: ChiragAgg5kCreated Sep 11, 2026Updated Sep 12, 2026
Labelsproduct / storageproduct / authproduct / vcsproduct / avatarssdk / cli

Summary

B1 — Priority: highest; affects normal client traffic.

Concurrent ordinary uploads through the production image's public HTTP port crash HTTP workers with a fatal Swoole\Error. Requests use separate file IDs and small bodies; this does not depend on large files or same-file locking.

Reproduction

  1. On the production image, create a bucket granting create("any").
  2. Issue concurrent unauthenticated multipart uploads through the public HTTP port, using separate file IDs and small bodies.
  3. Record per-request connection failures and HTTP responses, and inspect server logs for fatal errors and internal HTTP-worker exits.

Existing reproducer: scripts/concurrent-upload-small.py, using the corresponding private fixture state and RELEASE_ROOT / RELEASE_BASE.

Observed results

Environment Connection-level failures HTTP 201 responses
PostgreSQL + named Redis ACL 29/64 35/64
Independent fresh MariaDB + unauthenticated Redis 4/16 12/16

Both runs emitted fatal Swoole\Error and HTTP-worker exit status 255:

Socket#16 has already been bound to another coroutine#14,
reading of the same socket in coroutine#9 at the same time is not allowed
.../utopia-php/abuse/src/Abuse/Adapters/TimeLimit/Redis.php:49

The public health endpoint can recover after requests have already been lost. Docker container restart counts alone miss these internal worker exits.

A separate duplicate-chunk E2E case first exposed the same fatal error. Other chunk requests then waited about 120 seconds and received 429 File upload is busy. That case was interrupted, not reported as passing.

Expected behavior

Concurrent ordinary uploads should not share a Redis socket unsafely or crash HTTP workers. Valid uploads should complete normally; any intentional rejection should return an HTTP response rather than terminate the connection.

Suspected ownership — not a verified fix

app/init/resources.php:332 captures the shared redis resource in the timelimit factory. Concurrent abuse checks use that socket in app/controllers/shared/api.php:587.

Pool/lease or coroutine-scoped ownership must cover this path too. Root-cause analysis alone is not a verified fix.

Evidence references

These are paths in the original investigation artifacts, not attachments or public links on this issue:

  • Production crash: evidence/production-worker-crash.txt
  • Production server log: logs/production-concurrency-crash.log
  • 64-request results: evidence/production-parallel-upload-results.json
  • Independent MariaDB server log: logs/mariadb-production-crash.log
  • 16-request MariaDB results: mariadb/evidence/production-parallel-upload-results.json
  • Initial duplicate-upload crash: evidence/concurrent-upload-worker-crash.txt

The initial run's trailing health request mistakenly used a key lacking public scope. That unrelated 401 is not the blocker; the saved per-request failures and server fatal traces are the evidence. The script's trailing health call has since been corrected.

Verification required

  • Re-run concurrent small, distinct-file uploads through the public HTTP port on both environments above.
  • Verify per-request results and server logs: no connection-level failures, fatal Swoole errors, or internal HTTP-worker exits.
  • Re-run the interrupted duplicate-chunk E2E case and report its actual outcome separately.