geary 0.0.1
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 +7 -0
- data/README.markdown +76 -0
- data/bin/geary +9 -0
- data/lib/gearman/client.rb +80 -0
- data/lib/gearman/connection.rb +132 -0
- data/lib/gearman/error.rb +7 -0
- data/lib/gearman/packet.rb +9 -0
- data/lib/gearman/packet/repository.rb +35 -0
- data/lib/gearman/packet/sugar.rb +103 -0
- data/lib/gearman/worker.rb +61 -0
- data/lib/geary.rb +8 -0
- data/lib/geary/cli.rb +86 -0
- data/lib/geary/configuration.rb +18 -0
- data/lib/geary/error.rb +7 -0
- data/lib/geary/manager.rb +84 -0
- data/lib/geary/option_parser.rb +42 -0
- data/lib/geary/performer.rb +73 -0
- data/lib/geary/railtie.rb +9 -0
- data/lib/geary/worker.rb +49 -0
- data/spec/gearman/client_spec.rb +36 -0
- data/spec/gearman/connection_spec.rb +67 -0
- data/spec/gearman/packet/sugar_spec.rb +58 -0
- data/spec/gearman/packet_spec.rb +22 -0
- data/spec/gearman/worker_spec.rb +68 -0
- data/spec/geary/cli_spec.rb +40 -0
- data/spec/geary/manager_spec.rb +123 -0
- data/spec/geary/option_parser_spec.rb +17 -0
- data/spec/geary/performer_spec.rb +128 -0
- data/spec/geary/worker_spec.rb +23 -0
- metadata +222 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'celluloid'
|
2
|
+
require 'gearman/connection'
|
3
|
+
require 'gearman/packet'
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
module Gearman
|
7
|
+
class Worker
|
8
|
+
include Celluloid
|
9
|
+
|
10
|
+
trap_exit :reconnect
|
11
|
+
finalizer :disconnect
|
12
|
+
|
13
|
+
def initialize(address)
|
14
|
+
@address = URI(address)
|
15
|
+
configure_connection Connection.method(:new_link)
|
16
|
+
end
|
17
|
+
|
18
|
+
def can_do(ability)
|
19
|
+
@connection.write(Packet::CAN_DO.new(function_name: ability))
|
20
|
+
end
|
21
|
+
|
22
|
+
def pre_sleep
|
23
|
+
@connection.write(Packet::PRE_SLEEP.new)
|
24
|
+
@connection.next(Packet::NOOP)
|
25
|
+
end
|
26
|
+
|
27
|
+
def grab_job
|
28
|
+
@connection.write(Packet::GRAB_JOB.new)
|
29
|
+
@connection.next(Packet::JOB_ASSIGN, Packet::NO_JOB)
|
30
|
+
end
|
31
|
+
|
32
|
+
def work_exception(handle, data)
|
33
|
+
@connection.write(Packet::WORK_EXCEPTION.new(handle: handle, data: data))
|
34
|
+
end
|
35
|
+
|
36
|
+
def work_complete(handle, data)
|
37
|
+
@connection.write(Packet::WORK_COMPLETE.new(handle: handle, data: data))
|
38
|
+
end
|
39
|
+
|
40
|
+
def disconnect
|
41
|
+
if @connection
|
42
|
+
@connection.terminate if @connection.alive?
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def build_connection
|
47
|
+
@connection = @connect.call(@address)
|
48
|
+
end
|
49
|
+
|
50
|
+
def reconnect(*_)
|
51
|
+
disconnect
|
52
|
+
build_connection
|
53
|
+
end
|
54
|
+
|
55
|
+
def configure_connection(connection_routine)
|
56
|
+
@connect = connection_routine
|
57
|
+
reconnect
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
data/lib/geary.rb
ADDED
data/lib/geary/cli.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
require 'celluloid'
|
4
|
+
|
5
|
+
require 'geary/option_parser'
|
6
|
+
require 'geary/manager'
|
7
|
+
|
8
|
+
module Geary
|
9
|
+
|
10
|
+
class CLI
|
11
|
+
|
12
|
+
Shutdown = Class.new(StandardError) unless defined? Shutdown
|
13
|
+
|
14
|
+
attr_reader :configuration, :internal_signal_queue, :external_signal_queue
|
15
|
+
|
16
|
+
def initialize(argv, stdout = STDOUT, stderr = STDERR, kernel = Kernel,
|
17
|
+
pipe = IO.pipe)
|
18
|
+
@argv = argv
|
19
|
+
@stdout = stdout
|
20
|
+
@stdout = stderr
|
21
|
+
@kernel = kernel
|
22
|
+
@internal_signal_queue, @external_signal_queue = pipe
|
23
|
+
@configuration = OptionParser.new.parse(@argv)
|
24
|
+
end
|
25
|
+
|
26
|
+
def execute!
|
27
|
+
Celluloid.logger.level = configuration.log_level
|
28
|
+
|
29
|
+
%w(INT TERM).each do |signal|
|
30
|
+
trap signal do
|
31
|
+
external_signal_queue.puts(signal)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
munge_environment_given(configuration)
|
36
|
+
load_rails
|
37
|
+
|
38
|
+
manager = Manager.new(configuration: configuration)
|
39
|
+
manager.start
|
40
|
+
|
41
|
+
begin
|
42
|
+
loop do
|
43
|
+
IO.select([internal_signal_queue])
|
44
|
+
signal = internal_signal_queue.gets.strip
|
45
|
+
|
46
|
+
handle(signal)
|
47
|
+
end
|
48
|
+
rescue Shutdown
|
49
|
+
manager.async.stop
|
50
|
+
|
51
|
+
manager.wait(:done)
|
52
|
+
|
53
|
+
@kernel.exit(0)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def handle(signal)
|
58
|
+
if %w(INT TERM).include?(signal)
|
59
|
+
raise Shutdown
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def munge_environment_given(configuration)
|
66
|
+
$:.concat(configuration.included_paths)
|
67
|
+
configuration.required_files.each { |file| require file }
|
68
|
+
end
|
69
|
+
|
70
|
+
def load_rails
|
71
|
+
begin
|
72
|
+
require 'rails'
|
73
|
+
rescue LoadError
|
74
|
+
Celluloid.logger.debug "Unable to load Rails"
|
75
|
+
else
|
76
|
+
Celluloid.logger.debug "Loading Rails"
|
77
|
+
|
78
|
+
require 'geary/railtie'
|
79
|
+
require 'config/environment'
|
80
|
+
|
81
|
+
::Rails.application.eager_load!
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'virtus'
|
3
|
+
require 'virtus/uri'
|
4
|
+
|
5
|
+
module Geary
|
6
|
+
class Configuration
|
7
|
+
include Virtus
|
8
|
+
|
9
|
+
attribute :server_addresses, Array[URI], default: ['gearman://localhost:4730']
|
10
|
+
attribute :concurrency, Integer, default: 25
|
11
|
+
attribute :included_paths, Array, default: %w(.)
|
12
|
+
attribute :required_files, Array, default: []
|
13
|
+
attribute :failure_monitor_interval, Integer, default: 1
|
14
|
+
attribute :jitter, Float, default: 0.01
|
15
|
+
attribute :log_level, Object, default: Logger::INFO
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
data/lib/geary/error.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'celluloid'
|
2
|
+
require 'geary/error'
|
3
|
+
require 'geary/performer'
|
4
|
+
|
5
|
+
module Geary
|
6
|
+
class Manager
|
7
|
+
include Celluloid
|
8
|
+
|
9
|
+
UnexpectedRestart = Class.new(Error) unless defined? UnexpectedRestart
|
10
|
+
|
11
|
+
attr_reader :configuration, :performers
|
12
|
+
|
13
|
+
trap_exit :performer_crashed
|
14
|
+
|
15
|
+
def initialize(options = {})
|
16
|
+
@configuration = options.fetch(:configuration)
|
17
|
+
@performer_type = options.fetch(:performer_type, Performer)
|
18
|
+
@performers = []
|
19
|
+
@crashes = []
|
20
|
+
@server_addresses_by_performer = {}
|
21
|
+
end
|
22
|
+
|
23
|
+
def start
|
24
|
+
async.monitor_crashes
|
25
|
+
|
26
|
+
configuration.server_addresses.each do |server_address|
|
27
|
+
configuration.concurrency.times do
|
28
|
+
start_performer(server_address)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def stop
|
34
|
+
@performers.select(&:alive?).each(&:terminate)
|
35
|
+
|
36
|
+
after(0) { signal(:done) }
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def monitor_crashes
|
42
|
+
every(configuration.failure_monitor_interval) do
|
43
|
+
@crashes.reject! do |server_address|
|
44
|
+
momentarily { start_performer(server_address) }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def performer_crashed(performer, reason)
|
50
|
+
if String(reason).size > 0
|
51
|
+
forget_performer(performer) do |server_address|
|
52
|
+
@crashes.unshift(server_address)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def forget_performer(performer, &wants_server_address)
|
58
|
+
_id = performer.object_id
|
59
|
+
|
60
|
+
@performers.delete(performer) do
|
61
|
+
raise UnexpectedRestart, "we don't know about Performer #{_id}"
|
62
|
+
end
|
63
|
+
|
64
|
+
server_address = @server_addresses_by_performer.delete(_id)
|
65
|
+
|
66
|
+
wants_server_address.call(server_address)
|
67
|
+
end
|
68
|
+
|
69
|
+
def start_performer(server_address)
|
70
|
+
performer = @performer_type.new_link(server_address)
|
71
|
+
|
72
|
+
@performers << performer
|
73
|
+
@server_addresses_by_performer[performer.object_id] = server_address
|
74
|
+
|
75
|
+
performer.async.start
|
76
|
+
end
|
77
|
+
|
78
|
+
def momentarily(&action)
|
79
|
+
after(rand + configuration.jitter, &action)
|
80
|
+
true
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
require 'geary/configuration'
|
4
|
+
|
5
|
+
module Geary
|
6
|
+
class OptionParser
|
7
|
+
|
8
|
+
def parse(args)
|
9
|
+
Configuration.new.tap do |configuration|
|
10
|
+
parser_which_configures(configuration).parse!(Array(args))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def parser_which_configures(configuration)
|
15
|
+
::OptionParser.new do |parser|
|
16
|
+
parser.on('-s', '--server SERVERS', Array) do |server_addresses|
|
17
|
+
configuration.server_addresses = server_addresses
|
18
|
+
end
|
19
|
+
|
20
|
+
parser.on('-r', '--require FILES', Array) do |files|
|
21
|
+
configuration.required_files = files
|
22
|
+
end
|
23
|
+
|
24
|
+
parser.on('-I', '--include PATHS', Array) do |paths|
|
25
|
+
configuration.included_paths = paths
|
26
|
+
end
|
27
|
+
|
28
|
+
parser.on('-c', '--concurrency NUMBER', 'number of concurrent tasks to run per server') do |number|
|
29
|
+
configuration.concurrency = Integer(number)
|
30
|
+
end
|
31
|
+
|
32
|
+
parser.on('-l', '--level LOG_LEVEL', 'log level (FATAL|ERROR|WARN|INFO|DEBUG)') do |level|
|
33
|
+
begin
|
34
|
+
configuration.log_level = Logger.const_get(String(level).upcase)
|
35
|
+
rescue NameError
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'celluloid'
|
2
|
+
require 'forwardable'
|
3
|
+
require 'gearman/worker'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module Geary
|
7
|
+
|
8
|
+
class Performer
|
9
|
+
include Celluloid
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
finalizer :disconnect
|
13
|
+
trap_exit :reconnect
|
14
|
+
|
15
|
+
def initialize(address)
|
16
|
+
@address = address
|
17
|
+
configure_connection Gearman::Worker.method(:new_link)
|
18
|
+
end
|
19
|
+
|
20
|
+
def start
|
21
|
+
@gearman.can_do('Geary.default')
|
22
|
+
|
23
|
+
loop do
|
24
|
+
packet = @gearman.grab_job
|
25
|
+
|
26
|
+
case packet
|
27
|
+
when Gearman::Packet::JOB_ASSIGN
|
28
|
+
perform(packet)
|
29
|
+
when Gearman::Packet::NO_JOB
|
30
|
+
@gearman.pre_sleep
|
31
|
+
else
|
32
|
+
break
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def perform(packet)
|
38
|
+
job = JSON.parse(packet.data)
|
39
|
+
job_result = nil
|
40
|
+
|
41
|
+
begin
|
42
|
+
worker = ::Object.const_get(job['class']).new
|
43
|
+
|
44
|
+
job_result = worker.perform(*job['args'])
|
45
|
+
rescue => error
|
46
|
+
@gearman.async.work_exception(packet.handle, error.message)
|
47
|
+
else
|
48
|
+
@gearman.async.work_complete(packet.handle, job_result)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def disconnect
|
53
|
+
if @gearman
|
54
|
+
@gearman.terminate if @gearman.alive?
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def reconnect(actor, reason)
|
59
|
+
disconnect
|
60
|
+
build_connection
|
61
|
+
end
|
62
|
+
|
63
|
+
def build_connection
|
64
|
+
@gearman = @connect.call(@address)
|
65
|
+
end
|
66
|
+
|
67
|
+
def configure_connection(connection_routine)
|
68
|
+
@connect = connection_routine
|
69
|
+
reconnect(current_actor, nil)
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
data/lib/geary/worker.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'gearman/client'
|
3
|
+
require 'geary/error'
|
4
|
+
|
5
|
+
module Geary
|
6
|
+
module Worker
|
7
|
+
|
8
|
+
def perform_async(*args)
|
9
|
+
payload = payload_for(args)
|
10
|
+
|
11
|
+
operation do |gearman|
|
12
|
+
gearman.submit_job_bg('Geary.default', payload.to_json)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
def use_gearman_client(*args)
|
19
|
+
@gearman_client = Gearman::Client.new(*args)
|
20
|
+
end
|
21
|
+
|
22
|
+
def gearman_client
|
23
|
+
@gearman_client || use_gearman_client('gearman://localhost:4730')
|
24
|
+
end
|
25
|
+
|
26
|
+
def payload_for(args)
|
27
|
+
payload = { class: self.to_s, args: args }
|
28
|
+
end
|
29
|
+
|
30
|
+
def operation(&block)
|
31
|
+
attempts = 0
|
32
|
+
failure_threshold = 1
|
33
|
+
|
34
|
+
begin
|
35
|
+
block.call(gearman_client)
|
36
|
+
rescue
|
37
|
+
attempts += 1
|
38
|
+
|
39
|
+
if attempts > failure_threshold
|
40
|
+
raise Error
|
41
|
+
else
|
42
|
+
gearman_client.reconnect
|
43
|
+
retry
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|