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
@@ -1,8 +0,0 @@
1
- require 'bundler/gem_tasks'
2
- require 'rspec/core/rake_task'
3
-
4
- RSpec::Core::RakeTask.new(:spec) do |spec|
5
- spec.pattern = 'spec/**/*_spec.rb'
6
- spec.rspec_opts = ['--color --format documentation']
7
- end
8
- task default: :spec
@@ -1,18 +0,0 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
-
5
- Gem::Specification.new do |spec|
6
- spec.name = 'celluloid-task-pooledfiber'
7
- spec.version = '0.2.0'
8
- spec.authors = ['Chris Heald']
9
- spec.email = ['cheald@gmail.com']
10
-
11
- spec.summary = 'An alternate Task implementation for Celluloid which improves performance by reusing a pool of fibers to run tasks'
12
- spec.description = 'An alternate Task implementation for Celluloid which improves performance by reusing a pool of fibers to run tasks'
13
- spec.homepage = 'http://github.com/cheald/celluloid-task-pooledfiber'
14
- spec.license = 'MIT'
15
-
16
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|examples|spec|features)/}) }
17
- spec.require_paths = ['lib']
18
- end
@@ -1,26 +0,0 @@
1
- require 'celluloid' unless defined? Celluloid
2
- require 'thread'
3
-
4
- require 'celluloid/util/fiber_pool'
5
-
6
- module Celluloid
7
- class Task
8
- # Tasks with a pooled Fiber backend
9
- class PooledFiber < Fibered
10
- def self.fiber_pool
11
- @fiber_pool ||= Util::FiberPool.new
12
- end
13
-
14
- def create(&block)
15
- queue = Thread.current[:celluloid_queue]
16
- actor_system = Thread.current[:celluloid_actor_system]
17
- @fiber = PooledFiber.fiber_pool.acquire do
18
- Thread.current[:celluloid_role] = :actor
19
- Thread.current[:celluloid_queue] = queue
20
- Thread.current[:celluloid_actor_system] = actor_system
21
- block.call
22
- end
23
- end
24
- end
25
- end
26
- end
@@ -1,95 +0,0 @@
1
- module Celluloid
2
- module Util
3
- class FiberPool
4
- attr_accessor :stats
5
-
6
- def initialize(trim_size = 64)
7
- @trim_size = trim_size
8
- @pool = {}
9
- @stats = { created: 0, acquired: 0, trimmed: 0, sweep_counter: 0, terminated: 0, terminated_threads: 0, sweeps: 0 }
10
- @mutex = Mutex.new
11
- end
12
-
13
- def acquire(&block)
14
- trim
15
- sweep
16
- @stats[:acquired] += 1
17
- fiber = fiber_pool.shift || create_fiber
18
- fiber.resume(block)
19
- fiber
20
- end
21
-
22
- private
23
-
24
- def create_fiber
25
- pool = fiber_pool
26
- @stats[:created] += 1
27
- Fiber.new do |blk|
28
- loop do
29
- # At this point, we have a pooled fiber ready for Celluloid to call #resume on.
30
- Fiber.yield
31
-
32
- # Once Celluloid resumes the fiber, we need to execute the block the task was
33
- # created with. This may call suspend/resume; that's fine.
34
- blk.call
35
-
36
- # Once Celluloid completes the task, we release this Fiber back to the pool
37
- pool << Fiber.current
38
-
39
- # ...and yield for the next #acquire call.
40
- blk = Fiber.yield
41
- break if blk == :terminate
42
- end
43
- end
44
- end
45
-
46
- # If the fiber pool has grown too much, shift off and discard
47
- # extra fibers so they may be GC'd. This isn't ideal, but it
48
- # should guard against runaway resource consumption.
49
- def trim
50
- pool = fiber_pool
51
- while pool.length > @trim_size
52
- @stats[:trimmed] += 1
53
- pool.shift.resume(:terminate)
54
- end
55
- end
56
-
57
- def sweep
58
- @mutex.synchronize do
59
- @stats[:sweep_counter] += 1
60
- if @stats[:sweep_counter] > 10_000
61
- alive = []
62
- Thread.list.each do |thread|
63
- alive << thread.object_id if thread.alive? && @pool.key?(thread.object_id)
64
- end
65
-
66
- (@pool.keys - alive).each do |thread_id|
67
- @pool[thread_id].each do |_fiber|
68
- @stats[:terminated] += 1
69
- # We can't resume the fiber here because we might resume cross-thread
70
- # TODO: How do we deal with alive fibers in a dead thread?
71
- # fiber.resume(:terminate)
72
- end
73
- @stats[:terminated_threads] += 1
74
- @pool.delete thread_id
75
- end
76
- @stats[:sweep_counter] = 0
77
- @stats[:sweeps] += 1
78
- end
79
- end
80
- end
81
-
82
- # Fiber pool for this thread. Fibers can't cross threads, so we have to maintain a
83
- # pool per thread.
84
- #
85
- # We keep our pool in an instance variable rather than in thread locals so that we
86
- # can sweep out old Fibers from dead threads. This keeps live Fiber instances in
87
- # a thread local from keeping the thread from being GC'd.
88
- def fiber_pool
89
- @mutex.synchronize do
90
- @pool[Thread.current.object_id] ||= []
91
- end
92
- end
93
- end
94
- end
95
- end
@@ -1,5 +0,0 @@
1
- require 'spec_helper'
2
-
3
- RSpec.describe Celluloid::Task::PooledFiber, actor_system: :within do
4
- it_behaves_like 'a Celluloid Task'
5
- end
@@ -1,60 +0,0 @@
1
- $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
-
3
- require 'rubygems'
4
- require 'bundler/setup'
5
-
6
- require 'celluloid/test'
7
- require 'celluloid/rspec'
8
-
9
- require 'celluloid/tasks/pooled_fiber'
10
-
11
- $CELLULOID_DEBUG = true
12
-
13
- require 'celluloid/probe'
14
-
15
- Dir['./spec/support/*.rb'].map { |f| require f }
16
-
17
- RSpec.configure do |config|
18
- config.filter_run focus: true
19
- config.run_all_when_everything_filtered = true
20
-
21
- config.around do |ex|
22
- Celluloid.actor_system = nil
23
- Thread.list.each do |thread|
24
- next if thread == Thread.current
25
- if defined?(JRUBY_VERSION)
26
- # Avoid disrupting jRuby's "fiber" threads.
27
- next if /Fiber/ =~ thread.to_java.getNativeThread.get_name
28
- end
29
- thread.kill
30
- end
31
-
32
- ex.run
33
- end
34
-
35
- config.around actor_system: :global do |ex|
36
- Celluloid.boot
37
- ex.run
38
- Celluloid.shutdown
39
- end
40
-
41
- config.around actor_system: :within do |ex|
42
- Celluloid::ActorSystem.new.within do
43
- ex.run
44
- end
45
- end
46
-
47
- config.mock_with :rspec do |mocks|
48
- mocks.verify_doubled_constant_names = true
49
- mocks.verify_partial_doubles = true
50
- end
51
-
52
- config.around(:each) do |example|
53
- example.run
54
- end
55
-
56
- # Must be *after* the around hook above
57
- require 'rspec/retry'
58
- config.verbose_retry = true
59
- config.default_sleep_interval = 3
60
- end
@@ -1,49 +0,0 @@
1
- class MockActor
2
- attr_reader :tasks
3
-
4
- def initialize
5
- @tasks = []
6
- end
7
-
8
- def setup_thread
9
- end
10
- end
11
-
12
- RSpec.shared_examples 'a Celluloid Task' do
13
- let(:task_type) { :foobar }
14
- let(:suspend_state) { :doing_something }
15
- let(:actor) { MockActor.new }
16
-
17
- subject { Celluloid.task_class.new(task_type, {}) { Celluloid::Task.suspend(suspend_state) } }
18
-
19
- before :each do
20
- Thread.current[:celluloid_actor_system] = Celluloid.actor_system
21
- Thread.current[:celluloid_actor] = actor
22
- end
23
-
24
- after :each do
25
- Thread.current[:celluloid_actor] = nil
26
- Thread.current[:celluloid_actor_system] = nil
27
- end
28
-
29
- it 'begins with status :new' do
30
- expect(subject.status).to be :new
31
- end
32
-
33
- it 'resumes' do
34
- expect(subject).to be_running
35
- subject.resume
36
- expect(subject.status).to eq(suspend_state)
37
- subject.resume
38
- expect(subject).not_to be_running
39
- end
40
-
41
- it 'raises exceptions outside' do
42
- task = Celluloid.task_class.new(task_type, {}) do
43
- fail 'failure'
44
- end
45
- expect do
46
- task.resume
47
- end.to raise_exception('failure')
48
- end
49
- end
@@ -1,47 +0,0 @@
1
- begin
2
- require 'active_support/core_ext/hash/keys'
3
- require 'active_support/core_ext/hash/deep_merge'
4
- rescue LoadError
5
- class Hash
6
- def stringify_keys
7
- each_key do |key|
8
- self[key.to_s] = delete(key)
9
- end
10
- self
11
- end unless {}.respond_to?(:stringify_keys)
12
-
13
- def symbolize_keys
14
- each_key do |key|
15
- self[(key.to_sym rescue key) || key] = delete(key)
16
- end
17
- self
18
- end unless {}.respond_to?(:symbolize_keys)
19
-
20
- def deep_symbolize_keys
21
- each_key do |key|
22
- value = delete(key)
23
- self[(key.to_sym rescue key) || key] = value
24
-
25
- value.deep_symbolize_keys if value.is_a? Hash
26
- end
27
- self
28
- end unless {}.respond_to?(:deep_symbolize_keys)
29
- end
30
- end
31
-
32
- begin
33
- require 'active_support/core_ext/string/inflections'
34
- rescue LoadError
35
- class String
36
- def constantize
37
- names = split('::')
38
- names.shift if names.empty? || names.first.empty?
39
-
40
- constant = Object
41
- names.each do |name|
42
- constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
43
- end
44
- constant
45
- end
46
- end unless ''.respond_to?(:constantize)
47
- end
@@ -1,82 +0,0 @@
1
- require 'erb'
2
- require 'yaml'
3
-
4
- module Toiler
5
- class EnvironmentLoader
6
- attr_reader :options
7
-
8
- def self.load(options)
9
- new(options).load
10
- end
11
-
12
- def self.load_for_rails_console
13
- load(config_file: (Rails.root + 'config' + 'toiler.yml'))
14
- end
15
-
16
- def initialize(options)
17
- @options = options
18
- end
19
-
20
- def load
21
- initialize_logger
22
- load_rails if options[:rails]
23
- require_workers if options[:require]
24
- Toiler.options.merge!(config_file_options)
25
- Toiler.options.merge!(options)
26
- initialize_aws
27
- end
28
-
29
- private
30
-
31
- def config_file_options
32
- if (path = options[:config_file])
33
- unless File.exist?(path)
34
- Toiler.logger.warn "Config file #{path} does not exist"
35
- path = nil
36
- end
37
- end
38
-
39
- return {} unless path
40
-
41
- YAML.load(ERB.new(IO.read(path)).result).deep_symbolize_keys
42
- end
43
-
44
- def initialize_aws
45
- # aws-sdk tries to load the credentials from the ENV variables: AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
46
- # when not explicit supplied
47
- fail 'AWS Credentials needed!' if Toiler.options[:aws].empty? && (ENV['AWS_ACCESS_KEY_ID'].nil? || ENV['AWS_SECRET_ACCESS_KEY'].nil?)
48
- return if Toiler.options[:aws].empty?
49
-
50
- ::Aws.config[:region] = Toiler.options[:aws][:region]
51
- ::Aws.config[:credentials] = ::Aws::Credentials.new Toiler.options[:aws][:access_key_id], Toiler.options[:aws][:secret_access_key]
52
- end
53
-
54
- def initialize_logger
55
- Toiler::Logging.initialize_logger(options[:logfile]) if options[:logfile]
56
- Toiler.logger.level = Logger::DEBUG if options[:verbose]
57
- end
58
-
59
- def load_rails
60
- # Adapted from: https://github.com/mperham/sidekiq/blob/master/lib/sidekiq/cli.rb
61
-
62
- require 'rails'
63
- if ::Rails::VERSION::MAJOR < 4
64
- require File.expand_path('config/environment.rb')
65
- ::Rails.application.eager_load!
66
- else
67
- # Painful contortions, see 1791 for discussion
68
- require File.expand_path('config/application.rb')
69
- ::Rails::Application.initializer 'toiler.eager_load' do
70
- ::Rails.application.config.eager_load = true
71
- end
72
- require File.expand_path('config/environment.rb')
73
- end
74
-
75
- Toiler.logger.info 'Rails environment loaded'
76
- end
77
-
78
- def require_workers
79
- require options[:require]
80
- end
81
- end
82
- end
@@ -1,56 +0,0 @@
1
- module Toiler
2
- class Fetcher
3
- include Celluloid
4
- include Celluloid::Internals::Logger
5
-
6
- FETCH_LIMIT = 10.freeze
7
-
8
- attr_accessor :queue, :wait, :batch, :condition
9
-
10
- finalizer :shutdown
11
-
12
- def initialize(queue, client)
13
- debug "Initializing Fetcher for queue #{queue}..."
14
- @condition = Celluloid::Condition.new
15
- @queue = Queue.new queue, client
16
- @wait = Toiler.options[:wait] || 20
17
- @batch = Toiler.worker_class_registry[queue].batch?
18
- async.poll_messages
19
- debug "Finished initializing Fetcher for queue #{queue}"
20
- end
21
-
22
- def shutdown
23
- debug "Fetcher #{queue.name} shutting down..."
24
- instance_variables.each { |iv| remove_instance_variable iv }
25
- end
26
-
27
- def processor_finished
28
- @condition.broadcast
29
- end
30
-
31
- def wait_for_available_processors
32
- manager = Toiler.manager
33
- @condition.wait if manager.dead? || manager.free_processors(queue) == 0
34
- end
35
-
36
- def poll_messages
37
- # AWS limits the batch size by 10
38
- options = {
39
- message_attribute_names: %w(All),
40
- wait_time_seconds: wait
41
- }
42
-
43
- loop do
44
- while (count = Toiler.manager.free_processors(queue.name)) == 0
45
- wait_for_available_processors
46
- end
47
- options[:max_number_of_messages] = (batch || count > FETCH_LIMIT) ? FETCH_LIMIT : count
48
- debug "Fetcher #{queue.name} retreiving messages with options: #{options.inspect}..."
49
- msgs = queue.receive_messages options
50
- debug "Fetcher #{queue.name} retreived #{msgs.count} messages..."
51
- next if msgs.empty?
52
- Toiler.manager.assign_messages queue.name, msgs
53
- end
54
- end
55
- end
56
- end