go
63 lines · 9 steps
Deduping in-flight requests in Gin
A middleware that collapses identical concurrent requests so only one runs while the rest wait for and share its response.
Explained by
highlit
1package handler
2
3type flightResult struct {
4 status int
5 body []byte
6}
7
8type inflightEntry struct {
9 done chan struct{}
10 result flightResult
11}
12
13type flightRegistry struct {
14 mu sync.Mutex
15 pending map[string]*inflightEntry
16}
17
18var registry = &flightRegistry{pending: make(map[string]*inflightEntry)}
19
20func Dedupe() gin.HandlerFunc {
21 return func(c *gin.Context) {
22 key := c.Request.Method + " " + c.FullPath() + "?" + c.Request.URL.RawQuery
23
24 registry.mu.Lock()
25 if entry, ok := registry.pending[key]; ok {
26 registry.mu.Unlock()
27 <-entry.done
28 c.Header("X-Deduped", "1")
29 c.Data(entry.result.status, "application/json", entry.result.body)
30 c.Abort()
31 return
32 }
33 entry := &inflightEntry{done: make(chan struct{})}
34 registry.pending[key] = entry
35 registry.mu.Unlock()
36
37 rec := &bodyRecorder{ResponseWriter: c.Writer, buf: &bytes.Buffer{}}
38 c.Writer = rec
39
40 c.Next()
41
42 entry.result = flightResult{status: rec.Status(), body: rec.buf.Bytes()}
43 close(entry.done)
44
45 time.AfterFunc(2*time.Second, func() {
46 registry.mu.Lock()
47 if registry.pending[key] == entry {
48 delete(registry.pending, key)
49 }
50 registry.mu.Unlock()
51 })
52 }
53}
54
55type bodyRecorder struct {
56 gin.ResponseWriter
57 buf *bytes.Buffer
58}
59
60func (r *bodyRecorder) Write(b []byte) (int, error) {
61 r.buf.Write(b)
62 return r.ResponseWriter.Write(b)
63}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1A shared map guarded by a mutex plus a done channel lets many goroutines wait on one in-flight computation.
- 2Wrapping the ResponseWriter captures the response body so it can be replayed to duplicate callers.
- 3Cleaning up the registry entry after a short delay bounds how long results are shared without leaking memory.
Related explainers
go
package streaming import ( "bufio"
Streaming NDJSON logs over HTTP in Go
http-streaming
channels
select
Advanced
10 steps
python
import time import uuid from django.utils.deprecation import MiddlewareMixin
Attaching per-request context in Django
middleware
request lifecycle
multi-tenancy
Intermediate
7 steps
go
package api import ( "crypto/sha256"
ETag conditional requests in Gin
http-caching
etag
conditional-requests
Intermediate
6 steps
javascript
import { useState, useEffect, useCallback, useRef } from 'react'; const cache = new Map(); const inflight = new Map();
Building a stale-while-revalidate hook in React
caching
request-deduplication
custom-hooks
Advanced
10 steps
go
func (w *Watcher) resetDebounce(d time.Duration) { if !w.timer.Stop() { select { case <-w.timer.C:
Debouncing a stream of events in Go
debounce
timers
channels
Advanced
7 steps
php
<?php namespace App\Services;
Building a cached daily leaderboard in Laravel
caching
aggregation
eager-loading
Intermediate
9 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/deduping-in-flight-requests-in-gin-explained-go-262a/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.