-
-
Notifications
You must be signed in to change notification settings - Fork 217
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
Added support for async-websocket. #219
Conversation
63e15bd
to
b06244d
Compare
end | ||
end | ||
|
||
class Socket < Slack::RealTime::Socket | ||
attr_reader :client | ||
|
||
def start_async(client) | ||
@client = client | ||
client.run_loop | ||
Thread.new do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We generally want the caller to be responsible for calling this, otherwise we're spawing a thread per client (or a bot)? Or this this necessary even if you run the code within a Async::Reactor.run
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did it to make it the same as how Celluloid is working, which spawns one thread per actor AFAIK. However, it's not necessary. We can push that requirement further up the call chain, but it might mean that the top level needs to embed the code in a reactor. Let me try to figure out the race conditions first then we can figure out how to push that code further up.
@dblock What's your preference for how it behaves? If you want to make a server with multiple connections/clients, can we make something to wrap around that? so that we can have a top level reactor, or enforce event machine reactor creation? Threading makes everything tricky and on Ruby you will have worse performance except in very specific situations. So if we can avoid it, it will make code simpler. |
@ioquatix This is generally used in a stack of libraries, for example https://github.com/slack-ruby/slack-shellbot So we tell users to put reactor type stuff as high as possible. For example slack-ruby-bot-server is the first opinionated library that currently defaults to Celluloid and can/should default to async in the next big release. So for this library we should be removing any I removed one in I also fixed the example and I think this PR if green is ready to go. I'll wait for the dust to settle and to hear from you ;) |
Before we merge this, I would like to ensure we have the right concurrency model.
How it works and why is explained here: https://github.com/socketry/async#asyncreactorrun I will play around with it a bit more. |
Thanks so much for your help @ioquatix. If you want to play with a real bot, slack-shellbot is a fun one. You'll have to replace references to Celluloid in https://github.com/slack-ruby/slack-ruby-bot-server. |
Async branch for slack-ruby-bot in slack-ruby/slack-ruby-bot#198. |
Async branch for slack-ruby-bot-server in slack-ruby/slack-ruby-bot-server#75 @ioquatix How does one do timers in async correctly? What's the equivalent of this: class Foobar
include Celluloid
def whatever
every 10 do
# timers
end
end
end |
Async actually uses the same require 'async/reactor'
Async::Reactor.run do |task|
while true
task.sleep(2)
puts "Hello World"
end
end |
slack-ruby/slack-shellbot#11, and https://shell.playplay.io/ is running with this, lets see how it behaves over the next few days |
That ping thread is still noticing disconnects, so I want to put the ping thread back to working and then we can debug what's going on. |
The following is a thread-safe way to stop a reactor: thread = Thread.new do
@reactor = Async::Reactor.new
@reactor.run(&block)
end
@reactor.stop
thread.join It's safe to call from anywhere. With respect to your specific question, you probably don't want to kill the reactor unless you can guarantee you created it at the very top level (otherwise you will end up potentially stopping other unrelated tasks. In this specific case, you are fine to do that since you are creating it at the top of a new thread. If you weren't doing that, you need to capture the task, and call def start_async(client, task: ::Async::Task.current)
task.async do
client.run_loop
end
end
# Elsewhere:
task = start_async(client)
# To stop:
task.stop |
In theory, this also works: def start_async(client)
::Async::Reactor.run do
client.run_loop
end
end
# Elsewhere:
task = start_async(client)
# To stop:
task.stop # This might actually be the reactor if none was created, because the operation blocked, calling stop is a no-op, but it's still valid to do it. |
How does the life-cycle of Firstly, it's a bit confusing since it's not a socket, is it? Then, it has several methods:
I can sort of see why we have Where exactly should we call |
You can see slack-ruby/slack-ruby-bot-server#75 for the changes I made to the ping thread, seems to work OK. |
You're right that Slack::RealTime::Socket is not a socket, it's a connection. It's probably an artifact of the past because it represents a websocket. It can exist in two ways, synchronously and asynchronously, so there's a bit of confusion between the two "modes" I think. You either I am not sure why or where we want to stop the reactor explicitly, do we? |
e6f4509
to
6df313c
Compare
0250cd0
to
694b574
Compare
I'm merging this as is. |
Closes #210.
Seems to work for the example, but integration tests not so much. They are green because we don't run integration tests with a slack token to avoid exposing it in PRs. So WIP.