現象
GoでLocalhostを立て、そこにブラウザでアクセスする形式(同一マシン内)のアプリケーションを作成しているのですが、
ときどきローカルホストから全くレスポンスが返ってこなくなるという事象が生じており、非常に困っています。
たとえば以下のようになります。
- しばらく正常に動作している
- リクエスト1をおこなう →すると突然レスポンスが返ってこない
- このあと、リクエスト2、リクエスト3と続けてリクエストを出しても、全て無反応となる。
上記のように、ときどきローカルホストが全く無反応の状態になります。
ローカルホストが動作しているWindowsのdos窓を見ると、リクエスト1の時点でログの流れがストップしています。
しかしここでdos窓をクリックしてf5キーを押すと、突然ログが流れ始めます。そして、リクエスト1,2,3に対するレスポンスが一度に返ってきます。
環境などは以下です。
Win10 64bit
Go1.13
github.com/gorilla/mux
を利用
go
1r := mux.NewRouter() 2r.HandleFunc("/path", Func).Methods("GET", "POST", "OPTIONS") ... 3 4srv := http.Server{ 5 Addr: ":48000", 6 Handler: r, 7 TLSConfig: nil, 8 ReadTimeout: 0, 9 ReadHeaderTimeout: 0, 10 WriteTimeout: 0, 11 IdleTimeout: 0, 12 MaxHeaderBytes: 0, 13 TLSNextProto: nil, 14 ConnState: nil, 15 ErrorLog: nil, 16 BaseContext: nil, 17 ConnContext: nil, 18} 19srv.SetKeepAlivesEnabled(false) //keepaliveが悪影響を及ぼす場合があると聞いてオフにしてみた 20err := srv.ListenAndServe() 21if err != nil { 22 log.Fatalf(err.Error()) 23} 24
条件、再現性など
上記停止現象の発生条件は分かりません。
メソッドはget のときも postのときもあります。
またlocalhost内でエラー(ロジックに由来するエラー)が起きているときもありますが、全く問題が無い(リクエストは所定の形式でなされており、ローカルホストでの処理中のエラーも起きていない)場合もあります。
リクエストはブラウザ(fetch
を使用)から送る場合でも、ツール(postman
を使用)で送る場合でも発生します。
発生頻度はそれなりに高く、リクエスト数十回に1回くらい見かける印象です(プロダクトでこの頻度だと使い物にならないと思います)。
以上のような状況であるため、この場合にこうなるというのは見つかられていません。あるとき突然なるという認識です。
ただしその状態が生じた場合、「dos窓をクリックしてf5キー」によって確実に回復します。これは今のところ例外無しです。
試したこと
正直なにが起きているのかよく理解できていません。
F5キーで回復するというところに何か現象を理解するヒントがあるようには思えるのですが。
keepaliveが悪影響を及ぼす場合があるという情報を見かけたので、srv.SetKeepAlivesEnabled(false)
としてみましたが改善しませんでした。
質問
以上のような状況ですが、どのように対処したらよいでしょうか。
アプリケーションの動作の根幹にかかわる部分で、かなり深刻なのですが、解決方法が分からず非常に困っています。
アドバイスいただければ幸いです。
よろしくお願いいたします。
追記:サンプルコード
ご指摘を受け、実際のプログラムの挙動に似せたものをサンプルコードとして作成してみました。
これを作っている過程で気づいたのですが、このプログラムはグローバル変数としてキャッシュを持っています。
そのキャッシュデータに対する同時アクセス制御(mutex
)に問題があるのではないかと思い、mutex
まわりをいくつか修正してみたところ、停止現象がかなり減った感じがしています。
そのため現時点ではグローバル変数への同時アクセスが影響しているのではないかと疑っています。
(ただし、それがF5で解消するというのもよく分からない話のように思え、この点の疑問は残っています)
実際のプログラムに似せるため、プログラム内にファイルシステムへのアクセスが存在します。ご注意お願いします。
go
1package main 2 3import ( 4 "bufio" 5 "encoding/json" 6 "io/ioutil" 7 "log" 8 "net/http" 9 "os" 10 "path" 11 "strconv" 12 "sync" 13 14 "github.com/gorilla/mux" 15) 16 17var ( 18 DataCache = map[string]string{} 19 Datapath string 20 mutex sync.RWMutex 21) 22 23type res_ping struct { 24 Msg string `json:"msg"` 25} 26 27func GetPing(w http.ResponseWriter, r *http.Request) { 28 log.Println("GetPingStart") 29 defer log.Println("GetPingEnd") 30 31 res := res_ping{Msg: "server_alive"} 32 b, _ := json.Marshal(res) 33 w.Write(b) 34} 35 36type res_get struct { 37 Msg string `json:"msg"` 38} 39 40func GetData(w http.ResponseWriter, r *http.Request) { 41 log.Println("GetDataStart") 42 defer log.Println("GetDataEnd") 43 44 line, _ := mux.Vars(r)["line"] 45 46 if line == "" { 47 line = "0" 48 } 49 mutex.RLock() 50 msg := DataCache[line] 51 mutex.RUnlock() 52 53 res := res_get{Msg: msg} 54 b, _ := json.Marshal(res) 55 w.Write(b) 56 57} 58 59type req_post struct { 60 Data string `json:"data"` 61} 62 63type res_post struct { 64 Msg string `json:"msg"` 65} 66 67func PostData(w http.ResponseWriter, r *http.Request) { 68 log.Println("PostDataStart") 69 defer log.Println("PostDataEnd") 70 71 body := r.Body 72 defer body.Close() 73 b, _ := ioutil.ReadAll(body) 74 req := req_post{} 75 _ = json.Unmarshal(b, &req) 76 data := req.Data 77 78 mutex.Lock() 79 defer mutex.Unlock() 80 file, _ := os.OpenFile(Datapath, os.O_WRONLY|os.O_APPEND, 0666) 81 defer file.Close() 82 file.WriteString(data + "\n") 83 file.Close() 84 readdata() 85 86 res := res_post{Msg: "OK"} 87 rb, _ := json.Marshal(res) 88 w.Write(rb) 89 90} 91 92func SetJsonheader(w http.ResponseWriter) { 93 w.Header().Set("Content-Type", "application/json") 94} 95 96func Wrap(f func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) { 97 return func(w http.ResponseWriter, r *http.Request) { 98 EnableCors(&w) 99 SetJsonheader(w) 100 f(w, r) 101 } 102} 103 104func EnableCors(w *http.ResponseWriter) { 105 (*w).Header().Set("Access-Control-Allow-Origin", "http://localhost:8004") 106 (*w).Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE") 107 (*w).Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization") 108} 109 110func setup() { 111 log.Println(Datapath) 112 file, err := os.Create(Datapath) 113 if err != nil { 114 log.Fatal(err) 115 } 116 defer file.Close() 117 // 実際の挙動に近づけるためにファイル書き込み(データ多め)をおこなっています。問題があれば適宜修正してください。 118 for i := 0; i < 10000; i++ { 119 _, _ = file.WriteString("data898999898989898998989ddddd\n") 120 } 121 file.Close() 122 123 readdata() 124 //DataCache["0"] = "data0" 125 //DataCache["1"] = "data1" 126 //DataCache["2"] = "data2" 127} 128 129func readdata() { 130 file, _ := os.Open(Datapath) 131 defer file.Close() 132 sc := bufio.NewScanner(file) 133 count := 0 134 for sc.Scan() { 135 DataCache[strconv.Itoa(count)] = sc.Text() 136 count++ 137 } 138} 139 140func main() { 141 142 datadir := os.TempDir() 143 datapath := path.Join(datadir, "data_6f3eb709jlgq0w2h.txt") 144 Datapath = datapath 145 setup() 146 defer os.Remove(datapath) 147 148 r := mux.NewRouter() 149 r.HandleFunc("/ping", Wrap(GetPing)).Methods("GET", "POST", "OPTIONS") 150 r.HandleFunc("/data/{line}", Wrap(GetData)).Methods("GET", "OPTIONS") 151 r.HandleFunc("/data", Wrap(GetData)).Methods("GET", "OPTIONS") 152 r.HandleFunc("/data", Wrap(PostData)).Methods("POST", "OPTIONS") 153 154 srv := http.Server{ 155 Addr: ":48000", 156 Handler: r, 157 TLSConfig: nil, 158 ReadTimeout: 0, 159 ReadHeaderTimeout: 0, 160 WriteTimeout: 0, 161 IdleTimeout: 0, 162 MaxHeaderBytes: 0, 163 TLSNextProto: nil, 164 ConnState: nil, 165 ErrorLog: nil, 166 BaseContext: nil, 167 ConnContext: nil, 168 } 169 log.Println("start") 170 srv.SetKeepAlivesEnabled(false) 171 err := srv.ListenAndServe() 172 if err != nil { 173 log.Fatalf(err.Error()) 174 } 175} 176
回答1件
あなたの回答
tips
プレビュー