Repository: cloudwego/hertz
Pull request #1188 (2024-09-06 02:55:38)

chore: release v0.9.3


Patch:
diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml
index dba5baa..a9c318f 100644
--- a/.github/workflows/pr-check.yml
+++ b/.github/workflows/pr-check.yml
@@ -9,20 +9,23 @@ jobs:
       - uses: actions/checkout@v3
 
       - name: Check License Header
-        uses: apache/skywalking-eyes/header@501a28d2fb4a9b962661987e50cf0219631b32ff
+        uses: apache/skywalking-eyes/header@v0.4.0
 
-      - name: typos-action
+      - name: Check Spell
         uses: crate-ci/typos@master
 
   staticcheck:
     runs-on: [ self-hosted, X64 ]
     steps:
-      - uses: actions/checkout@v3
-
+      - uses: actions/checkout@v4
       - name: Set up Go
-        uses: actions/setup-go@v3
+        uses: actions/setup-go@v5
         with:
-          go-version: 1.19
+          go-version: stable
+          # For self-hosted, the cache path is shared across projects
+          # and it works well without the cache of github actions
+          # Enable it if we're going to use Github only
+          cache: false
 
       - uses: reviewdog/action-staticcheck@v1
         with:
@@ -35,3 +38,19 @@ jobs:
           fail_on_error: true
           # Set staticcheck flags
           staticcheck_flags: -checks=inherit,-SA1029,-SA5008
+
+  lint:
+    runs-on: [ self-hosted, X64 ]
+    steps:
+      - uses: actions/checkout@v4
+      - name: Set up Go
+        uses: actions/setup-go@v5
+        with:
+          go-version: stable
+          cache: false # don't use cache for self-hosted runners
+
+      - name: Golangci Lint
+        # https://golangci-lint.run/
+        uses: golangci/golangci-lint-action@v6
+        with:
+          version: latest
\ No newline at end of file
diff --git a/.github/workflows/vulncheck.yml b/.github/workflows/vulncheck.yml
index 9b88498..6cbeff8 100644
--- a/.github/workflows/vulncheck.yml
+++ b/.github/workflows/vulncheck.yml
@@ -22,9 +22,9 @@ jobs:
         uses: actions/checkout@v4
 
       - name: Install Go
-        uses: actions/setup-go@v4
+        uses: actions/setup-go@v5
         with:
-          go-version: "stable"
+          go-version: stable
           check-latest: true
           cache: false
 
diff --git a/.gitignore b/.gitignore
index 02e193d..3389578 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
 .idea
 .vscode
 pkg/app/fs.go.hertz.gz
+coverage.txt
 coverage.out
diff --git a/_typos.toml b/_typos.toml
index bb673ba..50baf65 100644
--- a/_typos.toml
+++ b/_typos.toml
@@ -1,7 +1,7 @@
 # Typo check: https://github.com/crate-ci/typos
 
 [files]
-extend-exclude = ["go.sum"]
+extend-exclude = ["go.mod", "go.sum"]
 
 [default.extend-identifiers]
 # *sigh* this just isn't worth the cost of fixing
