Files
freemo d9e5668cec fix(skills): comprehensive final audit pass for programming-patterns skill
Fixes and improvements from exhaustive audit:

Consistency fixes in SKILL.md:
- 'Pipe & Filter' → 'Pipe and Filter' (one stray '&' found and corrected)
- 'Singleton for factory instance' → clarified to 'register factory as
  singleton-scoped via DI container' (less misleading wording)
- Documentation Format section updated with note that SKILL.md itself is the
  authoritative source for related-pattern combinations

Coverage fix — Related Patterns sections:
- Added '## Related Patterns' to ALL 94 pattern files (was 0/94)
- Each section lists 3–6 related patterns with relationship descriptions
- Covers: why they're related, when to prefer one vs the other,
  and which are often confused

SOLID principles → Creational → Structural → Behavioral → Architectural →
Concurrency → Functional → Resilience → Data Access → Messaging →
Testing → Error Handling → Microservice — all 13 categories covered

Code verification:
- Python: 0 failures (all 85 testable blocks pass)
- Go: 0 failures (all 76 testable blocks pass)
- JavaScript: 0 failures (all 78 testable blocks pass)
- All 239 code blocks verified correct after edits

Final skill state:
- 108 files, 36,524 lines across 13 reference categories
- 94/94 pattern files have Related Patterns sections
- 2,815-line SKILL.md with 67 decision trees, 23 scenarios,
  0 broken references, 0 naming inconsistencies
2026-04-15 13:22:13 -04:00

9.0 KiB

Message Queue Pattern

Problem

When a producer generates work that needs to be processed, calling the consumer directly creates tight coupling and means the producer blocks until processing completes. If the consumer is slow or down, the producer fails too. And if there's more work than one consumer can handle, there's no way to distribute load.

Solution

Place a queue between producers and consumers. Producers enqueue messages and continue immediately. Consumers dequeue messages and process them independently. Key properties:

  • Point-to-point — Each message is delivered to exactly one consumer.
  • Competing consumers — Multiple consumers can read from the same queue to scale horizontally.
  • Durability — Messages persist in the queue until acknowledged.
  • Decoupling — Producer and consumer don't need to be alive at the same time.

When to Use

  • Distributing work across multiple worker processes
  • Smoothing traffic spikes (queue absorbs bursts)
  • Background job processing (emails, reports, image processing)
  • When producer throughput exceeds consumer throughput
  • Cross-service async communication

When to Avoid

  • When you need immediate responses (use request-reply)
  • When all consumers need to see every message (use pub-sub)
  • Simple in-process function calls
  • When message ordering is critical and can't be partitioned

Pseudocode

class MessageQueue:
    queue = FIFO buffer

    function enqueue(message):
        queue.push(message)

    function dequeue():
        message = queue.pop()
        return message

    function acknowledge(message):
        mark message as processed

Python Implementation

import threading
import time
import queue
from dataclasses import dataclass, field
from typing import Any


@dataclass
class Message:
    id: int
    body: Any
    attempts: int = 0


class MessageQueue:
    def __init__(self, name: str, max_size: int = 100):
        self.name = name
        self._queue: queue.Queue[Message] = queue.Queue(maxsize=max_size)
        self._next_id = 0
        self._lock = threading.Lock()
        self.enqueued = 0
        self.processed = 0

    def enqueue(self, body: Any) -> Message:
        with self._lock:
            self._next_id += 1
            msg = Message(id=self._next_id, body=body)
            self.enqueued += 1
        self._queue.put(msg)
        return msg

    def dequeue(self, timeout: float = 1.0):
        try:
            msg = self._queue.get(timeout=timeout)
            msg.attempts += 1
            return msg
        except queue.Empty:
            return None

    def acknowledge(self, msg: Message):
        with self._lock:
            self.processed += 1

    @property
    def size(self):
        return self._queue.qsize()


# --- Demo ---
mq = MessageQueue("tasks")
stop_event = threading.Event()
results = []
results_lock = threading.Lock()


def producer(name: str, items: list):
    for item in items:
        msg = mq.enqueue(item)
        print(f"  [{name}] Enqueued msg#{msg.id}: {item}")
        time.sleep(0.02)


def consumer(name: str):
    while not stop_event.is_set():
        msg = mq.dequeue(timeout=0.2)
        if msg is None:
            continue
        # Process
        time.sleep(0.05)
        mq.acknowledge(msg)
        with results_lock:
            results.append((name, msg.id, msg.body))
        print(f"  [{name}] Processed msg#{msg.id}: {msg.body}")


# Start 2 consumers (competing consumers)
consumers = [
    threading.Thread(target=consumer, args=(f"Consumer-{i}",), daemon=True)
    for i in range(1, 3)
]
for c in consumers:
    c.start()

# Produce messages
print("=== Producing messages ===")
producer("Producer", [f"task-{i}" for i in range(1, 7)])

# Wait for processing
time.sleep(1)
stop_event.set()
for c in consumers:
    c.join(timeout=1)

print()
print("=== Results ===")
for worker, msg_id, body in sorted(results, key=lambda x: x[1]):
    print(f"  msg#{msg_id} ({body}) → processed by {worker}")

print()
print(f"Enqueued: {mq.enqueued}, Processed: {mq.processed}, Remaining: {mq.size}")

Output (consumer assignment may vary):

