Как программно доказать, что этот код имеет состояние гонки? - PullRequest
0 голосов
/ 06 января 2019

Мне сказали, что у этого кода есть расы по своему замыслу, хотя, как ни старайся, я не могу доказать это.

func (h *handler) loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        h.Log = log.WithFields(log.Fields{
            "method":     r.Method,
            "requestURI": r.RequestURI,
        })
        next.ServeHTTP(w, r)
    })
}

Я попробовал go build -race, затем запустил двоичный файл: PORT=3000 ./main и загрузил создателей, таких как hey -n 10000 -c 200 http://localhost:3000.

Остальной код здесь: https://raw.githubusercontent.com/kaihendry/context-youtube/master/5/main.go

или

type handler struct{ Log *log.Entry }

func New() (h *handler) { return &handler{Log: log.WithFields(log.Fields{"test": "FAIL"})} }

func (h *handler) index(w http.ResponseWriter, r *http.Request) {
    h.Log.Info("index")
    fmt.Fprintf(w, "hello")
}

func (h *handler) about(w http.ResponseWriter, r *http.Request) {
    h.Log.Info("about")
    fmt.Fprintf(w, "about")
}

func main() {
    h := New()
    app := mux.NewRouter()
    app.HandleFunc("/", h.index)
    app.HandleFunc("/about", h.about)
    app.Use(h.loggingMiddleware)
    if err := http.ListenAndServe(":"+os.Getenv("PORT"), app); err != nil {
        log.WithError(err).Fatal("error listening")
    }
}

Если я не могу доказать, что у него есть состояние гонки, могу ли я считать, что настройка h.Log безопасна?

Ответы [ 2 ]

0 голосов
/ 06 января 2019

Существует программный способ, для которого вам нужно сделать 2 вещи:

  • Воспроизведение колоритного состояния
  • и используйте опцию -race при запуске инструмента go

Лучше всего, если вы напишите для него модульный тест, поэтому этот тест также воспроизводим и запускается / проверяется автоматически при каждой сборке / развертывании.

Хорошо, как это воспроизвести?

Просто напишите тест, который запускает 2 процедуры, одну, которая вызывает обработчик index, и другую, которая вызывает обработчик about, намеренно без синхронизации, это то, что запускает детектор гонки.

Используйте пакет net/http/httptest для простого тестирования обработчиков. httptest.NewServer() вручает вам готовый сервер, «вооруженный» обработчиком, который вы передаете ему.

Вот простой тестовый пример, который вызовет состояние гонки. Поместите его в файл с именем main_test.go рядом с файлом main.go:

package main

import (
    "fmt"
    "net/http"
    "net/http/httptest"
    "sync"
    "testing"

    "github.com/gorilla/mux"
)

func TestRace(t *testing.T) {
    h := New()
    app := mux.NewRouter()
    app.HandleFunc("/", h.index)
    app.HandleFunc("/about", h.about)
    app.Use(h.loggingMiddleware)

    server := httptest.NewServer(app)
    defer server.Close()

    wg := &sync.WaitGroup{}
    for _, path := range []string{"/", "/about"} {
        path := path
        wg.Add(1)
        go func() {
            defer wg.Done()
            req, err := http.NewRequest(http.MethodGet, server.URL+path, nil)
            fmt.Println(server.URL + path)
            if err != nil {
                panic(err)
            }
            res, err := http.DefaultClient.Do(req)
            if err != nil {
                panic(err)
            }
            defer res.Body.Close()
        }()
    }
    wg.Wait()
}

Вы должны запустить его с

go test -race

И пример вывода будет:

http://127.0.0.1:33007/
http://127.0.0.1:33007/about
==================
WARNING: DATA RACE
Write at 0x00c000098030 by goroutine 17:
  play.(*handler).loggingMiddleware.func1()
      /home/icza/tmp/gows/src/play/main.go:16 +0x1ce
  net/http.HandlerFunc.ServeHTTP()
      /usr/local/go/src/net/http/server.go:1964 +0x51
  github.com/gorilla/mux.(*Router).ServeHTTP()
      /home/icza/tmp/gows/src/github.com/gorilla/mux/mux.go:212 +0x12e
  net/http.serverHandler.ServeHTTP()
      /usr/local/go/src/net/http/server.go:2741 +0xc4
  net/http.(*conn).serve()
      /usr/local/go/src/net/http/server.go:1847 +0x80a

Previous write at 0x00c000098030 by goroutine 16:
  play.(*handler).loggingMiddleware.func1()
      /home/icza/tmp/gows/src/play/main.go:16 +0x1ce
  net/http.HandlerFunc.ServeHTTP()
      /usr/local/go/src/net/http/server.go:1964 +0x51
  github.com/gorilla/mux.(*Router).ServeHTTP()
      /home/icza/tmp/gows/src/github.com/gorilla/mux/mux.go:212 +0x12e
  net/http.serverHandler.ServeHTTP()
      /usr/local/go/src/net/http/server.go:2741 +0xc4
  net/http.(*conn).serve()
      /usr/local/go/src/net/http/server.go:1847 +0x80a

Goroutine 17 (running) created at:
  net/http.(*Server).Serve()
      /usr/local/go/src/net/http/server.go:2851 +0x4c5
  net/http/httptest.(*Server).goServe.func1()
      /usr/local/go/src/net/http/httptest/server.go:280 +0xac

Goroutine 16 (running) created at:
  net/http.(*Server).Serve()
      /usr/local/go/src/net/http/server.go:2851 +0x4c5
  net/http/httptest.(*Server).goServe.func1()
      /usr/local/go/src/net/http/httptest/server.go:280 +0xac
==================
2019/01/06 14:58:50  info index                     method=GET requestURI=/
2019/01/06 14:58:50  info about                     method=GET requestURI=/about
--- FAIL: TestRace (0.00s)
    testing.go:771: race detected during execution of test
FAIL
exit status 1
FAIL    play    0.011s

Тест не пройден, показывая, что существуют гонки данных.

Примечания:

Синхронизация с sync.WaitGroup заключается в ожидании двух запущенных подпрограмм, а не в синхронизации доступа к регистратору обработчика (который вызывает гонку данных). Это так, если вы исправите гонку данных, тест запустится и завершится должным образом (ожидая завершения двух запущенных тестовых программ).

0 голосов
/ 06 января 2019

Представьте, что вы получаете два входящих соединения почти одновременно, которые попали в один и тот же обработчик. Первое подключение начинает работать:

h.Log = log.WithFields(log.Fields{
    "method":     rFirst.Method,
    "requestURI": rFirst.RequestURI,
})

Но подожди! Второе соединение появляется! Может быть, среда выполнения хочет приостановить эту программу и запустить второе соединение. Тогда ...

h.Log = log.WithFields(log.Fields{
    "method":     rSecond.Method,
    "requestURI": rSecond.RequestURI,
})
next.ServeHTTP(wSecond, rSecond)

Фу ... с этим покончено. Давайте вернемся к нашей первой программе.

// What's in h.Log now, with this sequence of events?
next.ServeHTTP(wFirst, rFirst)

Или ...

Ваш второй набор примеров не меняет значение h.Log, но вызывает методы для него. Это может или не может быть безопасно в самом общем случае. Документация для log.Logger содержит волшебную фразу: «Логгер может использоваться одновременно из нескольких программ». (Если вы на самом деле импортировали "github.com/sirupsen/logrus" как log, , который имеет аналогичное утверждение в своей документации .)

можно ли предположить, что настройка h.Log безопасна?

Без sync.Mutex или чего-то подобного, защищающего его, не совсем. У вас определенно нет гарантии, что, если вы установите его в строке 1, оно будет иметь то же значение в строке 2, если какая-то другая программа может изменить его. Модель Go Memory имеет более точное определение того, какие побочные эффекты гарантированно будут видны, когда.

...