Fix concurrent map write panic in timeout middleware
- Add thread-safe header wrapper in timeoutWriter - Check context cancellation before writing responses in handlers - Protect header access after timeout with mutex - Prevents race condition when requests timeout while handlers are still running
This commit is contained in:
parent
e0a4c8642e
commit
7d39bd18bc
2
Makefile
2
Makefile
@ -21,7 +21,7 @@ clean:
|
|||||||
rm -rf bin/
|
rm -rf bin/
|
||||||
|
|
||||||
run: build
|
run: build
|
||||||
./bin/routewatch
|
DEBUG=routewatch ./bin/routewatch 2>&1 | tee log.txt
|
||||||
|
|
||||||
asupdate:
|
asupdate:
|
||||||
@echo "Updating AS info data..."
|
@echo "Updating AS info data..."
|
||||||
|
@ -557,6 +557,14 @@ func (s *Server) handleASDetail() http.HandlerFunc {
|
|||||||
IPv6Count: len(ipv6Prefixes),
|
IPv6Count: len(ipv6Prefixes),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if context is still valid before writing response
|
||||||
|
select {
|
||||||
|
case <-r.Context().Done():
|
||||||
|
// Request was cancelled, don't write response
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
tmpl := templates.ASDetailTemplate()
|
tmpl := templates.ASDetailTemplate()
|
||||||
if err := tmpl.Execute(w, data); err != nil {
|
if err := tmpl.Execute(w, data); err != nil {
|
||||||
@ -694,6 +702,14 @@ func (s *Server) handlePrefixDetail() http.HandlerFunc {
|
|||||||
OriginCount: len(originMap),
|
OriginCount: len(originMap),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if context is still valid before writing response
|
||||||
|
select {
|
||||||
|
case <-r.Context().Done():
|
||||||
|
// Request was cancelled, don't write response
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
tmpl := templates.PrefixDetailTemplate()
|
tmpl := templates.PrefixDetailTemplate()
|
||||||
if err := tmpl.Execute(w, data); err != nil {
|
if err := tmpl.Execute(w, data); err != nil {
|
||||||
@ -814,6 +830,14 @@ func (s *Server) handlePrefixLength() http.HandlerFunc {
|
|||||||
"Count": len(prefixes),
|
"Count": len(prefixes),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if context is still valid before writing response
|
||||||
|
select {
|
||||||
|
case <-r.Context().Done():
|
||||||
|
// Request was cancelled, don't write response
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
tmpl := template.Must(template.ParseFiles("internal/templates/prefix_length.html"))
|
tmpl := template.Must(template.ParseFiles("internal/templates/prefix_length.html"))
|
||||||
if err := tmpl.Execute(w, data); err != nil {
|
if err := tmpl.Execute(w, data); err != nil {
|
||||||
s.logger.Error("Failed to render prefix length template", "error", err)
|
s.logger.Error("Failed to render prefix length template", "error", err)
|
||||||
|
@ -108,6 +108,7 @@ type timeoutWriter struct {
|
|||||||
http.ResponseWriter
|
http.ResponseWriter
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
written bool
|
written bool
|
||||||
|
header http.Header // cached header to prevent concurrent access
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tw *timeoutWriter) Write(b []byte) (int, error) {
|
func (tw *timeoutWriter) Write(b []byte) (int, error) {
|
||||||
@ -133,6 +134,18 @@ func (tw *timeoutWriter) WriteHeader(statusCode int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (tw *timeoutWriter) Header() http.Header {
|
func (tw *timeoutWriter) Header() http.Header {
|
||||||
|
tw.mu.Lock()
|
||||||
|
defer tw.mu.Unlock()
|
||||||
|
|
||||||
|
if tw.written {
|
||||||
|
// Return a copy to prevent modifications after timeout
|
||||||
|
if tw.header == nil {
|
||||||
|
tw.header = make(http.Header)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tw.header
|
||||||
|
}
|
||||||
|
|
||||||
return tw.ResponseWriter.Header()
|
return tw.ResponseWriter.Header()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,6 +166,7 @@ func TimeoutMiddleware(timeout time.Duration) func(http.Handler) http.Handler {
|
|||||||
|
|
||||||
tw := &timeoutWriter{
|
tw := &timeoutWriter{
|
||||||
ResponseWriter: w,
|
ResponseWriter: w,
|
||||||
|
header: make(http.Header),
|
||||||
}
|
}
|
||||||
|
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
@ -178,8 +192,12 @@ func TimeoutMiddleware(timeout time.Duration) func(http.Handler) http.Handler {
|
|||||||
tw.markWritten() // Prevent the handler from writing after timeout
|
tw.markWritten() // Prevent the handler from writing after timeout
|
||||||
execTime := time.Since(startTime)
|
execTime := time.Since(startTime)
|
||||||
|
|
||||||
|
// Write directly to the underlying writer since we've marked tw as written
|
||||||
|
// This is safe because markWritten() prevents the handler from writing
|
||||||
|
tw.mu.Lock()
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.WriteHeader(http.StatusRequestTimeout)
|
w.WriteHeader(http.StatusRequestTimeout)
|
||||||
|
tw.mu.Unlock()
|
||||||
|
|
||||||
response := map[string]interface{}{
|
response := map[string]interface{}{
|
||||||
"status": "error",
|
"status": "error",
|
||||||
|
Loading…
Reference in New Issue
Block a user