Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wsutil.readData() blocks until desired opcode/frame is received #85

Closed
kenfoo opened this issue Sep 21, 2019 · 7 comments
Closed

wsutil.readData() blocks until desired opcode/frame is received #85

kenfoo opened this issue Sep 21, 2019 · 7 comments

Comments

@kenfoo
Copy link

kenfoo commented Sep 21, 2019

In wsutil.readData(), whenever a control frame (pings) or unwanted OpCode is received, it performs a "continue" in the for loop to read the next frame (rd.NextFrame()), repeating until a wanted opcode type is received.

For most users using goroutines this is fine as it blocks until the desired data is available.

However when used with epoll this is problematic as it may potentially block. A simple solution would be to return nil,0,nil instead of performing a continue to read the next frame, but that might actually break the expected behavior of this function.

@gobwas
Copy link
Owner

gobwas commented Sep 22, 2019

Hi @kenfoo,

This is intended behaviour of this method – that is, the word "Data" means exactly what you have discovered – read the data message, not the control.

When using epoll somehow wsutil helpers is not the best way to deal with frames.

@gobwas gobwas closed this as completed Sep 22, 2019
@oguzhane
Copy link

oguzhane commented Feb 8, 2020

@kenfoo
maybe, one of the ways below can help you to avoid blocking goroutine.

you could use SetReadDeadline to stop waiting reading data, if it fits your case.

..
netConn.SetReadDeadline(time.Now().Add(50 * time.Millisecond))
data, opCode, err := wsutil.ReadClientData(netConn)
if os.IsTimeout(err) {
  ..
}

Another way could be exposing EWOULDBLOCK error by reading data directly from file descriptor with syscall.Read.

type SysConn struct {
  ..
  fd int
}

func (s SysConn) Read(p []byte) (int, error) {
        ..
	n, err := syscall.Read(s.fd, p)
        ..
	return n, err
}

data, opCode, err := wsutil.ReadClientData(sysConn)
if err  == syscall.EAGAIN {
  ..
}

Would be helpful to check implementation of *netFD.Read to have graceful handling.
see:
https://golang.org/src/net/fd_unix.go
https://golang.org/src/internal/poll/fd_unix.go

@knadh
Copy link

knadh commented Jul 29, 2020

@oguzhane We were experimenting along similar lines and stumbled upon this thread. SetReadDeadline() does not work unfortunately as the micro timeouts leave the connection in a dirty state (partially read bytes maybe) when there's heavy traffic, causing subsequent reads to fail because of malformed messages.

@oguzhane
Copy link

@knadh I have ended up going for the approach reading data from the file descripter by using syscall.RawConn. However, i have not tested it under high load of traffic yet.
The problem with this approach is it does not work well with crypto/tls since standart Golang TLS implementation does not support event-based mechanism.

@knadh
Copy link

knadh commented Aug 14, 2020

@oguzhane we tried reading directly from the file descriptor and under high loads, it ran into the same issues with malformed / partial messages.

@kenfoo
Copy link
Author

kenfoo commented Aug 14, 2020

Hi. If it is of any help to anyway, we ended up simply reimplementing what wsutil.readData did, but modified it such that it doesn't block or loop, and instead returns the bytes, the opcode and error in all cases.

@knadh
Copy link

knadh commented Aug 14, 2020

@kenfoo we attempted this unsuccessfully. It'd be nice if you could share your solution!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants