Repository: go-redis/redis
Pull request #1832 (2021-07-22 02:27:47)

Bump github.com/onsi/gomega from 1.10.5 to 1.14.0
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.10.5 to 1.14.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/onsi/gomega/releases">github.com/onsi/gomega's releases</a>.</em></p>
<blockquote>
<h2>v1.14.0</h2>
<h2>1.14.0</h2>
<h3>Features</h3>
<ul>
<li>gmeasure.SamplingConfig now suppers a MinSamplingInterval [e94dbca]</li>
<li>Eventually and Consistently support functions that make assertions [2f04e6e]
<ul>
<li>Eventually and Consistently now allow their passed-in functions to make assertions.
These assertions must pass or the function is considered to have failed and is retried.</li>
<li>Eventually and Consistently can now take functions with no return values.  These implicitly return nil
if they contain no failed assertion.  Otherwise they return an error wrapping the first assertion failure.  This allows
these functions to be used with the Succeed() matcher.</li>
<li>Introduce InterceptGomegaFailure - an analogue to InterceptGomegaFailures - that captures the first assertion failure
and halts execution in its passed-in callback.</li>
</ul>
</li>
</ul>
<h3>Fixes</h3>
<ul>
<li>Call Verify GHTTPWithGomega receiver funcs (<a href="https://github-redirect.dependabot.com/onsi/gomega/issues/454">#454</a>) [496e6fd]</li>
<li>Build a binary with an expected name (<a href="https://github-redirect.dependabot.com/onsi/gomega/issues/446">#446</a>) [7356360]</li>
</ul>
<h2>v1.13.0</h2>
<ul>
<li>
<p>Set consistently and eventually defaults on init (<a href="https://github-redirect.dependabot.com/onsi/gomega/issues/443">#443</a>)</p>
<p>Using environment variables</p>
<p>Closes <a href="https://github-redirect.dependabot.com/onsi/gomega/issues/434">#434</a></p>
<p>Signed-off-by: toby lorne <a href="mailto:toby@toby.codes">toby@toby.codes</a></p>
</li>
<li>
<p>gmeasure provides BETA support for benchmarking (<a href="https://github-redirect.dependabot.com/onsi/gomega/issues/447">#447</a>)</p>
<p>gmeasure is a new gomega subpackage intended to provide measurement and benchmarking support for durations and values.  gmeasure replaces Ginkgo V1s deprecated Measure nodes and provides a migration path for users migrating to Ginkgo V2.</p>
<p>gmeasure is organized around an Experiment metaphor. Experiments can record several different Measurements, with each Measurement comprised of multiple data points.  Measurements can hold time.Durations and float64 values and gmeasure includes support measuring the duraiton of callback functions and for sampling functions repeatedly to build an ensemble of data points.  In addition, gmeasure introduces a Stopwatch abtraction for easily measuring and recording durations of code segments.</p>
<p>Once measured, users can readily generate Stats for Measurements to capture their key statistics and these stats can be ranked using a Ranking and associated RankingCriteria.</p>
<p>Experiments can be Cached to disk to speed up subsequent runs.  Experiments are cached by name and version number which makes it easy to manage and bust the cache.</p>
<p>Finally, gmeasure integrates with Ginkgo V2 via the new ReportEntry abstraction.  Experiments, Measurements, and Rankings can all be registered via AddReportEntry.  Doing so generates colorful reports as part of Ginkgo's test output.</p>
<p>gmeasure is currently in beta and will go GA around when Ginkgo V2 goes GA.</p>
</li>
</ul>
<h2>v1.12.0</h2>
<h3>Features</h3>
<ul>
<li>Add Satisfy() matcher (<a href="https://github-redirect.dependabot.com/onsi/gomega/issues/437">#437</a>) [c548f31]</li>
<li>tweak truncation message [3360b8c]</li>
<li>Add format.GomegaStringer (<a href="https://github-redirect.dependabot.com/onsi/gomega/issues/427">#427</a>) [cc80b6f]</li>
<li>Add Clear() method to gbytes.Buffer [c3c0920]</li>
</ul>
<h3>Fixes</h3>
<ul>
<li>Fix error message in BeNumericallyMatcher (<a href="https://github-redirect.dependabot.com/onsi/gomega/issues/432">#432</a>) [09c074a]</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/onsi/gomega/blob/master/CHANGELOG.md">github.com/onsi/gomega's changelog</a>.</em></p>
<blockquote>
<h2>1.14.0</h2>
<h3>Features</h3>
<ul>
<li>gmeasure.SamplingConfig now suppers a MinSamplingInterval [e94dbca]</li>
<li>Eventually and Consistently support functions that make assertions [2f04e6e]
<ul>
<li>Eventually and Consistently now allow their passed-in functions to make assertions.
These assertions must pass or the function is considered to have failed and is retried.</li>
<li>Eventually and Consistently can now take functions with no return values.  These implicitly return nil
if they contain no failed assertion.  Otherwise they return an error wrapping the first assertion failure.  This allows
these functions to be used with the Succeed() matcher.</li>
<li>Introduce InterceptGomegaFailure - an analogue to InterceptGomegaFailures - that captures the first assertion failure
and halts execution in its passed-in callback.</li>
</ul>
</li>
</ul>
<h3>Fixes</h3>
<ul>
<li>Call Verify GHTTPWithGomega receiver funcs (<a href="https://github-redirect.dependabot.com/onsi/gomega/issues/454">#454</a>) [496e6fd]</li>
<li>Build a binary with an expected name (<a href="https://github-redirect.dependabot.com/onsi/gomega/issues/446">#446</a>) [7356360]</li>
</ul>
<h2>1.13.0</h2>
<h3>Features</h3>
<ul>
<li>gmeasure provides BETA support for benchmarking (<a href="https://github-redirect.dependabot.com/onsi/gomega/issues/447">#447</a>) [8f2dfbf]</li>
<li>Set consistently and eventually defaults on init (<a href="https://github-redirect.dependabot.com/onsi/gomega/issues/443">#443</a>) [12eb778]</li>
</ul>
<h2>1.12.0</h2>
<h3>Features</h3>
<ul>
<li>Add Satisfy() matcher (<a href="https://github-redirect.dependabot.com/onsi/gomega/issues/437">#437</a>) [c548f31]</li>
<li>tweak truncation message [3360b8c]</li>
<li>Add format.GomegaStringer (<a href="https://github-redirect.dependabot.com/onsi/gomega/issues/427">#427</a>) [cc80b6f]</li>
<li>Add Clear() method to gbytes.Buffer [c3c0920]</li>
</ul>
<h3>Fixes</h3>
<ul>
<li>Fix error message in BeNumericallyMatcher (<a href="https://github-redirect.dependabot.com/onsi/gomega/issues/432">#432</a>) [09c074a]</li>
<li>Bump github.com/onsi/ginkgo from 1.12.1 to 1.16.2 (<a href="https://github-redirect.dependabot.com/onsi/gomega/issues/442">#442</a>) [e5f6ea0]</li>
<li>Bump github.com/golang/protobuf from 1.4.3 to 1.5.2 (<a href="https://github-redirect.dependabot.com/onsi/gomega/issues/431">#431</a>) [adae3bf]</li>
<li>Bump golang.org/x/net (<a href="https://github-redirect.dependabot.com/onsi/gomega/issues/441">#441</a>) [3275b35]</li>
</ul>
<h2>1.11.0</h2>
<h3>Features</h3>
<ul>
<li>feature: add index to gstruct element func (<a href="https://github-redirect.dependabot.com/onsi/gomega/issues/419">#419</a>) [334e00d]</li>
<li>feat(gexec) Add CompileTest functions. Close <a href="https://github-redirect.dependabot.com/onsi/gomega/issues/410">#410</a> (<a href="https://github-redirect.dependabot.com/onsi/gomega/issues/411">#411</a>) [47c613f]</li>
</ul>
<h3>Fixes</h3>
<ul>
<li>Check more carefully for nils in WithTransform (<a href="https://github-redirect.dependabot.com/onsi/gomega/issues/423">#423</a>) [3c60a15]</li>
<li>fix: typo in Makefile [b82522a]</li>
<li>Allow WithTransform function to accept a nil value (<a href="https://github-redirect.dependabot.com/onsi/gomega/issues/422">#422</a>) [b75d2f2]</li>
<li>fix: print value type for interface{} containers (<a href="https://github-redirect.dependabot.com/onsi/gomega/issues/409">#409</a>) [f08e2dc]</li>
<li>fix(BeElementOf): consistently flatten expected values [1fa9468]</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/onsi/gomega/commit/812e6427267bd99709212aef5edfd80c3c3b8595"><code>812e642</code></a> v1.14.0</li>
<li><a href="https://github.com/onsi/gomega/commit/26cf82b88653aeedb730f19d9fb93146ebdb7ee2"><code>26cf82b</code></a> silence (at least temporarily) the github test workflow's vetting of go.mod a...</li>
<li><a href="https://github.com/onsi/gomega/commit/496e6fd03a085827a2b90c365fdb11ed5a68853a"><code>496e6fd</code></a> Call Verify GHTTPWithGomega receiver funcs (<a href="https://github-redirect.dependabot.com/onsi/gomega/issues/454">#454</a>)</li>
<li><a href="https://github.com/onsi/gomega/commit/9da0d1334f01b8e0868ac7b7e663c350d45f4661"><code>9da0d13</code></a> go mod tidy</li>
<li><a href="https://github.com/onsi/gomega/commit/55e9553786c2ed1847b6d74eb3c0915819bcfe48"><code>55e9553</code></a> bump ginkgo</li>
<li><a href="https://github.com/onsi/gomega/commit/e94dbca369204ff76a6ecbf3a9674cde5c09f731"><code>e94dbca</code></a> gmeasure.SamplingConfig now suppers a MinSamplingInterval</li>
<li><a href="https://github.com/onsi/gomega/commit/2f04e6e3467d2c0c695786c3e7880f1e940274cf"><code>2f04e6e</code></a> Eventually and Consistently support functions that make assertions</li>
<li><a href="https://github.com/onsi/gomega/commit/febd7a25a60e3f7283922ef829c800c74eb95659"><code>febd7a2</code></a> remove travis ci</li>
<li><a href="https://github.com/onsi/gomega/commit/735636028d705d6a2442a661980253ad6d080d09"><code>7356360</code></a> Build a binary with an expected name (<a href="https://github-redirect.dependabot.com/onsi/gomega/issues/446">#446</a>)</li>
<li><a href="https://github.com/onsi/gomega/commit/dbc6ecdcc526d81592dab902de6cbaf7be9a8a13"><code>dbc6ecd</code></a> v1.13.0</li>
<li>Additional commits viewable in <a href="https://github.com/onsi/gomega/compare/v1.10.5...v1.14.0">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/onsi/gomega&package-manager=go_modules&previous-version=1.10.5&new-version=1.14.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>

monkey92t: We temporarily use the old version and wait for gomega to fix it.
https://github.com/onsi/gomega/issues/457

Patch:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..253640c
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,15 @@
+name: release
+
+on: push
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+      - name: Release
+        uses: softprops/action-gh-release@v1
+        if: startsWith(github.ref, 'refs/tags/')
+        with:
+          body: Check CHANGELOG.md for details
diff --git a/Makefile b/Makefile
index 5501164..b16709c 100644
--- a/Makefile
+++ b/Makefile
@@ -14,7 +14,7 @@ bench: testdeps
 
 testdata/redis:
 	mkdir -p $@
-	wget -qO- https://download.redis.io/releases/redis-6.2.1.tar.gz | tar xvz --strip-components=1 -C $@
+	wget -qO- https://download.redis.io/releases/redis-6.2.5.tar.gz | tar xvz --strip-components=1 -C $@
 
 testdata/redis/src/redis-server: testdata/redis
 	cd $< && make all
diff --git a/RELEASING.md b/RELEASING.md
new file mode 100644
index 0000000..9e50c10
--- /dev/null
+++ b/RELEASING.md
@@ -0,0 +1,21 @@
+# Releasing
+
+1. Run `release.sh` script which updates versions in go.mod files and pushes a new branch to GitHub:
+
+```shell
+./scripts/release.sh -t v1.0.0
+```
+
+2. Open a pull request and wait for the build to finish.
+
+3. Merge the pull request and run `tag.sh` to create tags for packages:
+
+```shell
+./scripts/tag.sh -t v1.0.0
+```
+
+4. Push the tags:
+
+```shell
+git push origin --tags
+```
diff --git a/command.go b/command.go
index 77d0bad..6f77258 100644
--- a/command.go
+++ b/command.go
@@ -2637,6 +2637,188 @@ func newGeoLocationParser(q *GeoRadiusQuery) proto.MultiBulkParse {
 
 //------------------------------------------------------------------------------
 
+// GeoSearchQuery is used for GEOSearch/GEOSearchStore command query.
+type GeoSearchQuery struct {
+	Member string
+
+	// Latitude and Longitude when using FromLonLat option.
+	Longitude float64
+	Latitude  float64
+
+	// Distance and unit when using ByRadius option.
+	// Can use m, km, ft, or mi. Default is km.
+	Radius     float64
+	RadiusUnit string
+
+	// Height, width and unit when using ByBox option.
+	// Can be m, km, ft, or mi. Default is km.
+	BoxWidth  float64
+	BoxHeight float64
+	BoxUnit   string
+
+	// Can be ASC or DESC. Default is no sort order.
+	Sort     string
+	Count    int
+	CountAny bool
+}
+
+type GeoSearchLocationQuery struct {
+	GeoSearchQuery
+
+	WithCoord bool
+	WithDist  bool
+	WithHash  bool
+}
+
+type GeoSearchStoreQuery struct {
+	GeoSearchQuery
+
+	// When using the StoreDist option, the command stores the items in a
+	// sorted set populated with their distance from the center of the circle or box,
+	// as a floating-point number, in the same unit specified for that shape.
+	StoreDist bool
+}
+
+func geoSearchLocationArgs(q *GeoSearchLocationQuery, args []interface{}) []interface{} {
+	args = geoSearchArgs(&q.GeoSearchQuery, args)
+
+	if q.WithCoord {
+		args = append(args, "withcoord")
+	}
+	if q.WithDist {
+		args = append(args, "withdist")
+	}
+	if q.WithHash {
+		args = append(args, "withhash")
+	}
+
+	return args
+}
+
+func geoSearchArgs(q *GeoSearchQuery, args []interface{}) []interface{} {
+	if q.Member != "" {
+		args = append(args, "frommember", q.Member)
+	} else {
+		args = append(args, "fromlonlat", q.Longitude, q.Latitude)
+	}
+
+	if q.Radius > 0 {
+		if q.RadiusUnit == "" {
+			q.RadiusUnit = "km"
+		}
+		args = append(args, "byradius", q.Radius, q.RadiusUnit)
+	} else {
+		if q.BoxUnit == "" {
+			q.BoxUnit = "km"
+		}
+		args = append(args, "bybox", q.BoxWidth, q.BoxHeight, q.BoxUnit)
+	}
+
+	if q.Sort != "" {
+		args = append(args, q.Sort)
+	}
+
+	if q.Count > 0 {
+		args = append(args, "count", q.Count)
+		if q.CountAny {
+			args = append(args, "any")
+		}
+	}
+
+	return args
+}
+
+type GeoSearchLocationCmd struct {
+	baseCmd
+
+	opt *GeoSearchLocationQuery
+	val []GeoLocation
+}
+
+var _ Cmder = (*GeoSearchLocationCmd)(nil)
+
+func NewGeoSearchLocationCmd(
+	ctx context.Context, opt *GeoSearchLocationQuery, args ...interface{},
+) *GeoSearchLocationCmd {
+	return &GeoSearchLocationCmd{
+		baseCmd: baseCmd{
+			ctx:  ctx,
+			args: args,
+		},
+		opt: opt,
+	}
+}
+
+func (cmd *GeoSearchLocationCmd) Val() []GeoLocation {
+	return cmd.val
+}
+
+func (cmd *GeoSearchLocationCmd) Result() ([]GeoLocation, error) {
+	return cmd.val, cmd.err
+}
+
+func (cmd *GeoSearchLocationCmd) String() string {
+	return cmdString(cmd, cmd.val)
+}
+
+func (cmd *GeoSearchLocationCmd) readReply(rd *proto.Reader) error {
+	n, err := rd.ReadArrayLen()
+	if err != nil {
+		return err
+	}
+
+	cmd.val = make([]GeoLocation, n)
+	for i := 0; i < n; i++ {
+		_, err = rd.ReadArrayLen()
+		if err != nil {
+			return err
+		}
+
+		var loc GeoLocation
+
+		loc.Name, err = rd.ReadString()
+		if err != nil {
+			return err
+		}
+		if cmd.opt.WithDist {
+			loc.Dist, err = rd.ReadFloatReply()
+			if err != nil {
+				return err
+			}
+		}
+		if cmd.opt.WithHash {
+			loc.GeoHash, err = rd.ReadIntReply()
+			if err != nil {
+				return err
+			}
+		}
+		if cmd.opt.WithCoord {
+			nn, err := rd.ReadArrayLen()
+			if err != nil {
+				return err
+			}
+			if nn != 2 {
+				return fmt.Errorf("got %d coordinates, expected 2", nn)
+			}
+
+			loc.Longitude, err = rd.ReadFloatReply()
+			if err != nil {
+				return err
+			}
+			loc.Latitude, err = rd.ReadFloatReply()
+			if err != nil {
+				return err
+			}
+		}
+
+		cmd.val[i] = loc
+	}
+
+	return nil
+}
+
+//------------------------------------------------------------------------------
+
 type GeoPos struct {
 	Longitude, Latitude float64
 }
diff --git a/commands.go b/commands.go
index 8fd0397..80b2bab 100644
--- a/commands.go
+++ b/commands.go
@@ -248,6 +248,7 @@ type Cmdable interface {
 	XTrimMinIDApprox(ctx context.Context, key string, minID string, limit int64) *IntCmd
 	XInfoGroups(ctx context.Context, key string) *XInfoGroupsCmd
 	XInfoStream(ctx context.Context, key string) *XInfoStreamCmd
+	XInfoStreamFull(ctx context.Context, key string, count int) *XInfoStreamFullCmd
 	XInfoConsumers(ctx context.Context, key string, group string) *XInfoConsumersCmd
 
 	BZPopMax(ctx context.Context, timeout time.Duration, keys ...string) *ZWithKeyCmd
@@ -383,6 +384,9 @@ type Cmdable interface {
 	GeoRadiusStore(ctx context.Context, key string, longitude, latitude float64, query *GeoRadiusQuery) *IntCmd
 	GeoRadiusByMember(ctx context.Context, key, member string, query *GeoRadiusQuery) *GeoLocationCmd
 	GeoRadiusByMemberStore(ctx context.Context, key, member string, query *GeoRadiusQuery) *IntCmd
+	GeoSearch(ctx context.Context, key string, q *GeoSearchQuery) *StringSliceCmd
+	GeoSearchLocation(ctx context.Context, key string, q *GeoSearchLocationQuery) *GeoSearchLocationCmd
+	GeoSearchStore(ctx context.Context, key, store string, q *GeoSearchStoreQuery) *IntCmd
 	GeoDist(ctx context.Context, key string, member1, member2, unit string) *FloatCmd
 	GeoHash(ctx context.Context, key string, members ...string) *StringSliceCmd
 }
@@ -3347,6 +3351,38 @@ func (c cmdable) GeoRadiusByMemberStore(
 	return cmd
 }
 
+func (c cmdable) GeoSearch(ctx context.Context, key string, q *GeoSearchQuery) *StringSliceCmd {
+	args := make([]interface{}, 0, 13)
+	args = append(args, "geosearch", key)
+	args = geoSearchArgs(q, args)
+	cmd := NewStringSliceCmd(ctx, args...)
+	_ = c(ctx, cmd)
+	return cmd
+}
+
+func (c cmdable) GeoSearchLocation(
+	ctx context.Context, key string, q *GeoSearchLocationQuery,
+) *GeoSearchLocationCmd {
+	args := make([]interface{}, 0, 16)
+	args = append(args, "geosearch", key)
+	args = geoSearchLocationArgs(q, args)
+	cmd := NewGeoSearchLocationCmd(ctx, q, args...)
+	_ = c(ctx, cmd)
+	return cmd
+}
+
+func (c cmdable) GeoSearchStore(ctx context.Context, key, store string, q *GeoSearchStoreQuery) *IntCmd {
+	args := make([]interface{}, 0, 15)
+	args = append(args, "geosearchstore", store, key)
+	args = geoSearchArgs(&q.GeoSearchQuery, args)
+	if q.StoreDist {
+		args = append(args, "storedist")
+	}
+	cmd := NewIntCmd(ctx, args...)
+	_ = c(ctx, cmd)
+	return cmd
+}
+
 func (c cmdable) GeoDist(
 	ctx context.Context, key string, member1, member2, unit string,
 ) *FloatCmd {
diff --git a/extra/redisotel/go.sum b/extra/redisotel/go.sum
index 1aa1088..2eac7fe 100644
--- a/extra/redisotel/go.sum
+++ b/extra/redisotel/go.sum
@@ -18,8 +18,8 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
-github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
@@ -37,21 +37,12 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
 github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-go.opentelemetry.io/otel v0.19.0 h1:Lenfy7QHRXPZVsw/12CWpxX6d/JkrX8wrx2vO8G80Ng=
-go.opentelemetry.io/otel v0.19.0/go.mod h1:j9bF567N9EfomkSidSfmMwIwIBuP37AMAIzVW85OxSg=
-go.opentelemetry.io/otel v0.20.0 h1:eaP0Fqu7SXHwvjiqDq83zImeehOHX8doTvU9AwXON8g=
-go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo=
-go.opentelemetry.io/otel/metric v0.19.0 h1:dtZ1Ju44gkJkYvo+3qGqVXmf88tc+a42edOywypengg=
-go.opentelemetry.io/otel/metric v0.19.0/go.mod h1:8f9fglJPRnXuskQmKpnad31lcLJ2VmNNqIsx/uIwBSc=
-go.opentelemetry.io/otel/metric v0.20.0 h1:4kzhXFP+btKm4jwxpjIqjs41A7MakRFUS86bqLHTIw8=
-go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU=
-go.opentelemetry.io/otel/oteltest v0.19.0 h1:YVfA0ByROYqTwOxqHVZYZExzEpfZor+MU1rU+ip2v9Q=
-go.opentelemetry.io/otel/oteltest v0.19.0/go.mod h1:tI4yxwh8U21v7JD6R3BcA/2+RBoTKFexE/PJ/nSO7IA=
-go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw=
-go.opentelemetry.io/otel/trace v0.19.0 h1:1ucYlenXIDA1OlHVLDZKX0ObXV5RLaq06DtUKz5e5zc=
-go.opentelemetry.io/otel/trace v0.19.0/go.mod h1:4IXiNextNOpPnRlI4ryK69mn5iC84bjBWZQA5DXz/qg=
-go.opentelemetry.io/otel/trace v0.20.0 h1:1DL6EXUdcg95gukhuRRvLDO/4X5THh/5dIV52lqtnbw=
-go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw=
+go.opentelemetry.io/otel v1.0.0-RC1 h1:4CeoX93DNTWt8awGK9JmNXzF9j7TyOu9upscEdtcdXc=
+go.opentelemetry.io/otel v1.0.0-RC1/go.mod h1:x9tRa9HK4hSSq7jf2TKbqFbtt58/TGk0f9XiEYISI1I=
+go.opentelemetry.io/otel/oteltest v1.0.0-RC1 h1:G685iP3XiskCwk/z0eIabL55XUl2gk0cljhGk9sB0Yk=
+go.opentelemetry.io/otel/oteltest v1.0.0-RC1/go.mod h1:+eoIG0gdEOaPNftuy1YScLr1Gb4mL/9lpDkZ0JjMRq4=
+go.opentelemetry.io/otel/trace v1.0.0-RC1 h1:jrjqKJZEibFrDz+umEASeU3LvdVyWKlnTh7XEfwrT58=
+go.opentelemetry.io/otel/trace v1.0.0-RC1/go.mod h1:86UHmyHWFEtWjfWPSbu0+d0Pf9Q6e1U+3ViBOc+NXAg=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
diff --git a/go.mod b/go.mod
index c5ac426..f15f1b2 100644
--- a/go.mod
+++ b/go.mod
@@ -6,6 +6,6 @@ require (
 	github.com/cespare/xxhash/v2 v2.1.1
 	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f
 	github.com/google/go-cmp v0.5.6 // indirect
-	github.com/onsi/ginkgo v1.15.0
-	github.com/onsi/gomega v1.10.5
+	github.com/onsi/ginkgo v1.16.4
+	github.com/onsi/gomega v1.15.0
 )
diff --git a/go.sum b/go.sum
index 76abf5b..20fc640 100644
--- a/go.sum
+++ b/go.sum
@@ -1,34 +1,44 @@
 github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
 github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
 github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
 github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
 github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
-github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
-github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
+github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
-github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4=
-github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
+github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
+github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
-github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ=
-github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
+github.com/onsi/gomega v1.15.0 h1:WjP/FQ/sk43MRmnEcT+MlDw2TFvkrXlprrPST/IudjU=
+github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -39,8 +49,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U=
-golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
+golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -52,11 +62,15 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
@@ -70,13 +84,17 @@ google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
 google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
 google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
-google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
diff --git a/internal/pool/conncheck.go b/internal/pool/conncheck.go
deleted file mode 100644
index 5dd60df..0000000
--- a/internal/pool/conncheck.go
+++ /dev/null
@@ -1,45 +0,0 @@
-// +build linux darwin dragonfly freebsd netbsd openbsd solaris illumos
-
-package pool
-
-import (
-	"errors"
-	"io"
-	"net"
-	"syscall"
-)
-
-var errUnexpectedRead = errors.New("unexpected read from socket")
-
-func connCheck(conn net.Conn) error {
-	sysConn, ok := conn.(syscall.Conn)
-	if !ok {
-		return nil
-	}
-	rawConn, err := sysConn.SyscallConn()
-	if err != nil {
-		return err
-	}
-
-	var sysErr error
-	err = rawConn.Read(func(fd uintptr) bool {
-		var buf [1]byte
-		n, err := syscall.Read(int(fd), buf[:])
-		switch {
-		case n == 0 && err == nil:
-			sysErr = io.EOF
-		case n > 0:
-			sysErr = errUnexpectedRead
-		case err == syscall.EAGAIN || err == syscall.EWOULDBLOCK:
-			sysErr = nil
-		default:
-			sysErr = err
-		}
-		return true
-	})
-	if err != nil {
-		return err
-	}
-
-	return sysErr
-}
diff --git a/internal/pool/conncheck_dummy.go b/internal/pool/conncheck_dummy.go
deleted file mode 100644
index 1daf986..0000000
--- a/internal/pool/conncheck_dummy.go
+++ /dev/null
@@ -1,9 +0,0 @@
-// +build !linux,!darwin,!dragonfly,!freebsd,!netbsd,!openbsd,!solaris,!illumos
-
-package pool
-
-import "net"
-
-func connCheck(conn net.Conn) error {
-	return nil
-}
diff --git a/internal/pool/pool.go b/internal/pool/pool.go
index 577923a..91b55e4 100644
--- a/internal/pool/pool.go
+++ b/internal/pool/pool.go
@@ -520,7 +520,7 @@ func (p *ConnPool) reapStaleConn() *Conn {
 
 func (p *ConnPool) isStaleConn(cn *Conn) bool {
 	if p.opt.IdleTimeout == 0 && p.opt.MaxConnAge == 0 {
-		return connCheck(cn.netConn) != nil
+		return false
 	}
 
 	now := time.Now()
@@ -531,5 +531,5 @@ func (p *ConnPool) isStaleConn(cn *Conn) bool {
 		return true
 	}
 
-	return connCheck(cn.netConn) != nil
+	return false
 }
diff --git a/scripts/release.sh b/scripts/release.sh
new file mode 100755
index 0000000..8edb196
--- /dev/null
+++ b/scripts/release.sh
@@ -0,0 +1,65 @@
+#!/bin/bash
+
+set -e
+
+help() {
+    cat <<- EOF
+Usage: TAG=tag $0
+
+Updates version in go.mod files and pushes a new brash to GitHub.
+
+VARIABLES:
+  TAG        git tag, for example, v1.0.0
+EOF
+    exit 0
+}
+
+if [ -z "$TAG" ]
+then
+    printf "TAG is required\n\n"
+    help
+fi
+
+TAG_REGEX="^v(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(\\-[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?(\\+[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?$"
+if ! [[ "${TAG}" =~ ${TAG_REGEX} ]]; then
+    printf "TAG is not valid: ${TAG}\n\n"
+    exit 1
+fi
+
+TAG_FOUND=`git tag --list ${TAG}`
+if [[ ${TAG_FOUND} = ${TAG} ]] ; then
+    printf "tag ${TAG} already exists\n\n"
+    exit 1
+fi
+
+if ! git diff --quiet
+then
+    printf "working tree is not clean\n\n"
+    git status
+    exit 1
+fi
+
+git checkout master
+
+PACKAGE_DIRS=$(find . -mindepth 2 -type f -name 'go.mod' -exec dirname {} \; \
+  | sed 's/^\.\///' \
+  | sort)
+
+for dir in $PACKAGE_DIRS
+do
+    sed --in-place \
+      "s/uptrace\/bun\([^ ]*\) v.*/uptrace\/bun\1 ${TAG}/" "${dir}/go.mod"
+done
+
+for dir in $PACKAGE_DIRS
+do
+    printf "${dir}: go mod tidy\n"
+    (cd ./${dir} && go mod tidy)
+done
+
+sed --in-place "s/\(return \)\"[^\"]*\"/\1\"${TAG#v}\"/" ./version.go
+
+git checkout -b release/${TAG} master
+git add -u
+git commit -m "Release $TAG"
+git push origin release/${TAG}
diff --git a/scripts/tag.sh b/scripts/tag.sh
new file mode 100755
index 0000000..b9c8cba
--- /dev/null
+++ b/scripts/tag.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+
+set -e
+
+help() {
+    cat <<- EOF
+Usage: TAG=tag $0
+
+Creates git tags for public Go packages.
+
+VARIABLES:
+  TAG        git tag, for example, v1.0.0
+EOF
+    exit 0
+}
+
+if [ -z "$TAG" ]
+then
+    printf "TAG env var is required\n\n";
+    help
+fi
+
+if ! grep -Fq "\"${TAG#v}\"" version.go
+then
+    printf "version.go does not contain ${TAG#v}\n"
+    exit 1
+fi
+
+PACKAGE_DIRS=$(find . -mindepth 2 -type f -name 'go.mod' -exec dirname {} \; \
+  | grep -E -v "example|internal" \
+  | sed 's/^\.\///' \
+  | sort)
+
+git tag ${TAG}
+
+for dir in $PACKAGE_DIRS
+do
+    printf "tagging ${dir}/${TAG}\n"
+    git tag ${dir}/${TAG}
+done


Test patch:
diff --git a/commands_test.go b/commands_test.go
index 12f75cc..a331f7f 100644
--- a/commands_test.go
+++ b/commands_test.go
@@ -4635,7 +4635,7 @@ var _ = Describe("Commands", func() {
 				}
 				msgs, start, err := client.XAutoClaim(ctx, xca).Result()
 				Expect(err).NotTo(HaveOccurred())
-				Expect(start).To(Equal("2-0"))
+				Expect(start).To(Equal("3-0"))
 				Expect(msgs).To(Equal([]redis.XMessage{{
 					ID:     "1-0",
 					Values: map[string]interface{}{"uno": "un"},
@@ -4647,19 +4647,16 @@ var _ = Describe("Commands", func() {
 				xca.Start = start
 				msgs, start, err = client.XAutoClaim(ctx, xca).Result()
 				Expect(err).NotTo(HaveOccurred())
-				Expect(start).To(Equal("3-0"))
+				Expect(start).To(Equal("0-0"))
 				Expect(msgs).To(Equal([]redis.XMessage{{
-					ID:     "2-0",
-					Values: map[string]interface{}{"dos": "deux"},
-				}, {
 					ID:     "3-0",
 					Values: map[string]interface{}{"tres": "troix"},
 				}}))
 
 				ids, start, err := client.XAutoClaimJustID(ctx, xca).Result()
 				Expect(err).NotTo(HaveOccurred())
-				Expect(start).To(Equal("3-0"))
-				Expect(ids).To(Equal([]string{"2-0", "3-0"}))
+				Expect(start).To(Equal("0-0"))
+				Expect(ids).To(Equal([]string{"3-0"}))
 			})
 
 			It("should XClaim", func() {
@@ -5145,6 +5142,204 @@ var _ = Describe("Commands", func() {
 				nil,
 			}))
 		})
+
+		It("should geo search", func() {
+			q := &redis.GeoSearchQuery{
+				Member:    "Catania",
+				BoxWidth:  400,
+				BoxHeight: 100,
+				BoxUnit:   "km",
+				Sort:      "asc",
+			}
+			val, err := client.GeoSearch(ctx, "Sicily", q).Result()
+			Expect(err).NotTo(HaveOccurred())
+			Expect(val).To(Equal([]string{"Catania"}))
+
+			q.BoxHeight = 400
+			val, err = client.GeoSearch(ctx, "Sicily", q).Result()
+			Expect(err).NotTo(HaveOccurred())
+			Expect(val).To(Equal([]string{"Catania", "Palermo"}))
+
+			q.Count = 1
+			val, err = client.GeoSearch(ctx, "Sicily", q).Result()
+			Expect(err).NotTo(HaveOccurred())
+			Expect(val).To(Equal([]string{"Catania"}))
+
+			q.CountAny = true
+			val, err = client.GeoSearch(ctx, "Sicily", q).Result()
+			Expect(err).NotTo(HaveOccurred())
+			Expect(val).To(Equal([]string{"Palermo"}))
+
+			q = &redis.GeoSearchQuery{
+				Member:     "Catania",
+				Radius:     100,
+				RadiusUnit: "km",
+				Sort:       "asc",
+			}
+			val, err = client.GeoSearch(ctx, "Sicily", q).Result()
+			Expect(err).NotTo(HaveOccurred())
+			Expect(val).To(Equal([]string{"Catania"}))
+
+			q.Radius = 400
+			val, err = client.GeoSearch(ctx, "Sicily", q).Result()
+			Expect(err).NotTo(HaveOccurred())
+			Expect(val).To(Equal([]string{"Catania", "Palermo"}))
+
+			q.Count = 1
+			val, err = client.GeoSearch(ctx, "Sicily", q).Result()
+			Expect(err).NotTo(HaveOccurred())
+			Expect(val).To(Equal([]string{"Catania"}))
+
+			q.CountAny = true
+			val, err = client.GeoSearch(ctx, "Sicily", q).Result()
+			Expect(err).NotTo(HaveOccurred())
+			Expect(val).To(Equal([]string{"Palermo"}))
+
+			q = &redis.GeoSearchQuery{
+				Longitude: 15,
+				Latitude:  37,
+				BoxWidth:  200,
+				BoxHeight: 200,
+				BoxUnit:   "km",
+				Sort:      "asc",
+			}
+			val, err = client.GeoSearch(ctx, "Sicily", q).Result()
+			Expect(err).NotTo(HaveOccurred())
+			Expect(val).To(Equal([]string{"Catania"}))
+
+			q.BoxWidth, q.BoxHeight = 400, 400
+			val, err = client.GeoSearch(ctx, "Sicily", q).Result()
+			Expect(err).NotTo(HaveOccurred())
+			Expect(val).To(Equal([]string{"Catania", "Palermo"}))
+
+			q.Count = 1
+			val, err = client.GeoSearch(ctx, "Sicily", q).Result()
+			Expect(err).NotTo(HaveOccurred())
+			Expect(val).To(Equal([]string{"Catania"}))
+
+			q.CountAny = true
+			val, err = client.GeoSearch(ctx, "Sicily", q).Result()
+			Expect(err).NotTo(HaveOccurred())
+			Expect(val).To(Equal([]string{"Palermo"}))
+
+			q = &redis.GeoSearchQuery{
+				Longitude:  15,
+				Latitude:   37,
+				Radius:     100,
+				RadiusUnit: "km",
+				Sort:       "asc",
+			}
+			val, err = client.GeoSearch(ctx, "Sicily", q).Result()
+			Expect(err).NotTo(HaveOccurred())
+			Expect(val).To(Equal([]string{"Catania"}))
+
+			q.Radius = 200
+			val, err = client.GeoSearch(ctx, "Sicily", q).Result()
+			Expect(err).NotTo(HaveOccurred())
+			Expect(val).To(Equal([]string{"Catania", "Palermo"}))
+
+			q.Count = 1
+			val, err = client.GeoSearch(ctx, "Sicily", q).Result()
+			Expect(err).NotTo(HaveOccurred())
+			Expect(val).To(Equal([]string{"Catania"}))
+
+			q.CountAny = true
+			val, err = client.GeoSearch(ctx, "Sicily", q).Result()
+			Expect(err).NotTo(HaveOccurred())
+			Expect(val).To(Equal([]string{"Palermo"}))
+		})
+
+		It("should geo search with options", func() {
+			q := &redis.GeoSearchLocationQuery{
+				GeoSearchQuery: redis.GeoSearchQuery{
+					Longitude:  15,
+					Latitude:   37,
+					Radius:     200,
+					RadiusUnit: "km",
+					Sort:       "asc",
+				},
+				WithHash:  true,
+				WithDist:  true,
+				WithCoord: true,
+			}
+			val, err := client.GeoSearchLocation(ctx, "Sicily", q).Result()
+			Expect(err).NotTo(HaveOccurred())
+			Expect(val).To(Equal([]redis.GeoLocation{
+				{
+					Name:      "Catania",
+					Longitude: 15.08726745843887329,
+					Latitude:  37.50266842333162032,
+					Dist:      56.4413,
+					GeoHash:   3479447370796909,
+				},
+				{
+					Name:      "Palermo",
+					Longitude: 13.36138933897018433,
+					Latitude:  38.11555639549629859,
+					Dist:      190.4424,
+					GeoHash:   3479099956230698,
+				},
+			}))
+		})
+
+		It("should geo search store", func() {
+			q := &redis.GeoSearchStoreQuery{
+				GeoSearchQuery: redis.GeoSearchQuery{
+					Longitude:  15,
+					Latitude:   37,
+					Radius:     200,
+					RadiusUnit: "km",
+					Sort:       "asc",
+				},
+				StoreDist: false,
+			}
+
+			val, err := client.GeoSearchStore(ctx, "Sicily", "key1", q).Result()
+			Expect(err).NotTo(HaveOccurred())
+			Expect(val).To(Equal(int64(2)))
+
+			q.StoreDist = true
+			val, err = client.GeoSearchStore(ctx, "Sicily", "key2", q).Result()
+			Expect(err).NotTo(HaveOccurred())
+			Expect(val).To(Equal(int64(2)))
+
+			loc, err := client.GeoSearchLocation(ctx, "key1", &redis.GeoSearchLocationQuery{
+				GeoSearchQuery: q.GeoSearchQuery,
+				WithCoord:      true,
+				WithDist:       true,
+				WithHash:       true,
+			}).Result()
+			Expect(err).NotTo(HaveOccurred())
+			Expect(loc).To(Equal([]redis.GeoLocation{
+				{
+					Name:      "Catania",
+					Longitude: 15.08726745843887329,
+					Latitude:  37.50266842333162032,
+					Dist:      56.4413,
+					GeoHash:   3479447370796909,
+				},
+				{
+					Name:      "Palermo",
+					Longitude: 13.36138933897018433,
+					Latitude:  38.11555639549629859,
+					Dist:      190.4424,
+					GeoHash:   3479099956230698,
+				},
+			}))
+
+			v, err := client.ZRangeWithScores(ctx, "key2", 0, -1).Result()
+			Expect(err).NotTo(HaveOccurred())
+			Expect(v).To(Equal([]redis.Z{
+				{
+					Score:  56.441257870158204,
+					Member: "Catania",
+				},
+				{
+					Score:  190.44242984775784,
+					Member: "Palermo",
+				},
+			}))
+		})
 	})
 
 	Describe("marshaling/unmarshaling", func() {
diff --git a/internal/pool/conncheck_test.go b/internal/pool/conncheck_test.go
deleted file mode 100644
index 0332174..0000000
--- a/internal/pool/conncheck_test.go
+++ /dev/null
@@ -1,46 +0,0 @@
-// +build linux darwin dragonfly freebsd netbsd openbsd solaris illumos
-
-package pool
-
-import (
-	"net"
-	"net/http/httptest"
-	"testing"
-	"time"
-)
-
-func Test_connCheck(t *testing.T) {
-	// tests with real conns
-	ts := httptest.NewServer(nil)
-	defer ts.Close()
-
-	t.Run("good conn", func(t *testing.T) {
-		conn, err := net.DialTimeout(ts.Listener.Addr().Network(), ts.Listener.Addr().String(), time.Second)
-		if err != nil {
-			t.Fatalf(err.Error())
-		}
-		defer conn.Close()
-		if err = connCheck(conn); err != nil {
-			t.Fatalf(err.Error())
-		}
-		conn.Close()
-
-		if err = connCheck(conn); err == nil {
-			t.Fatalf("expect has error")
-		}
-	})
-
-	t.Run("bad conn 2", func(t *testing.T) {
-		conn, err := net.DialTimeout(ts.Listener.Addr().Network(), ts.Listener.Addr().String(), time.Second)
-		if err != nil {
-			t.Fatalf(err.Error())
-		}
-		defer conn.Close()
-
-		ts.Close()
-
-		if err = connCheck(conn); err == nil {
-			t.Fatalf("expect has err")
-		}
-	})
-}
diff --git a/internal/pool/main_test.go b/internal/pool/main_test.go
index c54a38d..2365dbc 100644
--- a/internal/pool/main_test.go
+++ b/internal/pool/main_test.go
@@ -2,12 +2,9 @@ package pool_test
 
 import (
 	"context"
-	"fmt"
 	"net"
 	"sync"
-	"syscall"
 	"testing"
-	"time"
 
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
@@ -35,87 +32,5 @@ func perform(n int, cbs ...func(int)) {
 }
 
 func dummyDialer(context.Context) (net.Conn, error) {
-	// return &net.TCPConn{}, nil
-	return newDummyConn(), nil
-}
-
-func newDummyConn() net.Conn {
-	return &dummyConn{
-		rawConn: &dummyRawConn{},
-	}
-}
-
-var _ net.Conn = (*dummyConn)(nil)
-var _ syscall.Conn = (*dummyConn)(nil)
-
-type dummyConn struct {
-	rawConn *dummyRawConn
-}
-
-func (d *dummyConn) SyscallConn() (syscall.RawConn, error) {
-	return d.rawConn, nil
-}
-
-var errDummy = fmt.Errorf("dummyConn err")
-
-func (d *dummyConn) Read(b []byte) (n int, err error) {
-	return 0, errDummy
-}
-
-func (d *dummyConn) Write(b []byte) (n int, err error) {
-	return 0, errDummy
-}
-
-func (d *dummyConn) Close() error {
-	d.rawConn.Close()
-	return nil
-}
-
-func (d *dummyConn) LocalAddr() net.Addr {
-	return &net.TCPAddr{}
-}
-
-func (d *dummyConn) RemoteAddr() net.Addr {
-	return &net.TCPAddr{}
-}
-
-func (d *dummyConn) SetDeadline(t time.Time) error {
-	return nil
-}
-
-func (d *dummyConn) SetReadDeadline(t time.Time) error {
-	return nil
-}
-
-func (d *dummyConn) SetWriteDeadline(t time.Time) error {
-	return nil
-}
-
-var _ syscall.RawConn = (*dummyRawConn)(nil)
-
-type dummyRawConn struct {
-	closed bool
-	mux    sync.Mutex
-}
-
-func (d *dummyRawConn) Control(f func(fd uintptr)) error {
-	return nil
-}
-
-func (d *dummyRawConn) Read(f func(fd uintptr) (done bool)) error {
-	d.mux.Lock()
-	defer d.mux.Unlock()
-	if d.closed {
-		return fmt.Errorf("dummyRawConn closed")
-	}
-	return nil
-}
-
-func (d *dummyRawConn) Write(f func(fd uintptr) (done bool)) error {
-	return nil
-}
-func (d *dummyRawConn) Close() {
-	d.mux.Lock()
-	d.closed = true
-	d.mux.Unlock()
+	return &net.TCPConn{}, nil
 }
diff --git a/internal/pool/pool_test.go b/internal/pool/pool_test.go
index 6c94fc2..795aef3 100644
--- a/internal/pool/pool_test.go
+++ b/internal/pool/pool_test.go
@@ -6,10 +6,10 @@ import (
 	"testing"
 	"time"
 
+	"github.com/go-redis/redis/v8/internal/pool"
+
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
-
-	"github.com/go-redis/redis/v8/internal/pool"
 )
 
 var _ = Describe("ConnPool", func() {
@@ -285,8 +285,6 @@ var _ = Describe("conns reaper", func() {
 					cn.SetUsedAt(time.Now().Add(-2 * idleTimeout))
 				case "aged":
 					cn.SetCreatedAt(time.Now().Add(-2 * maxAge))
-				case "conncheck":
-					cn.Close()
 				}
 				conns = append(conns, cn)
 				staleConns = append(staleConns, cn)
@@ -373,7 +371,6 @@ var _ = Describe("conns reaper", func() {
 
 	assert("idle")
 	assert("aged")
-	assert("conncheck")
 })
 
 var _ = Describe("race", func() {
diff --git a/main_test.go b/main_test.go
index dd9d874..0cb2b1d 100644
--- a/main_test.go
+++ b/main_test.go
@@ -12,10 +12,10 @@ import (
 	"testing"
 	"time"
 
+	"github.com/go-redis/redis/v8"
+
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
-
-	"github.com/go-redis/redis/v8"
 )
 
 const (
@@ -117,7 +117,7 @@ func TestGinkgoSuite(t *testing.T) {
 	RunSpecs(t, "go-redis")
 }
 
-// ------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
 
 func redisOptions() *redis.Options {
 	return &redis.Options{
@@ -364,7 +364,7 @@ func startSentinel(port, masterName, masterPort string) (*redisProcess, error) {
 	return p, nil
 }
 
-// ------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
 
 type badConnError string
 
@@ -409,7 +409,7 @@ func (cn *badConn) Write([]byte) (int, error) {
 	return 0, badConnError("bad connection")
 }
 
-// ------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
 
 type hook struct {
 	beforeProcess func(ctx context.Context, cmd redis.Cmder) (context.Context, error)
diff --git a/pool_test.go b/pool_test.go
index 8131819..08acc6d 100644
--- a/pool_test.go
+++ b/pool_test.go
@@ -87,9 +87,8 @@ var _ = Describe("pool", func() {
 		cn.SetNetConn(&badConn{})
 		client.Pool().Put(ctx, cn)
 
-		// connCheck will automatically remove damaged connections.
 		err = client.Ping(ctx).Err()
-		Expect(err).NotTo(HaveOccurred())
+		Expect(err).To(MatchError("bad connection"))
 
 		val, err := client.Ping(ctx).Result()
 		Expect(err).NotTo(HaveOccurred())
diff --git a/sentinel_test.go b/sentinel_test.go
index 5faf9cf..7b4aabd 100644
--- a/sentinel_test.go
+++ b/sentinel_test.go
@@ -191,7 +191,7 @@ var _ = Describe("NewFailoverClusterClient", func() {
 		err = master.Shutdown(ctx).Err()
 		Expect(err).NotTo(HaveOccurred())
 		Eventually(func() error {
-			return master.Ping(ctx).Err()
+			return sentinelMaster.Ping(ctx).Err()
 		}, "15s", "100ms").Should(HaveOccurred())
 
 		// Check that client picked up new master.
diff --git a/tx_test.go b/tx_test.go
index 11e5b0d..4681122 100644
--- a/tx_test.go
+++ b/tx_test.go
@@ -123,7 +123,7 @@ var _ = Describe("Tx", func() {
 		Expect(num).To(Equal(int64(N)))
 	})
 
-	It("should remove from bad connection", func() {
+	It("should recover from bad connection", func() {
 		// Put bad connection in the pool.
 		cn, err := client.Pool().Get(context.Background())
 		Expect(err).NotTo(HaveOccurred())
@@ -134,14 +134,17 @@ var _ = Describe("Tx", func() {
 		do := func() error {
 			err := client.Watch(ctx, func(tx *redis.Tx) error {
 				_, err := tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
-					return pipe.Ping(ctx).Err()
+					pipe.Ping(ctx)
+					return nil
 				})
 				return err
 			})
 			return err
 		}
 
-		// connCheck will automatically remove damaged connections.
+		err = do()
+		Expect(err).To(MatchError("bad connection"))
+
 		err = do()
 		Expect(err).NotTo(HaveOccurred())
 	})