Open9

golang 雑memo

kpkp

golang server で panic を起こすだけだとどうなるのか?

curl -X GET localhost:8080/health -i

response 自体が返らなくなってしまう(500も帰らない)

curl: (52) Empty reply from server

ので、recover して何かしらの reponse を返すようにしなければならない

kpkp

埋め込み構造体のメソッドで 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")
}
kpkp

この場合は Interface を満たさない。通常のフィールドとなり、呼び出す際に hoge.hoge.MethodHoge となるため。

type HogeInterface interface {
	MethodHoge()
}

type HogeImpl struct {
}

type hogeImpl struct {
	hoge HogeImpl
}

func (h *HogeImpl) MethodHoge() {
	fmt.Println("hogehoge")
}
kpkp

使い所の例

特定の 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)
kpkp

tx のリーク

transaction を始めた後、 commit or rollback を実行しないと、当然だが tx は閉じられない。
結果、そのコネクションは open のままとなる。
で、open conn の上限 に達すると、それ以後に begin しようとした時に、後続が待ち状態になる。

で、ずっと commit or rollback されない(コネクションがプールに戻されない?)ため、後続は待ったまま処理が進まない。

		tx, err := mysql.GetDB().Begin()
		if err != nil {
			panic(err)
		}
kpkp

なぜ?いつ init 関数を使うべき?

  • 静的な設定の定義など

エラーハンドリングを伴う・すべき関数の処理は init では行わない(err 返せない・ハンドリングできず panic を起こすしかないので)

kpkp

非同期処理

■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
}
kpkp

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)
		}
	}
kpkp

システムコマンドの実行(exec.Command)と標準ライブラリの使用(os)の違い

結論、可能な限り os パッケージを使用した方がいい
exec.Command を使用する場合:
新しいプロセスが作成される

  • シェルが起動される
  • コマンドが実行される
  • プロセスが終了する
  • 結果を親プロセスが受け取る

os パッケージを使用する場合:

  • 直接システムコールが呼び出される
  • 追加のプロセス作成が不要
  • プロセス間通信のオーバーヘッドがない