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

Revert #93, bring back the ping worker. #103

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .rubocop_todo.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2019-03-23 14:16:02 -0400 using RuboCop version 0.58.2.
# on 2019-05-17 17:30:35 -0400 using RuboCop version 0.58.2.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
Expand All @@ -16,6 +16,11 @@ Layout/IndentHeredoc:
- 'sample_apps/sample_app_activerecord/commands/help.rb'
- 'sample_apps/sample_app_mongoid/commands/help.rb'

# Offense count: 1
Lint/HandleExceptions:
Exclude:
- 'lib/slack-ruby-bot-server/ping.rb'

# Offense count: 3
# Configuration parameters: Blacklist.
# Blacklist: (?-mix:(^|\s)(EO[A-Z]{1}|END)(\s|$))
Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,22 @@ SlackRubyBotServer::Service.instance # MyService
SlackRubyBotServer::Service.instance.url # https://www.example.com
```

#### Ping Worker

Each `SlackRubyBotServer::Server` instance will start a ping worker that will periodically check the online status of the bot via the Slack web [auth.test](https://api.slack.com/methods/auth.test) and [users.getPresence](https://api.slack.com/methods/users.getPresence) APIs, and will forcefully close the connection if the bot goes offline, causing an automatic reconnect.

You can configure the ping interval, number of retries before restart, and disable the ping worker entirely.

```ruby
SlackRubyBotServer.configure do |config|
config.ping = {
enabled: true, # set to false to disable the ping worker
ping_interval: 30, # interval in seconds
retry_count: 3 # number of unsuccessful retries until a restart
}
end
```

### Access Tokens

By default the implementation of [Team](lib/slack-ruby-bot-server/models/team) stores a `bot_access_token` as `token` that grants a certain amount of privileges to the bot user as described in [Slack OAuth Docs](https://api.slack.com/docs/oauth) along with `activated_user_access_token` that represents the token of the installing user. You may not want a bot user at all, or may require different auth scopes, such as `users.profile:read` to access user profile information via `Slack::Web::Client#users_profile_get`. To change required scopes make the following changes.
Expand Down
1 change: 1 addition & 0 deletions lib/slack-ruby-bot-server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
require 'slack-ruby-bot'
require 'slack-ruby-bot-server/service'
require 'slack-ruby-bot-server/server'
require 'slack-ruby-bot-server/ping'
require 'slack-ruby-bot-server/config'

require 'slack-ruby-bot-server/ext'
Expand Down
2 changes: 2 additions & 0 deletions lib/slack-ruby-bot-server/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ module Config

attr_accessor :server_class
attr_accessor :service_class
attr_accessor :ping
attr_accessor :database_adapter

def reset!
self.ping = nil
self.server_class = SlackRubyBotServer::Server
self.service_class = SlackRubyBotServer::Service
self.database_adapter = if defined?(::Mongoid)
Expand Down
149 changes: 149 additions & 0 deletions lib/slack-ruby-bot-server/ping.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
require 'active_support/core_ext/string/filters'

module SlackRubyBotServer
class Ping
attr_reader :client
attr_reader :options
attr_reader :error_count

def initialize(client, options = {})
@options = options
@client = client
@error_count = 0
end

def start!
::Async::Reactor.run do |task|
run(task)
end
end

private

def run(task)
logger.debug "PING: #{owner}, every #{ping_interval} second(s)"
loop do
task.sleep ping_interval
break unless check!
end
logger.debug "PING: #{owner}, done."
rescue StandardError => e
logger.error e
raise e
end

def check!
if online?
@error_count = 0
true
else
down!
end
rescue StandardError => e
case e.message
when 'account_inactive', 'invalid_auth' then
logger.warn "Error pinging team #{owner.id}: #{e.message}, terminating."
false
else
message = e.message.truncate(24, separator: "\n", omission: '...')
logger.warn "Error pinging team #{owner.id}: #{message}."
true
end
end

def offline?
!online?
end

def online?
presence.online
end

def presence
owner.ping![:presence]
end

def down!
logger.warn "DOWN: #{owner}, #{retries_left} #{retries_left == 1 ? 'retry' : 'retries'} left"
@error_count += 1
return true if retries_left?
restart!
end

def restart!
logger.warn "RESTART: #{owner}"
close_driver
emit_close
close_connection
false
rescue StandardError => e
logger.warn "Error restarting team #{owner.id}: #{e.message}."
true
end

def close_connection
return unless connection
connection.close
rescue Async::Wrapper::Cancelled
# ignore, from connection.close
rescue StandardError => e
logger.warn "Error closing connection for #{owner.id}: #{e.message}."
raise e
end

def close_driver
return unless driver
driver.close
rescue StandardError => e
logger.warn "Error closing driver for #{owner.id}: #{e.message}."
raise e
end

def emit_close
return unless driver
driver.emit(:close, WebSocket::Driver::CloseEvent.new(1001, 'bot offline'))
rescue StandardError => e
logger.warn "Error sending :close event to driver for #{owner.id}: #{e.message}."
raise e
end

def ping_interval
options[:ping_interval] || 60
end

def retries_left?
retry_count - error_count >= 0
end

def retries_left
[0, retry_count - error_count].max
end

def retry_count
options[:retry_count] || 2
end

def socket
client.instance_variable_get(:@socket) if client
end

def driver
socket.instance_variable_get(:@driver) if socket
end

def connection
driver.instance_variable_get(:@socket) if driver
end

def logger
@logger ||= begin
STDOUT.sync = true
Logger.new(STDOUT)
end
end

def owner
client.owner
end
end
end
12 changes: 12 additions & 0 deletions lib/slack-ruby-bot-server/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class Server < SlackRubyBot::Server
def initialize(attrs = {})
attrs = attrs.dup
@team = attrs.delete(:team)
@ping_options = attrs.delete(:ping) || {}
raise 'Missing team' unless @team
attrs[:token] = @team.token
super(attrs)
Expand All @@ -23,8 +24,19 @@ def restart!(wait = 1)

private

attr_reader :ping_options

def create_ping
return unless !ping_options.key?(:enabled) || ping_options[:enabled]
SlackRubyBotServer::Ping.new(client, ping_options)
end

def open!
client.owner = team
client.on :open do |_event|
worker = create_ping
worker.start! if worker
end
end
end
end
1 change: 1 addition & 0 deletions lib/slack-ruby-bot-server/service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def start!(team)
run_callbacks :starting, team
logger.info "Starting team #{team}."
options = { team: team }
options[:ping] = SlackRubyBotServer::Config.ping if SlackRubyBotServer::Config.ping
server = SlackRubyBotServer::Config.server_class.new(options)
start_server! team, server
run_callbacks :started, team
Expand Down
2 changes: 1 addition & 1 deletion slack-ruby-bot-server.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Gem::Specification.new do |spec|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec)/}) }
spec.require_paths = ['lib']

spec.add_dependency 'async-websocket'
spec.add_dependency 'async-websocket', '~> 0.8.0'
spec.add_dependency 'foreman'
spec.add_dependency 'grape'
spec.add_dependency 'grape-roar', '>= 0.4.0'
Expand Down
Loading