douduan3203 2016-11-16 22:18
浏览 19

超时处理程序中的比赛条件

I can see two main issues in the example code below, but I don't know how to solve them correctly.

If the timeout handler does not get the signal through the errCh that the next handler has completed or an error occured, it will reply "408 Request timeout" to the request.

The problem here is that the ResponseWriter is not safe to be used by multiple goroutines. And the timeout handler starts a new goroutine when executing the next handler.

Issues:

  1. How to prevent the next handler from writing into the ResponseWriter when the ctx's Done channel times out in the timeout handler.

  2. How to prevent the timeout handler from replying 408 status code when the next handler is writing into the ResponseWriter but it has not finished yet and the ctx's Done channel times out in the timeout handler.


package main

import (
  "context"
  "fmt"
  "net/http"
  "time"
)

func main() {
  http.Handle("/race", handlerFunc(timeoutHandler))
  http.ListenAndServe(":8080", nil)
}

func timeoutHandler(w http.ResponseWriter, r *http.Request) error {
  const seconds = 1
  ctx, cancel := context.WithTimeout(r.Context(), time.Duration(seconds)*time.Second)
  defer cancel()

  r = r.WithContext(ctx)

  errCh := make(chan error, 1)
  go func() {
    // w is not safe for concurrent use by multiple goroutines
    errCh <- nextHandler(w, r)
  }()

  select {
  case err := <-errCh:
    return err
  case <-ctx.Done():
    // w is not safe for concurrent use by multiple goroutines
    http.Error(w, "Request timeout", 408)
    return nil
  }
}

func nextHandler(w http.ResponseWriter, r *http.Request) error {
  // just for fun to simulate a better race condition
  const seconds = 1
  time.Sleep(time.Duration(seconds) * time.Second)
  fmt.Fprint(w, "nextHandler")
  return nil
}

type handlerFunc func(w http.ResponseWriter, r *http.Request) error

func (fn handlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  if err := fn(w, r); err != nil {
    http.Error(w, "Server error", 500)
  }
}
  • 写回答

1条回答 默认 最新

  • dt4320279 2016-11-17 19:35
    关注

    Here is a possible solution, which is based on @Andy's comment.

    A new responseRecorder will be passed to the nextHandler, and the recorded response will be copied back to the client:

    func timeoutHandler(w http.ResponseWriter, r *http.Request) error {
        const seconds = 1
        ctx, cancel := context.WithTimeout(r.Context(),
            time.Duration(seconds)*time.Second)
        defer cancel()
    
        r = r.WithContext(ctx)
    
        errCh := make(chan error, 1)
        w2 := newResponseRecorder()
        go func() {
            errCh <- nextHandler(w2, r)
        }()
    
        select {
        case err := <-errCh:
            if err != nil {
                return err
            }
    
            w2.cloneHeader(w.Header())
            w.WriteHeader(w2.status)
            w.Write(w2.buf.Bytes())
            return nil
        case <-ctx.Done():
            http.Error(w, "Request timeout", 408)
            return nil
        }
    }
    

    And here is the responseRecorder:

    type responseRecorder struct {
        http.ResponseWriter
        header http.Header
        buf    *bytes.Buffer
        status int
    }
    
    func newResponseRecorder() *responseRecorder {
        return &responseRecorder{
            header: http.Header{},
            buf:    &bytes.Buffer{},
        }
    }
    
    func (w *responseRecorder) Header() http.Header {
        return w.header
    }
    
    func (w *responseRecorder) cloneHeader(dst http.Header) {
        for k, v := range w.header {
            tmp := make([]string, len(v))
            copy(tmp, v)
            dst[k] = tmp
        }
    }
    
    func (w *responseRecorder) Write(data []byte) (int, error) {
        if w.status == 0 {
            w.WriteHeader(http.StatusOK)
        }
        return w.buf.Write(data)
    }
    
    func (w *responseRecorder) WriteHeader(status int) {
        w.status = status
    }
    
    评论

报告相同问题?

悬赏问题

  • ¥15 Vue3 大型图片数据拖动排序
  • ¥15 划分vlan后不通了
  • ¥15 GDI处理通道视频时总是带有白色锯齿
  • ¥20 用雷电模拟器安装百达屋apk一直闪退
  • ¥15 算能科技20240506咨询(拒绝大模型回答)
  • ¥15 自适应 AR 模型 参数估计Matlab程序
  • ¥100 角动量包络面如何用MATLAB绘制
  • ¥15 merge函数占用内存过大
  • ¥15 使用EMD去噪处理RML2016数据集时候的原理
  • ¥15 神经网络预测均方误差很小 但是图像上看着差别太大