サイト案内

運営してるひと: @sters9

妻と娘と猫と神奈川県に住んでいます。最近は Go, Ruby, Rails, Kubernetes, GCP, Datadog あたりをしていますがもっといろいろやりたい!

サイト案内

開発環境の紹介

プライバシーポリシー

tools.gomiba.co

サイト内検索

アーカイブ

2024/04 (5) 2024/03 (4) 2024/01 (3)

2023/12 (1) 2023/11 (3) 2023/10 (1) 2023/09 (1) 2023/08 (2) 2023/05 (4) 2023/04 (4) 2023/03 (4) 2023/02 (2) 2023/01 (1)

2022/12 (1) 2022/11 (4) 2022/10 (3) 2022/09 (2) 2022/08 (4) 2022/07 (5) 2022/06 (4) 2022/05 (9) 2022/04 (8) 2022/03 (10) 2022/02 (21) 2022/01 (8)

2021/12 (11) 2021/11 (1) 2021/10 (4) 2021/09 (2) 2021/08 (1) 2021/07 (2) 2021/06 (5) 2021/05 (10) 2021/04 (1) 2021/03 (8) 2021/02 (12) 2021/01 (8)

2020/05 (2) 2020/04 (2) 2020/02 (2) 2020/01 (1)

2019/12 (3) 2019/11 (2) 2019/10 (5) 2019/09 (3) 2019/07 (6) 2019/06 (4) 2019/04 (3) 2019/01 (2)

2018/12 (6) 2018/10 (4) 2018/09 (6) 2018/08 (7) 2018/07 (16) 2018/06 (7) 2018/05 (7) 2018/04 (5) 2018/03 (3) 2018/02 (10) 2018/01 (6)

2017/12 (8) 2017/11 (6) 2017/10 (10) 2017/09 (12) 2017/08 (12) 2017/07 (3) 2017/06 (1) 2017/01 (4)

2016/12 (5) 2016/10 (3) 2016/09 (1) 2016/07 (2) 2016/06 (1) 2016/04 (1) 2016/02 (1) 2016/01 (2)

2015/12 (1) 2015/10 (1) 2015/09 (3) 2015/06 (1) 2015/01 (1)

2014/08 (2) 2014/07 (3) 2014/05 (1) 2014/01 (7)

2013/12 (2) 2013/11 (4) 2013/10 (1) 2013/09 (1) 2013/08 (3) 2013/07 (4) 2013/06 (5) 2013/05 (2) 2013/04 (7) 2013/03 (1)

wneessen/go-mailを使うとSMTPリクエストが途切れる

前回: Goでメール(SMTPクライアント)はwneessen/go-mailがよさそう

wneessen/go-mail を使っているとSMTPリクエストが中断されることがたまにあって困っていたのでデバッグログで様子を見ることにした。

client.SetLogger(...)
client.SetDebugLog(true)

こんなふうにして、デバッグログを設定できるようになっている。これは標準のsmtpパッケージに手を入れて、各命令ごとにログを出すように仕込まれている。標準のsmtpパッケージはもう更新しないぞ、とあるのでそれ自体はまあいいんじゃないかな。

smtp package - net/smtp - Go Packages

そうしてログを眺めてみると、2つのリクエストが並行して起こったとき、1つめのリクエストが進行中に、2つめのEHLOを送るとバグることがわかった。

wneessen/go-mail のコード、標準のsmtpパッケージの流れを追ってみると、コネクションプールのようなものがない。そのため、クライアントを使い回した上で、リクエストを並列させると、同じコネクションで、異なるリクエストを送っていることがわかった。これによって、SMTPサーバ側が混線?して、正しい処理を行えなくなり、リクエストの処理を停止している、ような気がする。

wneessen/go-mail にコネクションプールを実装することはできそうだけど、大きく変更が必要そうなので、一旦アプリケーション側でクライアントをプールをしてみることにした。これには sql.DB のコネクションプールの実装がわかりやすかったので同じように実装した。

go/sql.go at 152ffca82fa53008bd2872f7163c7a1885da880e · golang/go

func (c *clientImpl) getFreeClient() (*gomail.Client, error) {
	expire := time.Now().Add(c.timeoutForWaitFree)

	for {
		if clock.Now().After(expire) {
			return nil, fmt.Errorf("available mail client is not found")
		}

		c.mux.Lock()
		numFree := len(c.freeClients)
		if numFree == 0 {
			c.mux.Unlock()
			time.Sleep(c.waitForFree)
			continue
		}

		client := c.freeClients[0]
		copy(c.freeClients, c.freeClients[1:])
		c.freeClients = c.freeClients[:numFree-1]
		c.mux.Unlock()

		return client, nil
	}
}

func (c *clientImpl) releaseClient(client *gomail.Client) {
	c.freeMux.Lock()
	defer c.freeMux.Unlock()

	c.freeClients = append(c.freeClients, client)
}

でもって、SMTPリクエストが途切れることがなくなったのだった :ok_hand:


この記事を書いているときに思ったけれどこれならsync.Poolでもいいかも?サイズの制御ができないからコネクションが無限に増える可能性があるところが気になるくらい

sync package - sync - Go Packages