Skip to content

Commit 433c00d

Browse files
authored
Merge pull request #148 from nhooyr/fix-review
Fixes from @albrow's Wasm review
2 parents ef5a88d + 9fc9f7a commit 433c00d

File tree

9 files changed

+77
-34
lines changed

9 files changed

+77
-34
lines changed

Diff for: README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ go get nhooyr.io/websocket
2323
- JSON and ProtoBuf helpers in the [wsjson](https://godoc.org/nhooyr.io/websocket/wsjson) and [wspb](https://godoc.org/nhooyr.io/websocket/wspb) subpackages
2424
- Highly optimized by default
2525
- Concurrent writes out of the box
26-
- [Complete WASM](https://godoc.org/nhooyr.io/websocket#hdr-WASM) support
26+
- [Complete Wasm](https://godoc.org/nhooyr.io/websocket#hdr-Wasm) support
2727

2828
## Roadmap
2929

@@ -130,7 +130,7 @@ The ping API is also nicer. gorilla/websocket requires registering a pong handle
130130
which results in awkward control flow. With nhooyr/websocket you use the Ping method on the Conn
131131
that sends a ping and also waits for the pong.
132132

133-
Additionally, nhooyr.io/websocket can compile to [WASM](https://godoc.org/nhooyr.io/websocket#hdr-WASM) for the browser.
133+
Additionally, nhooyr.io/websocket can compile to [Wasm](https://godoc.org/nhooyr.io/websocket#hdr-Wasm) for the browser.
134134

135135
In terms of performance, the differences mostly depend on your application code. nhooyr/websocket
136136
reuses message buffers out of the box if you use the wsjson and wspb subpackages.

Diff for: assert_test.go

+11-2
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ package websocket_test
22

33
import (
44
"context"
5-
"encoding/hex"
65
"fmt"
76
"math/rand"
87
"reflect"
8+
"strings"
99

1010
"github.com/google/go-cmp/cmp"
1111

@@ -99,7 +99,16 @@ func randBytes(n int) []byte {
9999
}
100100

101101
func randString(n int) string {
102-
return hex.EncodeToString(randBytes(n))[:n]
102+
s := strings.ToValidUTF8(string(randBytes(n)), "_")
103+
if len(s) > n {
104+
return s[:n]
105+
}
106+
if len(s) < n {
107+
// Pad with =
108+
extra := n - len(s)
109+
return s + strings.Repeat("=", extra)
110+
}
111+
return s
103112
}
104113

105114
func assertEcho(ctx context.Context, c *websocket.Conn, typ websocket.MessageType, n int) error {

Diff for: conn.go

-6
Original file line numberDiff line numberDiff line change
@@ -120,12 +120,6 @@ func (c *Conn) Subprotocol() string {
120120
return c.subprotocol
121121
}
122122

123-
func (c *Conn) setCloseErr(err error) {
124-
c.closeErrOnce.Do(func() {
125-
c.closeErr = fmt.Errorf("websocket closed: %w", err)
126-
})
127-
}
128-
129123
func (c *Conn) close(err error) {
130124
c.closeOnce.Do(func() {
131125
runtime.SetFinalizer(c, nil)

Diff for: conn_common.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// This file contains *Conn symbols relevant to both
2-
// WASM and non WASM builds.
2+
// Wasm and non Wasm builds.
33

44
package websocket
55

@@ -202,3 +202,9 @@ func (c *Conn) CloseRead(ctx context.Context) context.Context {
202202
func (c *Conn) SetReadLimit(n int64) {
203203
c.msgReadLimit = n
204204
}
205+
206+
func (c *Conn) setCloseErr(err error) {
207+
c.closeErrOnce.Do(func() {
208+
c.closeErr = fmt.Errorf("websocket closed: %w", err)
209+
})
210+
}

Diff for: doc.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@
1818
// Use the errors.As function new in Go 1.13 to check for websocket.CloseError.
1919
// See the CloseError example.
2020
//
21-
// WASM
21+
// Wasm
2222
//
23-
// The client side fully supports compiling to WASM.
23+
// The client side fully supports compiling to Wasm.
2424
// It wraps the WebSocket browser API.
2525
//
2626
// See https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
2727
//
28-
// Thus the unsupported features (not compiled in) for WASM are:
28+
// Thus the unsupported features (not compiled in) for Wasm are:
2929
//
3030
// - Accept and AcceptOptions
3131
// - Conn.Ping

Diff for: frame.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -213,8 +213,8 @@ const (
213213

214214
StatusNoStatusRcvd
215215

216-
// This StatusCode is only exported for use with WASM.
217-
// In non WASM Go, the returned error will indicate whether the connection was closed or not or what happened.
216+
// This StatusCode is only exported for use with Wasm.
217+
// In non Wasm Go, the returned error will indicate whether the connection was closed or not or what happened.
218218
StatusAbnormalClosure
219219

220220
StatusInvalidFramePayloadData
@@ -226,8 +226,8 @@ const (
226226
StatusTryAgainLater
227227
StatusBadGateway
228228

229-
// This StatusCode is only exported for use with WASM.
230-
// In non WASM Go, the returned error will indicate whether there was a TLS handshake failure.
229+
// This StatusCode is only exported for use with Wasm.
230+
// In non Wasm Go, the returned error will indicate whether there was a TLS handshake failure.
231231
StatusTLSHandshake
232232
)
233233

File renamed without changes.

Diff for: websocket_js.go

+48-14
Original file line numberDiff line numberDiff line change
@@ -23,29 +23,32 @@ type Conn struct {
2323

2424
msgReadLimit int64
2525

26-
readClosed int64
27-
closeOnce sync.Once
28-
closed chan struct{}
29-
closeErr error
26+
readClosed int64
27+
closeOnce sync.Once
28+
closed chan struct{}
29+
closeErrOnce sync.Once
30+
closeErr error
3031

3132
releaseOnClose func()
3233
releaseOnMessage func()
3334

34-
readch chan wsjs.MessageEvent
35+
readSignal chan struct{}
36+
readBufMu sync.Mutex
37+
readBuf []wsjs.MessageEvent
3538
}
3639

3740
func (c *Conn) close(err error) {
3841
c.closeOnce.Do(func() {
3942
runtime.SetFinalizer(c, nil)
4043

41-
c.closeErr = fmt.Errorf("websocket closed: %w", err)
44+
c.setCloseErr(err)
4245
close(c.closed)
4346
})
4447
}
4548

4649
func (c *Conn) init() {
4750
c.closed = make(chan struct{})
48-
c.readch = make(chan wsjs.MessageEvent, 1)
51+
c.readSignal = make(chan struct{}, 1)
4952
c.msgReadLimit = 32768
5053

5154
c.releaseOnClose = c.ws.OnClose(func(e wsjs.CloseEvent) {
@@ -61,15 +64,28 @@ func (c *Conn) init() {
6164
})
6265

6366
c.releaseOnMessage = c.ws.OnMessage(func(e wsjs.MessageEvent) {
64-
c.readch <- e
67+
c.readBufMu.Lock()
68+
defer c.readBufMu.Unlock()
69+
70+
c.readBuf = append(c.readBuf, e)
71+
72+
// Lets the read goroutine know there is definitely something in readBuf.
73+
select {
74+
case c.readSignal <- struct{}{}:
75+
default:
76+
}
6577
})
6678

6779
runtime.SetFinalizer(c, func(c *Conn) {
68-
c.ws.Close(int(StatusInternalError), "")
69-
c.close(errors.New("connection garbage collected"))
80+
c.setCloseErr(errors.New("connection garbage collected"))
81+
c.closeWithInternal()
7082
})
7183
}
7284

85+
func (c *Conn) closeWithInternal() {
86+
c.Close(StatusInternalError, "something went wrong")
87+
}
88+
7389
// Read attempts to read a message from the connection.
7490
// The maximum time spent waiting is bounded by the context.
7591
func (c *Conn) Read(ctx context.Context) (MessageType, []byte, error) {
@@ -89,16 +105,32 @@ func (c *Conn) Read(ctx context.Context) (MessageType, []byte, error) {
89105
}
90106

91107
func (c *Conn) read(ctx context.Context) (MessageType, []byte, error) {
92-
var me wsjs.MessageEvent
93108
select {
94109
case <-ctx.Done():
95110
c.Close(StatusPolicyViolation, "read timed out")
96111
return 0, nil, ctx.Err()
97-
case me = <-c.readch:
112+
case <-c.readSignal:
98113
case <-c.closed:
99114
return 0, nil, c.closeErr
100115
}
101116

117+
c.readBufMu.Lock()
118+
defer c.readBufMu.Unlock()
119+
120+
me := c.readBuf[0]
121+
// We copy the messages forward and decrease the size
122+
// of the slice to avoid reallocating.
123+
copy(c.readBuf, c.readBuf[1:])
124+
c.readBuf = c.readBuf[:len(c.readBuf)-1]
125+
126+
if len(c.readBuf) > 0 {
127+
// Next time we read, we'll grab the message.
128+
select {
129+
case c.readSignal <- struct{}{}:
130+
default:
131+
}
132+
}
133+
102134
switch p := me.Data.(type) {
103135
case string:
104136
return MessageText, []byte(p), nil
@@ -118,8 +150,10 @@ func (c *Conn) Write(ctx context.Context, typ MessageType, p []byte) error {
118150
// to match the Go API. It can only error if the message type
119151
// is unexpected or the passed bytes contain invalid UTF-8 for
120152
// MessageText.
121-
c.Close(StatusInternalError, "something went wrong")
122-
return fmt.Errorf("failed to write: %w", err)
153+
err := fmt.Errorf("failed to write: %w", err)
154+
c.setCloseErr(err)
155+
c.closeWithInternal()
156+
return err
123157
}
124158
return nil
125159
}

Diff for: websocket_js_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,12 @@ func TestConn(t *testing.T) {
3636
t.Fatal(err)
3737
}
3838

39-
err = assertJSONEcho(ctx, c, 16)
39+
err = assertJSONEcho(ctx, c, 1024)
4040
if err != nil {
4141
t.Fatal(err)
4242
}
4343

44-
err = assertEcho(ctx, c, websocket.MessageBinary, 16)
44+
err = assertEcho(ctx, c, websocket.MessageBinary, 1024)
4545
if err != nil {
4646
t.Fatal(err)
4747
}

0 commit comments

Comments
 (0)