fix(concurrency,durability): address Opus review critical findings

- create_queue: SaveQueue under per-queue lock, not global SyncQueues.Lock
  (fixes fatal concurrent map iteration/write on idempotent CreateQueue of existing FIFO queue)
- delete_message_batch: reject empty ReceiptHandle (JSON protocol bypass deleted all unreceived msgs)
- send_message/send_message_batch: register dedupId only after successful persist
  (retry with same dedupId no longer returns 200 without persisting)
- memory_guard: global HeapAlloc guard (SHARED_SQS_MAX_HEAP_MB, 0=off) in send paths
This commit is contained in:
Repinoid
2026-10-10 17:07:52 +03:00
parent ea7b539d19
commit 521e3d85d0
5 changed files with 80 additions and 12 deletions
+12 -3
View File
@@ -71,7 +71,8 @@ func CreateQueueV1(req *http.Request) (int, interfaces.AbstractResponseBody) {
}
models.SyncQueues.Lock()
if _, exists := models.SyncQueues.Queues[key]; !exists {
existing, exists := models.SyncQueues.Queues[key]
if !exists {
// Проверка лимита очередей тенанта
if t.MaxQueues > 0 && countTenantQueues(t.AccessKey) >= t.MaxQueues {
models.SyncQueues.Unlock()
@@ -79,10 +80,18 @@ func CreateQueueV1(req *http.Request) (int, interfaces.AbstractResponseBody) {
}
log.Infof("Creating Queue: %s (tenant: %s)", queueName, t.ID)
models.SyncQueues.AddQueue(key, queue)
existing = queue
}
// Сохраняем очередь в Redis пока держим Lock — консистентный снапшот
persistence.SaveQueue(key, models.SyncQueues.Queues[key])
models.SyncQueues.Unlock()
// SaveQueue маршалит map'ы очереди (Duplicates/FIFO*/Tags) в JSON. Это нужно
// делать под per-queue блокировкой, а НЕ под глобальным SyncQueues.Lock:
// мутаторы (send/tag/CMV) пишут в эти map под queue.mu, и идемпотентный
// CreateQueue существующей очереди без queue.mu даёт fatal
// "concurrent map iteration and map write" (краш всего процесса).
existing.LockMu()
persistence.SaveQueue(key, existing)
existing.UnlockMu()
respStruct := models.CreateQueueResponse{
Xmlns: models.BaseXmlns,
Result: models.CreateQueueResult{QueueUrl: queueUrl},