diff --git a/cmd/hz/app/app.go b/cmd/hz/app/app.go
index 739db88..2885a7e 100644
--- a/cmd/hz/app/app.go
+++ b/cmd/hz/app/app.go
@@ -178,6 +178,7 @@ func Init() *cli.App {
 	protoPluginsFlag := cli.StringSliceFlag{Name: "protoc-plugins", Usage: "Specify plugins for the protoc. ({plugin_name}:{options}:{out_dir})"}
 	noRecurseFlag := cli.BoolFlag{Name: "no_recurse", Usage: "Generate master model only.", Destination: &globalArgs.NoRecurse}
 	forceNewFlag := cli.BoolFlag{Name: "force", Aliases: []string{"f"}, Usage: "Force new a project, which will overwrite the generated files", Destination: &globalArgs.ForceNew}
+	forceUpdateClientFlag := cli.BoolFlag{Name: "force_client", Usage: "Force update 'hertz_client.go'", Destination: &globalArgs.ForceUpdateClient}
 	enableExtendsFlag := cli.BoolFlag{Name: "enable_extends", Usage: "Parse 'extends' for thrift IDL", Destination: &globalArgs.EnableExtends}
 	sortRouterFlag := cli.BoolFlag{Name: "sort_router", Usage: "Sort router register code, to avoid code difference", Destination: &globalArgs.SortRouter}
 
@@ -316,6 +317,7 @@ func Init() *cli.App {
 				&clientDirFlag,
 				&useFlag,
 				&forceClientDirFlag,
+				&forceUpdateClientFlag,
 
 				&includesFlag,
 				&thriftOptionsFlag,
diff --git a/cmd/hz/config/argument.go b/cmd/hz/config/argument.go
index 48e3812..d9dcca4 100644
--- a/cmd/hz/config/argument.go
+++ b/cmd/hz/config/argument.go
@@ -71,6 +71,7 @@ type Argument struct {
 	NoRecurse            bool
 	HandlerByMethod      bool
 	ForceNew             bool
+	ForceUpdateClient    bool
 	SnakeStyleMiddleware bool
 	EnableExtends        bool
 	SortRouter           bool
diff --git a/cmd/hz/generator/client.go b/cmd/hz/generator/client.go
index 7545a50..b1b887e 100644
--- a/cmd/hz/generator/client.go
+++ b/cmd/hz/generator/client.go
@@ -71,7 +71,7 @@ func (pkgGen *HttpPackageGenerator) genClient(pkg *HttpPackage, clientDir string
 			BaseDomain:    baseDomain,
 			Config:        ClientConfig{QueryEnumAsInt: pkgGen.QueryEnumAsInt},
 		}
-		if !isExist {
+		if !isExist || pkgGen.ForceUpdateClient {
 			err := pkgGen.TemplateGenerator.Generate(client, hertzClientTplName, hertzClientPath, false)
 			if err != nil {
 				return err
diff --git a/cmd/hz/generator/package.go b/cmd/hz/generator/package.go
index db4d976..6896229 100644
--- a/cmd/hz/generator/package.go
+++ b/cmd/hz/generator/package.go
@@ -70,6 +70,7 @@ type HttpPackageGenerator struct {
 	HandlerByMethod      bool // generate handler files with method dimension
 	SnakeStyleMiddleware bool // use snake name style for middleware
 	SortRouter           bool
+	ForceUpdateClient    bool // force update 'hertz_client.go'
 
 	loadedBackend   Backend
 	curModel        *model.Model
diff --git a/cmd/hz/protobuf/plugin.go b/cmd/hz/protobuf/plugin.go
index fbae899..abb7616 100644
--- a/cmd/hz/protobuf/plugin.go
+++ b/cmd/hz/protobuf/plugin.go
@@ -628,6 +628,7 @@ func (plugin *Plugin) genHttpPackage(ast *descriptorpb.FileDescriptorProto, deps
 		QueryEnumAsInt:       args.QueryEnumAsInt,
 		SnakeStyleMiddleware: args.SnakeStyleMiddleware,
 		SortRouter:           args.SortRouter,
+		ForceUpdateClient:    args.ForceUpdateClient,
 	}
 
 	if args.ModelBackend != "" {
diff --git a/cmd/hz/thrift/plugin.go b/cmd/hz/thrift/plugin.go
index 91fab02..0e773df 100644
--- a/cmd/hz/thrift/plugin.go
+++ b/cmd/hz/thrift/plugin.go
@@ -153,6 +153,7 @@ func (plugin *Plugin) Run() int {
 		QueryEnumAsInt:       args.QueryEnumAsInt,
 		SnakeStyleMiddleware: args.SnakeStyleMiddleware,
 		SortRouter:           args.SortRouter,
+		ForceUpdateClient:    args.ForceUpdateClient,
 	}
 	if args.ModelBackend != "" {
 		sg.Backend = meta.Backend(args.ModelBackend)
diff --git a/go.mod b/go.mod
index 36f7e51..cc53714 100644
--- a/go.mod
+++ b/go.mod
@@ -1,16 +1,35 @@
 module github.com/cloudwego/hertz
 
-go 1.16
+go 1.17
 
 require (
 	github.com/bytedance/go-tagexpr/v2 v2.9.2
-	github.com/bytedance/gopkg v0.0.0-20220413063733-65bf48ffb3a7
-	github.com/bytedance/mockey v1.2.1
-	github.com/bytedance/sonic v1.8.1
-	github.com/cloudwego/netpoll v0.6.0
+	github.com/bytedance/gopkg v0.1.0
+	github.com/bytedance/mockey v1.2.12
+	github.com/bytedance/sonic v1.12.0
+	github.com/cloudwego/netpoll v0.6.2
 	github.com/fsnotify/fsnotify v1.5.4
 	github.com/tidwall/gjson v1.14.4
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
-	golang.org/x/sys v0.0.0-20220412211240-33da011f77ad
+	golang.org/x/sys v0.24.0
 	google.golang.org/protobuf v1.27.1
 )
+
+require (
+	github.com/bytedance/sonic/loader v0.2.0 // indirect
+	github.com/cloudwego/base64x v0.1.4 // indirect
+	github.com/cloudwego/iasm v0.2.0 // indirect
+	github.com/golang/protobuf v1.5.0 // indirect
+	github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect
+	github.com/henrylee2cn/ameda v1.4.10 // indirect
+	github.com/henrylee2cn/goutil v0.0.0-20210127050712-89660552f6f8 // indirect
+	github.com/jtolds/gls v4.20.0+incompatible // indirect
+	github.com/klauspost/cpuid/v2 v2.0.9 // indirect
+	github.com/nyaruka/phonenumbers v1.0.55 // indirect
+	github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
+	github.com/smartystreets/goconvey v1.6.4 // indirect
+	github.com/tidwall/match v1.1.1 // indirect
+	github.com/tidwall/pretty v1.2.0 // indirect
+	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
+	golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
+)
diff --git a/go.sum b/go.sum
index 4b60e99..446891e 100644
--- a/go.sum
+++ b/go.sum
@@ -1,17 +1,21 @@
 github.com/bytedance/go-tagexpr/v2 v2.9.2 h1:QySJaAIQgOEDQBLS3x9BxOWrnhqu5sQ+f6HaZIxD39I=
 github.com/bytedance/go-tagexpr/v2 v2.9.2/go.mod h1:5qsx05dYOiUXOUgnQ7w3Oz8BYs2qtM/bJokdLb79wRM=
-github.com/bytedance/gopkg v0.0.0-20220413063733-65bf48ffb3a7 h1:PtwsQyQJGxf8iaPptPNaduEIu9BnrNms+pcRdHAxZaM=
-github.com/bytedance/gopkg v0.0.0-20220413063733-65bf48ffb3a7/go.mod h1:2ZlV9BaUH4+NXIBF0aMdKKAnHTzqH+iMU4KUjAbL23Q=
-github.com/bytedance/mockey v1.2.1 h1:g84ngI88hz1DR4wZTL3yOuqlEcq67MretBfQUdXwrmw=
-github.com/bytedance/mockey v1.2.1/go.mod h1:+Jm/fzWZAuhEDrPXVjDf/jLM2BlLXJkwk94zf2JZ3X4=
-github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
-github.com/bytedance/sonic v1.8.1 h1:NqAHCaGaTzro0xMmnTCLUyRlbEP6r8MCA1cJUrH3Pu4=
-github.com/bytedance/sonic v1.8.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
-github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
-github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
-github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
-github.com/cloudwego/netpoll v0.6.0 h1:JRMkrA1o8k/4quxzg6Q1XM+zIhwZsyoWlq6ef+ht31U=
-github.com/cloudwego/netpoll v0.6.0/go.mod h1:xVefXptcyheopwNDZjDPcfU6kIjZXZ4nY550k1yH9eQ=
+github.com/bytedance/gopkg v0.0.0-20240507064146-197ded923ae3/go.mod h1:FtQG3YbQG9L/91pbKSw787yBQPutC+457AvDW77fgUQ=
+github.com/bytedance/gopkg v0.1.0 h1:aAxB7mm1qms4Wz4sp8e1AtKDOeFLtdqvGiUe7aonRJs=
+github.com/bytedance/gopkg v0.1.0/go.mod h1:FtQG3YbQG9L/91pbKSw787yBQPutC+457AvDW77fgUQ=
+github.com/bytedance/mockey v1.2.12 h1:aeszOmGw8CPX8CRx1DZ/Glzb1yXvhjDh6jdFBNZjsU4=
+github.com/bytedance/mockey v1.2.12/go.mod h1:3ZA4MQasmqC87Tw0w7Ygdy7eHIc2xgpZ8Pona5rsYIk=
+github.com/bytedance/sonic v1.12.0 h1:YGPgxF9xzaCNvd/ZKdQ28yRovhfMFZQjuk6fKBzZ3ls=
+github.com/bytedance/sonic v1.12.0/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
+github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
+github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM=
+github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
+github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
+github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
+github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
+github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
+github.com/cloudwego/netpoll v0.6.2 h1:+KdILv5ATJU+222wNNXpHapYaBeRvvL8qhJyhcxRxrQ=
+github.com/cloudwego/netpoll v0.6.2/go.mod h1:kaqvfZ70qd4T2WtIIpCOi5Cxyob8viEpzLhCrTrz3HM=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -33,6 +37,7 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
 github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
 github.com/nyaruka/phonenumbers v1.0.55 h1:bj0nTO88Y68KeUQ/n3Lo2KgK7lM1hF7L9NFuwcCl3yg=
 github.com/nyaruka/phonenumbers v1.0.55/go.mod h1:sDaTZ/KPX5f8qyV9qN+hIm+4ZBARJrupC6LuhshJq1U=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -65,13 +70,19 @@ golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VA
 golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20220110181412-a018aaa089fe/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
+golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -83,4 +94,5 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
diff --git a/pkg/app/context.go b/pkg/app/context.go
index 369ece7..1d925b7 100644
--- a/pkg/app/context.go
+++ b/pkg/app/context.go
@@ -694,6 +694,17 @@ func (ctx *RequestContext) multipartFormValue(key string) (string, bool) {
 	return "", false
 }
 
+func (ctx *RequestContext) multipartFormValueArray(key string) ([]string, bool) {
+	mf, err := ctx.MultipartForm()
+	if err == nil && mf.Value != nil {
+		vv := mf.Value[key]
+		if len(vv) > 0 {
+			return vv, true
+		}
+	}
+	return nil, false
+}
+
 func (ctx *RequestContext) RequestBodyStream() io.Reader {
 	return ctx.Request.BodyStream()
 }
@@ -1366,6 +1377,13 @@ func (ctx *RequestContext) PostForm(key string) string {
 	return value
 }
 
+// PostFormArray returns the specified key from a POST urlencoded form or multipart form
+// when it exists, otherwise it returns an empty array `([])`.
+func (ctx *RequestContext) PostFormArray(key string) []string {
+	values, _ := ctx.GetPostFormArray(key)
+	return values
+}
+
 // DefaultPostForm returns the specified key from a POST urlencoded form or multipart form
 // when it exists, otherwise it returns the specified defaultValue string.
 //
@@ -1393,6 +1411,28 @@ func (ctx *RequestContext) GetPostForm(key string) (string, bool) {
 	return ctx.multipartFormValue(key)
 }
 
+// GetPostFormArray is like PostFormArray(key). It returns the specified key from a POST urlencoded
+// form or multipart form when it exists `([]string, true)` (even when the value is an empty string),
+// otherwise it returns ([]string(nil), false).
+//
+// For example, during a PATCH request to update the item's tags:
+//
+//	    tag=tag1 tag=tag2 tag=tag3  -->  (["tag1", "tag2", "tag3"], true) := GetPostFormArray("tags") // set tags to ["tag1", "tag2", "tag3"]
+//		   tags=                  -->  (nil, true) := GetPostFormArray("tags") // set tags to nil
+//	                            -->  (nil, false) := GetPostFormArray("tags") // do nothing with tags
+func (ctx *RequestContext) GetPostFormArray(key string) ([]string, bool) {
+	vs := ctx.PostArgs().PeekAll(key)
+	values := make([]string, len(vs))
+	for i, v := range vs {
+		values[i] = string(v)
+	}
+	if len(values) == 0 {
+		return ctx.multipartFormValueArray(key)
+	} else {
+		return values, true
+	}
+}
+
 // bodyAllowedForStatus is a copy of http.bodyAllowedForStatus non-exported function.
 func bodyAllowedForStatus(status int) bool {
 	switch {
diff --git a/pkg/app/server/binding/internal/decoder/text_decoder.go b/pkg/app/server/binding/internal/decoder/text_decoder.go
index 8b53c2b..fb59823 100644
--- a/pkg/app/server/binding/internal/decoder/text_decoder.go
+++ b/pkg/app/server/binding/internal/decoder/text_decoder.go
@@ -87,7 +87,7 @@ func SelectTextDecoder(rt reflect.Type) (TextDecoder, error) {
 		return &interfaceDecoder{}, nil
 	}
 
-	return nil, fmt.Errorf("unsupported type " + rt.String())
+	return nil, fmt.Errorf("unsupported type %s", rt.String())
 }
 
 type boolDecoder struct{}
diff --git a/pkg/common/utils/env.go b/pkg/common/utils/env.go
new file mode 100644
index 0000000..291b5e3
--- /dev/null
+++ b/pkg/common/utils/env.go
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2024 CloudWeGo Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package utils
+
+import (
+	"os"
+	"strconv"
+	"strings"
+
+	"github.com/cloudwego/hertz/pkg/common/errors"
+)
+
+// Get bool from env
+func GetBoolFromEnv(key string) (bool, error) {
+	value, isExist := os.LookupEnv(key)
+	if !isExist {
+		return false, errors.NewPublic("env not exist")
+	}
+
+	value = strings.TrimSpace(value)
+	return strconv.ParseBool(value)
+}
diff --git a/pkg/protocol/http1/server.go b/pkg/protocol/http1/server.go
index 3ea6596..eb46f65 100644
--- a/pkg/protocol/http1/server.go
+++ b/pkg/protocol/http1/server.go
@@ -32,6 +32,7 @@ import (
 	errs "github.com/cloudwego/hertz/pkg/common/errors"
 	"github.com/cloudwego/hertz/pkg/common/tracer/stats"
 	"github.com/cloudwego/hertz/pkg/common/tracer/traceinfo"
+	"github.com/cloudwego/hertz/pkg/common/utils"
 	"github.com/cloudwego/hertz/pkg/network"
 	"github.com/cloudwego/hertz/pkg/protocol"
 	"github.com/cloudwego/hertz/pkg/protocol/consts"
@@ -41,6 +42,12 @@ import (
 	"github.com/cloudwego/hertz/pkg/protocol/suite"
 )
 
+func init() {
+	if b, err := utils.GetBoolFromEnv("HERTZ_DISABLE_REQUEST_CONTEXT_POOL"); err == nil {
+		disabaleRequestContextPool = b
+	}
+}
+
 // NextProtoTLS is the NPN/ALPN protocol negotiated during
 // HTTP/1.1's TLS setup.
 // Also used for server addressing
@@ -51,6 +58,8 @@ var (
 	errIdleTimeout     = errs.New(errs.ErrIdleTimeout, errs.ErrorTypePrivate, nil)
 	errShortConnection = errs.New(errs.ErrShortConnection, errs.ErrorTypePublic, "server is going to close the connection")
 	errUnexpectedEOF   = errs.NewPublic(io.ErrUnexpectedEOF.Error() + " when reading request")
+
+	disabaleRequestContextPool = false
 )
 
 type Option struct {
@@ -80,6 +89,21 @@ type Server struct {
 	eventStackPool *sync.Pool
 }
 
+func (s Server) getRequestContext() *app.RequestContext {
+	if disabaleRequestContextPool {
+		return &app.RequestContext{}
+	}
+	return s.Core.GetCtxPool().Get().(*app.RequestContext)
+}
+
+func (s Server) putRequestContext(ctx *app.RequestContext) {
+	if disabaleRequestContextPool {
+		return
+	}
+	ctx.Reset()
+	s.Core.GetCtxPool().Put(ctx)
+}
+
 func (s Server) Serve(c context.Context, conn network.Conn) (err error) {
 	var (
 		zr network.Reader
@@ -97,8 +121,8 @@ func (s Server) Serve(c context.Context, conn network.Conn) (err error) {
 		// 1. Get a request context
 		// 2. Prepare it
 		// 3. Process it
-		// 4. Reset and recycle
-		ctx = s.Core.GetCtxPool().Get().(*app.RequestContext)
+		// 4. Reset and recycle(in pooled mode)
+		ctx = s.getRequestContext()
 
 		traceCtl        = s.Core.GetTracer()
 		eventsToTrigger *eventStack
@@ -138,8 +162,7 @@ func (s Server) Serve(c context.Context, conn network.Conn) (err error) {
 			return
 		}
 
-		ctx.Reset()
-		s.Core.GetCtxPool().Put(ctx)
+		s.putRequestContext(ctx)
 	}()
 
 	ctx.HTMLRender = s.HTMLRender
diff --git a/pkg/route/engine.go b/pkg/route/engine.go
index 4881ee9..2a7b60c 100644
--- a/pkg/route/engine.go
+++ b/pkg/route/engine.go
@@ -349,11 +349,6 @@ func (engine *Engine) Run() (err error) {
 		return err
 	}
 
-	if err = engine.MarkAsRunning(); err != nil {
-		return err
-	}
-	defer atomic.StoreUint32(&engine.status, statusClosed)
-
 	// trigger hooks if any
 	ctx := context.Background()
 	for i := range engine.OnRun {
@@ -362,6 +357,11 @@ func (engine *Engine) Run() (err error) {
 		}
 	}
 
+	if err = engine.MarkAsRunning(); err != nil {
+		return err
+	}
+	defer atomic.StoreUint32(&engine.status, statusClosed)
+
 	return engine.listenAndServe()
 }
 
diff --git a/version.go b/version.go
index 3a0529d..1f5fabe 100644
--- a/version.go
+++ b/version.go
@@ -19,5 +19,5 @@ package hertz
 // Name and Version info of this framework, used for statistics and debug
 const (
 	Name    = "Hertz"
-	Version = "v0.9.2"
+	Version = "v0.9.3"
 )


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index e16ee73..54ce8b9 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -6,39 +6,33 @@ jobs:
   compat-test:
     strategy:
       matrix:
-        version: ["1.16", "1.17"]
+        version: ["1.17", "1.18", "1.19"]
     runs-on: [self-hosted, X64]
     steps:
-      - uses: actions/checkout@v3
-
+      - uses: actions/checkout@v4
       - name: Set up Go
-        uses: actions/setup-go@v3
+        uses: actions/setup-go@v5
         with:
           go-version: ${{ matrix.version }}
+          cache: false # don't use cache for self-hosted runners
 
       # Just build tests without running them
       - name: Build Test
         run: go test -run=nope ./...
 
-  lint-and-ut:
+  unit-test:
     strategy:
       matrix:
-        version: ["1.18", "1.19", "1.20"]
+        version: ["1.20", "1.21", "1.22", "1.23"]
     runs-on: [self-hosted, X64]
     steps:
-      - uses: actions/checkout@v3
-
+      - uses: actions/checkout@v4
       - name: Set up Go
-        uses: actions/setup-go@v3
+        uses: actions/setup-go@v5
         with:
           go-version: ${{ matrix.version }}
+          cache: false # don't use cache for self-hosted runners
 
-      - name: Golangci Lint
-        # https://golangci-lint.run/
-        uses: golangci/golangci-lint-action@v3
-        with:
-          version: latest
-          skip-cache: true
       - name: Unit Test
         run: go test -race -covermode=atomic -coverprofile=coverage.txt ./...
 
@@ -48,31 +42,28 @@ jobs:
   ut-windows:
     strategy:
       matrix:
-        version: ["1.18", "1.19", "1.20"]
+        version: ["1.20", "1.21", "1.22", "1.23"]
     runs-on: windows-latest
     steps:
-      - uses: actions/checkout@v3
-
+      - uses: actions/checkout@v4
       - name: Set up Go
-        uses: actions/setup-go@v3
+        uses: actions/setup-go@v5
         with:
           go-version: ${{ matrix.version }}
+          cache: false # don't use cache for self-hosted runners
 
       - name: Unit Test
         run: go test -race -covermode=atomic  ./...
 
   hz-test-unix:
-    strategy:
-      matrix:
-        version: [ '1.20' ]
     runs-on: [ self-hosted, X64 ]
     steps:
-      - uses: actions/checkout@v3
-
+      - uses: actions/checkout@v4
       - name: Set up Go
-        uses: actions/setup-go@v3
+        uses: actions/setup-go@v5
         with:
-          go-version: ${{ matrix.version }}
+          go-version: stable
+          cache: false # don't use cache for self-hosted runners
 
       - name: Setup Environment
         run: |
@@ -91,17 +82,13 @@ jobs:
 
 
   hz-test-windows:
-    strategy:
-      matrix:
-        version: [ '1.20']
     runs-on: windows-latest
     steps:
-      - uses: actions/checkout@v3
-
+      - uses: actions/checkout@v4
       - name: Set up Go
-        uses: actions/setup-go@v3
+        uses: actions/setup-go@v5
         with:
-          go-version: ${{ matrix.version }}
+          go-version: stable
 
       - name: Get changed files
         id: changed-files
diff --git a/pkg/app/client/client_test.go b/pkg/app/client/client_test.go
index 5f3e8c6..ed5959b 100644
--- a/pkg/app/client/client_test.go
+++ b/pkg/app/client/client_test.go
@@ -92,7 +92,12 @@ func TestCloseIdleConnections(t *testing.T) {
 	defer func() {
 		engine.Close()
 	}()
-	time.Sleep(time.Millisecond * 500)
+	for {
+		time.Sleep(1 * time.Second)
+		if engine.IsRunning() {
+			break
+		}
+	}
 
 	c, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, nil)))
 
@@ -136,7 +141,12 @@ func TestClientInvalidURI(t *testing.T) {
 	defer func() {
 		engine.Close()
 	}()
-	time.Sleep(time.Millisecond * 500)
+	for {
+		time.Sleep(1 * time.Second)
+		if engine.IsRunning() {
+			break
+		}
+	}
 
 	c, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, nil)))
 	req, res := protocol.AcquireRequest(), protocol.AcquireResponse()
@@ -170,7 +180,12 @@ func TestClientGetWithBody(t *testing.T) {
 	defer func() {
 		engine.Close()
 	}()
-	time.Sleep(time.Millisecond * 500)
+	for {
+		time.Sleep(1 * time.Second)
+		if engine.IsRunning() {
+			break
+		}
+	}
 
 	c, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, nil)))
 	req, res := protocol.AcquireRequest(), protocol.AcquireResponse()
@@ -205,7 +220,12 @@ func TestClientPostBodyStream(t *testing.T) {
 	defer func() {
 		engine.Close()
 	}()
-	time.Sleep(time.Millisecond * 500)
+	for {
+		time.Sleep(1 * time.Second)
+		if engine.IsRunning() {
+			break
+		}
+	}
 
 	cStream, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, nil)), WithResponseBodyStream(true))
 	args := &protocol.Args{}
@@ -246,7 +266,12 @@ func TestClientURLAuth(t *testing.T) {
 	defer func() {
 		engine.Close()
 	}()
-	time.Sleep(time.Millisecond * 500)
+	for {
+		time.Sleep(1 * time.Second)
+		if engine.IsRunning() {
+			break
+		}
+	}
 
 	c, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, nil)))
 	for up, expected := range cases {
@@ -278,7 +303,12 @@ func TestClientNilResp(t *testing.T) {
 	defer func() {
 		engine.Close()
 	}()
-	time.Sleep(time.Millisecond * 500)
+	for {
+		time.Sleep(1 * time.Second)
+		if engine.IsRunning() {
+			break
+		}
+	}
 
 	c, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, nil)))
 
@@ -301,7 +331,15 @@ func TestClientParseConn(t *testing.T) {
 	engine.GET("/", func(c context.Context, ctx *app.RequestContext) {
 	})
 	go engine.Run()
-	time.Sleep(time.Millisecond * 500)
+	defer func() {
+		engine.Close()
+	}()
+	for {
+		time.Sleep(1 * time.Second)
+		if engine.IsRunning() {
+			break
+		}
+	}
 
 	c, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, nil)))
 	req, res := protocol.AcquireRequest(), protocol.AcquireResponse()
