lita-external 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6b242b5e5003c4c337de082c0d5fb4ae54b2cf8d
4
- data.tar.gz: ecce7cc2ffb2b52974c357bc52475cbd82b045d6
3
+ metadata.gz: a1c2d9d25c29abacfddd161ede9780063507d99f
4
+ data.tar.gz: fde3992420ea774d4d51f3d741554559818520b5
5
5
  SHA512:
6
- metadata.gz: 1a28dbd1270f68c5c2dfd043498a8da6b42f6ef9f8b619628ccc8df4c6a838152380748606aaef5d5e3fdcb14372df9edd5ed2ab669c6b9724132e64fca81c79
7
- data.tar.gz: 9c50803803e0bbc5fcc620f7ae39778ec8951877a7abfe378d8cadba62775e190311f8ab6651c4ab1cd254af620ad789c20702a673568b4ab03022284123f4cc
6
+ metadata.gz: 5e39ea973afe6caf81d1db432dbc5bbae5cbfbd45ddf3f0a2c7160bb023aca346c699cefd46bbe71f6bc304ec5cd6f25297655a6616d137865adad278b18258f
7
+ data.tar.gz: 9b9de40182114352af9239552956afa65fddce072cf7660409aac95aaee6dba6cf11ef850cd07ed44857ab63bcccaebaf05f6f821e77d967ea61db833dc3ee9d
data/README.md CHANGED
@@ -24,6 +24,55 @@ Or install it yourself as:
24
24
 
25
25
  TODO: Write usage instructions here
26
26
 
