dosire-god 0.7.9

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 (108) hide show
  1. data/History.txt +261 -0
  2. data/Manifest.txt +107 -0
  3. data/README.txt +59 -0
  4. data/Rakefile +35 -0
  5. data/bin/god +127 -0
  6. data/examples/events.god +84 -0
  7. data/examples/gravatar.god +54 -0
  8. data/examples/single.god +66 -0
  9. data/ext/god/extconf.rb +55 -0
  10. data/ext/god/kqueue_handler.c +123 -0
  11. data/ext/god/netlink_handler.c +167 -0
  12. data/init/god +42 -0
  13. data/lib/god/behavior.rb +52 -0
  14. data/lib/god/behaviors/clean_pid_file.rb +21 -0
  15. data/lib/god/behaviors/clean_unix_socket.rb +21 -0
  16. data/lib/god/behaviors/notify_when_flapping.rb +51 -0
  17. data/lib/god/cli/command.rb +206 -0
  18. data/lib/god/cli/run.rb +177 -0
  19. data/lib/god/cli/version.rb +23 -0
  20. data/lib/god/condition.rb +96 -0
  21. data/lib/god/conditions/always.rb +23 -0
  22. data/lib/god/conditions/complex.rb +86 -0
  23. data/lib/god/conditions/cpu_usage.rb +80 -0
  24. data/lib/god/conditions/degrading_lambda.rb +52 -0
  25. data/lib/god/conditions/disk_usage.rb +27 -0
  26. data/lib/god/conditions/flapping.rb +128 -0
  27. data/lib/god/conditions/http_response_code.rb +168 -0
  28. data/lib/god/conditions/lambda.rb +25 -0
  29. data/lib/god/conditions/memory_usage.rb +82 -0
  30. data/lib/god/conditions/process_exits.rb +72 -0
  31. data/lib/god/conditions/process_running.rb +74 -0
  32. data/lib/god/conditions/tries.rb +44 -0
  33. data/lib/god/configurable.rb +57 -0
  34. data/lib/god/contact.rb +106 -0
  35. data/lib/god/contacts/email.rb +95 -0
  36. data/lib/god/dependency_graph.rb +41 -0
  37. data/lib/god/diagnostics.rb +37 -0
  38. data/lib/god/driver.rb +206 -0
  39. data/lib/god/errors.rb +24 -0
  40. data/lib/god/event_handler.rb +111 -0
  41. data/lib/god/event_handlers/dummy_handler.rb +13 -0
  42. data/lib/god/event_handlers/kqueue_handler.rb +17 -0
  43. data/lib/god/event_handlers/netlink_handler.rb +13 -0
  44. data/lib/god/logger.rb +120 -0
  45. data/lib/god/metric.rb +59 -0
  46. data/lib/god/process.rb +327 -0
  47. data/lib/god/registry.rb +32 -0
  48. data/lib/god/simple_logger.rb +53 -0
  49. data/lib/god/socket.rb +96 -0
  50. data/lib/god/sugar.rb +47 -0
  51. data/lib/god/system/portable_poller.rb +42 -0
  52. data/lib/god/system/process.rb +42 -0
  53. data/lib/god/system/slash_proc_poller.rb +82 -0
  54. data/lib/god/task.rb +487 -0
  55. data/lib/god/timeline.rb +25 -0
  56. data/lib/god/trigger.rb +43 -0
  57. data/lib/god/watch.rb +183 -0
  58. data/lib/god.rb +644 -0
  59. data/test/configs/child_events/child_events.god +44 -0
  60. data/test/configs/child_events/simple_server.rb +3 -0
  61. data/test/configs/child_polls/child_polls.god +37 -0
  62. data/test/configs/child_polls/simple_server.rb +12 -0
  63. data/test/configs/complex/complex.god +59 -0
  64. data/test/configs/complex/simple_server.rb +3 -0
  65. data/test/configs/contact/contact.god +74 -0
  66. data/test/configs/contact/simple_server.rb +3 -0
  67. data/test/configs/daemon_events/daemon_events.god +37 -0
  68. data/test/configs/daemon_events/simple_server.rb +8 -0
  69. data/test/configs/daemon_events/simple_server_stop.rb +11 -0
  70. data/test/configs/daemon_polls/daemon_polls.god +17 -0
  71. data/test/configs/daemon_polls/simple_server.rb +6 -0
  72. data/test/configs/degrading_lambda/degrading_lambda.god +31 -0
  73. data/test/configs/degrading_lambda/tcp_server.rb +15 -0
  74. data/test/configs/matias/matias.god +50 -0
  75. data/test/configs/real.rb +59 -0
  76. data/test/configs/running_load/running_load.god +16 -0
  77. data/test/configs/stress/simple_server.rb +3 -0
  78. data/test/configs/stress/stress.god +15 -0
  79. data/test/configs/task/logs/.placeholder +0 -0
  80. data/test/configs/task/task.god +26 -0
  81. data/test/configs/test.rb +61 -0
  82. data/test/helper.rb +151 -0
  83. data/test/suite.rb +6 -0
  84. data/test/test_behavior.rb +21 -0
  85. data/test/test_condition.rb +50 -0
  86. data/test/test_conditions_disk_usage.rb +56 -0
  87. data/test/test_conditions_http_response_code.rb +109 -0
  88. data/test/test_conditions_process_running.rb +44 -0
  89. data/test/test_conditions_tries.rb +67 -0
  90. data/test/test_contact.rb +109 -0
  91. data/test/test_dependency_graph.rb +62 -0
  92. data/test/test_driver.rb +11 -0
  93. data/test/test_event_handler.rb +80 -0
  94. data/test/test_god.rb +598 -0
  95. data/test/test_handlers_kqueue_handler.rb +16 -0
  96. data/test/test_logger.rb +63 -0
  97. data/test/test_metric.rb +72 -0
  98. data/test/test_process.rb +246 -0
  99. data/test/test_registry.rb +15 -0
  100. data/test/test_socket.rb +42 -0
  101. data/test/test_sugar.rb +42 -0
  102. data/test/test_system_portable_poller.rb +17 -0
  103. data/test/test_system_process.rb +30 -0
  104. data/test/test_task.rb +262 -0
  105. data/test/test_timeline.rb +37 -0
  106. data/test/test_trigger.rb +59 -0
  107. data/test/test_watch.rb +279 -0
  108. metadata +186 -0