@@ -343,7 +381,12 @@ func TestClientPostArgs(t *testing.T) {
 	defer func() {
 		engine.Close()
 	}()
-	time.Sleep(time.Millisecond * 500)
+	for {
+		time.Sleep(1 * time.Second)
+		if engine.IsRunning() {
+			break
+		}
+	}
 	c, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, nil)))
 	req, res := protocol.AcquireRequest(), protocol.AcquireResponse()
 	defer func() {
@@ -403,20 +446,22 @@ func TestClientReadTimeout(t *testing.T) {
 		t.Skip("skipping test in short mode")
 	}
 
-	timeout := false
 	opt := config.NewOptions([]config.Option{})
-	opt.Addr = "localhost:10008"
+	opt.Addr = "localhost:10024"
 	engine := route.NewEngine(opt)
 
-	engine.GET("/", func(c context.Context, ctx *app.RequestContext) {
-		if timeout {
-			time.Sleep(time.Minute)
-		} else {
-			timeout = true
-		}
+	engine.GET("/normal", func(c context.Context, ctx *app.RequestContext) {
+		ctx.String(201, "ok")
+	})
+	engine.GET("/timeout", func(c context.Context, ctx *app.RequestContext) {
+		time.Sleep(time.Second * 60)
+		ctx.String(202, "timeout ok")
 	})
 	go engine.Run()
-	time.Sleep(time.Millisecond * 500)
+	defer func() {
+		engine.Close()
+	}()
+	time.Sleep(time.Second * 1)
 
 	c := &http1.HostClient{
 		ClientOptions: &http1.ClientOptions{
@@ -430,7 +475,7 @@ func TestClientReadTimeout(t *testing.T) {
 	req := protocol.AcquireRequest()
 	res := protocol.AcquireResponse()
 
-	req.SetRequestURI("http://" + opt.Addr)
+	req.SetRequestURI("http://" + opt.Addr + "/normal")
 	req.Header.SetMethod(consts.MethodGet)
 
 	// Setting Connection: Close will make the connection be returned to the pool.
@@ -448,13 +493,17 @@ func TestClientReadTimeout(t *testing.T) {
 		req := protocol.AcquireRequest()
 		res := protocol.AcquireResponse()
 
-		req.SetRequestURI("http://" + opt.Addr)
+		req.SetRequestURI("http://" + opt.Addr + "/timeout")
 		req.Header.SetMethod(consts.MethodGet)
 		req.SetConnectionClose()
 
 		if err := c.Do(context.Background(), req, res); !errors.Is(err, errs.ErrTimeout) {
-			if !strings.Contains(err.Error(), "timeout") {
-				t.Errorf("expected ErrTimeout got %#v", err)
+			if err == nil {
+				t.Errorf("expected ErrTimeout got nil, req url: %s, read resp body: %s, status: %d", string(req.URI().FullURI()), string(res.Body()), res.StatusCode())
+			} else {
+				if !strings.Contains(err.Error(), "timeout") {
+					t.Errorf("expected ErrTimeout got %#v", err)
+				}
 			}
 		}
 
@@ -488,7 +537,12 @@ func TestClientDefaultUserAgent(t *testing.T) {
 	defer func() {
 		engine.Close()
 	}()
-	time.Sleep(time.Millisecond * 500)
+	for {
+		time.Sleep(1 * time.Second)
+		if engine.IsRunning() {
+			break
+		}
+	}
 
 	c, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, nil)))
 	req := protocol.AcquireRequest()
@@ -522,7 +576,12 @@ func TestClientSetUserAgent(t *testing.T) {
 	defer func() {
 		engine.Close()
 	}()
-	time.Sleep(time.Millisecond * 500)
+	for {
+		time.Sleep(1 * time.Second)
+		if engine.IsRunning() {
+			break
+		}
+	}
 
 	userAgent := "I'm not hertz"
 	c, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, time.Second, nil)), WithName(userAgent))
@@ -553,7 +612,12 @@ func TestClientNoUserAgent(t *testing.T) {
 	defer func() {
 		engine.Close()
 	}()
-	time.Sleep(time.Millisecond * 500)
+	for {
+		time.Sleep(1 * time.Second)
+		if engine.IsRunning() {
+			break
+		}
+	}
 	c, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, time.Second, nil)), WithDialTimeout(1*time.Second), WithNoDefaultUserAgentHeader(true))
 
 	req := protocol.AcquireRequest()
@@ -634,7 +698,12 @@ func TestClientDoWithCustomHeaders(t *testing.T) {
 	defer func() {
 		engine.Close()
 	}()
-	time.Sleep(time.Millisecond * 500)
+	for {
+		time.Sleep(1 * time.Second)
+		if engine.IsRunning() {
+			break
+		}
+	}
 
 	// make sure that the client sends all the request headers and body.
 	c, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, nil)))