27
+ ## How it works
28
+
29
+ ### Context
30
+
31
+ We are are very happy with Lita, and we are using it very extensively. So much that per moment it receive too much traffic and end up being limited by the CPU (parsing JSON webhooks mostly).
32
+
33
+ When this happen, HTTP and Chat requests start to be queued and Lita becomes unresponsive. So much that a simple ping can sometimes takes minutes.
34
+
35
+ ### Proof of concept
36
+
37
+ As a PoC I implemented https://github.com/Shopify/lita-external. It's basically an abstract adapter that uses 2 Redis queues as communication mechanism.
38
+
39
+ Here's what it looks like conceptually:
40
+
41
+ ```
42
+ +------------+
43
+ | +-------------+
44
+ +-------> Worker 2 | |
45
+ | | <--------+ |
46
+ | +------------+ | |
47
+ | | |
48
+ +----+--+ +------------+ +----+----v----+ +-------------+ +------------+
49
+ | | | +---> +----> | | Chat |
50
+ | Nginx +----> Worker 1 | | Redis | | Master <----> Service |
51
+ | | | <---+ <----+ | | |
52
+ +-------+ +------------+ +--------------+ +-------------+ +------------+
53
+
54
+ ```
55
+
56
+ - All the workers accepts HTTP requests
57
+ - Technically, the master also accept HTTP, but we simply don't send anything to it.
58
+ - We can add as many workers as we want, on multiple servers if needed.
59
+ - Incomming chat messages are serialized with `Marshal` and pushed in `lita:messages:inbound`
60
+ - All the workers maintain a `BLPOP` on `lita:messages:inbound`. When they are dispatched a message they process it in a thread pool.
61
+ - When workers need to send a chat message (or change topic or whatever), they push it in `lita:messages:outbound`.
62
+ - The master maintain a `BLPOP` on `lita:messages:outbound`, and simply send them to the chat service.
63
+
64
+ ### Status
65
+
66
+ Since very recently we are running `lita-external` in production, without any problems so far (again it's very recent).
67
+
68
+ ### Additional benefits
69
+
70
+ Beyond giving us more CPU capacity and horizontal capacity, it also allow give us:
71
+
72
+ - Multi server capability which is not enough but a requirement for high availability of Lita. (It also require Redis failover and master election)
73
+ - Zero downtime deploys. We can now restart lita without droping HTTP requests. We still have a very small chat downtime when restarting the master but that's more acceptable.
74
+
75
+
27
76
  ## Development
28
77
 
29
78
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -1,3 +1,5 @@
1
+ require 'puma/thread_pool'
2
+
1
3
  module Lita
2
4
  module Adapters
3
5
  class External < Adapter
@@ -32,7 +34,14 @@ module Lita
32
34
  end
33
35
  rescue => error
34
36
  Lita.logger.error("Inbound message failed: #{error.class}: #{error.message}")
35
- Lita.config.robot.error_handler(error)
37
+ if Lita.config.robot.error_handler
38
+ case Lita.config.robot.error_handler.arity
39
+ when 1, -1
40
+ Lita.config.robot.error_handler.call(error)
41
+ when 2, -2
42
+ Lita.config.robot.error_handler.call(error, {})
43
+ end
44
+ end
36
45
  end
37
46
  end
38
47
  end
@@ -1,6 +1,16 @@
1
1
  module Lita
2
2
  module External
3
+ class ::Lita::Robot
4
+ def run(&block)
5
+ run_app
6
+ adapter.run(&block)
7
+ rescue Interrupt
8
+ shut_down
9
+ end
10
+ end
11
+
3
12
  class Robot < ::Lita::Robot
13
+ CommandFailed = Class.new(StandardError)
4
14
 
5
15
  class Ballot
6
16
  attr_accessor :veto
@@ -25,8 +35,9 @@ module Lita
25
35
 
26
36
  trigger(:master_loaded)
27
37
 
28
- watch_outbound_queue
29
- super
38
+ super do
39
+ watch_outbound_queue
40
+ end
30
41
  end
31
42
 
32
43
  def shut_down
@@ -39,24 +50,30 @@ module Lita
39
50
  Lita.logger.info("Watching outbound queue")
40
51
  until @stopping
41
52
  begin
42
- if command = External.blocking_redis.blpop('messages:outbound', timeout: 1)
43
- process_outbound_command(command.last)
53
+ if payload = External.blocking_redis.blpop('messages:outbound', timeout: 1)
54
+ command, args = Marshal.load(payload.last)
55
+ Lita.logger.debug("Triggering #{command}")
56
+ begin
57
+ adapter.public_send(command, *args)
58
+ rescue RuntimeError => error
59
+ raise CommandFailed, "#{command}(#{args.map(&:inspect).join(', ')}) failed because: #{error.message}"
60
+ end
44
61
  end
45
62
  rescue => error
46
63
  Lita.logger.error("Outbound message failed: #{error.class}: #{error.message}")
64
+ Lita.logger.debug { "Outbound message failed: command=#{command} args=#{args.inspect}" }
47
65
  if Lita.config.robot.error_handler
48
- Lita.config.robot.error_handler.call(error)
66
+ case Lita.config.robot.error_handler.arity
67
+ when 1, -1
68
+ Lita.config.robot.error_handler.call(error)
69
+ when 2, -2
70
+ Lita.config.robot.error_handler.call(error, {})
71
+ end
49
72
  end
50
73
  end
51
74
  end
52
75
  end
53
76
  end
54
-
55
- def process_outbound_command(payload)
56
- command, args = Marshal.load(payload)
57
- Lita.logger.debug("Triggering #{command}")
58
- adapter.public_send(command, *args)
59
- end
60
77
  end
61
78
  end
62
79
  end
@@ -1,5 +1,5 @@
1
1
  module Lita
2
2
  module External
3
- VERSION = "0.1.1"
3
+ VERSION = "0.1.2"
4
4
  end
5
5
  end
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
19
19
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
20
  spec.require_paths = ["lib"]
21
21
 
22
- spec.add_dependency 'lita', '~> 4.6'
22
+ spec.add_dependency 'lita', '>= 4.7'
23
23
 
24
24
  spec.add_development_dependency "bundler", "~> 1.10"
25
25
  spec.add_development_dependency "rake", "~> 10.0"
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lita-external
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jean Boussier
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2015-11-13 00:00:00.000000000 Z
11
+ date: 2018-10-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lita
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '4.6'
19
+ version: '4.7'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '4.6'
26
+ version: '4.7'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -111,7 +111,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
111
111
  version: '0'
112
112
  requirements: []
113
113
  rubyforge_project:
114
- rubygems_version: 2.4.6
114
+ rubygems_version: 2.6.14
115
115
  signing_key:
116
116
  specification_version: 4
117
117
  summary: Meta Lita adapter that use a redis queue