data/lib/god.rb ADDED
@@ -0,0 +1,644 @@
1
+ $:.unshift File.dirname(__FILE__) # For use/testing when no gem is installed
2
+
3
+ # rubygems
4
+ require 'rubygems'
5
+
6
+ # core
7
+ require 'stringio'
8
+ require 'fileutils'
9
+
10
+ begin
11
+ require 'fastthread'
12
+ rescue LoadError
13
+ ensure
14
+ require 'thread'
15
+ end
16
+
17
+ # stdlib
18
+
19
+ # internal requires
20
+ require 'god/errors'
21
+ require 'god/simple_logger'
22
+ require 'god/logger'
23
+
24
+ require 'god/system/process'
25
+ require 'god/system/portable_poller'
26
+ require 'god/system/slash_proc_poller'
27
+
28
+ require 'god/dependency_graph'
29
+ require 'god/timeline'
30
+ require 'god/configurable'
31
+
32
+ require 'god/task'
33
+
34
+ require 'god/behavior'
35
+ require 'god/behaviors/clean_pid_file'
36
+ require 'god/behaviors/clean_unix_socket'
37
+ require 'god/behaviors/notify_when_flapping'
38
+
39
+ require 'god/condition'
40
+ require 'god/conditions/process_running'
41
+ require 'god/conditions/process_exits'
42
+ require 'god/conditions/tries'
43
+ require 'god/conditions/memory_usage'
44
+ require 'god/conditions/cpu_usage'
45
+ require 'god/conditions/always'
46
+ require 'god/conditions/lambda'
47
+ require 'god/conditions/degrading_lambda'
48
+ require 'god/conditions/flapping'
49
+ require 'god/conditions/http_response_code'
50
+ require 'god/conditions/disk_usage'
51
+ require 'god/conditions/complex'
52
+
53
+ require 'god/contact'
54
+ require 'god/contacts/email'
55
+ begin
56
+ require 'god/contacts/jabber'
57
+ rescue LoadError
58
+ end
59
+
60
+ require 'god/socket'
61
+ require 'god/driver'
62
+
63
+ require 'god/metric'
64
+ require 'god/watch'
65
+
66
+ require 'god/trigger'
67
+ require 'god/event_handler'
68
+ require 'god/registry'
69
+ require 'god/process'
70
+
71
+ require 'god/sugar'
72
+
73
+ require 'god/cli/version'
74
+ require 'god/cli/command'
75
+
76
+ require 'god/diagnostics'
77
+
78
+ $:.unshift File.join(File.dirname(__FILE__), *%w[.. ext god])
79
+
80
+ # App wide logging system
81
+ LOG = God::Logger.new
82
+
83
+ def applog(watch, level, text)
84
+ LOG.log(watch, level, text)
85
+ end
86
+
87
+ # The $run global determines whether god should be started when the
88
+ # program would normally end. This should be set to true if when god
89
+ # should be started (e.g. `god -c <config file>`) and false otherwise
90
+ # (e.g. `god status`)
91
+ $run ||= nil
92
+
93
+ GOD_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
94
+
95
+ # Return the binding of god's root level
96
+ def root_binding
97
+ binding
98
+ end
99
+
100
+ module Kernel
101
+ alias_method :abort_orig, :abort
102
+
103
+ def abort(text = nil)
104
+ $run = false
105
+ applog(nil, :error, text) if text
106
+ exit(1)
107
+ end
108
+
109
+ alias_method :exit_orig, :exit
110
+
111
+ def exit(code = 0)
112
+ $run = false
113
+ exit_orig(code)
114
+ end
115
+ end
116
+
117
+ class Module
118
+ def safe_attr_accessor(*args)
119
+ args.each do |arg|
120
+ define_method((arg.to_s + "=").intern) do |other|
121
+ if !self.running && self.inited
122
+ abort "God.#{arg} must be set before any Tasks are defined"
123
+ end
124
+
125
+ if self.running && self.inited
126
+ applog(nil, :warn, "God.#{arg} can't be set while god is running")
127
+ return
128
+ end
129
+
130
+ instance_variable_set(('@' + arg.to_s).intern, other)
131
+ end
132
+
133
+ define_method(arg) do
134
+ instance_variable_get(('@' + arg.to_s).intern)
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ module God
141
+ VERSION = '0.7.9'
142
+
143
+ LOG_BUFFER_SIZE_DEFAULT = 100
144
+ PID_FILE_DIRECTORY_DEFAULTS = ['/var/run/god', '~/.god/pids']
145
+ DRB_PORT_DEFAULT = 17165
146
+ DRB_ALLOW_DEFAULT = ['127.0.0.1']
147
+ LOG_LEVEL_DEFAULT = :info
148
+
149
+ class << self
150
+ # user configurable
151
+ safe_attr_accessor :pid,
152
+ :host,
153
+ :port,
154
+ :allow,
155
+ :log_buffer_size,
156
+ :pid_file_directory,
157
+ :log_file,
158
+ :log_level,
159
+ :use_events
160
+
161
+ # internal
162
+ attr_accessor :inited,
163
+ :running,
164
+ :pending_watches,
165
+ :pending_watch_states,
166
+ :server,
167
+ :watches,
168
+ :groups,
169
+ :contacts,
170
+ :contact_groups,
171
+ :main
172
+ end
173
+
174
+ # initialize class instance variables
175
+ self.pid = nil
176
+ self.host = nil
177
+ self.port = nil
178
+ self.allow = nil
179
+ self.log_buffer_size = nil
180
+ self.pid_file_directory = nil
181
+ self.log_level = nil
182
+
183
+ # Initialize internal data.
184
+ #
185
+ # Returns nothing
186
+ def self.internal_init
187
+ # only do this once
188
+ return if self.inited
189
+
190
+ # variable init
191
+ self.watches = {}
192
+ self.groups = {}
193
+ self.pending_watches = []
194
+ self.pending_watch_states = {}
195
+ self.contacts = {}
196
+ self.contact_groups = {}
197
+
198
+ # set defaults
199
+ self.log_buffer_size ||= LOG_BUFFER_SIZE_DEFAULT
200
+ self.port ||= DRB_PORT_DEFAULT
201
+ self.allow ||= DRB_ALLOW_DEFAULT
202
+ self.log_level ||= LOG_LEVEL_DEFAULT
203
+
204
+ # additional setup
205
+ self.setup
206
+
207
+ # log level
208
+ log_level_map = {:debug => Logger::DEBUG,
209
+ :info => Logger::INFO,
210
+ :warn => Logger::WARN,
211
+ :error => Logger::ERROR,
212
+ :fatal => Logger::FATAL}
213
+ LOG.level = log_level_map[self.log_level]
214
+
215
+ # init has been executed
216
+ self.inited = true
217
+
218
+ # not yet running
219
+ self.running = false
220
+ end
221
+
222
+ # Instantiate a new, empty Watch object and pass it to the mandatory
223
+ # block. The attributes of the watch will be set by the configuration
224
+ # file.
225
+ #
226
+ # Aborts on duplicate watch name
227
+ # invalid watch
228
+ # conflicting group name
229
+ #
230
+ # Returns nothing
231
+ def self.watch(&block)
232
+ self.task(Watch, &block)
233
+ end
234
+
235
+ # Instantiate a new, empty Task object and yield it to the mandatory
236
+ # block. The attributes of the task will be set by the configuration
237
+ # file.
238
+ #
239
+ # Aborts on duplicate task name
240
+ # invalid task
241
+ # conflicting group name
242
+ #
243
+ # Returns nothing
244
+ def self.task(klass = Task)
245
+ self.internal_init
246
+
247
+ t = klass.new
248
+ yield(t)
249
+
250
+ # do the post-configuration
251
+ t.prepare
252
+
253
+ # if running, completely remove the watch (if necessary) to
254
+ # prepare for the reload
255
+ existing_watch = self.watches[t.name]
256
+ if self.running && existing_watch
257
+ self.pending_watch_states[existing_watch.name] = existing_watch.state
258
+ self.unwatch(existing_watch)
259
+ end
260
+
261
+ # ensure the new watch has a unique name
262
+ if self.watches[t.name] || self.groups[t.name]
263
+ abort "Task name '#{t.name}' already used for a Task or Group"
264
+ end
265
+
266
+ # ensure watch is internally valid
267
+ t.valid? || abort("Task '#{t.name}' is not valid (see above)")
268
+
269
+ # add to list of watches
270
+ self.watches[t.name] = t
271
+
272
+ # add to pending watches
273
+ self.pending_watches << t
274
+
275
+ # add to group if specified
276
+ if t.group
277
+ # ensure group name hasn't been used for a watch already
278
+ if self.watches[t.group]
279
+ abort "Group name '#{t.group}' already used for a Task"
280
+ end
281
+
282
+ self.groups[t.group] ||= []
283
+ self.groups[t.group] << t
284
+ end
285
+
286
+ # register watch
287
+ t.register!
288
+
289
+ # log
290
+ if self.running && existing_watch
291
+ applog(t, :info, "#{t.name} Reloaded config")
292
+ elsif self.running
293
+ applog(t, :info, "#{t.name} Loaded config")
294
+ end
295
+ end
296
+
297
+ # Unmonitor and remove the given watch from god.
298
+ # +watch+ is the Watch to remove
299
+ #
300
+ # Returns nothing
301
+ def self.unwatch(watch)
302
+ # unmonitor
303
+ watch.unmonitor unless watch.state == :unmonitored
304
+
305
+ # unregister
306
+ watch.unregister!
307
+
308
+ # remove from watches
309
+ self.watches.delete(watch.name)
310
+
311
+ # remove from groups
312
+ if watch.group
313
+ self.groups[watch.group].delete(watch)
314
+ end
315
+
316
+ applog(watch, :info, "#{watch.name} unwatched")
317
+ end
318
+
319
+ # Instantiate a new Contact of the given kind and send it to the block.
320
+ # Then prepare, validate, and record the Contact.
321
+ # +kind+ is the contact class specifier
322
+ #
323
+ # Aborts on invalid kind
324
+ # duplicate contact name
325
+ # invalid contact
326
+ # conflicting group name
327
+ #
328
+ # Returns nothing
329
+ def self.contact(kind)
330
+ self.internal_init
331
+
332
+ # create the contact
333
+ begin
334
+ c = Contact.generate(kind)
335
+ rescue NoSuchContactError => e
336
+ abort e.message
337
+ end
338
+
339
+ # send to block so config can set attributes
340
+ yield(c) if block_given?
341
+
342
+ # call prepare on the contact
343
+ c.prepare
344
+
345
+ # remove existing contacts of same name
346
+ existing_contact = self.contacts[c.name]
347
+ if self.running && existing_contact
348
+ self.uncontact(existing_contact)
349
+ end
350
+
351
+ # warn and noop if the contact has been defined before
352
+ if self.contacts[c.name] || self.contact_groups[c.name]
353
+ applog(nil, :warn, "Contact name '#{c.name}' already used for a Contact or Contact Group")
354
+ return
355
+ end
356
+
357
+ # abort if the Contact is invalid, the Contact will have printed
358
+ # out its own error messages by now
359
+ unless Contact.valid?(c) && c.valid?
360
+ abort "Exiting on invalid contact"
361
+ end
362
+
363
+ # add to list of contacts
364
+ self.contacts[c.name] = c
365
+
366
+ # add to contact group if specified
367
+ if c.group
368
+ # ensure group name hasn't been used for a contact already
369
+ if self.contacts[c.group]
370
+ abort "Contact Group name '#{c.group}' already used for a Contact"
371
+ end
372
+
373
+ self.contact_groups[c.group] ||= []
374
+ self.contact_groups[c.group] << c
375
+ end
376
+ end
377
+
378
+ # Remove the given contact from god.
379
+ # +contact+ is the Contact to remove
380
+ #
381
+ # Returns nothing
382
+ def self.uncontact(contact)
383
+ self.contacts.delete(contact.name)
384
+ if contact.group
385
+ self.contact_groups[contact.group].delete(contact)
386
+ end
387
+ end
388
+
389
+ # Control the lifecycle of the given task(s).
390
+ # +name+ is the name of a task/group (String)
391
+ # +command+ is the command to run (String)
392
+ # one of: "start"
393
+ # "monitor"
394
+ # "restart"
395
+ # "stop"
396
+ # "unmonitor"
397
+ # "remove"
398
+ #
399
+ # Returns String[]:task_names
400
+ def self.control(name, command)
401
+ # get the list of items
402
+ items = Array(self.watches[name] || self.groups[name]).dup
403
+
404
+ jobs = []
405
+
406
+ # do the command
407
+ case command
408
+ when "start", "monitor"
409
+ items.each { |w| jobs << Thread.new { w.monitor if w.state != :up } }
410
+ when "restart"
411
+ items.each { |w| jobs << Thread.new { w.move(:restart) } }
412
+ when "stop"
413
+ items.each { |w| jobs << Thread.new { w.unmonitor.action(:stop) if w.state != :unmonitored } }
414
+ when "unmonitor"
415
+ items.each { |w| jobs << Thread.new { w.unmonitor if w.state != :unmonitored } }
416
+ when "remove"
417
+ items.each { |w| self.unwatch(w) }
418
+ else
419
+ raise InvalidCommandError.new
420
+ end
421
+
422
+ jobs.each { |j| j.join }
423
+
424
+ items.map { |x| x.name }
425
+ end
426
+
427
+ # Unmonitor and stop all tasks.
428
+ #
429
+ # Returns true on success
430
+ # false if all tasks could not be stopped within 10 seconds
431
+ def self.stop_all
432
+ self.watches.sort.each do |name, w|
433
+ Thread.new do
434
+ w.unmonitor if w.state != :unmonitored
435
+ w.action(:stop) if w.alive?
436
+ end
437
+ end
438
+
439
+ 10.times do
440
+ return true unless self.watches.map { |name, w| w.alive? }.any?
441
+ sleep 1
442
+ end
443
+
444
+ return false
445
+ end
446
+
447
+ # Force the termination of god.
448
+ # * Clean up pid file if one exists
449
+ # * Stop DRb service
450
+ # * Hard exit using exit!
451
+ #
452
+ # Never returns because the process will no longer exist!
453
+ def self.terminate
454
+ FileUtils.rm_f(self.pid) if self.pid
455
+ self.server.stop if self.server
456
+ exit!(0)
457
+ end
458
+
459
+ # Gather the status of each task.
460
+ #
461
+ # Examples
462
+ # God.status
463
+ # # => { 'mongrel' => :up, 'nginx' => :up }
464
+ #
465
+ # Returns { String:task_name => Symbol:status, ... }
466
+ def self.status
467
+ info = {}
468
+ self.watches.map do |name, w|
469
+ info[name] = {:state => w.state, :group => w.group}
470
+ end
471
+ info
472
+ end
473
+
474
+ # Log lines for the given task since the specified time.
475
+ # +watch_name+ is the name of the task (may be abbreviated)
476
+ # +since+ is the Time since which to report log lines
477
+ #
478
+ # Raises God::NoSuchWatchError if no tasks matched
479
+ #
480
+ # Returns String:joined_log_lines
481
+ def self.running_log(watch_name, since)
482
+ matches = pattern_match(watch_name, self.watches.keys)
483
+
484
+ unless matches.first
485
+ raise NoSuchWatchError.new
486
+ end
487
+
488
+ LOG.watch_log_since(matches.first, since)
489
+ end
490
+
491
+ # Load a config file into a running god instance. Rescues any exceptions
492
+ # that the config may raise and reports these back to the caller.
493
+ # +code+ is a String containing the config file
494
+ # +filename+ is the filename of the config file
495
+ #
496
+ # Returns [String[]:task_names, String:errors]
497
+ def self.running_load(code, filename)
498
+ errors = ""
499
+ watches = []
500
+
501
+ begin
502
+ LOG.start_capture
503
+
504
+ Gem.clear_paths
505
+ eval(code, root_binding, filename)
506
+ self.pending_watches.each do |w|
507
+ if previous_state = self.pending_watch_states[w.name]
508
+ w.monitor unless previous_state == :unmonitored
509
+ else
510
+ w.monitor if w.autostart?
511
+ end
512
+ end
513
+ watches = self.pending_watches.dup
514
+ self.pending_watches.clear
515
+ self.pending_watch_states.clear
516
+ rescue Exception => e
517
+ # don't ever let running_load take down god
518
+ errors << LOG.finish_capture
519
+
520
+ unless e.instance_of?(SystemExit)
521
+ errors << e.message << "\n"
522
+ errors << e.backtrace.join("\n")
523
+ end
524
+ end
525
+
526
+ names = watches.map { |x| x.name }
527
+ [names, errors]
528
+ end
529
+
530
+ # Load the given file(s) according to the given glob.
531
+ # +glob+ is the glob-enabled path to load
532
+ #
533
+ # Returns nothing
534
+ def self.load(glob)
535
+ Dir[glob].each do |f|
536
+ Kernel.load f
537
+ end
538
+ end
539
+
540
+ def self.setup
541
+ if self.pid_file_directory
542
+ # pid file dir was specified, ensure it is created and writable
543
+ unless File.exist?(self.pid_file_directory)
544
+ begin
545
+ FileUtils.mkdir_p(self.pid_file_directory)
546
+ rescue Errno::EACCES => e
547
+ abort "Failed to create pid file directory: #{e.message}"
548
+ end
549
+ end
550
+
551
+ unless File.writable?(self.pid_file_directory)
552
+ abort "The pid file directory (#{self.pid_file_directory}) is not writable by #{Etc.getlogin}"
553
+ end
554
+ else
555
+ # no pid file dir specified, try defaults
556
+ PID_FILE_DIRECTORY_DEFAULTS.each do |idir|
557
+ dir = File.expand_path(idir)
558
+ begin
559
+ FileUtils.mkdir_p(dir)
560
+ if File.writable?(dir)
561
+ self.pid_file_directory = dir
562
+ break
563
+ end
564
+ rescue Errno::EACCES => e
565
+ end
566
+ end
567
+
568
+ unless self.pid_file_directory
569
+ dirs = PID_FILE_DIRECTORY_DEFAULTS.map { |x| File.expand_path(x) }
570
+ abort "No pid file directory exists, could be created, or is writable at any of #{dirs.join(', ')}"
571
+ end
572
+ end
573
+
574
+ applog(nil, :info, "Using pid file directory: #{self.pid_file_directory}")
575
+ end
576
+
577
+ # Initialize and startup the machinery that makes god work.
578
+ #
579
+ # Returns nothing
580
+ def self.start
581
+ self.internal_init
582
+
583
+ # instantiate server
584
+ self.server = Socket.new(self.port)
585
+
586
+ # start monitoring any watches set to autostart
587
+ self.watches.values.each { |w| w.monitor if w.autostart? }
588
+
589
+ # clear pending watches
590
+ self.pending_watches.clear
591
+
592
+ # mark as running
593
+ self.running = true
594
+
595
+ # don't exit
596
+ self.main =
597
+ Thread.new do
598
+ loop do
599
+ sleep 60
600
+ end
601
+ end
602
+
603
+ self.main.join
604
+ end
605
+
606
+ # To be called on program exit to start god
607
+ #
608
+ # Returns nothing
609
+ def self.at_exit
610
+ self.start
611
+ end
612
+
613
+ # private
614
+
615
+ # Match a shortened pattern against a list of String candidates.
616
+ # The pattern is expanded into a regular expression by
617
+ # inserting .* between each character.
618
+ # +pattern+ is the String containing the abbreviation
619
+ # +list+ is the Array of Strings to match against
620
+ #
621
+ # Examples
622
+ #
623
+ # list = %w{ foo bar bars }
624
+ # pattern = 'br'
625
+ # God.pattern_match(list, pattern)
626
+ # # => ['bar', 'bars']
627
+ #
628
+ # Returns String[]:matched_elements
629
+ def self.pattern_match(pattern, list)
630
+ regex = pattern.split('').join('.*')
631
+
632
+ list.select do |item|
633
+ item =~ Regexp.new(regex)
634
+ end
635
+ end
636
+ end
637
+
638
+ # Runs immediately before the program exits. If $run is true,
639
+ # start god, if $run is false, exit normally.
640
+ #
641
+ # Returns nothing
642
+ at_exit do
643
+ God.at_exit if $run
644
+ end
@@ -0,0 +1,44 @@
1
+ God.watch do |w|
2
+ w.name = "child-events"
3
+ w.interval = 5.seconds
4
+ w.start = File.join(GOD_ROOT, *%w[test configs child_events simple_server.rb])
5
+ # w.log = File.join(GOD_ROOT, *%w[test configs child_events god.log])
6
+
7
+ # determine the state on startup
8
+ w.transition(:init, { true => :up, false => :start }) do |on|
9
+ on.condition(:process_running) do |c|
10
+ c.running = true
11
+ end
12
+ end
13
+
14
+ # determine when process has finished starting
15
+ w.transition([:start, :restart], :up) do |on|
16
+ on.condition(:process_running) do |c|
17
+ c.running = true
18
+ end
19
+
20
+ # failsafe
21
+ on.condition(:tries) do |c|
22
+ c.times = 2
23
+ c.transition = :start
24
+ end
25
+ end
26
+
27
+ # start if process is not running
28
+ w.transition(:up, :start) do |on|
29
+ on.condition(:process_exits)
30
+ end
31
+
32
+ # lifecycle
33
+ w.lifecycle do |on|
34
+ on.condition(:flapping) do |c|
35
+ c.to_state = [:start, :restart]
36
+ c.times = 5
37
+ c.within = 20.seconds
38
+ c.transition = :unmonitored
39
+ c.retry_in = 10.seconds
40
+ c.retry_times = 2
41
+ c.retry_within = 5.minutes
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,3 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ loop { puts 'server'; sleep 1 }