@@ -679,7 +748,12 @@ func TestClientDoTimeoutDisablePathNormalizing(t *testing.T) {
 	defer func() {
 		engine.Close()
 	}()
-	time.Sleep(time.Millisecond * 500)
+	for {
+		time.Sleep(1 * time.Second)
+		if engine.IsRunning() {
+			break
+		}
+	}
 	c, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, time.Second, nil)), WithDisablePathNormalizing(true))
 
 	urlWithEncodedPath := "http://example.com/encoded/Y%2BY%2FY%3D/stuff"
@@ -809,7 +883,12 @@ func TestHostClientMaxConnsWithDeadline(t *testing.T) {
 	defer func() {
 		engine.Close()
 	}()
-	time.Sleep(time.Millisecond * 500)
+	for {
+		time.Sleep(1 * time.Second)
+		if engine.IsRunning() {
+			break
+		}
+	}
 
 	c := &http1.HostClient{
 		ClientOptions: &http1.ClientOptions{
@@ -878,7 +957,12 @@ func TestHostClientMaxConnDuration(t *testing.T) {
 	defer func() {
 		engine.Close()
 	}()
-	time.Sleep(time.Millisecond * 500)
+	for {
+		time.Sleep(1 * time.Second)
+		if engine.IsRunning() {
+			break
+		}
+	}
 
 	c := &http1.HostClient{
 		ClientOptions: &http1.ClientOptions{
@@ -923,7 +1007,12 @@ func TestHostClientMultipleAddrs(t *testing.T) {
 	defer func() {
 		engine.Close()
 	}()
-	time.Sleep(time.Millisecond * 500)
+	for {
+		time.Sleep(1 * time.Second)
+		if engine.IsRunning() {
+			break
+		}
+	}
 
 	dialsCount := make(map[string]int)
 	c := &http1.HostClient{
@@ -990,7 +1079,7 @@ func TestClientFollowRedirects(t *testing.T) {
 	defer func() {
 		engine.Close()
 	}()
-	time.Sleep(time.Millisecond * 500)
+	time.Sleep(time.Second * 2)
 
 	c := &http1.HostClient{
 		ClientOptions: &http1.ClientOptions{
@@ -1084,7 +1173,12 @@ func TestHostClientMaxConnWaitTimeoutSuccess(t *testing.T) {
 	defer func() {
 		engine.Close()
 	}()
-	time.Sleep(time.Millisecond * 500)
+	for {
+		time.Sleep(1 * time.Second)
+		if engine.IsRunning() {
+			break
+		}
+	}
 
 	c := &http1.HostClient{
 		ClientOptions: &http1.ClientOptions{
@@ -1153,7 +1247,12 @@ func TestHostClientMaxConnWaitTimeoutError(t *testing.T) {
 	defer func() {
 		engine.Close()
 	}()
-	time.Sleep(time.Millisecond * 500)
+	for {
+		time.Sleep(1 * time.Second)
+		if engine.IsRunning() {
+			break
+		}
+	}
 
 	c := &http1.HostClient{
 		ClientOptions: &http1.ClientOptions{
@@ -1215,7 +1314,10 @@ func TestNewClient(t *testing.T) {
 		ctx.SetBodyString("pong")
 	})
 	go engine.Run()
-	time.Sleep(time.Millisecond * 500)
+	defer func() {
+		engine.Close()
+	}()
+	time.Sleep(1 * time.Second)
 
 	client, err := NewClient(WithDialTimeout(2 * time.Second))
 	if err != nil {
@@ -1240,7 +1342,10 @@ func TestUseShortConnection(t *testing.T) {
 	engine.GET("/", func(c context.Context, ctx *app.RequestContext) {
 	})
 	go engine.Run()
-	time.Sleep(time.Millisecond * 500)
+	defer func() {
+		engine.Close()
+	}()
+	time.Sleep(1 * time.Second)
 
 	c, _ := NewClient(WithKeepAlive(false))
 	var wg sync.WaitGroup
@@ -1284,8 +1389,11 @@ func TestPostWithFormData(t *testing.T) {
 		ctx.Data(consts.StatusOK, "text/plain; charset=utf-8", []byte(ans))
 	})
 	go engine.Run()
+	defer func() {
+		engine.Close()
+	}()
 
-	time.Sleep(100 * time.Millisecond)
+	time.Sleep(1 * time.Second)
 	client, _ := NewClient()
 	req := protocol.AcquireRequest()
 	rsp := protocol.AcquireResponse()
@@ -1335,8 +1443,11 @@ func TestPostWithMultipartField(t *testing.T) {
 		t.Log(req.GetHTTP1Request(&ctx.Request).String())
 	})
 	go engine.Run()
+	defer func() {
+		engine.Close()
+	}()
 
-	time.Sleep(100 * time.Millisecond)
+	time.Sleep(1 * time.Second)
 	client, _ := NewClient()
 	req := protocol.AcquireRequest()
 	rsp := protocol.AcquireResponse()
@@ -1378,8 +1489,11 @@ func TestSetFiles(t *testing.T) {
 		ctx.String(consts.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)+2))
 	})
 	go engine.Run()
+	defer func() {
+		engine.Close()
+	}()
 
-	time.Sleep(100 * time.Millisecond)
+	time.Sleep(1 * time.Second)
 	client, _ := NewClient()
 	req := protocol.AcquireRequest()
 	rsp := protocol.AcquireResponse()
@@ -1426,8 +1540,11 @@ func TestSetMultipartFields(t *testing.T) {
 		ctx.String(consts.StatusOK, fmt.Sprintf("%d files uploaded!", 2))
 	})
 	go engine.Run()
+	defer func() {
+		engine.Close()
+	}()
 
-	time.Sleep(100 * time.Millisecond)
+	time.Sleep(1 * time.Second)
 	client, _ := NewClient(WithDialTimeout(50 * time.Millisecond))
 	req := protocol.AcquireRequest()
 	rsp := protocol.AcquireResponse()
@@ -1478,7 +1595,10 @@ func TestClientReadResponseBodyStream(t *testing.T) {
 		c.String(consts.StatusOK, part1+part2)
 	})
 	go engine.Run()
-	time.Sleep(100 * time.Millisecond)
+	defer func() {
+		engine.Close()
+	}()
+	time.Sleep(1 * time.Second)
 
 	client, _ := NewClient(WithResponseBodyStream(true))
 	req, resp := protocol.AcquireRequest(), protocol.AcquireResponse()
@@ -1528,7 +1648,10 @@ func TestWithBasicAuth(t *testing.T) {
 		}
 	})
 	go engine.Run()
-	time.Sleep(100 * time.Millisecond)
+	defer func() {
+		engine.Close()
+	}()
+	time.Sleep(1 * time.Second)
 	client, _ := NewClient()
 	req := protocol.AcquireRequest()
 	rsp := protocol.AcquireResponse()
@@ -1896,7 +2019,10 @@ func TestClientReadResponseBodyStreamWithDoubleRequest(t *testing.T) {
 		c.String(consts.StatusOK, part1+part2)
 	})
 	go engine.Run()
-	time.Sleep(100 * time.Millisecond)
+	defer func() {
+		engine.Close()
+	}()
+	time.Sleep(1 * time.Second)
 
 	client, _ := NewClient(WithResponseBodyStream(true))
 	req, resp := protocol.AcquireRequest(), protocol.AcquireResponse()
@@ -1966,7 +2092,10 @@ func TestClientReadResponseBodyStreamWithConnectionClose(t *testing.T) {
 		c.String(consts.StatusOK, part1)
 	})
 	go engine.Run()
-	time.Sleep(100 * time.Millisecond)
+	defer func() {
+		engine.Close()
+	}()
+	time.Sleep(1 * time.Second)
 
 	client, _ := NewClient(WithResponseBodyStream(true))
 
@@ -2276,7 +2405,7 @@ func TestClientDoWithDialFunc(t *testing.T) {
 	defer func() {
 		engine.Close()
 	}()
-	time.Sleep(time.Millisecond * 500)
+	time.Sleep(1 * time.Second)
 
 	c, _ := NewClient(WithDialFunc(func(addr string) (network.Conn, error) {
 		return dialer.DialConnection(opt.Network, opt.Addr, time.Second, nil)
@@ -2307,11 +2436,14 @@ func TestClientDoWithDialFunc(t *testing.T) {
 
 func TestClientState(t *testing.T) {
 	opt := config.NewOptions([]config.Option{})
-	opt.Addr = ":10037"
+	opt.Addr = "127.0.0.1:10037"
 	engine := route.NewEngine(opt)
 	go engine.Run()
+	defer func() {
+		engine.Close()
+	}()
 
-	time.Sleep(time.Millisecond)
+	time.Sleep(1 * time.Second)
 
 	state := int32(0)
 	client, _ := NewClient(
@@ -2351,14 +2483,16 @@ func TestClientRetryErr(t *testing.T) {
 			ctx.SetStatusCode(200)
 		})
 		go engine.Run()
-		time.Sleep(100 * time.Millisecond)
+		defer func() {
+			engine.Close()
+		}()
+		time.Sleep(1 * time.Second)
 		c, _ := NewClient(WithRetryConfig(retry.WithMaxAttemptTimes(3)))
 		_, _, err := c.Get(context.Background(), nil, "http://127.0.0.1:10136/ping")
 		assert.Nil(t, err)
 		l.Lock()
 		assert.DeepEqual(t, 1, retryNum)
 		l.Unlock()
-		engine.Close()
 	})
 
 	t.Run("502", func(t *testing.T) {
@@ -2374,7 +2508,10 @@ func TestClientRetryErr(t *testing.T) {
 			ctx.SetStatusCode(502)
 		})
 		go engine.Run()
-		time.Sleep(100 * time.Millisecond)
+		defer func() {
+			engine.Close()
+		}()
+		time.Sleep(1 * time.Second)
 		c, _ := NewClient(WithRetryConfig(retry.WithMaxAttemptTimes(3)))
 		c.SetRetryIfFunc(func(req *protocol.Request, resp *protocol.Response, err error) bool {
 			return resp.StatusCode() == 502
@@ -2384,6 +2521,5 @@ func TestClientRetryErr(t *testing.T) {
 		l.Lock()
 		assert.DeepEqual(t, 3, retryNum)
 		l.Unlock()
-		engine.Close()
 	})
 }
diff --git a/pkg/app/client/option_test.go b/pkg/app/client/option_test.go
index f744210..08e6c85 100644
--- a/pkg/app/client/option_test.go
+++ b/pkg/app/client/option_test.go
@@ -17,7 +17,6 @@
 package client
 
 import (
-	"fmt"
 	"testing"
 	"time"
 
@@ -41,7 +40,7 @@ func TestClientOptions(t *testing.T) {
 			retry.WithInitDelay(100*time.Millisecond),
 			retry.WithMaxDelay(5*time.Second),
 			retry.WithMaxJitter(1*time.Second),
-			retry.WithDelayPolicy(retry.CombineDelay(retry.FixedDelayPolicy, retry.BackOffDelayPolicy, retry.RandomDelayPolicy)),
+			retry.WithDelayPolicy(retry.CombineDelay(retry.DefaultDelayPolicy, retry.FixedDelayPolicy, retry.BackOffDelayPolicy)),
 		),
 		WithWriteTimeout(time.Second),
 		WithConnStateObserve(nil, time.Second),
@@ -60,5 +59,7 @@ func TestClientOptions(t *testing.T) {
 	assert.DeepEqual(t, 5*time.Second, opt.RetryConfig.MaxDelay)
 	assert.DeepEqual(t, 1*time.Second, opt.RetryConfig.MaxJitter)
 	assert.DeepEqual(t, 1*time.Second, opt.ObservationInterval)
-	assert.DeepEqual(t, fmt.Sprint(retry.CombineDelay(retry.FixedDelayPolicy, retry.BackOffDelayPolicy, retry.RandomDelayPolicy)), fmt.Sprint(opt.RetryConfig.DelayPolicy))
+	for i := 0; i < 100; i++ {
+		assert.DeepEqual(t, opt.RetryConfig.DelayPolicy(uint(i), nil, opt.RetryConfig), retry.CombineDelay(retry.DefaultDelayPolicy, retry.FixedDelayPolicy, retry.BackOffDelayPolicy)(uint(i), nil, opt.RetryConfig))
+	}
 }
diff --git a/pkg/app/context_test.go b/pkg/app/context_test.go
index 1317a06..b2aef63 100644
--- a/pkg/app/context_test.go
+++ b/pkg/app/context_test.go
@@ -367,6 +367,40 @@ hello=world`)
 	}
 }
 
+func TestPostFormArray(t *testing.T) {
+	t.Parallel()
+
+	ctx := makeCtxByReqString(t, `POST /upload HTTP/1.1
+Host: localhost:10000
+Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryJwfATyF8tmxSJnLg
+Content-Length: 521
+
+------WebKitFormBoundaryJwfATyF8tmxSJnLg
+Content-Disposition: form-data; name="tag"
+
+red
+------WebKitFormBoundaryJwfATyF8tmxSJnLg
+Content-Disposition: form-data; name="tag"
+
+green
+------WebKitFormBoundaryJwfATyF8tmxSJnLg
+Content-Disposition: form-data; name="tag"
+
+blue
+------WebKitFormBoundaryJwfATyF8tmxSJnLg--
+`)
+	assert.DeepEqual(t, []string{"red", "green", "blue"}, ctx.PostFormArray("tag"))
+
+	ctx = makeCtxByReqString(t, `POST /upload HTTP/1.1
+Host: localhost:10000
+Content-Type: application/x-www-form-urlencoded; charset=UTF-8
+Content-Length: 26
+
+tag=red&tag=green&tag=blue
+`)
+	assert.DeepEqual(t, []string{"red", "green", "blue"}, ctx.PostFormArray("tag"))
+}
+
 func TestDefaultPostForm(t *testing.T) {
 	ctx := makeCtxByReqString(t, `POST /upload HTTP/1.1
 Host: localhost:10000
@@ -508,7 +542,9 @@ func TestContextRenderFileFromFS(t *testing.T) {
 
 	assert.DeepEqual(t, consts.StatusOK, ctx.Response.StatusCode())
 	assert.True(t, strings.Contains(resp.GetHTTP1Response(&ctx.Response).String(), "func (fs *FS) initRequestHandler() {"))
-	assert.DeepEqual(t, consts.MIMETextPlainUTF8, string(ctx.Response.Header.Peek("Content-Type")))
+	// when Go version <= 1.16, mime.TypeByExtension will return Content-Type='text/plain; charset=utf-8',
+	// otherwise it will return Content-Type='text/x-go; charset=utf-8'
+	assert.NotEqual(t, "", string(ctx.Response.Header.Peek("Content-Type")))
 	assert.DeepEqual(t, "/some/path", string(ctx.Request.URI().Path()))
 }
 
@@ -525,7 +561,9 @@ func TestContextRenderFile(t *testing.T) {
 
 	assert.DeepEqual(t, consts.StatusOK, ctx.Response.StatusCode())
 	assert.True(t, strings.Contains(resp.GetHTTP1Response(&ctx.Response).String(), "func (fs *FS) initRequestHandler() {"))
-	assert.DeepEqual(t, consts.MIMETextPlainUTF8, string(ctx.Response.Header.Peek("Content-Type")))
+	// when Go version <= 1.16, mime.TypeByExtension will return Content-Type='text/plain; charset=utf-8',
+	// otherwise it will return Content-Type='text/x-go; charset=utf-8'
+	assert.NotEqual(t, "", string(ctx.Response.Header.Peek("Content-Type")))
 }
 
 func TestContextRenderAttachment(t *testing.T) {
@@ -930,6 +968,14 @@ func TestGetPostForm(t *testing.T) {
 	assert.DeepEqual(t, true, exists)
 }
 
+func TestGetPostFormArray(t *testing.T) {
+	c := NewContext(0)
+	c.Request.Header.SetContentTypeBytes([]byte(consts.MIMEApplicationHTMLForm))
+	c.Request.SetBodyString("a=1&b=2&b=3")
+	v, _ := c.GetPostFormArray("b")
+	assert.DeepEqual(t, []string{"2", "3"}, v)
+}
+
 func TestRemoteAddr(t *testing.T) {
 	c := NewContext(0)
 	c.Request.SetRequestURI("http://aaa.com?a=1&b=")
diff --git a/pkg/app/server/hertz_test.go b/pkg/app/server/hertz_test.go
index 3352d34..09e77dd 100644
--- a/pkg/app/server/hertz_test.go
+++ b/pkg/app/server/hertz_test.go
@@ -158,7 +158,10 @@ func TestLoadHTMLGlob(t *testing.T) {
 		})
 	})
 	go engine.Run()
-	time.Sleep(200 * time.Millisecond)
+	defer func() {
+		engine.Close()
+	}()
+	time.Sleep(1 * time.Second)
 	resp, _ := http.Get("http://127.0.0.1:8893/index")
 	assert.DeepEqual(t, consts.StatusOK, resp.StatusCode)
 	b := make([]byte, 100)
@@ -182,7 +185,10 @@ func TestLoadHTMLFiles(t *testing.T) {
 		})
 	})
 	go engine.Run()
-	time.Sleep(200 * time.Millisecond)
+	defer func() {
+		engine.Close()
+	}()
+	time.Sleep(1 * time.Second)
 	resp, _ := http.Get("http://127.0.0.1:8891/raw")
 	assert.DeepEqual(t, consts.StatusOK, resp.StatusCode)
 	b := make([]byte, 100)
@@ -218,28 +224,28 @@ func Test_getServerName(t *testing.T) {
 }
 
 func TestServer_Run(t *testing.T) {
-	hertz := New(WithHostPorts("127.0.0.1:8888"))
+	hertz := New(WithHostPorts("127.0.0.1:8899"))
 	hertz.GET("/test", func(c context.Context, ctx *app.RequestContext) {
 		path := ctx.Request.URI().PathOriginal()
 		ctx.SetBodyString(string(path))
 	})
 	hertz.POST("/redirect", func(c context.Context, ctx *app.RequestContext) {
-		ctx.Redirect(consts.StatusMovedPermanently, []byte("http://127.0.0.1:8888/test"))
+		ctx.Redirect(consts.StatusMovedPermanently, []byte("http://127.0.0.1:8899/test"))
 	})
 	go hertz.Run()
-	time.Sleep(100 * time.Microsecond)
-	resp, err := http.Get("http://127.0.0.1:8888/test")
+	time.Sleep(1 * time.Second)
+	resp, err := http.Get("http://127.0.0.1:8899/test")
 	assert.Nil(t, err)
 	assert.DeepEqual(t, consts.StatusOK, resp.StatusCode)
 	b := make([]byte, 5)
 	resp.Body.Read(b)
 	assert.DeepEqual(t, "/test", string(b))
 
-	resp, err = http.Get("http://127.0.0.1:8888/foo")
+	resp, err = http.Get("http://127.0.0.1:8899/foo")
 	assert.Nil(t, err)
 	assert.DeepEqual(t, consts.StatusNotFound, resp.StatusCode)
 
-	resp, err = http.Post("http://127.0.0.1:8888/redirect", "", nil)
+	resp, err = http.Post("http://127.0.0.1:8899/redirect", "", nil)
 	assert.Nil(t, err)
 	assert.DeepEqual(t, consts.StatusOK, resp.StatusCode)
 	b = make([]byte, 5)
@@ -260,7 +266,10 @@ func TestNotAbsolutePath(t *testing.T) {
 		ctx.Write(ctx.Request.Body())
 	})
 	go engine.Run()
-	time.Sleep(200 * time.Microsecond)
+	defer func() {
+		engine.Close()
+	}()
+	time.Sleep(1 * time.Second)
 
 	s := "POST ?a=b HTTP/1.1\r\nHost: a.b.c\r\nContent-Length: 5\r\nContent-Type: foo/bar\r\n\r\nabcdef4343"
 	zr := mock.NewZeroCopyReader(s)
@@ -299,7 +308,10 @@ func TestNotAbsolutePathWithRawPath(t *testing.T) {
 	engine.POST("/a", func(c context.Context, ctx *app.RequestContext) {
 	})
 	go engine.Run()
-	time.Sleep(200 * time.Microsecond)
+	defer func() {
+		engine.Close()
+	}()
+	time.Sleep(1 * time.Second)
 
 	s := "POST ?a=b HTTP/1.1\r\nHost: a.b.c\r\nContent-Length: 5\r\nContent-Type: foo/bar\r\n\r\nabcdef4343"
 	zr := mock.NewZeroCopyReader(s)
@@ -374,7 +386,10 @@ func TestWithBasePath(t *testing.T) {
 	engine.POST("/test", func(c context.Context, ctx *app.RequestContext) {
 	})
 	go engine.Run()
-	time.Sleep(500 * time.Microsecond)
+	defer func() {
+		engine.Close()
+	}()
+	time.Sleep(1 * time.Second)
 	var r http.Request
 	r.ParseForm()
 	r.Form.Add("xxxxxx", "xxx")
@@ -389,7 +404,10 @@ func TestNotEnoughBodySize(t *testing.T) {
 	engine.POST("/test", func(c context.Context, ctx *app.RequestContext) {
 	})
 	go engine.Run()
-	time.Sleep(200 * time.Microsecond)
+	defer func() {
+		engine.Close()
+	}()
+	time.Sleep(1 * time.Second)
 	var r http.Request
 	r.ParseForm()
 	r.Form.Add("xxxxxx", "xxx")
@@ -406,7 +424,10 @@ func TestEnoughBodySize(t *testing.T) {
 	engine.POST("/test", func(c context.Context, ctx *app.RequestContext) {
 	})
 	go engine.Run()
-	time.Sleep(200 * time.Microsecond)
+	defer func() {
+		engine.Close()
+	}()
+	time.Sleep(1 * time.Second)
 	var r http.Request
 	r.ParseForm()
 	r.Form.Add("xxxxxx", "xxx")
diff --git a/pkg/common/test/assert/assert.go b/pkg/common/test/assert/assert.go
index ed15783..c82d84c 100644
--- a/pkg/common/test/assert/assert.go
+++ b/pkg/common/test/assert/assert.go
@@ -62,7 +62,7 @@ func Nil(t testing.TB, data interface{}) {
 
 func NotNil(t testing.TB, data interface{}) {
 	t.Helper()
-	if data == nil {
+	if data != nil {
 		return
 	}
 
diff --git a/pkg/common/utils/ioutil_test.go b/pkg/common/utils/ioutil_test.go
index e9a573b..ed7ebf8 100644
--- a/pkg/common/utils/ioutil_test.go
+++ b/pkg/common/utils/ioutil_test.go
@@ -153,7 +153,7 @@ func TestIoutilCopyBufferWithIoReaderFrom(t *testing.T) {
 	assert.DeepEqual(t, true, ok)
 	written, err := CopyBuffer(ioReaderFrom, src, buf)
 	assert.DeepEqual(t, written, int64(0))
-	assert.NotNil(t, err)
+	assert.Nil(t, err)
 	assert.DeepEqual(t, []byte(nil), writeBuffer.Bytes())
 }
 
@@ -182,7 +182,7 @@ func TestIoutilCopyBufferWithNilBuffer(t *testing.T) {
 	written, err := CopyBuffer(dst, src, nil)
 
 	assert.DeepEqual(t, written, srcLen)
-	assert.NotNil(t, err)
+	assert.Nil(t, err)
 	assert.DeepEqual(t, []byte(str), writeBuffer.Bytes())
 }
 
@@ -196,7 +196,7 @@ func TestIoutilCopyBufferWithNilBufferAndIoLimitedReader(t *testing.T) {
 	written, err := CopyBuffer(dst, &reader, nil)
 
 	assert.DeepEqual(t, written, srcLen)
-	assert.NotNil(t, err)
+	assert.Nil(t, err)
 	assert.DeepEqual(t, []byte(str), writeBuffer.Bytes())
 
 	// test l.N < 1
@@ -209,7 +209,7 @@ func TestIoutilCopyBufferWithNilBufferAndIoLimitedReader(t *testing.T) {
 	written, err = CopyBuffer(dst, &reader, nil)
 
 	assert.DeepEqual(t, written, srcLen)
-	assert.NotNil(t, err)
+	assert.Nil(t, err)
 	assert.DeepEqual(t, []byte(str), writeBuffer.Bytes())
 }
 
diff --git a/pkg/protocol/http1/req/header_test.go b/pkg/protocol/http1/req/header_test.go
index d0978b3..29192c7 100644
--- a/pkg/protocol/http1/req/header_test.go
+++ b/pkg/protocol/http1/req/header_test.go
@@ -441,7 +441,7 @@ func TestTryRead(t *testing.T) {
 	s := "P"
 	zr := mock.NewZeroCopyReader(s)
 	err := tryRead(&rh, zr, 0)
-	assert.NotNil(t, err)
+	assert.Nil(t, err)
 }
 
 func TestParseFirstLine(t *testing.T) {
@@ -481,7 +481,11 @@ func TestParseFirstLine(t *testing.T) {
 	for _, tc := range tests {
 		header := &protocol.RequestHeader{}
 		_, err := parseFirstLine(header, tc.input)
-		assert.NotNil(t, err)
+		if tc.err != nil {
+			assert.NotNil(t, err)
+		} else {
+			assert.Nil(t, err)
+		}
 	}
 }
 
diff --git a/pkg/protocol/http1/server_test.go b/pkg/protocol/http1/server_test.go
index 2263ece..d478b36 100644
--- a/pkg/protocol/http1/server_test.go
+++ b/pkg/protocol/http1/server_test.go
@@ -218,6 +218,46 @@ func TestDefaultWriter(t *testing.T) {
 	assert.DeepEqual(t, "hello, hertz", string(response.Body()))
 }
 
+func TestServerDisableReqCtxPool(t *testing.T) {
+	server := &Server{}
+	reqCtx := &app.RequestContext{}
+	server.Core = &mockCore{
+		ctxPool: &sync.Pool{New: func() interface{} {
+			reqCtx.Set("POOL_KEY", "in pool")
+			return reqCtx
+		}},
+		mockHandler: func(c context.Context, ctx *app.RequestContext) {
+			if ctx.GetString("POOL_KEY") != "in pool" {
+				t.Fatal("reqCtx is not in pool")
+			}
+		},
+		isRunning: true,
+	}
+	defaultConn := mock.NewConn("GET / HTTP/1.1\nHost: foobar.com\n\n")
+	err := server.Serve(context.TODO(), defaultConn)
+	assert.Nil(t, err)
+	disabaleRequestContextPool = true
+	defer func() {
+		// reset global variable
+		disabaleRequestContextPool = false
+	}()
+	server.Core = &mockCore{
+		ctxPool: &sync.Pool{New: func() interface{} {
+			reqCtx.Set("POOL_KEY", "in pool")
+			return reqCtx
+		}},
+		mockHandler: func(c context.Context, ctx *app.RequestContext) {
+			if len(ctx.GetString("POOL_KEY")) != 0 {
+				t.Fatal("must not get pool key")
+			}
+		},
+		isRunning: true,
+	}
+	defaultConn = mock.NewConn("GET / HTTP/1.1\nHost: foobar.com\n\n")
+	err = server.Serve(context.TODO(), defaultConn)
+	assert.Nil(t, err)
+}
+
 func TestHijackResponseWriter(t *testing.T) {
 	server := &Server{}
 	reqCtx := &app.RequestContext{}
diff --git a/pkg/protocol/multipart_test.go b/pkg/protocol/multipart_test.go
index 6b96ae8..f6e4c1d 100644
--- a/pkg/protocol/multipart_test.go
+++ b/pkg/protocol/multipart_test.go
@@ -174,7 +174,7 @@ func TestWriteMultipartFormFile(t *testing.T) {
 	assert.True(t, strings.Contains(bodyBuffer.String(), string(buf1)))
 
 	// test file not found
-	assert.NotNil(t, WriteMultipartFormFile(w, multipartFile.ParamName, "test.go", multipartFile.Reader))
+	assert.Nil(t, WriteMultipartFormFile(w, multipartFile.ParamName, "test.go", multipartFile.Reader))
 
 	// Test Add File Function
 	err = AddFile(w, "responseCode", "./response.go")
diff --git a/pkg/route/engine_test.go b/pkg/route/engine_test.go
index ea1bc5f..a5da5dc 100644
--- a/pkg/route/engine_test.go
+++ b/pkg/route/engine_test.go
@@ -875,20 +875,22 @@ func TestEngineShutdown(t *testing.T) {
 	defaultTransporter = standard.NewTransporter
 	mockCtxCallback := func(ctx context.Context) {}
 	// Test case 1: serve not running error
-	engine := NewEngine(config.NewOptions(nil))
+	opt := config.NewOptions(nil)
+	opt.Addr = "127.0.0.1:10027"
+	engine := NewEngine(opt)
 	ctx1, cancel1 := context.WithTimeout(context.Background(), time.Second)
 	defer cancel1()
 	err := engine.Shutdown(ctx1)
 	assert.DeepEqual(t, errStatusNotRunning, err)
 
 	// Test case 2: serve successfully running and shutdown
-	engine = NewEngine(config.NewOptions(nil))
+	engine = NewEngine(opt)
 	engine.OnShutdown = []CtxCallback{mockCtxCallback}
 	go func() {
 		engine.Run()
 	}()
 	// wait for engine to start
-	time.Sleep(100 * time.Millisecond)
+	time.Sleep(1 * time.Second)
 
 	ctx2, cancel2 := context.WithTimeout(context.Background(), time.Second)
 	defer cancel2()
@@ -897,14 +899,14 @@ func TestEngineShutdown(t *testing.T) {
 	assert.DeepEqual(t, statusClosed, atomic.LoadUint32(&engine.status))
 
 	// Test case 3: serve successfully running and shutdown with deregistry error
-	engine = NewEngine(config.NewOptions(nil))
+	engine = NewEngine(opt)
 	engine.OnShutdown = []CtxCallback{mockCtxCallback}
 	engine.options.Registry = &mockDeregsitryErr{}
 	go func() {
 		engine.Run()
 	}()
 	// wait for engine to start
-	time.Sleep(100 * time.Millisecond)
+	time.Sleep(1 * time.Second)
 
 	ctx3, cancel3 := context.WithTimeout(context.Background(), time.Second)
 	defer cancel3()