golang 雑memo
golang server で panic を起こすだけだとどうなるのか?
curl -X GET localhost:8080/health -i
response 自体が返らなくなってしまう(500も帰らない)
curl: (52) Empty reply from server
ので、recover して何かしらの reponse を返すようにしなければならない
埋め込み構造体のメソッドで Interface を満たす
以下は、hogeImplは HogeInterface を満たす。hogeImpl自体は MethodHogeを持っていないが、埋め込まれた構造体の HogeImpl がInterfaceを満たすメソッドを持つため。そちらが使われる。
type HogeInterface interface {
MethodHoge()
}
type HogeImpl struct {
}
type hogeImpl struct {
HogeImpl
}
func (h *HogeImpl) MethodHoge() {
fmt.Println("hogehoge")
}
この場合は Interface を満たさない。通常のフィールドとなり、呼び出す際に hoge.hoge.MethodHoge
となるため。
type HogeInterface interface {
MethodHoge()
}
type HogeImpl struct {
}
type hogeImpl struct {
hoge HogeImpl
}
func (h *HogeImpl) MethodHoge() {
fmt.Println("hogehoge")
}
使い所の例
特定の method だけ上書きした構造体を作成したい場合
例えば、 http.ResponseWriter からは通常、response の status code は取得できない。
そこで、以下のような構造体を作成する。
WriteHeader 時だけ、独自のMethodを使用。通常の http.ResponseWriter に加え、独自フィールドにstatus を保存するようにする。
こうすることで、WriteHeader だけ独自メソッドが使われ、それ以外は http.ResponseWriter の method が http.ResponseWriter の Interface を満たすために使われる
type responseWriterWrapper struct {
http.ResponseWriter
statusCode int
}
func newResponseWriterWrapper(w http.ResponseWriter) *responseWriterWrapper {
return &responseWriterWrapper{ResponseWriter: w, statusCode: 0}
}
func (rww *responseWriterWrapper) WriteHeader(code int) {
rww.statusCode = code
rww.ResponseWriter.WriteHeader(code)
}
例えば、middleware で こんな感じで w(http.ResponseWriter) を受け取り wrapper を作成し、渡す。で処理後に wrapper の status code を参照することができる
wrr := newResponseWriterWrapper(w)
next.ServeHTTP(wrr, r)
fmt.Println(wrr.statusCode)
tx のリーク
transaction を始めた後、 commit or rollback を実行しないと、当然だが tx は閉じられない。
結果、そのコネクションは open のままとなる。
で、open conn の上限 に達すると、それ以後に begin しようとした時に、後続が待ち状態になる。
で、ずっと commit or rollback されない(コネクションがプールに戻されない?)ため、後続は待ったまま処理が進まない。
tx, err := mysql.GetDB().Begin()
if err != nil {
panic(err)
}
なぜ?いつ init 関数を使うべき?
- 静的な設定の定義など
エラーハンドリングを伴う・すべき関数の処理は init では行わない(err 返せない・ハンドリングできず panic を起こすしかないので)
非同期処理
■goroutine の終了を待つ
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
}()
wg.Wait()
■goroutine の終了を待つ + err を受け取って処理
var wg sync.WaitGroup
errChan := make(chan error, 1)
wg.Add(1)
go func() {
defer wg.Done()
...
if err != nil {
errChan <- err
}
errChan <- nil
}()
wg.Wait()
close(errChan)
if err := <-errChan; err != nil {
err 処理 hogehoge
}
bulk insert の1例:isucon 11 final を参考に
- 注意点: insert が 0 件の時を考慮しておく
valueStrings := make([]string, 0, len(targets))
valueArgs := make([]interface{}, 0, len(targets)*2)
for _, user := range targets {
valueStrings = append(valueStrings, "(?, ?)")
valueArgs = append(valueArgs, req.ID, user.ID)
}
if len(targets) > 0 {
if _, err := tx.Exec(fmt.Sprintf("INSERT INTO `unread_announcements` (`announcement_id`, `user_id`) VALUES %s", strings.Join(valueStrings, ",")), valueArgs...); err != nil {
c.Logger().Error(err)
return c.NoContent(http.StatusInternalServerError)
}
}
システムコマンドの実行(exec.Command)と標準ライブラリの使用(os)の違い
結論、可能な限り os パッケージを使用した方がいい
exec.Command を使用する場合:
新しいプロセスが作成される
- シェルが起動される
- コマンドが実行される
- プロセスが終了する
- 結果を親プロセスが受け取る
os パッケージを使用する場合:
- 直接システムコールが呼び出される
- 追加のプロセス作成が不要
- プロセス間通信のオーバーヘッドがない