toiler 0.1.5 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -0
  3. data/Gemfile +7 -4
  4. data/Gemfile.lock +26 -57
  5. data/README.md +15 -0
  6. data/lib/toiler.rb +15 -44
  7. data/lib/toiler/actor/fetcher.rb +89 -0
  8. data/lib/toiler/actor/processor.rb +106 -0
  9. data/lib/toiler/actor/supervisor.rb +45 -0
  10. data/lib/toiler/actor/utils/actor_logging.rb +28 -0
  11. data/lib/toiler/aws/message.rb +64 -0
  12. data/lib/toiler/aws/queue.rb +61 -0
  13. data/lib/toiler/cli.rb +67 -94
  14. data/lib/toiler/utils/argument_parser.rb +50 -0
  15. data/lib/toiler/utils/environment_loader.rb +104 -0
  16. data/lib/toiler/utils/logging.rb +37 -0
  17. data/lib/toiler/version.rb +2 -1
  18. data/lib/toiler/worker.rb +35 -15
  19. data/toiler.gemspec +4 -4
  20. metadata +32 -32
  21. data/celluloid-task-pooledfiber/.gitignore +0 -9
  22. data/celluloid-task-pooledfiber/.rspec +0 -2
  23. data/celluloid-task-pooledfiber/.travis.yml +0 -5
  24. data/celluloid-task-pooledfiber/Gemfile +0 -11
  25. data/celluloid-task-pooledfiber/LICENSE.txt +0 -21
  26. data/celluloid-task-pooledfiber/README.md +0 -37
  27. data/celluloid-task-pooledfiber/Rakefile +0 -8
  28. data/celluloid-task-pooledfiber/celluloid-task-pooled-fiber.gemspec +0 -18
  29. data/celluloid-task-pooledfiber/lib/celluloid/task/pooled_fiber.rb +0 -26
  30. data/celluloid-task-pooledfiber/lib/celluloid/util/fiber_pool.rb +0 -95
  31. data/celluloid-task-pooledfiber/spec/celluloid/tasks/pooled_fiber_spec.rb +0 -5
  32. data/celluloid-task-pooledfiber/spec/spec_helper.rb +0 -60
  33. data/celluloid-task-pooledfiber/spec/support/shared_examples_for_task.rb +0 -49
  34. data/lib/toiler/core_ext.rb +0 -47
  35. data/lib/toiler/environment_loader.rb +0 -82
  36. data/lib/toiler/fetcher.rb +0 -56
  37. data/lib/toiler/logging.rb +0 -42
  38. data/lib/toiler/manager.rb +0 -71
  39. data/lib/toiler/message.rb +0 -60
  40. data/lib/toiler/processor.rb +0 -86
  41. data/lib/toiler/queue.rb +0 -53
  42. data/lib/toiler/scheduler.rb +0 -16
  43. data/lib/toiler/supervisor.rb +0 -66
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3fb0e35e85549aef85080de3be1df4680f745d21
4
- data.tar.gz: da042af1e5dd0afcefca0bb3818cca82218ecb19
3
+ metadata.gz: 7ae93426e47a18dc09639561ed7587a92edf718d
4
+ data.tar.gz: 4642b9ecc2357810f894f816764df5f5ebe78680
5
5
  SHA512:
6
- metadata.gz: 7d8e30edeabdcb59975e20b5089db2fde3aaeec146cad0e039828f053039e6ee4b10df4a926783bc92e867ec8ccc903968719eb9988dd72f1a32de95eeae088f
7
- data.tar.gz: 0ccafdfa4ece45209397679734eef81fe5021ca9223a997548ecb1b6438b8ab7b713fe31d4f0f45b2b75b397eae2fdb5c59bf5a880de10588663db06df5e6e70
6
+ metadata.gz: 3b047fdb8cce9cee315f7ef58646a0a64724591b6949d6f6a4201d41481a0b0f3a3f76ee8d61ffcaf5b32e5e1de4e93166eff56f474e1e5f4410fc1cf382538b
7
+ data.tar.gz: 22a1f742e648f17b0c7051f6372f90ca7a619756bcd2bf681344a453456fc11b36c5a06855ef6b06f5a499d2efbea342fd36be29e0c8ea6cd39b0bd72aec403a
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.2.2
data/Gemfile CHANGED
@@ -1,7 +1,10 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- # gem 'celluloid'
4
- gem 'celluloid', '~> 0.17.pre15'
5
- # gem 'celluloid-task-pooledfiber', github: 'celluloid/celluloid-task-pooledfiber', branch: '0.2.0-prerelease'
6
- # gem 'celluloid-io'
3
+ gem 'concurrent-ruby', '~> 0.9.0'
4
+ gem 'concurrent-ruby-edge', '~> 0.1.0'
7
5
  gem 'aws-sdk'
6
+
7
+
8
+ group :development do
9
+ gem 'rubocop'
10
+ end
data/Gemfile.lock CHANGED
@@ -1,69 +1,38 @@
1
1
  GEM
2
2
  remote: https://rubygems.org/
3
3
  specs:
4
- aws-sdk (2.0.42)
5
- aws-sdk-resources (= 2.0.42)
6
- aws-sdk-core (2.0.42)
7
- builder (~> 3.0)
4
+ ast (2.0.0)
5
+ astrolabe (1.3.0)
6
+ parser (>= 2.2.0.pre.3, < 3.0)
7
+ aws-sdk (2.1.3)
8
+ aws-sdk-resources (= 2.1.3)
9
+ aws-sdk-core (2.1.3)
8
10
  jmespath (~> 1.0)
9
- multi_json (~> 1.0)
10
- aws-sdk-resources (2.0.42)
11
- aws-sdk-core (= 2.0.42)
12
- builder (3.2.2)
13
- celluloid (0.17.0.pre15)
14
- bundler
15
- celluloid-essentials
16
- celluloid-extras
17
- celluloid-fsm
18
- celluloid-pool
19
- celluloid-supervision
20
- dotenv
21
- nenv
22
- rspec-logsplit (>= 0.1.2)
23
- timers (~> 4.0.0)
24
- celluloid-essentials (0.20.0.pre17)
25
- bundler
26
- dotenv
27
- nenv
28
- rspec-logsplit (>= 0.1.2)
29
- timers (~> 4.0.0)
30
- celluloid-extras (0.1.4)
31
- bundler
32
- dotenv
33
- nenv
34
- rspec-logsplit (>= 0.1.2)
35
- timers (~> 4.0.0)
36
- celluloid-fsm (0.9.0.pre13)
37
- bundler
38
- dotenv
39
- nenv
40
- rspec-logsplit (>= 0.1.2)
41
- timers (~> 4.0.0)
42
- celluloid-pool (0.11.0.pre1)
43
- bundler
44
- dotenv
45
- nenv
46
- rspec-logsplit (>= 0.1.2)
47
- timers (~> 4.0.0)
48
- celluloid-supervision (0.20.0.pre6)
49
- bundler
50
- dotenv
51
- nenv
52
- rspec-logsplit (>= 0.1.2)
53
- timers (~> 4.0.0)
54
- dotenv (2.0.1)
55
- hitimes (1.2.2)
11
+ aws-sdk-resources (2.1.3)
12
+ aws-sdk-core (= 2.1.3)
13
+ concurrent-ruby (0.9.0)
14
+ concurrent-ruby-edge (0.1.0)
15
+ concurrent-ruby (~> 0.9.0)
56
16
  jmespath (1.0.2)
57
17
  multi_json (~> 1.0)
58
- multi_json (1.11.0)
59
- nenv (0.2.0)
60
- rspec-logsplit (0.1.3)
61
- timers (4.0.1)
62
- hitimes
18
+ multi_json (1.11.2)
19
+ parser (2.3.0.pre.2)
20
+ ast (>= 1.1, < 3.0)
21
+ powerpack (0.1.1)
22
+ rainbow (2.0.0)
23
+ rubocop (0.32.1)
24
+ astrolabe (~> 1.3)
25
+ parser (>= 2.2.2.5, < 3.0)
26
+ powerpack (~> 0.1)
27
+ rainbow (>= 1.99.1, < 3.0)
28
+ ruby-progressbar (~> 1.4)
29
+ ruby-progressbar (1.7.5)
63
30
 
64
31
  PLATFORMS
65
32
  ruby
66
33
 
67
34
  DEPENDENCIES
68
35
  aws-sdk
69
- celluloid (~> 0.17.pre15)
36
+ concurrent-ruby (~> 0.9.0)
37
+ concurrent-ruby-edge (~> 0.1.0)
38
+ rubocop
data/README.md CHANGED
@@ -8,6 +8,8 @@ a different approach at loadbalancing and uses long-polling.
8
8
  Toiler allows to specify the amount of processors (threads) that should be spawned for each queue.
9
9
  Instead of [shoryuken's](https://github.com/phstc/shoryuken) loadbalancing approach, Toiler delegates this work to the kernel scheduling threads.
10
10
 
11
+ Because Toiler uses threads to provide concurrency, **each thread instatiates a new worker**, as it should be expected, so please **use class variables to store shared variables like clients**.
12
+
11
13
  ###Long-Polling
12
14
  A Fetcher thread is spawned for each queue.
13
15
  Fetchers are resposible for polling SQS and retreiving messages.
@@ -20,6 +22,9 @@ Workers can configure a parser Class or Proc to parse an SQS message body before
20
22
  ###Batches
21
23
  Toiler allows a Worker to be able to receive a batch of messages instead of a single one.
22
24
 
25
+ ###Auto Visibility Extension
26
+ Toiler has the ability to automatically extend the visibility timeout of and SQS message to prevent the message from re-entering the queue if processing of such message is taking longer than the queue's visibility timeout.
27
+
23
28
  ##Instalation
24
29
 
25
30
  Add this line to your application's Gemfile:
@@ -52,7 +57,17 @@ class MyWorker
52
57
  # toiler_options auto_visibility_timeout: true
53
58
  # toiler_options batch: true
54
59
 
60
+ #Example connection client that should be shared across all instances of MyWorker
61
+ @@client = ConnectionClient.new
62
+
63
+ def initialize
64
+ @last_message = nil
65
+ end
66
+
55
67
  def perform(sqs_msg, body)
68
+ #Workers are thread safe, yay!
69
+ #Each worker instance is assured to be processing only one message at a time
70
+ @last_message = sqs_msg
56
71
  puts body
57
72
  end
58
73
  end
data/lib/toiler.rb CHANGED
@@ -1,76 +1,47 @@
1
1
  require 'aws-sdk'
2
- require 'toiler/core_ext'
3
- require 'toiler/message'
4
- require 'toiler/queue'
2
+ require 'toiler/utils/environment_loader'
3
+ require 'toiler/utils/logging'
4
+ require 'toiler/utils/argument_parser'
5
5
  require 'toiler/worker'
6
- require 'toiler/environment_loader'
7
- require 'toiler/logging'
8
6
  require 'toiler/cli'
9
7
  require 'toiler/version'
10
8
 
9
+ # Main module
11
10
  module Toiler
12
- @worker_registry = {}
13
11
  @worker_class_registry = {}
14
12
  @options = {
15
13
  aws: {}
16
14
  }
15
+ @fetchers = {}
16
+ @processor_pools = {}
17
17
 
18
- module_function
18
+ attr_reader :worker_class_registry, :options, :fetchers, :processor_pools
19
+ module_function :worker_class_registry, :options, :fetchers, :processor_pools
19
20
 
20
- def options
21
- @options
22
- end
21
+ module_function
23
22
 
24
23
  def logger
25
- Toiler::Logging.logger
26
- end
27
-
28
- def worker_class_registry
29
- @worker_class_registry
30
- end
31
-
32
- def worker_registry
33
- @worker_registry
24
+ Toiler::Utils::Logging.logger
34
25
  end
35
26
 
36
27
  def queues
37
- @worker_registry.keys
28
+ worker_class_registry.keys
38
29
  end
39
30
 
40
31
  def fetcher(queue)
41
- Celluloid::Actor["fetcher_#{queue}".to_sym]
32
+ fetchers["fetcher_#{queue}".to_sym]
42
33
  end
43
34
 
44
35
  def set_fetcher(queue, val)
45
- Celluloid::Actor["fetcher_#{queue}".to_sym] = val
36
+ fetchers["fetcher_#{queue}".to_sym] = val
46
37
  end
47
38
 
48
39
  def processor_pool(queue)
49
- Celluloid::Actor["processor_pool_#{queue}".to_sym]
40
+ processor_pools["processor_pool_#{queue}".to_sym]
50
41
  end
51
42
 
52
43
  def set_processor_pool(queue, val)
53
- Celluloid::Actor["processor_pool_#{queue}".to_sym] = val
54
- end
55
-
56
- def scheduler(queue)
57
- Celluloid::Actor["scheduler_#{queue}".to_sym]
58
- end
59
-
60
- def manager
61
- Celluloid::Actor[:manager]
62
- end
63
-
64
- def set_manager(val)
65
- Celluloid::Actor[:manager] = val
66
- end
67
-
68
- def timer
69
- Celluloid::Actor[:timer]
70
- end
71
-
72
- def set_timer(val)
73
- Celluloid::Actor[:timer] = val
44
+ processor_pools["processor_pool_#{queue}".to_sym] = val
74
45
  end
75
46
 
76
47
  def default_options
@@ -0,0 +1,89 @@
1
+ require 'toiler/actor/utils/actor_logging'
2
+ require 'toiler/aws/queue'
3
+
4
+ module Toiler
5
+ module Actor
6
+ # Actor polling for messages only when processors are ready, otherwise idle
7
+ class Fetcher < Concurrent::Actor::RestartingContext
8
+ include Utils::ActorLogging
9
+
10
+ FETCH_LIMIT = 10.freeze
11
+
12
+ attr_accessor :queue, :wait, :visibility_timeout, :free_processors,
13
+ :scheduled
14
+
15
+ def initialize(queue, client)
16
+ debug "Initializing Fetcher for queue #{queue}..."
17
+ @queue = Toiler::Aws::Queue.new queue, client
18
+ @wait = Toiler.options[:wait] || 20
19
+ @free_processors = Concurrent::AtomicFixnum.new(0)
20
+ @batch = Toiler.worker_class_registry[queue].batch?
21
+ @visibility_timeout = @queue.visibility_timeout
22
+ @scheduled = Concurrent::AtomicBoolean.new
23
+ debug "Finished initializing Fetcher for queue #{queue}"
24
+ end
25
+
26
+ def default_executor
27
+ Concurrent.global_fast_executor
28
+ end
29
+
30
+ def on_message(msg)
31
+ method, *args = msg
32
+ send(method, *args)
33
+ rescue StandardError => e
34
+ error "Fetcher #{queue.name} raised exception #{e.class}"
35
+ end
36
+
37
+ private
38
+
39
+ def batch?
40
+ @batch
41
+ end
42
+
43
+ def processor_finished
44
+ debug "Fetcher #{queue.name} received processor finished signal..."
45
+ free_processors.increment
46
+ schedule_poll
47
+ end
48
+
49
+ def max_messages
50
+ batch? ? FETCH_LIMIT : [FETCH_LIMIT, free_processors.value].min
51
+ end
52
+
53
+ def poll_future
54
+ Concurrent.future do
55
+ queue.receive_messages message_attribute_names: %w(All),
56
+ wait_time_seconds: wait,
57
+ max_number_of_messages: max_messages
58
+ end
59
+ end
60
+
61
+ def poll_messages
62
+ poll_future.on_completion! do |_success, msgs, _reason|
63
+ scheduled.make_false
64
+ tell [:assign_messages, msgs] unless msgs.nil? || msgs.empty?
65
+ schedule_poll
66
+ end
67
+ end
68
+
69
+ def schedule_poll
70
+ return unless free_processors.value > 0 && scheduled.make_true
71
+ debug "Fetcher #{queue.name} scheduling polling..."
72
+ tell :poll_messages
73
+ end
74
+
75
+ def processor_pool
76
+ @processor_pool ||= Toiler.processor_pool queue.name
77
+ end
78
+
79
+ def assign_messages(messages)
80
+ messages = [messages] if batch?
81
+ messages.each do |m|
82
+ processor_pool.tell [:process, visibility_timeout, m]
83
+ free_processors.decrement
84
+ end
85
+ debug "Fetcher #{queue.name} assigned #{messages.count} messages"
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,106 @@
1
+ require 'json'
2
+ require 'toiler/actor/utils/actor_logging'
3
+
4
+ module Toiler
5
+ module Actor
6
+ # Responsible for processing sqs messages and notifying Fetcher when done
7
+ class Processor < Concurrent::Actor::RestartingContext
8
+ include Utils::ActorLogging
9
+
10
+ attr_accessor :queue, :worker, :fetcher, :body_parser,
11
+ :extend_callback
12
+
13
+ def initialize(queue)
14
+ @queue = queue
15
+ @worker = Toiler.worker_class_registry[queue].new
16
+ @fetcher = Toiler.fetcher queue
17
+ init_options
18
+ processor_finished
19
+ end
20
+
21
+ def default_executor
22
+ Concurrent.global_io_executor
23
+ end
24
+
25
+ def on_message(msg)
26
+ method, *args = msg
27
+ send(method, *args)
28
+ rescue StandardError => e
29
+ error "Processor #{queue} failed processing, reason: #{e.class}"
30
+ raise e
31
+ end
32
+
33
+ private
34
+
35
+ def init_options
36
+ @auto_visibility_timeout = @worker.class.auto_visibility_timeout?
37
+ @auto_delete = @worker.class.auto_delete?
38
+ toiler_options = @worker.class.toiler_options
39
+ @body_parser = toiler_options[:parser]
40
+ @extend_callback = toiler_options[:on_visibility_extend]
41
+ end
42
+
43
+ def auto_visibility_timeout?
44
+ @auto_visibility_timeout
45
+ end
46
+
47
+ def auto_delete?
48
+ @auto_delete
49
+ end
50
+
51
+ def process(visibility, sqs_msg)
52
+ debug "Processor #{queue} begins processing..."
53
+ body = get_body(sqs_msg)
54
+ timer = visibility_extender visibility, sqs_msg, body, &extend_callback
55
+
56
+ debug "Worker #{queue} starts performing..."
57
+ worker.perform sqs_msg, body
58
+ debug "Worker #{queue} finishes performing..."
59
+ sqs_msg.delete if auto_delete?
60
+ ensure
61
+ process_cleanup timer
62
+ end
63
+
64
+ def process_cleanup(timer)
65
+ debug "Processor #{queue} starts cleanup after perform..."
66
+ timer.shutdown if timer
67
+ ::ActiveRecord::Base.clear_active_connections! if defined? ActiveRecord
68
+ processor_finished
69
+ debug "Processor #{queue} finished cleanup after perform..."
70
+ end
71
+
72
+ def processor_finished
73
+ fetcher.tell :processor_finished
74
+ end
75
+
76
+ def visibility_extender(queue_visibility, sqs_msg, body)
77
+ return unless auto_visibility_timeout?
78
+ interval = queue_visibility - 5
79
+ Concurrent::TimerTask.execute execution_interval: interval,
80
+ timeout_interval: interval do
81
+ sqs_msg.visibility_timeout = queue_visibility
82
+ yield sqs_msg, body if block_given?
83
+ end
84
+ end
85
+
86
+ def get_body(sqs_msg)
87
+ if sqs_msg.is_a? Array
88
+ sqs_msg.map { |m| parse_body m }
89
+ else
90
+ parse_body sqs_msg
91
+ end
92
+ end
93
+
94
+ def parse_body(sqs_msg)
95
+ case body_parser
96
+ when :json then JSON.parse sqs_msg.body
97
+ when Proc then body_parser.call sqs_msg
98
+ when :text, nil then sqs_msg.body
99
+ else body_parser.load sqs_msg.body
100
+ end
101
+ rescue => e
102
+ raise "Error parsing the message body: #{e.message}"
103
+ end
104
+ end
105
+ end
106
+ end