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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f77d5c29817bfb9b9a524045e2a09dce3f07f469
4
- data.tar.gz: ff3c97b9eab7dfdc1528cd766f6018b508c36698
3
+ metadata.gz: 095d96d07d5e07c3b0cecfa55d5705e1c5dddb55
4
+ data.tar.gz: cfc61ebc105673602505d959956bb2a24a939e3a
5
5
  SHA512:
6
- metadata.gz: fe1f7745132368e2b7414fa27ac34a8be01a40af388655041dd61718fc6b8dbcf9db44ed9337b60df4ab72150a925dfafbd64b518ecabf9642e89a050d17d6bd
7
- data.tar.gz: 119c5a6aebb5d52341f1b901ac29457d6dcb9d810745c59d50b678bd0aec5c0bdafbdaabef506662d78338d7c0a94124c39e3ce4de96c289b4db06349622f74c
6
+ metadata.gz: 2c9443c031f087f3ae3633be51d70d9dfc7adfd817ac72972cd992a72716525d76c2965303bfbb726d7e06acb5143ec168b3e379b4253cbee1ed1643e25b7397
7
+ data.tar.gz: 7661d67300b397b7b97e8b24e011c8328bd3737c9f89b44cc668121589e5ec63364c5a957f09c148ea407b872bbda3ec9faf37abe0849d086a7039cf3fb15da9
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] [qbase, qtip, route-options.patch, tip]
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] [qparent]
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] [github/master]
166
+ [fa6a367872f4]
79
167
 
80
168
  * TODO.md, lib/symphony/queue.rb, spec/symphony/queue_spec.rb:
81
169
  Update the TO-DO list
@@ -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
@@ -4,7 +4,7 @@ History.rdoc
4
4
  Manifest.txt
5
5
  README.rdoc
6
6
  Rakefile
7
- TODO.md
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
@@ -58,7 +58,7 @@ and generate the API documentation.
58
58
 
59
59
  == License
60
60
 
61
- Copyright (c) 2011-2014, Michael Granger and Mahlon E. Smith
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.1'
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.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
@@ -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
@@ -179,7 +179,7 @@ events.
179
179
  acknowledge false # (default: true)
180
180
 
181
181
 
182
- == Worker Model
182
+ == Work Model
183
183
 
184
184
  By default, a task signals that is ready for more messages as soon as
185
185
  it finishes processing one. This isn't always the optimal environment
@@ -12,10 +12,10 @@ module Symphony
12
12
  Configurability
13
13
 
14
14
  # Library version constant
15
- VERSION = '0.8.0'
15
+ VERSION = '0.9.0'
16
16
 
17
17
  # Version-control revision constant
18
- REVISION = %q$Revision: 68352ad02888 $
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
- require 'symphony/mixins'
52
- require 'symphony/queue'
53
- require 'symphony/task'
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
 
@@ -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
- @running_tasks = {}
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 PIDs to task class
131
- attr_reader :running_tasks
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.start_missing_children unless self.shutting_down?
184
- self.wait_for_signals
185
- self.reap_children
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
- @shutting_down = true
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.running_tasks.empty?
153
+ break if self.task_pids.empty?
213
154
  sleep( 1 )
214
- end unless self.running_tasks.empty?
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.running_tasks.empty?
219
- self.log.warn " %d workers remain: sending KILL" % [ self.running_tasks.length ]
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" % [ self.config ]
228
- self.config.reload
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
- ### Start any tasks which aren't already running
267
- def start_missing_children
268
- missing_tasks = self.find_missing_tasks
269
- return if missing_tasks.empty?
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
- # Return unless the throttle period has lapsed
272
- unless self.throttle_seconds < (Time.now - @last_child_started)
273
- self.log.info "Not starting children: throttled for %0.2f seconds" %
274
- [ self.throttle_seconds ]
275
- return
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
- self.log.debug "Starting %d tasks out of %d" % [ missing_tasks.size, self.class.tasks.size ]
279
- missing_tasks.each do |task_class|
280
- pid = self.start_worker( task_class )
281
- self.log.debug " started task %p at pid %d" % [ task_class, pid ]
282
- self.running_tasks[ pid ] = task_class
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
- self.class.tasks.uniq.each do |task_type|
294
- count = self.class.tasks.count( task_type )
295
- missing = count - self.running_tasks.values.count( task_type )
296
- missing.times do
297
- missing_tasks << task_type
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
- ### Return the number of seconds between child startup times.
306
- def throttle_seconds
307
- return 0 unless @throttle.nonzero?
308
- return Math.log( @throttle ) * self.class.throttle_factor
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
- ### Add +adjustment+ to the throttle value, ensuring that it doesn't go
313
- ### below zero.
314
- def adjust_throttle( adjustment=1 )
315
- self.log.debug "Adjusting worker throttle by %d" % [ adjustment ]
316
- @throttle += adjustment
317
- @throttle = 0 if @throttle < 0
318
- @throttle = self.class.throttle_max if @throttle > self.class.throttle_max
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.running_tasks.empty?
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.running_tasks.length, self.running_tasks.keys ]
329
- Process.kill( signal, *self.running_tasks.keys )
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 => err
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.adjust_throttle( status.success? ? -1 : 1 )
379
- self.log.debug "Child %d exited: %p." % [ pid, status ]
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
- spid, status = Process.waitpid2( pid )
392
- if spid
393
- self.log.debug "Child %d exited: %p." % [ spid, status ]
394
- self.running_tasks.delete( spid )
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