=== Producing messages ===
  [Producer] Enqueued msg#1: task-1
  [Consumer-1] Processed msg#1: task-1
  [Producer] Enqueued msg#2: task-2
  [Consumer-2] Processed msg#2: task-2
  [Producer] Enqueued msg#3: task-3
  [Producer] Enqueued msg#4: task-4
  [Consumer-1] Processed msg#3: task-3
  [Consumer-2] Processed msg#4: task-4
  [Producer] Enqueued msg#5: task-5
  [Producer] Enqueued msg#6: task-6
  [Consumer-1] Processed msg#5: task-5
  [Consumer-2] Processed msg#6: task-6

=== Results ===
  msg#1 (task-1) → processed by Consumer-1
  msg#2 (task-2) → processed by Consumer-2
  msg#3 (task-3) → processed by Consumer-1
  msg#4 (task-4) → processed by Consumer-2
  msg#5 (task-5) → processed by Consumer-1
  msg#6 (task-6) → processed by Consumer-2

Enqueued: 6, Processed: 6, Remaining: 0

Go Implementation

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"
)

type Message struct {
	ID   int
	Body string
}

type MessageQueue struct {
	ch        chan Message
	nextID    int64
	processed int64
}

func NewMessageQueue(size int) *MessageQueue {
	return &MessageQueue{ch: make(chan Message, size)}
}

func (mq *MessageQueue) Enqueue(body string) Message {
	id := int(atomic.AddInt64(&mq.nextID, 1))
	msg := Message{ID: id, Body: body}
	mq.ch <- msg
	return msg
}

func (mq *MessageQueue) Dequeue() (Message, bool) {
	select {
	case msg := <-mq.ch:
		return msg, true
	case <-time.After(200 * time.Millisecond):
		return Message{}, false
	}
}

func (mq *MessageQueue) Ack() {
	atomic.AddInt64(&mq.processed, 1)
}

func main() {
	mq := NewMessageQueue(100)
	var wg sync.WaitGroup
	done := make(chan struct{})

	// Consumers
	for i := 1; i <= 2; i++ {
		wg.Add(1)
		go func(name string) {
			defer wg.Done()
			for {
				select {
				case <-done:
					return
				default:
				}
				msg, ok := mq.Dequeue()
				if !ok {
					select {
					case <-done:
						return
					default:
						continue
					}
				}
				time.Sleep(50 * time.Millisecond)
				mq.Ack()
				fmt.Printf("  [%s] Processed msg#%d: %s\n", name, msg.ID, msg.Body)
			}
		}(fmt.Sprintf("Consumer-%d", i))
	}

	// Producer
	fmt.Println("=== Producing messages ===")
	for i := 1; i <= 6; i++ {
		msg := mq.Enqueue(fmt.Sprintf("task-%d", i))
		fmt.Printf("  [Producer] Enqueued msg#%d: %s\n", msg.ID, msg.Body)
		time.Sleep(20 * time.Millisecond)
	}

	time.Sleep(1 * time.Second)
	close(done)
	wg.Wait()

	fmt.Printf("\nProcessed: %d\n", atomic.LoadInt64(&mq.processed))
}

JavaScript Implementation

class MessageQueue {
  constructor(name) {
    this.name = name;
    this._queue = [];
    this._nextId = 0;
    this.enqueued = 0;
    this.processed = 0;
    this._waiters = [];
  }

  enqueue(body) {
    this._nextId++;
    const msg = { id: this._nextId, body, attempts: 0 };
    this.enqueued++;

    if (this._waiters.length > 0) {
      const resolve = this._waiters.shift();
      msg.attempts++;
      resolve(msg);
    } else {
      this._queue.push(msg);
    }
    return msg;
  }

  async dequeue(timeoutMs = 1000) {
    if (this._queue.length > 0) {
      const msg = this._queue.shift();
      msg.attempts++;
      return msg;
    }
    return new Promise((resolve) => {
      const timer = setTimeout(() => {
        const idx = this._waiters.indexOf(resolve);
        if (idx !== -1) this._waiters.splice(idx, 1);
        resolve(null);
      }, timeoutMs);

      this._waiters.push((msg) => {
        clearTimeout(timer);
        resolve(msg);
      });
    });
  }

  acknowledge() {
    this.processed++;
  }
}

// --- Demo ---
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

(async () => {
  const mq = new MessageQueue("tasks");
  const results = [];

  async function consumer(name, stopSignal) {
    while (!stopSignal.stopped) {
      const msg = await mq.dequeue(200);
      if (!msg) continue;
      await sleep(50);
      mq.acknowledge();
      results.push({ worker: name, id: msg.id, body: msg.body });
      console.log(`  [${name}] Processed msg#${msg.id}: ${msg.body}`);
    }
  }

  const signal = { stopped: false };
  const c1 = consumer("Consumer-1", signal);
  const c2 = consumer("Consumer-2", signal);

  console.log("=== Producing messages ===");
  for (let i = 1; i <= 6; i++) {
    const msg = mq.enqueue(`task-${i}`);
    console.log(`  [Producer] Enqueued msg#${msg.id}: ${msg.body}`);
    await sleep(20);
  }

  await sleep(800);
  signal.stopped = true;
  await Promise.all([c1, c2]);

  console.log(`\nEnqueued: ${mq.enqueued}, Processed: ${mq.processed}`);
})();
  • Publish-Subscribe — publish-subscribe delivers one message to many subscribers; message queue delivers each message to exactly one consumer.
  • Producer-Consumer — a message queue is the durable, distributed implementation of the in-process producer-consumer pattern.
  • Dead Letter Queue — messages that cannot be processed after N retries are routed to a dead letter queue for inspection.
  • Retry — pair message queue consumers with retry logic and backoff to handle transient processing failures.