Commit c413b3db authored by Alex Browne's avatar Alex Browne Committed by Steven Allen

Add proper error handling

parent bfb657a7
......@@ -28,13 +28,16 @@ type Conn struct {
js.Value
messageHandler *js.Func
closeHandler *js.Func
errorHandler *js.Func
mut sync.Mutex
currDataMut sync.RWMutex
currData bytes.Buffer
closeOnce sync.Once
closeSignal chan struct{}
dataSignal chan struct{}
localAddr net.Addr
remoteAddr net.Addr
firstErr error
}
// NewConn creates a Conn given a regular js/wasm WebSocket Conn.
......@@ -56,7 +59,7 @@ func NewConn(raw js.Value) *Conn {
func (c *Conn) Read(b []byte) (int, error) {
if err := c.checkOpen(); err != nil {
return 0, io.EOF
return c.readAfterErr(b)
}
for {
......@@ -73,7 +76,7 @@ func (c *Conn) Read(b []byte) (int, error) {
case <-c.dataSignal:
continue
case <-c.closeSignal:
return 0, io.EOF
return c.readAfterErr(b)
}
} else {
return n, err
......@@ -81,6 +84,22 @@ func (c *Conn) Read(b []byte) (int, error) {
}
}
// readAfterError reads from c.currData. If there is no more data left it
// returns c.firstErr if non-nil and otherwise returns io.EOF.
func (c *Conn) readAfterErr(b []byte) (int, error) {
c.currDataMut.RLock()
n, err := c.currData.Read(b)
c.currDataMut.RUnlock()
if n == 0 {
if c.firstErr != nil {
return 0, c.firstErr
} else {
return 0, io.EOF
}
}
return n, err
}
// checkOpen returns an error if the connection is not open. Otherwise, it
// returns nil.
func (c *Conn) checkOpen() error {
......@@ -108,9 +127,21 @@ func (c *Conn) Write(b []byte) (n int, err error) {
// close error, subsequent and concurrent calls will return nil.
// This method is thread-safe.
func (c *Conn) Close() error {
c.signalClose()
c.Call("close")
c.releaseHandlers()
return nil
}
func (c *Conn) signalClose() {
c.closeOnce.Do(func() {
close(c.closeSignal)
})
}
func (c *Conn) releaseHandlers() {
c.mut.Lock()
defer c.mut.Unlock()
c.Call("close")
if c.messageHandler != nil {
c.Call("removeEventListener", "message", *c.messageHandler)
c.messageHandler.Release()
......@@ -119,7 +150,10 @@ func (c *Conn) Close() error {
c.Call("removeEventListener", "close", *c.closeHandler)
c.closeHandler.Release()
}
return nil
if c.errorHandler != nil {
c.Call("removeEventListener", "error", *c.errorHandler)
c.errorHandler.Release()
}
}
func (c *Conn) LocalAddr() net.Addr {
......@@ -180,11 +214,27 @@ func (c *Conn) setUpHandlers() {
c.Call("addEventListener", "message", messageHandler)
closeHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
close(c.closeSignal)
go func() {
c.signalClose()
c.mut.Lock()
// Store the error in c.firstErr. It will be returned by Read later on.
c.firstErr = errorEventToError(args[0])
c.mut.Unlock()
c.releaseHandlers()
}()
return nil
})
c.closeHandler = &closeHandler
c.Call("addEventListener", "close", closeHandler)
errorHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
// Unfortunately, the "error" event doesn't appear to give us any useful
// information. All we can do is close the connection.
c.Close()
return nil
})
c.errorHandler = &errorHandler
c.Call("addEventListener", "error", errorHandler)
}
func (c *Conn) waitForOpen() error {
......@@ -196,8 +246,14 @@ func (c *Conn) waitForOpen() error {
defer c.Call("removeEventListener", "open", handler)
defer handler.Release()
c.Call("addEventListener", "open", handler)
<-openSignal
return nil
select {
case <-openSignal:
return nil
case <-c.closeSignal:
// c.closeSignal means there was an error when trying to open the
// connection.
return c.firstErr
}
}
// arrayBufferToBytes converts a JavaScript ArrayBuffer to a slice of bytes.
......@@ -211,12 +267,26 @@ func arrayBufferToBytes(buffer js.Value) []byte {
return data
}
func convertJSError(val js.Value) error {
func errorEventToError(val js.Value) error {
var typ string
if gotType := val.Get("type"); gotType != js.Undefined() {
typ = gotType.String()
} else {
typ = val.Type().String()
}
return fmt.Errorf("JavaScript error: %s %s", typ, val.Get("message").String())
var reason string
if gotReason := val.Get("reason"); gotReason != js.Undefined() && gotReason.String() != "" {
reason = gotReason.String()
} else {
code := val.Get("code")
if code != js.Undefined() {
switch code := code.Int(); code {
case 1006:
reason = "code 1006: connection unexpectedly closed"
default:
reason = fmt.Sprintf("unexpected code: %d", code)
}
}
}
return fmt.Errorf("JavaScript error: (%s) %s", typ, reason)
}
......@@ -19,12 +19,10 @@ func (t *WebsocketTransport) maDial(ctx context.Context, raddr ma.Multiaddr) (ma
}
rawConn := js.Global().Get("WebSocket").New(wsurl)
rawConn.Call("addEventListener", "error", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
js.Global().Get("console").Call("log", args[0])
return nil
}))
conn := NewConn(rawConn)
conn.waitForOpen()
if err := conn.waitForOpen(); err != nil {
return nil, err
}
mnc, err := manet.WrapNetConn(conn)
if err != nil {
conn.Close()
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment