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.
- checksums.yaml +4 -4
- data/.ruby-version +1 -0
- data/Gemfile +7 -4
- data/Gemfile.lock +26 -57
- data/README.md +15 -0
- data/lib/toiler.rb +15 -44
- data/lib/toiler/actor/fetcher.rb +89 -0
- data/lib/toiler/actor/processor.rb +106 -0
- data/lib/toiler/actor/supervisor.rb +45 -0
- data/lib/toiler/actor/utils/actor_logging.rb +28 -0
- data/lib/toiler/aws/message.rb +64 -0
- data/lib/toiler/aws/queue.rb +61 -0
- data/lib/toiler/cli.rb +67 -94
- data/lib/toiler/utils/argument_parser.rb +50 -0
- data/lib/toiler/utils/environment_loader.rb +104 -0
- data/lib/toiler/utils/logging.rb +37 -0
- data/lib/toiler/version.rb +2 -1
- data/lib/toiler/worker.rb +35 -15
- data/toiler.gemspec +4 -4
- metadata +32 -32
- data/celluloid-task-pooledfiber/.gitignore +0 -9
- data/celluloid-task-pooledfiber/.rspec +0 -2
- data/celluloid-task-pooledfiber/.travis.yml +0 -5
- data/celluloid-task-pooledfiber/Gemfile +0 -11
- data/celluloid-task-pooledfiber/LICENSE.txt +0 -21
- data/celluloid-task-pooledfiber/README.md +0 -37
- data/celluloid-task-pooledfiber/Rakefile +0 -8
- data/celluloid-task-pooledfiber/celluloid-task-pooled-fiber.gemspec +0 -18
- data/celluloid-task-pooledfiber/lib/celluloid/task/pooled_fiber.rb +0 -26
- data/celluloid-task-pooledfiber/lib/celluloid/util/fiber_pool.rb +0 -95
- data/celluloid-task-pooledfiber/spec/celluloid/tasks/pooled_fiber_spec.rb +0 -5
- data/celluloid-task-pooledfiber/spec/spec_helper.rb +0 -60
- data/celluloid-task-pooledfiber/spec/support/shared_examples_for_task.rb +0 -49
- data/lib/toiler/core_ext.rb +0 -47
- data/lib/toiler/environment_loader.rb +0 -82
- data/lib/toiler/fetcher.rb +0 -56
- data/lib/toiler/logging.rb +0 -42
- data/lib/toiler/manager.rb +0 -71
- data/lib/toiler/message.rb +0 -60
- data/lib/toiler/processor.rb +0 -86
- data/lib/toiler/queue.rb +0 -53
- data/lib/toiler/scheduler.rb +0 -16
- data/lib/toiler/supervisor.rb +0 -66
@@ -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,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
|
data/lib/toiler/core_ext.rb
DELETED
@@ -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
|
data/lib/toiler/fetcher.rb
DELETED
@@ -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
|