diff --git a/README.md b/README.md
index 53507a60580e4048433539280addbc2e6285a1fb..5e7c150d5331ee9047eaf0a7e741583daca45cb8 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@ If you have any feedback, please feel free to open an issue.
 ## Install
 
 ```bash
-go get nhooyr.io/websocket
+go get nhooyr.io/websocket@0.2.0
 ```
 
 ## Features
@@ -85,9 +85,8 @@ c.Close(websocket.StatusNormalClosure, "")
 - Minimal API is easier to maintain and learn
 - Context based cancellation is more ergonomic and robust than setting deadlines
 - No ping support because TCP keep alives work fine for HTTP/1.1 and they do not make
-  sense with HTTP/2 (see #1)
-- net.Conn is never exposed as WebSocket's over HTTP/2 will not have a net.Conn.
-- Structures are nicer than functional options, see [google/go-cloud#908](https://github.com/google/go-cloud/issues/908#issuecomment-445034143)
+  sense with HTTP/2 (see [#1](https://github.com/nhooyr/websocket/issues/1))
+- net.Conn is never exposed as WebSocket over HTTP/2 will not have a net.Conn.
 - Using net/http's Client for dialing means we do not have to reinvent dialing hooks
   and configurations like other WebSocket libraries
 
@@ -105,7 +104,7 @@ in production.
 https://github.com/gorilla/websocket
 
 This package is the community standard but it is 6 years old and over time
-has accumulated cruft. There are many ways to do the same thing, usage is not clear
+has accumulated cruft. Using is not clear as there are many ways to do things
 and there are some rough edges. Just compare the godoc of
 [nhooyr/websocket](https://godoc.org/github.com/nhooyr/websocket) side by side with
 [gorilla/websocket](https://godoc.org/github.com/gorilla/websocket).
@@ -115,11 +114,10 @@ which makes it easy to use correctly.
 
 Furthermore, nhooyr/websocket has support for newer Go idioms such as context.Context and
 also uses net/http's Client and ResponseWriter directly for WebSocket handshakes.
-gorilla/websocket writes its handshakes directly to a net.Conn which means
+gorilla/websocket writes its handshakes to the underlying net.Conn which means
 it has to reinvent hooks for TLS and proxying and prevents support of HTTP/2.
 
-Another advantage of nhooyr/websocket is that it supports multiple concurrent writers out
-of the box.
+Another advantage of nhooyr/websocket is that it supports concurrent writers out of the box.
 
 ### x/net/websocket
 
@@ -138,8 +136,9 @@ and clarity.
 
 This library is fantastic in terms of performance. The author put in significant
 effort to ensure its speed and I have applied as many of its optimizations as
-I could into nhooyr/websocket. Definitely check out his fantastic [blog post](https://medium.freecodecamp.org/million-websockets-and-go-cc58418460bb) about performant WebSocket servers.
+I could into nhooyr/websocket. Definitely check out his fantastic [blog post](https://medium.freecodecamp.org/million-websockets-and-go-cc58418460bb) 
+about performant WebSocket servers.
 
 If you want a library that gives you absolute control over everything, this is the library,
-but for most users, the API provided by nhooyr/websocket will fit better as it is just as
-performant but much easier to use correctly and idiomatic.
+but for most users, the API provided by nhooyr/websocket will fit better as it is nearly just
+as performant but much easier to use correctly and idiomatic.
diff --git a/doc.go b/doc.go
index 0b873b48478be8dcbd3d33c406811843428db0ad..6ee4166aba6cb4b801f490f77f9ee7fd12734b8b 100644
--- a/doc.go
+++ b/doc.go
@@ -2,9 +2,6 @@
 //
 // See https://tools.ietf.org/html/rfc6455
 //
-// Please see https://nhooyr.io/websocket for overview docs and a
-// comparison with existing implementations.
-//
 // Conn, Dial, and Accept are the main entrypoints into this package. Use Dial to dial
 // a WebSocket server, Accept to accept a WebSocket client dial and then Conn to interact
 // with the resulting WebSocket connections.
@@ -12,4 +9,9 @@
 // The examples are the best way to understand how to correctly use the library.
 //
 // The wsjson and wspb subpackages contain helpers for JSON and ProtoBuf messages.
+//
+// Please see https://nhooyr.io/websocket for more overview docs and a
+// comparison with existing implementations.
+//
+// Please be sure to use the https://golang.org/x/xerrors package when inspecting returned errors.
 package websocket
diff --git a/example_echo_test.go b/example_echo_test.go
index a90257b82c5c1b774aead4861f08eddda029a95b..ab0e8e70c9e7c3828b518a45f34754508c54d116 100644
--- a/example_echo_test.go
+++ b/example_echo_test.go
@@ -16,8 +16,8 @@ import (
 	"nhooyr.io/websocket/wsjson"
 )
 
-// This example starts a WebSocket echo server and
-// then dials the server and sends 5 different messages
+// This example starts a WebSocket echo server,
+// dials the server and then sends 5 different messages
 // and prints out the server's responses.
 func Example_echo() {
 	// First we listen on port 0, that means the OS will
diff --git a/example_test.go b/example_test.go
index 7a0528ce342ce9444e96da12a62f7fca7fed26f4..57f0aa5ee072d069d9e39e505c3d140273d20c6a 100644
--- a/example_test.go
+++ b/example_test.go
@@ -36,7 +36,8 @@ func ExampleAccept() {
 		c.Close(websocket.StatusNormalClosure, "")
 	})
 
-	http.ListenAndServe("localhost:8080", fn)
+	err := http.ListenAndServe("localhost:8080", fn)
+	log.Fatal(err)
 }
 
 // This example dials a server, writes a single JSON message and then
@@ -47,15 +48,13 @@ func ExampleDial() {
 
 	c, _, err := websocket.Dial(ctx, "ws://localhost:8080", websocket.DialOptions{})
 	if err != nil {
-		log.Println(err)
-		return
+		log.Fatal(err)
 	}
 	defer c.Close(websocket.StatusInternalError, "the sky is falling")
 
 	err = wsjson.Write(ctx, c, "hi")
 	if err != nil {
-		log.Println(err)
-		return
+		log.Fatal(err)
 	}
 
 	c.Close(websocket.StatusNormalClosure, "")
diff --git a/statuscode.go b/statuscode.go
index 69b015c32652ea239a56ca7a71700a5f26ce929d..d4223745d3e7a33ecae74a49d8c81ff5faa1b240 100644
--- a/statuscode.go
+++ b/statuscode.go
@@ -42,6 +42,7 @@ const (
 
 // CloseError represents a WebSocket close frame.
 // It is returned by Conn's methods when the Connection is closed with a WebSocket close frame.
+// You will need to use https://golang.org/x/xerrors to check for this error.
 type CloseError struct {
 	Code   StatusCode
 	Reason string
diff --git a/websocket.go b/websocket.go
index 275af9da72d3be94438701401bb17e189148bbd0..912508d5635321679d8c2459a66035fac11ab9fb 100644
--- a/websocket.go
+++ b/websocket.go
@@ -61,8 +61,6 @@ type Conn struct {
 }
 
 func (c *Conn) close(err error) {
-	err = xerrors.Errorf("websocket closed: %w", err)
-
 	c.closeOnce.Do(func() {
 		runtime.SetFinalizer(c, nil)
 
@@ -71,7 +69,7 @@ func (c *Conn) close(err error) {
 			cerr = err
 		}
 
-		c.closeErr = cerr
+		c.closeErr = xerrors.Errorf("websocket closed: %w", cerr)
 
 		close(c.closed)
 	})
@@ -98,7 +96,7 @@ func (c *Conn) init() {
 	c.readDone = make(chan int)
 
 	runtime.SetFinalizer(c, func(c *Conn) {
-		c.Close(StatusInternalError, "connection garbage collected")
+		c.close(xerrors.New("connection garbage collected"))
 	})
 
 	go c.writeLoop()
@@ -238,7 +236,7 @@ func (c *Conn) handleControl(h header) {
 	case opClose:
 		ce, err := parseClosePayload(b)
 		if err != nil {
-			c.close(xerrors.Errorf("read invalid close payload: %w", err))
+			c.close(xerrors.Errorf("received invalid close payload: %w", err))
 			return
 		}
 		if ce.Code == StatusNoStatusRcvd {
@@ -302,7 +300,7 @@ func (c *Conn) readLoop() {
 	}
 }
 
-func (c *Conn) dataReadLoop(h header) (err error) {
+func (c *Conn) dataReadLoop(h header) error {
 	maskPos := 0
 	left := h.payloadLength
 	firstReadDone := false
@@ -355,7 +353,6 @@ func (c *Conn) writePong(p []byte) error {
 
 // Close closes the WebSocket connection with the given status code and reason.
 // It will write a WebSocket close frame with a timeout of 5 seconds.
-// Concurrent calls to Close are ok.
 func (c *Conn) Close(code StatusCode, reason string) error {
 	err := c.exportedClose(code, reason)
 	if err != nil {
@@ -400,7 +397,7 @@ func (c *Conn) writeClose(p []byte, cerr CloseError) error {
 		return err
 	}
 
-	if cerr != c.closeErr {
+	if !xerrors.Is(c.closeErr, cerr) {
 		return c.closeErr
 	}
 
@@ -420,9 +417,8 @@ func (c *Conn) writeSingleFrame(ctx context.Context, opcode opcode, p []byte) er
 		payload: p,
 	}:
 	case <-ctx.Done():
-		err := xerrors.Errorf("control frame write timed out: %w", ctx.Err())
-		c.close(err)
-		return err
+		c.close(xerrors.Errorf("control frame write timed out: %w", ctx.Err()))
+		return ctx.Err()
 	}
 
 	select {
@@ -487,7 +483,7 @@ func (w messageWriter) write(p []byte) (int, error) {
 		select {
 		case <-w.ctx.Done():
 			w.c.close(xerrors.Errorf("data write timed out: %w", w.ctx.Err()))
-			// Wait for writeLoop to complete so we know p is done.
+			// Wait for writeLoop to complete so we know p is done with.
 			<-w.c.writeDone
 			return 0, w.ctx.Err()
 		case _, ok := <-w.c.writeDone:
@@ -542,25 +538,21 @@ func (c *Conn) Reader(ctx context.Context) (MessageType, io.Reader, error) {
 }
 
 func (c *Conn) reader(ctx context.Context) (MessageType, io.Reader, error) {
-	for !atomic.CompareAndSwapInt64(&c.activeReader, 0, 1) {
-		select {
-		case <-c.closed:
-			return 0, nil, c.closeErr
-		case c.readBytes <- nil:
-			select {
-			case <-ctx.Done():
-				return 0, nil, ctx.Err()
-			case _, ok := <-c.readDone:
-				if !ok {
-					return 0, nil, c.closeErr
-				}
-				if atomic.LoadInt64(&c.activeReader) == 1 {
-					return 0, nil, xerrors.New("previous message not fully read")
-				}
-			}
-		case <-ctx.Done():
-			return 0, nil, ctx.Err()
+	if !atomic.CompareAndSwapInt64(&c.activeReader, 0, 1) {
+		// If the next read yields io.EOF we are good to go.
+		r := messageReader{
+			ctx: ctx,
+			c:   c,
 		}
+		_, err := r.Read(nil)
+		if err == nil {
+			return 0, nil, xerrors.New("previous message not fully read")
+		}
+		if !xerrors.Is(err, io.EOF) {
+			return 0, nil, xerrors.Errorf("failed to check if last message at io.EOF: %w", err)
+		}
+
+		atomic.StoreInt64(&c.activeReader, 1)
 	}
 
 	select {
@@ -586,7 +578,8 @@ type messageReader struct {
 func (r messageReader) Read(p []byte) (int, error) {
 	n, err := r.read(p)
 	if err != nil {
-		// Have to return io.EOF directly for now, cannot wrap.
+		// Have to return io.EOF directly for now, we cannot wrap as xerrors
+		// isn't used in stdlib.
 		if err == io.EOF {
 			return n, io.EOF
 		}