P1.1: per-queue блокировки вместо глобального лока на операции с очередью
Все операции с содержимым очереди (send/receive/delete/visibility/purge/attributes/ tags/admin/gauge/tick) переведены на per-queue RWMutex (QueueStore.GetLocked/ GetRLocked + queue.LockMu). Глобальный SyncQueues-лок остался только для map и неизменяемых полей. DLQ-переносы берут лок DLQ отдельно (порядок main→DLQ). marshalQueueMeta больше не мутирует очередь (DTO-снапшот). Устранён дедлок create_queue (setQueueAttributesV1 вызывается вне глобального лока). Добавлены конкурентные тесты; go test -race зелёный. Снимает боттлнек: синхронный Redis-I/O больше не держит глобальный лок.
This commit is contained in:
+58
-16
@@ -142,23 +142,65 @@ func asyncWrite(fn func()) {
|
||||
}()
|
||||
}
|
||||
|
||||
// marshalQueueMeta — сериализует очередь БЕЗ Messages (и без Messages в DLQ).
|
||||
// DeadLetterQueue сохраняется как ссылка (Name/URL/Arn), но без сообщений.
|
||||
// ВАЖНО: вызывать под SyncQueues.Lock() — временно nil'ит Messages.
|
||||
// queueMetaDTO — сериализуемая копия очереди БЕЗ Messages (и без сообщений DLQ).
|
||||
// Отдельный тип, т.к. models.Queue содержит sync.RWMutex и его нельзя копировать/
|
||||
// сериализовать целиком, а временное зануление Messages мутировало бы живую очередь.
|
||||
// Имена полей совпадают с models.Queue → json совместим при загрузке.
|
||||
type queueMetaDTO struct {
|
||||
Name string
|
||||
URL string
|
||||
Arn string
|
||||
VisibilityTimeout int
|
||||
ReceiveMessageWaitTimeSeconds int
|
||||
DelaySeconds int
|
||||
MaximumMessageSize int
|
||||
MessageRetentionPeriod int
|
||||
MaxReceiveCount int
|
||||
IsFIFO bool
|
||||
FIFOMessages map[string]int
|
||||
FIFOSequenceNumbers map[string]int
|
||||
EnableDuplicates bool
|
||||
Duplicates map[string]time.Time
|
||||
Tags map[string]string
|
||||
LastPurgeTime time.Time
|
||||
DeadLetterQueue *queueMetaDTO
|
||||
}
|
||||
|
||||
// queueToMetaDTO — строит снимок очереди без сообщений (и без сообщений DLQ),
|
||||
// НЕ мутируя исходную очередь. Рекурсия ограничена глубиной 1.
|
||||
func queueToMetaDTO(q *models.Queue) *queueMetaDTO {
|
||||
if q == nil {
|
||||
return nil
|
||||
}
|
||||
dto := &queueMetaDTO{
|
||||
Name: q.Name,
|
||||
URL: q.URL,
|
||||
Arn: q.Arn,
|
||||
VisibilityTimeout: q.VisibilityTimeout,
|
||||
ReceiveMessageWaitTimeSeconds: q.ReceiveMessageWaitTimeSeconds,
|
||||
DelaySeconds: q.DelaySeconds,
|
||||
MaximumMessageSize: q.MaximumMessageSize,
|
||||
MessageRetentionPeriod: q.MessageRetentionPeriod,
|
||||
MaxReceiveCount: q.MaxReceiveCount,
|
||||
IsFIFO: q.IsFIFO,
|
||||
FIFOMessages: q.FIFOMessages,
|
||||
FIFOSequenceNumbers: q.FIFOSequenceNumbers,
|
||||
EnableDuplicates: q.EnableDuplicates,
|
||||
Duplicates: q.Duplicates,
|
||||
Tags: q.Tags,
|
||||
LastPurgeTime: q.LastPurgeTime,
|
||||
}
|
||||
if q.DeadLetterQueue != nil {
|
||||
dto.DeadLetterQueue = queueToMetaDTO(q.DeadLetterQueue)
|
||||
}
|
||||
return dto
|
||||
}
|
||||
|
||||
// marshalQueueMeta — сериализует метаданные очереди БЕЗ сообщений,
|
||||
// НЕ мутируя исходную очередь (см. queueMetaDTO). Безопасно вызывать
|
||||
// под per-queue блокировкой только этой очереди.
|
||||
func marshalQueueMeta(queue *models.Queue) ([]byte, error) {
|
||||
savedMsgs := queue.Messages
|
||||
queue.Messages = nil
|
||||
var savedDLQMsgs []models.SqsMessage
|
||||
if queue.DeadLetterQueue != nil {
|
||||
savedDLQMsgs = queue.DeadLetterQueue.Messages
|
||||
queue.DeadLetterQueue.Messages = nil
|
||||
}
|
||||
data, err := json.Marshal(queue)
|
||||
queue.Messages = savedMsgs
|
||||
if queue.DeadLetterQueue != nil {
|
||||
queue.DeadLetterQueue.Messages = savedDLQMsgs
|
||||
}
|
||||
return data, err
|
||||
return json.Marshal(queueToMetaDTO(queue))
|
||||
}
|
||||
|
||||
// SaveQueue — сохраняет МЕТАДАННЫЕ очереди (без сообщений) в Redis асинхронно.
|
||||
|
||||
Reference in New Issue
Block a user