Just because your behaviour is internally powered by GenServer doesn’t mean it has to expose everything GenServer does. So for example, if the timeout option doesn’t make sense in WebSockex, you don’t need to support it in your own behaviour.
Hey @michalmuskala and @sasajuric, thanks for the input. I feel like I understood what you’re saying at the time that I made my decision. But none of what I am thinking brings back a good enough reason to not use GenServer
. Even if there was, in it’s current state WebSockex
has no reason not to be a GenServer
.
So I either completely forgot what it was I tried to avoid or I was wrong and my reasons weren’t good enough in the first place.
Let me think on it for a couple of days. If anything comes to mind, I’ll let you know.
Glancing at your code, one reason which comes to mind is that with async mode of connecting, you’ll invoke init_ack
in the middle of your initialization. This is somewhat clumsy to support with GenServer, especially at the library level, when you’re not aware whether the server process will be named or not.
One solution might be to start the task from init/1
which will establish the connection, perform the initial handshake, and then give the ownership to the parent with controlling_process.
I’m not very familiar with websocket details, so maybe I’m missing some possible caveats, but I think this might take off. The main benefit here is that your initialization then becomes straightforward, because you don’t need to ack in the middle, and get to keep async connecting and handshake.
WebSockex 0.3.0 Released!
Breaking Changes
- The parameters for the
handle_connect/2
callback have been reversed. The order is now(conn, state)
.
Enhancements
- Added initial connection timeouts. (
:socket_connect_timeout
and:socket_recv_timeout
)- Can be used as
start
orstart_link
option or as aConn.new
option. -
:socket_connect_timeout
- The timeout for opening a TCP connection. -
:socket_recv_timeout
- The timeout for receiving a HTTP response header.
- Can be used as
-
start
andstart_link
can now take aConn
struct in place of a url. - Added the ability to handle system messages while opening a connection.
- Added the ability to handle parent exit messages while opening a connection.
- Improve
:sys.get_status
,:sys.get_state
,:sys.replace_state
functions.- These are undocumented, but are meant primarily for debugging.
Bug Fixes
- Ensure
terminate/2
callback is called consistently. - Ensure when termination when a parent exit signal is received.
- Add the
system_code_change
function so that thecode_change
callback is actually used.
Take a look at the v0.2.0..v0.3.0
diff or the release on hex.pm
for more information.
I started down the path of starting in a different process and ended up realizing that I would need to use a gen_statem
.
As a result, I feel like the code is kind of a mess right now. But thinking about structuring it for an already defined gen
module has helped out. It’s still something I’m trying to do, but wanted to get another release out.
Hi guys, I try to use WebSockex to connect to Socket.io server but I was unable to maintain heartbeat, Is it possible to do that?
There’s no reason it can’t, but it doesn’t do so automatically. What exactly are you having a problem with?
WebSockex 0.3.1 Released!
Enhancements
- Handle system messages and parent exits while closing the connection.
- The output from
:sys.get_status
didn’t look pretty in:observer
; now it does! -
format_status/2
now an optional callback.
Bug Fixes
- SSL frames sent right after connecting will now be handled instead of being left in a dead Task mailbox.
- Fixed some places where
dialyzer
told me I had my specs screwed up.
Take a look at the v0.3.0..v0.3.1
diff or the release on hex.pm
for more information.
Glad you figured it out. If there’s anything else that you have problems with feel free to ask.
WebSockex 0.4.0 Released!
Just as a side note, I think the most exciting thing about this release is the addition of OTP based debugging capabilities. The debug messages themselves are useful but not perfect. If you would like more hooks or better output feel free to open an issue.
Breaking Changes
-
send_frame/2
is now synchronous and returns an error when connection is opening or closing.
Enhancements
- Added debug printing for the
:sys
module. - Rework
Conn.new
to accept other protocols. - Added an
InvalidFrameError
for frames unrecognized byFrame.encode_frame
. - Go through the disconnect cycle when there’s an error while with the
{:reply, frame, state}
callback response. - Send a close frame with the close code
1011
when there is an unexpected error. (Like anException
in the middle of a callback) - Add a more specific error when the
:websockex
application hasn’t been started yet. - Added a
:name
options for local registration.
Bug Fixes
- Fix a couple of places where the call stack wasn’t being properly tail-call optimized.-
Take a look at the v0.3.1...v0.4.0
diff or the release on hex.pm
for more information
Hi, I’m using the last version, and I this work really great.
But I have a question probably a silly one how can I close the connection and terminate the process with :normal
. I’m going to have a pool of clients and this is one of the things that I need. Other thing that I’m doing is handling disconnects inside the client so we reconnect automatically without terminating the process but that’s other thing we want.
Thanks in advance.
Yeah, I thought about this and I couldn’t come up with a good API solution that I liked.
For now, you can use exit(:normal)
in the terminate
callback.
If you want to open an issue, I will either add documentation or come up with a better solution for the next release.
OK thanks, could you summarize when the terminate callback is called?
Hopefully anytime that the process is going to exit for whatever reason.
If there is a time that terminate isn’t called then it is considered a bug.
@Azolo Is there a OS configuration necessary to be able to create 1K websockets connecting to the same Phoenix instance. I am getting this error
(MatchError) no match of right hand side value:
{:error, %WebSockex.ConnError{original: :closed}}
after about 200 processes created with this code
opts = [
{:debug, [:trace]},
{:socket_connect_timeout, Keyword.get(
opts, :socket_connect_timeout, @socket_connect_timeout)},
{:socket_recv_timeout, Keyword.get(
opts, :socket_recv_timeout, @socket_recv_timeout)},
]
WebSockex.start_link(conn_info, __MODULE__, state, [ {:debug, [:trace]} | opts] )
I would guess that is probably an OS file descriptor limit, but other than that I don’t know.
Mt best guess would be to try increasing the maximum file descriptors on the server and client using ulimit -n
.
@Azolo thank you for the prompt response… I increased the number of “open files” to 200K but still no luck.
I changed the code to display a specific error in
websockex/websockex/conn.ex
91 @spec socket_send(__MODULE__.t, binary) :: :ok | {:error, reason :: term}
92 def socket_send(conn, message) do
93 case conn.conn_mod.send(conn.socket, message) do
94 :ok -> :ok
95 {:error, error} -> {:error, %WebSockex.ConnError{original: "ERR_A"}}
96 end
97 end
so it looks like this is triggered in a “SEND”.
How would you recommend to debug the
“conn.conn_mod.send(conn.socket, message)” call?
===== Fragment of code which you can test
5 def start(server, users) do
6 all_users = 1..users
7
8 Logger.info("Simulator: registering #{users} users...")
9 all_users
10 |> Enum.map(fn i ->
11 Task.async(fn ->
12 {:ok, client} = WebSockex.start_link("ws://0.0.0.0:4000/socket/websocket", [])
15 end)
16 end)
17 |> Enum.each(&Task.await/1)
Ulimit does only increase the softlimit which can never be higher than the hard limit. The hardlimit needs to be increased first, on most systems you can do so via sysctl AFAIR.
Im in mobile so it’s hard to do a proper check right now.