0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

fiber.Ctxで返される値が変化してしまう問題を試す

Posted at

まずは、公式ドキュメントのZero Allocationの項目を読む。

ここに記載されている内容(発生原因と解決策)は、公式ドキュメントで書いてある内容を、再度書いているようなもの。公式ドキュメントを読んだのであれば、実際に値が変わる様子を見たい人のみ、以下を読む。

fiber.Ctxで返される値が変化してしまう問題とは

fiber.Ctxは、デフォルトでは、not immutableな値を返す。それをハンドラ外で保持するしており、複数のリクエストを受けた場合、値が書き変わってしまう可能性がある。
そのため、fiber.Ctxから得られる、params(ctx.Params(key))などは、ハンドラ外で使用する場合、注意して使用する必要がある。

問題の挙動を見てみる

func main() {
	addr := "localhost:3000"
	go server(addr)
	time.Sleep(3 * time.Second) // サーバ起動の待ち
	client(addr)
	time.Sleep(5 * time.Second) // wait関数の待ち
}

func client(addr string) {
	eg := errgroup.Group{}
	for i := 0; i < 10; i++ {
		i := i
		eg.Go(func() error {
			u := fmt.Sprintf("http://%s/users/%d", addr, i)
			_, err := http.Get(u)
			return err
		})
	}
	_ = eg.Wait()
}

func server(addr string) {
	app := fiber.New()
	app.Get("/users/:id", func(c *fiber.Ctx) error {
		go wait(c.Params("id"))
		return c.SendString("OK")
	})
	app.Listen(addr)
}

func wait(text string) {
	time.Sleep(2 * time.Second)
	fmt.Println(text)
}

結果

3
5
6
9
7
0
4
1
1
0

0が2つ、1が2つ出力されてしまっている

原因

公式ドキュメントで説明されている通り、fiberのCtxから返される値は、not immutableである。

そのため、ハンドラ外で値を使用していると、途中で変化してしまう可能性がある。
今回だと、go wait(c.Params("id"))

もう少し調べる

CopyString()を利用して調べる。

CopyString()を初めて見ると言う人は、この記事の冒頭で言っているのにも関わらず、公式ドキュメントを読んでいない人だと思うので、公式ドキュメントを読むことを勧めます。

// ハンドラ内のwait関数実行を以下に書き換える
go wait(utils.CopyString(c.Params("id")), c.Params("id"))

// wait関数を書き換える
func wait(a, text string) {
	b := utils.CopyString(a)
	time.Sleep(2 * time.Second)
	fmt.Printf("%s, %s -> %s\n", a, b, text)
}

出力結果

// ハンドラ内でCopy、wait関数内でCopy -> wait関数の引数
2, 2 -> 2
5, 5 -> 5
8, 8 -> 5
4, 4 -> 5
3, 3 -> 3
9, 9 -> 9
7, 7 -> 7
1, 1 -> 1
0, 0 -> 1
6, 6 -> 6

これからわかるように、utils.CopyString()でコピーした値は、1度しか出力されていないが、c.Params()で得られた値は、重複している。また、wait関数内でも途中で変更されている。例えば、8, 8 -> 5

最後に

公式ドキュメントをよく読むこと。そして遊ぶこと。

もちろん、ハンドラ内で値が閉じている場合は、Ctxから得られて値を使用すればいい。今回だと、go wait(c.Params("id"))となっているが、wait(c.Params("id"))と、なっていれば、ハンドラ内で実行されているので、問題はない。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?