symphony 0.8.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/ChangeLog +91 -3
- data/History.rdoc +13 -0
- data/Manifest.txt +10 -1
- data/README.rdoc +1 -1
- data/Rakefile +5 -5
- data/UPGRADING.md +38 -0
- data/USAGE.rdoc +1 -1
- data/lib/symphony.rb +86 -5
- data/lib/symphony/daemon.rb +94 -147
- data/lib/symphony/queue.rb +11 -4
- data/lib/symphony/routing.rb +5 -4
- data/lib/symphony/signal_handling.rb +1 -2
- data/lib/symphony/statistics.rb +96 -0
- data/lib/symphony/task.rb +71 -11
- data/lib/symphony/task_group.rb +165 -0
- data/lib/symphony/task_group/longlived.rb +98 -0
- data/lib/symphony/task_group/oneshot.rb +25 -0
- data/lib/symphony/tasks/oneshot_simulator.rb +61 -0
- data/lib/symphony/tasks/simulator.rb +22 -18
- data/spec/helpers.rb +67 -1
- data/spec/symphony/daemon_spec.rb +83 -31
- data/spec/symphony/queue_spec.rb +2 -2
- data/spec/symphony/routing_spec.rb +297 -0
- data/spec/symphony/statistics_spec.rb +71 -0
- data/spec/symphony/task_group/longlived_spec.rb +219 -0
- data/spec/symphony/task_group/oneshot_spec.rb +56 -0
- data/spec/symphony/task_group_spec.rb +93 -0
- data/spec/symphony_spec.rb +6 -0
- metadata +41 -18
- metadata.gz.sig +0 -0
- data/TODO.md +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 095d96d07d5e07c3b0cecfa55d5705e1c5dddb55
|
4
|
+
data.tar.gz: cfc61ebc105673602505d959956bb2a24a939e3a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2c9443c031f087f3ae3633be51d70d9dfc7adfd817ac72972cd992a72716525d76c2965303bfbb726d7e06acb5143ec168b3e379b4253cbee1ed1643e25b7397
|
7
|
+
data.tar.gz: 7661d67300b397b7b97e8b24e011c8328bd3737c9f89b44cc668121589e5ec63364c5a957f09c148ea407b872bbda3ec9faf37abe0849d086a7039cf3fb15da9
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data.tar.gz.sig
CHANGED
Binary file
|
data/ChangeLog
CHANGED
@@ -1,15 +1,103 @@
|
|
1
|
+
2015-05-29 Michael Granger <ged@FaerieMUD.org>
|
2
|
+
|
3
|
+
* .gems, .rvm.gems, .rvmrc:
|
4
|
+
Update dev environment
|
5
|
+
[44341600897a] [tip]
|
6
|
+
|
7
|
+
2015-02-27 Michael Granger <ged@FaerieMUD.org>
|
8
|
+
|
9
|
+
* .travis.yml:
|
10
|
+
Don't test under rbx
|
11
|
+
[c6e5888c9d68] [github/master]
|
12
|
+
|
13
|
+
* .travis.yml, Gemfile.ci:
|
14
|
+
Nope, backed out changeset 031ea486d462
|
15
|
+
[3031ee38d8a8]
|
16
|
+
|
17
|
+
* .travis.yml, Gemfile.ci:
|
18
|
+
Try using a specific gemset without metrics to avoid rusage
|
19
|
+
[031ea486d462]
|
20
|
+
|
21
|
+
* .travis.yml:
|
22
|
+
Adding travis-ci config
|
23
|
+
[a4bec430c2ce]
|
24
|
+
|
25
|
+
* .hgignore, .hoerc, .ruby-version, Gemfile, Manifest.txt, Rakefile,
|
26
|
+
UPGRADING.md, USAGE.rdoc, symphony.gemspec:
|
27
|
+
Update project files for prerelease
|
28
|
+
[5f344848f265]
|
29
|
+
|
30
|
+
* lib/symphony/daemon.rb, lib/symphony/task_group.rb,
|
31
|
+
spec/symphony/daemon_spec.rb, spec/symphony/queue_spec.rb:
|
32
|
+
Finish up the daemon task reconfig spec
|
33
|
+
[d3ea6af65086]
|
34
|
+
|
35
|
+
2015-02-27 mahlon <mahlon@martini.nu>
|
36
|
+
|
37
|
+
* Manifest.txt:
|
38
|
+
Update the manifest.
|
39
|
+
[74cfe663a36e]
|
40
|
+
|
41
|
+
2015-02-27 Michael Granger <ged@FaerieMUD.org>
|
42
|
+
|
43
|
+
* lib/symphony/queue.rb, lib/symphony/statistics.rb:
|
44
|
+
Squelch spammy debugging
|
45
|
+
[e098fa10f649]
|
46
|
+
|
47
|
+
* .rvm.gems, .rvmrc, .tm_properties, Manifest.txt, Rakefile, TODO.md,
|
48
|
+
lib/symphony.rb, lib/symphony/daemon.rb, lib/symphony/queue.rb,
|
49
|
+
lib/symphony/signal_handling.rb, lib/symphony/statistics.rb,
|
50
|
+
lib/symphony/task.rb, lib/symphony/task_group.rb,
|
51
|
+
lib/symphony/task_group/longlived.rb,
|
52
|
+
lib/symphony/task_group/oneshot.rb,
|
53
|
+
lib/symphony/tasks/oneshot_simulator.rb,
|
54
|
+
lib/symphony/tasks/simulator.rb, spec/helpers.rb,
|
55
|
+
spec/symphony/daemon_spec.rb, spec/symphony/statistics_spec.rb,
|
56
|
+
spec/symphony/task_group/longlived_spec.rb,
|
57
|
+
spec/symphony/task_group/oneshot_spec.rb,
|
58
|
+
spec/symphony/task_group_spec.rb, spec/symphony_spec.rb:
|
59
|
+
Implement task work models
|
60
|
+
* * * Load components earlier, and move configure() below other method
|
61
|
+
definitions. This fixes a chicken-and-egg with Configurability where
|
62
|
+
loading the config before loading Symphony caused a RuntimeError.
|
63
|
+
[a6b97e9ea2e1]
|
64
|
+
|
65
|
+
* lib/symphony/queue.rb:
|
66
|
+
Include the first backtrace frame when logging job errors
|
67
|
+
[47b5da330502]
|
68
|
+
|
69
|
+
2014-10-14 Michael Granger <ged@FaerieMUD.org>
|
70
|
+
|
71
|
+
* .rvm.gems, Rakefile:
|
72
|
+
Bump minimum version of Bunny to 1.5 for better connection recovery
|
73
|
+
[cd10e63d22d7]
|
74
|
+
|
75
|
+
2014-09-03 Michael Granger <ged@FaerieMUD.org>
|
76
|
+
|
77
|
+
* .hgtags:
|
78
|
+
Added tag v0.8.0 for changeset 8d6669a7e329
|
79
|
+
[af3d65f141e7]
|
80
|
+
|
81
|
+
* .hgsigs:
|
82
|
+
Added signature for changeset 68352ad02888
|
83
|
+
[8d6669a7e329] [v0.8.0]
|
84
|
+
|
85
|
+
* History.rdoc, Manifest.txt, lib/symphony.rb:
|
86
|
+
Bump the minor version, update history.
|
87
|
+
[68352ad02888]
|
88
|
+
|
1
89
|
2014-09-01 Michael Granger <ged@FaerieMUD.org>
|
2
90
|
|
3
91
|
* lib/symphony/routing.rb, spec/symphony/routing_spec.rb:
|
4
92
|
Add route options to routing mixin
|
5
|
-
[328bc1fbb538]
|
93
|
+
[328bc1fbb538]
|
6
94
|
|
7
95
|
2014-05-10 Michael Granger <ged@FaerieMUD.org>
|
8
96
|
|
9
97
|
* bin/symphony-task:
|
10
98
|
Use the config env stuff already in Symphony.load_config, and keep
|
11
99
|
the config file argument optional for symphony-task.
|
12
|
-
[0d91a148cacd]
|
100
|
+
[0d91a148cacd]
|
13
101
|
|
14
102
|
2014-05-09 Mahlon E. Smith <mahlon@laika.com>
|
15
103
|
|
@@ -75,7 +163,7 @@
|
|
75
163
|
|
76
164
|
* History.rdoc, lib/symphony.rb:
|
77
165
|
Bump the minor version, update history
|
78
|
-
[fa6a367872f4]
|
166
|
+
[fa6a367872f4]
|
79
167
|
|
80
168
|
* TODO.md, lib/symphony/queue.rb, spec/symphony/queue_spec.rb:
|
81
169
|
Update the TO-DO list
|
data/History.rdoc
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
== v0.9.0 [2015-06-01] Michael Granger <ged@FaerieMUD.org>
|
2
|
+
|
3
|
+
Improvements:
|
4
|
+
|
5
|
+
- Implement task work models
|
6
|
+
- Include the first backtrace frame when logging job errors
|
7
|
+
|
8
|
+
Bugfixes:
|
9
|
+
|
10
|
+
- Fix the patterns used by Symphony::Routing to match
|
11
|
+
RabbitMQ topic-queue matching.
|
12
|
+
|
13
|
+
|
1
14
|
== v0.8.0 [2014-09-01] Michael Granger <ged@FaerieMUD.org>
|
2
15
|
|
3
16
|
- Add route options to routing mixin
|
data/Manifest.txt
CHANGED
@@ -4,7 +4,7 @@ History.rdoc
|
|
4
4
|
Manifest.txt
|
5
5
|
README.rdoc
|
6
6
|
Rakefile
|
7
|
-
|
7
|
+
UPGRADING.md
|
8
8
|
USAGE.rdoc
|
9
9
|
bin/symphony
|
10
10
|
bin/symphony-task
|
@@ -16,14 +16,23 @@ lib/symphony/mixins.rb
|
|
16
16
|
lib/symphony/queue.rb
|
17
17
|
lib/symphony/routing.rb
|
18
18
|
lib/symphony/signal_handling.rb
|
19
|
+
lib/symphony/statistics.rb
|
19
20
|
lib/symphony/task.rb
|
21
|
+
lib/symphony/task_group.rb
|
22
|
+
lib/symphony/task_group/longlived.rb
|
23
|
+
lib/symphony/task_group/oneshot.rb
|
20
24
|
lib/symphony/tasks/auditor.rb
|
21
25
|
lib/symphony/tasks/failure_logger.rb
|
26
|
+
lib/symphony/tasks/oneshot_simulator.rb
|
22
27
|
lib/symphony/tasks/simulator.rb
|
23
28
|
spec/helpers.rb
|
24
29
|
spec/symphony/daemon_spec.rb
|
25
30
|
spec/symphony/mixins_spec.rb
|
26
31
|
spec/symphony/queue_spec.rb
|
27
32
|
spec/symphony/routing_spec.rb
|
33
|
+
spec/symphony/statistics_spec.rb
|
34
|
+
spec/symphony/task_group/longlived_spec.rb
|
35
|
+
spec/symphony/task_group/oneshot_spec.rb
|
36
|
+
spec/symphony/task_group_spec.rb
|
28
37
|
spec/symphony/task_spec.rb
|
29
38
|
spec/symphony_spec.rb
|
data/README.rdoc
CHANGED
@@ -58,7 +58,7 @@ and generate the API documentation.
|
|
58
58
|
|
59
59
|
== License
|
60
60
|
|
61
|
-
Copyright (c) 2011-
|
61
|
+
Copyright (c) 2011-2015, Michael Granger and Mahlon E. Smith
|
62
62
|
All rights reserved.
|
63
63
|
|
64
64
|
Redistribution and use in source and binary forms, with or without
|
data/Rakefile
CHANGED
@@ -12,7 +12,6 @@ GEMSPEC = 'symphony.gemspec'
|
|
12
12
|
Hoe.plugin :mercurial
|
13
13
|
Hoe.plugin :signing
|
14
14
|
Hoe.plugin :deveiate
|
15
|
-
Hoe.plugin :bundler
|
16
15
|
|
17
16
|
Hoe.plugins.delete :rubyforge
|
18
17
|
|
@@ -27,10 +26,10 @@ hoespec = Hoe.spec 'symphony' do |spec|
|
|
27
26
|
spec.developer 'Michael Granger', 'ged@FaerieMUD.org'
|
28
27
|
spec.developer 'Mahlon E. Smith', 'mahlon@martini.nu'
|
29
28
|
|
30
|
-
spec.dependency 'configurability', '~> 2.
|
29
|
+
spec.dependency 'configurability', '~> 2.2'
|
31
30
|
spec.dependency 'loggability', '~> 0.10'
|
32
31
|
spec.dependency 'pluggability', '~> 0.4'
|
33
|
-
spec.dependency 'bunny', '~> 1.
|
32
|
+
spec.dependency 'bunny', '~> 1.5'
|
34
33
|
spec.dependency 'sysexits', '~> 1.1'
|
35
34
|
spec.dependency 'yajl-ruby', '~> 1.2'
|
36
35
|
spec.dependency 'msgpack', '~> 0.5'
|
@@ -39,6 +38,7 @@ hoespec = Hoe.spec 'symphony' do |spec|
|
|
39
38
|
|
40
39
|
spec.dependency 'rspec', '~> 3.0', :developer
|
41
40
|
spec.dependency 'simplecov', '~> 0.8', :developer
|
41
|
+
spec.dependency 'timecop', '~> 0.7', :developer
|
42
42
|
|
43
43
|
spec.require_ruby_version( '>=2.0.0' )
|
44
44
|
spec.hg_sign_tags = true if spec.respond_to?( :hg_sign_tags= )
|
@@ -51,7 +51,7 @@ hoespec.spec.files.delete( '.gemtest' )
|
|
51
51
|
ENV['VERSION'] ||= hoespec.spec.version.to_s
|
52
52
|
|
53
53
|
# Run the tests before checking in
|
54
|
-
task 'hg:precheckin' => [ :check_history, :check_manifest, :spec ]
|
54
|
+
task 'hg:precheckin' => [ :check_history, :check_manifest, :gemspec, :spec ]
|
55
55
|
|
56
56
|
# Rebuild the ChangeLog immediately before release
|
57
57
|
task :prerelease => 'ChangeLog'
|
@@ -67,7 +67,7 @@ end
|
|
67
67
|
task :gemspec => GEMSPEC
|
68
68
|
file GEMSPEC => hoespec.spec.files do |task|
|
69
69
|
spec = $hoespec.spec
|
70
|
-
spec.version = "#{spec.version}.pre#{Time.now.strftime("%Y%m%d%H%M%S")}"
|
70
|
+
spec.version = "#{spec.version.bump}.0.pre#{Time.now.strftime("%Y%m%d%H%M%S")}"
|
71
71
|
File.open( task.name, 'w' ) do |fh|
|
72
72
|
fh.write( spec.to_ruby )
|
73
73
|
end
|
data/UPGRADING.md
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# Upgrading
|
2
|
+
|
3
|
+
## From 0.8.x to 0.9.0
|
4
|
+
|
5
|
+
This version introduces several new mechanisms for adjusting your workers to handle increased amounts of work.
|
6
|
+
|
7
|
+
This is accomplished via a new (backward-compatible) `tasks` config syntax, and a pluggable "work model" that can be set to control how workers running a particular task behave.
|
8
|
+
|
9
|
+
Tasks are now run inside of a "task group" which contains the logic of the work model. There are two initial work models that come with Symphony: `longlived` and `oneshot`.
|
10
|
+
|
11
|
+
The `longlived` work model is the default, and works similarly to how tasks worked prior to the 0.9 release: it starts up and executes tasks as they arrive, and then shuts down when the Symphony daemon shuts down. If you keep your configuration the same as it was before this release, nothing should change.
|
12
|
+
|
13
|
+
However, you can now tell your `longlived` task groups to automatically scale up the number of instances when the amount of work to be done increases. You can do this by converting the `tasks` config section to a Hash, with the task names as the keys and an integer as the value.
|
14
|
+
|
15
|
+
The old way:
|
16
|
+
|
17
|
+
symphony:
|
18
|
+
tasks:
|
19
|
+
- audit_logger
|
20
|
+
- failure_logger
|
21
|
+
- payments_processor
|
22
|
+
- user_mailer
|
23
|
+
- thumbnailer
|
24
|
+
|
25
|
+
the new way:
|
26
|
+
|
27
|
+
symphony:
|
28
|
+
tasks:
|
29
|
+
audit_logger: 1
|
30
|
+
failure_logger: 1
|
31
|
+
payments_processor: 1
|
32
|
+
user_mailer: 2
|
33
|
+
thumbnailer: 5
|
34
|
+
|
35
|
+
The value controls the _maximum_ number of that task class that can be running at one time, and the Symphony daemon will now scale the number of workers up to your specified maximum when the amount of work is trending upwards, and scale it back down to a single worker when work is trending down.
|
36
|
+
|
37
|
+
The `oneshot` work model is a new kind of worker that fetches and executes a single task and then exits. It's used for tasks that consume large amounts of memory or other resources that may not be released in between tasks such as 3D rendering or video-processing.
|
38
|
+
|
data/USAGE.rdoc
CHANGED
data/lib/symphony.rb
CHANGED
@@ -12,10 +12,10 @@ module Symphony
|
|
12
12
|
Configurability
|
13
13
|
|
14
14
|
# Library version constant
|
15
|
-
VERSION = '0.
|
15
|
+
VERSION = '0.9.0'
|
16
16
|
|
17
17
|
# Version-control revision constant
|
18
|
-
REVISION = %q$Revision:
|
18
|
+
REVISION = %q$Revision: 84c169f1c831 $
|
19
19
|
|
20
20
|
|
21
21
|
# The name of the environment variable to check for config file overrides
|
@@ -29,6 +29,80 @@ module Symphony
|
|
29
29
|
log_as :symphony
|
30
30
|
|
31
31
|
|
32
|
+
# Configurability API -- use the 'worker_daemon' section of the config
|
33
|
+
config_key :symphony
|
34
|
+
|
35
|
+
|
36
|
+
# Default configuration
|
37
|
+
CONFIG_DEFAULTS = {
|
38
|
+
throttle_max: 16,
|
39
|
+
throttle_factor: 1,
|
40
|
+
tasks: [],
|
41
|
+
scaling_interval: 0.1,
|
42
|
+
}
|
43
|
+
|
44
|
+
|
45
|
+
require 'symphony/mixins'
|
46
|
+
require 'symphony/queue'
|
47
|
+
require 'symphony/task'
|
48
|
+
require 'symphony/task_group'
|
49
|
+
extend Symphony::MethodUtilities
|
50
|
+
|
51
|
+
##
|
52
|
+
# The maximum throttle factor caused by failing workers
|
53
|
+
singleton_attr_accessor :throttle_max
|
54
|
+
self.throttle_max = CONFIG_DEFAULTS[:throttle_max]
|
55
|
+
|
56
|
+
##
|
57
|
+
# The factor which controls how much incrementing the throttle factor
|
58
|
+
# affects the pause between workers being started.
|
59
|
+
singleton_attr_accessor :throttle_factor
|
60
|
+
self.throttle_factor = CONFIG_DEFAULTS[:throttle_factor]
|
61
|
+
|
62
|
+
##
|
63
|
+
# The Array of Symphony::Task classes that are configured to run
|
64
|
+
singleton_attr_accessor :tasks
|
65
|
+
self.tasks = CONFIG_DEFAULTS[:tasks]
|
66
|
+
|
67
|
+
##
|
68
|
+
# The maximum amount of time between task group process checks
|
69
|
+
singleton_attr_accessor :scaling_interval
|
70
|
+
self.scaling_interval = CONFIG_DEFAULTS[:scaling_interval]
|
71
|
+
|
72
|
+
|
73
|
+
### Load the tasks with the specified +task_names+ and return them
|
74
|
+
### as an Array.
|
75
|
+
def self::load_configured_tasks( task_config )
|
76
|
+
if task_config.respond_to?( :each_pair )
|
77
|
+
return self.task_config_from_hash( task_config )
|
78
|
+
else
|
79
|
+
return self.task_config_from_array( task_config )
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
### Return the Hash of +tasks+ as a Hash of Classes and the maximum number to
|
85
|
+
### run.
|
86
|
+
def self::task_config_from_hash( task_config )
|
87
|
+
return task_config.each_with_object({}) do |(task_name, max), tasks|
|
88
|
+
task_class = Symphony::Task.get_subclass( task_name )
|
89
|
+
tasks[ task_class ] = max.to_i
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
### Return the Array of +tasks+ as a Hash of Classes and the maximum number to
|
95
|
+
### run.
|
96
|
+
def self::task_config_from_array( task_config )
|
97
|
+
return [] unless task_config
|
98
|
+
return task_config.uniq.each_with_object({}) do |task_name, tasks|
|
99
|
+
max = task_config.count( task_name )
|
100
|
+
task_class = Symphony::Task.get_subclass( task_name )
|
101
|
+
tasks[ task_class ] = max
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
|
32
106
|
### Get the loaded config (a Configurability::Config object)
|
33
107
|
def self::config
|
34
108
|
Configurability.loaded_config
|
@@ -48,9 +122,16 @@ module Symphony
|
|
48
122
|
end
|
49
123
|
|
50
124
|
|
51
|
-
|
52
|
-
|
53
|
-
|
125
|
+
### Configurability API -- configure the daemon.
|
126
|
+
def self::configure( config=nil )
|
127
|
+
config = self.defaults.merge( config || {} )
|
128
|
+
|
129
|
+
self.throttle_max = config[:throttle_max]
|
130
|
+
self.throttle_factor = config[:throttle_factor]
|
131
|
+
self.scaling_interval = config[:scaling_interval]
|
132
|
+
|
133
|
+
self.tasks = self.load_configured_tasks( config[:tasks] )
|
134
|
+
end
|
54
135
|
|
55
136
|
end # module Symphony
|
56
137
|
|
data/lib/symphony/daemon.rb
CHANGED
@@ -7,13 +7,12 @@ require 'loggability'
|
|
7
7
|
require 'symphony' unless defined?( Symphony )
|
8
8
|
require 'symphony/task'
|
9
9
|
require 'symphony/signal_handling'
|
10
|
+
require 'symphony/task_group'
|
10
11
|
|
11
12
|
# A daemon which manages startup and shutdown of one or more Workers
|
12
13
|
# running Tasks as they are published from a queue.
|
13
14
|
class Symphony::Daemon
|
14
|
-
extend Loggability
|
15
|
-
Configurability,
|
16
|
-
Symphony::MethodUtilities
|
15
|
+
extend Loggability
|
17
16
|
|
18
17
|
include Symphony::SignalHandling
|
19
18
|
|
@@ -21,16 +20,6 @@ class Symphony::Daemon
|
|
21
20
|
# Loggability API -- log to the symphony logger
|
22
21
|
log_to :symphony
|
23
22
|
|
24
|
-
# Configurability API -- use the 'worker_daemon' section of the config
|
25
|
-
config_key :symphony
|
26
|
-
|
27
|
-
|
28
|
-
# Default configuration
|
29
|
-
CONFIG_DEFAULTS = {
|
30
|
-
throttle_max: 16,
|
31
|
-
throttle_factor: 1,
|
32
|
-
tasks: []
|
33
|
-
}
|
34
23
|
|
35
24
|
# Signals we understand
|
36
25
|
QUEUE_SIGS = [
|
@@ -39,25 +28,10 @@ class Symphony::Daemon
|
|
39
28
|
]
|
40
29
|
|
41
30
|
|
42
|
-
|
43
31
|
#
|
44
32
|
# Class methods
|
45
33
|
#
|
46
34
|
|
47
|
-
##
|
48
|
-
# The maximum throttle factor caused by failing workers
|
49
|
-
singleton_attr_accessor :throttle_max
|
50
|
-
|
51
|
-
##
|
52
|
-
# The factor which controls how much incrementing the throttle factor
|
53
|
-
# affects the pause between workers being started.
|
54
|
-
singleton_attr_accessor :throttle_factor
|
55
|
-
|
56
|
-
##
|
57
|
-
# The Array of Symphony::Task classes that are configured to run
|
58
|
-
singleton_attr_accessor :tasks
|
59
|
-
|
60
|
-
|
61
35
|
### Get the daemon's version as a String.
|
62
36
|
def self::version_string( include_buildnum=false )
|
63
37
|
vstring = "%s %s" % [ self.name, Symphony::VERSION ]
|
@@ -69,26 +43,6 @@ class Symphony::Daemon
|
|
69
43
|
end
|
70
44
|
|
71
45
|
|
72
|
-
### Configurability API -- configure the daemon.
|
73
|
-
def self::configure( config=nil )
|
74
|
-
config = self.defaults.merge( config || {} )
|
75
|
-
|
76
|
-
self.throttle_max = config[:throttle_max]
|
77
|
-
self.throttle_factor = config[:throttle_factor]
|
78
|
-
|
79
|
-
self.tasks = self.load_configured_tasks( config[:tasks] )
|
80
|
-
end
|
81
|
-
|
82
|
-
|
83
|
-
### Load the tasks with the specified +task_names+ and return them
|
84
|
-
### as an Array.
|
85
|
-
def self::load_configured_tasks( task_names )
|
86
|
-
return task_names.map do |task_name|
|
87
|
-
Symphony::Task.get_subclass( task_name )
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
|
92
46
|
### Start the daemon.
|
93
47
|
def self::run( args )
|
94
48
|
Loggability.format_with( :color ) if $stdout.tty?
|
@@ -113,11 +67,9 @@ class Symphony::Daemon
|
|
113
67
|
|
114
68
|
### Create a new Daemon instance.
|
115
69
|
def initialize
|
116
|
-
@
|
70
|
+
@task_pids = {}
|
71
|
+
@task_groups = {}
|
117
72
|
@running = false
|
118
|
-
@shutting_down = false
|
119
|
-
@throttle = 0
|
120
|
-
@last_child_started = Time.now
|
121
73
|
|
122
74
|
self.set_up_signal_handling
|
123
75
|
end
|
@@ -127,8 +79,11 @@ class Symphony::Daemon
|
|
127
79
|
public
|
128
80
|
######
|
129
81
|
|
130
|
-
# The Hash of
|
131
|
-
attr_reader :
|
82
|
+
# The Hash of PID to task group
|
83
|
+
attr_reader :task_pids
|
84
|
+
|
85
|
+
# The Array of running task groups
|
86
|
+
attr_reader :task_groups
|
132
87
|
|
133
88
|
# A self-pipe for deferred signal-handling
|
134
89
|
attr_reader :selfpipe
|
@@ -136,13 +91,6 @@ class Symphony::Daemon
|
|
136
91
|
# The Symphony::Queue that jobs will be fetched from
|
137
92
|
attr_reader :queue
|
138
93
|
|
139
|
-
# The Configurability::Config object for the current configuration.
|
140
|
-
attr_reader :config
|
141
|
-
|
142
|
-
|
143
|
-
# Make a delegator for the class's task list
|
144
|
-
define_method( :tasks, &self.method(:tasks) )
|
145
|
-
|
146
94
|
|
147
95
|
### Returns +true+ if the daemon is still running.
|
148
96
|
def running?
|
@@ -150,12 +98,6 @@ class Symphony::Daemon
|
|
150
98
|
end
|
151
99
|
|
152
100
|
|
153
|
-
### Returns +true+ if the daemon is shutting down.
|
154
|
-
def shutting_down?
|
155
|
-
return @shutting_down
|
156
|
-
end
|
157
|
-
|
158
|
-
|
159
101
|
### Set up the daemon and start running.
|
160
102
|
def run
|
161
103
|
self.log.info "Starting task daemon"
|
@@ -168,8 +110,6 @@ class Symphony::Daemon
|
|
168
110
|
|
169
111
|
# Restore the default signal handlers
|
170
112
|
self.reset_signal_traps( *QUEUE_SIGS )
|
171
|
-
|
172
|
-
exit
|
173
113
|
end
|
174
114
|
|
175
115
|
|
@@ -177,12 +117,14 @@ class Symphony::Daemon
|
|
177
117
|
### take appropriate action.
|
178
118
|
def run_tasks
|
179
119
|
@running = true
|
120
|
+
self.create_task_groups
|
180
121
|
|
181
122
|
self.log.debug "Starting supervisor loop..."
|
182
123
|
while self.running?
|
183
|
-
self.
|
184
|
-
self.wait_for_signals
|
185
|
-
|
124
|
+
self.tickle_task_groups
|
125
|
+
if self.wait_for_signals( Symphony.scaling_interval )
|
126
|
+
self.reap_children
|
127
|
+
end
|
186
128
|
end
|
187
129
|
|
188
130
|
rescue => err
|
@@ -191,7 +133,6 @@ class Symphony::Daemon
|
|
191
133
|
|
192
134
|
ensure
|
193
135
|
self.log.info "Done running tasks."
|
194
|
-
@running = false
|
195
136
|
self.stop
|
196
137
|
end
|
197
138
|
|
@@ -199,7 +140,7 @@ class Symphony::Daemon
|
|
199
140
|
### Shut the daemon down gracefully.
|
200
141
|
def stop
|
201
142
|
self.log.warn "Stopping."
|
202
|
-
@
|
143
|
+
@running = false
|
203
144
|
|
204
145
|
self.ignore_signals( *QUEUE_SIGS )
|
205
146
|
|
@@ -209,14 +150,14 @@ class Symphony::Daemon
|
|
209
150
|
sleep( 1 )
|
210
151
|
self.kill_children
|
211
152
|
sleep( 1 )
|
212
|
-
break if self.
|
153
|
+
break if self.task_pids.empty?
|
213
154
|
sleep( 1 )
|
214
|
-
end unless self.
|
155
|
+
end unless self.task_pids.empty?
|
215
156
|
|
216
157
|
# Give up on our remaining children.
|
217
158
|
Signal.trap( :CHLD, :IGNORE )
|
218
|
-
if !self.
|
219
|
-
self.log.warn " %d workers remain: sending KILL" % [ self.
|
159
|
+
if !self.task_pids.empty?
|
160
|
+
self.log.warn " %d workers remain: sending KILL" % [ self.task_pids.length ]
|
220
161
|
self.kill_children( :KILL )
|
221
162
|
end
|
222
163
|
end
|
@@ -224,8 +165,11 @@ class Symphony::Daemon
|
|
224
165
|
|
225
166
|
### Reload the configuration.
|
226
167
|
def reload_config
|
227
|
-
self.log.warn "Reloading config %p" % [
|
228
|
-
|
168
|
+
self.log.warn "Reloading config %p" % [ Symphony.config ]
|
169
|
+
Symphony.config.reload
|
170
|
+
|
171
|
+
# And start them up again using the new config.
|
172
|
+
self.create_task_groups
|
229
173
|
end
|
230
174
|
|
231
175
|
|
@@ -263,70 +207,83 @@ class Symphony::Daemon
|
|
263
207
|
end
|
264
208
|
|
265
209
|
|
266
|
-
###
|
267
|
-
def
|
268
|
-
|
269
|
-
|
210
|
+
### Create task groups for each configured task.
|
211
|
+
def create_task_groups
|
212
|
+
old_task_groups = @task_groups || {}
|
213
|
+
@task_groups = {}
|
214
|
+
|
215
|
+
self.log.debug "Managing task groups: %p" % [ old_task_groups ]
|
270
216
|
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
217
|
+
Symphony.tasks.each do |task_class, max|
|
218
|
+
# If the task is still configured, restart all of its workers
|
219
|
+
if group = old_task_groups.delete( task_class )
|
220
|
+
self.log.info "%p still configured; restarting its task group." % [ task_class ]
|
221
|
+
self.restart_task_group( group, task_class, max )
|
222
|
+
@task_groups[ task_class ] = group
|
223
|
+
|
224
|
+
# If it's new, just start it up
|
225
|
+
else
|
226
|
+
self.log.info "Starting up new task group for %p" % [ task_class ]
|
227
|
+
@task_groups[ task_class ] = self.start_task_group( task_class, max )
|
228
|
+
end
|
276
229
|
end
|
277
230
|
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
self.
|
282
|
-
|
231
|
+
# Any task classes remaining are no longer configured, so stop them.
|
232
|
+
old_task_groups.each do |task_class, group|
|
233
|
+
self.log.info "%p no longer configured; stopping its task group." % [ task_class ]
|
234
|
+
self.stop_task_group( group )
|
235
|
+
end
|
283
236
|
end
|
284
237
|
|
285
|
-
@last_child_started = Time.now
|
286
|
-
end
|
287
238
|
|
239
|
+
### Start a new task group for the given +task_class+ and +max+ number of workers.
|
240
|
+
def start_task_group( task_class, max )
|
241
|
+
self.log.info "Starting a task group for %p" % [ task_class ]
|
242
|
+
Symphony::TaskGroup.create( task_class.work_model, task_class, max )
|
243
|
+
end
|
288
244
|
|
289
|
-
### Examine the running tasks and return any that are missing.
|
290
|
-
def find_missing_tasks
|
291
|
-
missing_tasks = []
|
292
245
|
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
end
|
246
|
+
### Tell the specified task +group+ to restart with the specified +max+ number of workers.
|
247
|
+
def restart_task_group( group, task_class, max )
|
248
|
+
self.log.info "Restarting task group for %p" % [ task_class ]
|
249
|
+
group.max_workers = max
|
250
|
+
group.restart_workers
|
299
251
|
end
|
300
252
|
|
301
|
-
return missing_tasks
|
302
|
-
end
|
303
|
-
|
304
253
|
|
305
|
-
###
|
306
|
-
def
|
307
|
-
|
308
|
-
|
254
|
+
### Shut down the workers for the specified task group.
|
255
|
+
def stop_task_group( group )
|
256
|
+
self.log.info "Shutting down the task group for %p" % [ group.task_class ]
|
257
|
+
group.stop_all_workers
|
309
258
|
end
|
310
259
|
|
311
260
|
|
312
|
-
###
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
261
|
+
### Tell the task groups to start or stop children based on their work model.
|
262
|
+
def tickle_task_groups
|
263
|
+
self.task_groups.each do |task_class, group|
|
264
|
+
new_pids = group.adjust_workers or next
|
265
|
+
new_pids.each do |pid|
|
266
|
+
self.task_pids[ pid ] = group
|
267
|
+
end
|
268
|
+
end
|
319
269
|
end
|
320
270
|
|
321
271
|
|
322
272
|
### Kill all current children with the specified +signal+. Returns +true+ if the signal was
|
323
273
|
### sent to one or more children.
|
324
274
|
def kill_children( signal=:TERM )
|
325
|
-
return false if self.
|
275
|
+
return false if self.task_pids.empty?
|
326
276
|
|
327
277
|
self.log.info "Sending %s signal to %d task pids: %p." %
|
328
|
-
[ signal, self.
|
329
|
-
|
278
|
+
[ signal, self.task_pids.length, self.task_pids.keys ]
|
279
|
+
self.task_pids.keys.each do |pid|
|
280
|
+
begin
|
281
|
+
Process.kill( signal, pid )
|
282
|
+
rescue Errno::ESRCH => err
|
283
|
+
self.log.error "%p when trying to %s child %d: %s" %
|
284
|
+
[ err.class, signal, pid, err.message ]
|
285
|
+
end
|
286
|
+
end
|
330
287
|
|
331
288
|
return true
|
332
289
|
rescue Errno::ESRCH
|
@@ -334,22 +291,6 @@ class Symphony::Daemon
|
|
334
291
|
end
|
335
292
|
|
336
293
|
|
337
|
-
### Start a new Symphony::Task and return its PID.
|
338
|
-
def start_worker( task_class )
|
339
|
-
return if self.shutting_down?
|
340
|
-
|
341
|
-
self.log.debug "Starting a %p." % [ task_class ]
|
342
|
-
task_class.before_fork
|
343
|
-
pid = Process.fork do
|
344
|
-
task_class.after_fork
|
345
|
-
task_class.run
|
346
|
-
end
|
347
|
-
Process.setpgid( pid, 0 )
|
348
|
-
|
349
|
-
return pid
|
350
|
-
end
|
351
|
-
|
352
|
-
|
353
294
|
### Clean up after any children that have died.
|
354
295
|
def reap_children( *pids )
|
355
296
|
self.log.debug "Reaping children."
|
@@ -362,7 +303,7 @@ class Symphony::Daemon
|
|
362
303
|
self.reap_specific_child( pid )
|
363
304
|
end
|
364
305
|
end
|
365
|
-
rescue Errno::ECHILD
|
306
|
+
rescue Errno::ECHILD
|
366
307
|
self.log.debug "No more children to reap."
|
367
308
|
end
|
368
309
|
|
@@ -375,9 +316,8 @@ class Symphony::Daemon
|
|
375
316
|
pid, status = Process.waitpid2( -1, Process::WNOHANG|Process::WUNTRACED )
|
376
317
|
self.log.debug " waitpid2 returned: [ %p, %p ]" % [ pid, status ]
|
377
318
|
while pid
|
378
|
-
self.
|
379
|
-
self.
|
380
|
-
self.running_tasks.delete( pid )
|
319
|
+
self.notify_group( pid, status )
|
320
|
+
self.task_pids.delete( pid )
|
381
321
|
|
382
322
|
pid, status = Process.waitpid2( -1, Process::WNOHANG|Process::WUNTRACED )
|
383
323
|
self.log.debug " waitpid2 returned: [ %p, %p ]" % [ pid, status ]
|
@@ -388,15 +328,22 @@ class Symphony::Daemon
|
|
388
328
|
### Wait on the child associated with the given +pid+, deleting it from the
|
389
329
|
### running tasks Hash if successful.
|
390
330
|
def reap_specific_child( pid )
|
391
|
-
|
392
|
-
if
|
393
|
-
self.
|
394
|
-
self.
|
395
|
-
self.adjust_throttle( status.success? ? -1 : 1 )
|
331
|
+
pid, status = Process.waitpid2( pid )
|
332
|
+
if pid
|
333
|
+
self.notify_group( pid, status )
|
334
|
+
self.task_pids.delete( pid )
|
396
335
|
else
|
397
336
|
self.log.debug "Child %d no reapy." % [ pid ]
|
398
337
|
end
|
399
338
|
end
|
400
339
|
|
401
340
|
|
341
|
+
### Notify the task group the specified +pid+ belongs to that its child exited
|
342
|
+
### with the specified +status+.
|
343
|
+
def notify_group( pid, status )
|
344
|
+
return unless self.running?
|
345
|
+
group = self.task_pids[ pid ]
|
346
|
+
group.on_child_exit( pid, status )
|
347
|
+
end
|
348
|
+
|
402
349
|
end # class Symphony::Daemon
|