jaspervdj / websockets

A Haskell library for creating WebSocket-capable servers
http://jaspervdj.be/websockets
BSD 3-Clause "New" or "Revised" License
407 stars 114 forks source link

Unable to `sendTextData` after `receiveData` gets an async exception. #182

Open michalrus opened 6 years ago

michalrus commented 6 years ago

It’s not possible to implement something like:

do
  msg <- timeout (10*1000*1000) (receiveData conn)
  case msg of
    Just msg -> …
    Nothing ->
      sendTextData conn "timeouted"
      threadDelay (1 * 1000 * 1000) -- even with this…
      sendClose conn

… the client will never see the "timeouted" message.

jaspervdj commented 6 years ago

@michalrus Yes, receiveData blocks until data is received (just like most of the lower-level networking functions in Haskell). In order to send and receive, you can use forkIO (or something like the async) package to kick off a writer thread. Does that solve your problem?

michalrus commented 6 years ago

Well, that receive blocks is understandable.

What I want is a time-outing receive.

So if you wrap it in http://hackage.haskell.org/package/base-4.12.0.0/docs/System-Timeout.html, the wrapped call returns with a Nothing, but then sendTextData called later on does… nothing.

System.Timeout.timeout works by sending an async exception to the current thread.

jaspervdj commented 6 years ago

Oh! I completely misunderstood the question, sorry. Yes, that makes sense to have.

jaspervdj commented 6 years ago

I think we should implement this the nice way but that will be a breaking change. We basically want to be able to extend Stream's interface with a Success | FatalError | Timeout result type from read and pass in an optional timeout.

As for the API, do you think the timeout should be global (i.e. set on socket/stream creation) or local (i.e. a parameter to receive)?

michalrus commented 6 years ago

I see…

IMO, local — see also #32. =)

I mean, these issues (+ the ones with ping timeout #174 #159) can certainly be worked around either like proposed by @rwbarton in #32, or some other way with Async, but they would be nice to have natively — being explicit about the fact that networking is, in essence, unreliable. ¯\_(ツ)_/¯

Also not all programmers might be able to implement this correctly, so it’s a better task for the library.

Cf. fallacy 1. of https://en.wikipedia.org/wiki/Fallacies_of_distributed_computing:

The network is reliable.

Software applications are written with little error-handling on networking errors. During a network outage, such applications may stall or infinitely wait for an answer packet, permanently consuming memory or other resources. When the failed network becomes available, those applications may also fail to retry any stalled operations or require a (manual) restart.

michalrus commented 6 years ago

Also, similar to this, imagine:

finally
  (forever $ receiveTextData conn >>= processMsgSomehow)
  (sendTextData conn "bye bye")

If I throw an async exception to this simple code above, sendTextData will throw a Network.WebSockets.ConnectionClosed exception despite WAI thread clearly still being alive (the finally hook happens in that WAI thread).

These API choices of WebSockets prevent writing slightly more, hmmm, refined(?) code, like the one above.