toiler 0.1.5 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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