god 0.11.0 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. data/Announce.txt +6 -6
  2. data/Gemfile +2 -0
  3. data/History.txt +19 -2
  4. data/{README.txt → LICENSE} +0 -37
  5. data/README.md +31 -0
  6. data/Rakefile +80 -38
  7. data/bin/god +21 -21
  8. data/doc/god.asciidoc +1487 -0
  9. data/doc/intro.asciidoc +20 -0
  10. data/ext/god/extconf.rb +3 -3
  11. data/ext/god/kqueue_handler.c +18 -18
  12. data/ext/god/netlink_handler.c +31 -31
  13. data/god.gemspec +24 -16
  14. data/lib/god.rb +261 -204
  15. data/lib/god/behavior.rb +14 -14
  16. data/lib/god/behaviors/clean_pid_file.rb +5 -5
  17. data/lib/god/behaviors/clean_unix_socket.rb +10 -10
  18. data/lib/god/behaviors/notify_when_flapping.rb +12 -12
  19. data/lib/god/cli/command.rb +59 -46
  20. data/lib/god/cli/run.rb +33 -37
  21. data/lib/god/cli/version.rb +6 -6
  22. data/lib/god/compat19.rb +1 -4
  23. data/lib/god/condition.rb +21 -21
  24. data/lib/god/conditions/always.rb +19 -6
  25. data/lib/god/conditions/complex.rb +18 -18
  26. data/lib/god/conditions/cpu_usage.rb +14 -14
  27. data/lib/god/conditions/degrading_lambda.rb +8 -8
  28. data/lib/god/conditions/disk_usage.rb +5 -5
  29. data/lib/god/conditions/flapping.rb +23 -23
  30. data/lib/god/conditions/http_response_code.rb +35 -19
  31. data/lib/god/conditions/lambda.rb +2 -2
  32. data/lib/god/conditions/memory_usage.rb +13 -13
  33. data/lib/god/conditions/process_exits.rb +14 -20
  34. data/lib/god/conditions/process_running.rb +16 -25
  35. data/lib/god/conditions/socket_responding.rb +132 -0
  36. data/lib/god/conditions/tries.rb +10 -10
  37. data/lib/god/configurable.rb +10 -10
  38. data/lib/god/contact.rb +20 -20
  39. data/lib/god/contacts/email.rb +7 -4
  40. data/lib/god/contacts/jabber.rb +1 -1
  41. data/lib/god/driver.rb +96 -64
  42. data/lib/god/errors.rb +9 -9
  43. data/lib/god/event_handler.rb +19 -19
  44. data/lib/god/event_handlers/dummy_handler.rb +4 -4
  45. data/lib/god/event_handlers/kqueue_handler.rb +3 -3
  46. data/lib/god/event_handlers/netlink_handler.rb +2 -2
  47. data/lib/god/logger.rb +13 -13
  48. data/lib/god/metric.rb +50 -22
  49. data/lib/god/process.rb +53 -52
  50. data/lib/god/registry.rb +7 -7
  51. data/lib/god/simple_logger.rb +14 -14
  52. data/lib/god/socket.rb +11 -11
  53. data/lib/god/sugar.rb +30 -15
  54. data/lib/god/sys_logger.rb +2 -2
  55. data/lib/god/system/portable_poller.rb +8 -8
  56. data/lib/god/system/process.rb +8 -8
  57. data/lib/god/system/slash_proc_poller.rb +13 -13
  58. data/lib/god/task.rb +237 -188
  59. data/lib/god/timeline.rb +5 -5
  60. data/lib/god/trigger.rb +11 -11
  61. data/lib/god/watch.rb +205 -53
  62. data/test/configs/child_events/child_events.god +5 -5
  63. data/test/configs/child_events/simple_server.rb +1 -1
  64. data/test/configs/child_polls/child_polls.god +4 -4
  65. data/test/configs/child_polls/simple_server.rb +4 -4
  66. data/test/configs/complex/complex.god +7 -7
  67. data/test/configs/complex/simple_server.rb +1 -1
  68. data/test/configs/contact/contact.god +1 -1
  69. data/test/configs/contact/simple_server.rb +1 -1
  70. data/test/configs/daemon_events/daemon_events.god +5 -5
  71. data/test/configs/daemon_events/simple_server.rb +1 -1
  72. data/test/configs/daemon_events/simple_server_stop.rb +1 -1
  73. data/test/configs/daemon_polls/daemon_polls.god +3 -3
  74. data/test/configs/daemon_polls/simple_server.rb +1 -1
  75. data/test/configs/degrading_lambda/degrading_lambda.god +3 -3
  76. data/test/configs/keepalive/keepalive.god +9 -0
  77. data/test/configs/keepalive/keepalive.rb +12 -0
  78. data/test/configs/lifecycle/lifecycle.god +2 -2
  79. data/test/configs/matias/matias.god +6 -6
  80. data/test/configs/real.rb +7 -7
  81. data/test/configs/running_load/running_load.god +2 -2
  82. data/test/configs/stop_options/simple_server.rb +1 -1
  83. data/test/configs/stress/simple_server.rb +1 -1
  84. data/test/configs/stress/stress.god +2 -2
  85. data/test/configs/task/task.god +5 -5
  86. data/test/configs/test.rb +7 -7
  87. data/test/helper.rb +8 -8
  88. data/test/test_behavior.rb +3 -3
  89. data/test/test_campfire.rb +1 -2
  90. data/test/test_condition.rb +10 -10
  91. data/test/test_conditions_disk_usage.rb +12 -12
  92. data/test/test_conditions_http_response_code.rb +24 -24
  93. data/test/test_conditions_process_running.rb +7 -7
  94. data/test/test_conditions_socket_responding.rb +122 -0
  95. data/test/test_conditions_tries.rb +12 -12
  96. data/test/test_contact.rb +19 -19
  97. data/test/test_driver.rb +17 -3
  98. data/test/test_event_handler.rb +12 -12
  99. data/test/test_god.rb +195 -117
  100. data/test/test_handlers_kqueue_handler.rb +4 -4
  101. data/test/test_jabber.rb +1 -1
  102. data/test/test_logger.rb +17 -17
  103. data/test/test_metric.rb +16 -16
  104. data/test/test_process.rb +47 -41
  105. data/test/test_prowl.rb +1 -1
  106. data/test/test_registry.rb +2 -2
  107. data/test/test_socket.rb +3 -3
  108. data/test/test_sugar.rb +7 -7
  109. data/test/test_system_portable_poller.rb +1 -1
  110. data/test/test_system_process.rb +5 -5
  111. data/test/test_task.rb +57 -57
  112. data/test/test_timeline.rb +8 -8
  113. data/test/test_trigger.rb +16 -16
  114. data/test/test_watch.rb +69 -62
  115. metadata +182 -69
  116. data/lib/god/dependency_graph.rb +0 -41
  117. data/lib/god/diagnostics.rb +0 -37
  118. data/test/test_dependency_graph.rb +0 -62
@@ -0,0 +1,20 @@
1
+ God: The Ruby Framework for Process Management
2
+ ==============================================
3
+ Tom Preston-Werner <tom@mojombo.com>
4
+
5
+ God is an easy to configure, easy to extend monitoring framework written in
6
+ Ruby.
7
+
8
+ Keeping your server processes and tasks running should be a simple part of
9
+ your deployment process. God aims to be the simplest, most powerful monitoring
10
+ application available.
11
+
12
+ Features
13
+ --------
14
+
15
+ * Config file is written in Ruby
16
+ * Easily write your own custom conditions in Ruby
17
+ * Supports both poll and event based conditions
18
+ * Different poll conditions can have different intervals
19
+ * Integrated notification system (write your own too!)
20
+ * Easily control non-daemonizing scripts
@@ -16,7 +16,7 @@ when /bsd/i, /darwin/i
16
16
  puts "Missing 'sys/event.h' header"
17
17
  fail = true
18
18
  end
19
-
19
+
20
20
  if fail
21
21
  puts
22
22
  puts "Events handler could not be compiled (see above error). Your god installation will not support event conditions."
@@ -31,7 +31,7 @@ when /linux/i
31
31
  puts "You may need to install a header package for your system"
32
32
  fail = true
33
33
  end
34
-
34
+
35
35
  unless have_header('linux/connector.h') && have_header('linux/cn_proc.h')
36
36
  puts
37
37
  puts "Missing 'linux/connector.h', or 'linux/cn_proc.h' header(s)"
@@ -39,7 +39,7 @@ when /linux/i
39
39
  puts "You may need to install a header package for your system"
40
40
  fail = true
41
41
  end
42
-
42
+
43
43
  if fail
44
44
  puts
45
45
  puts "Events handler could not be compiled (see above error). Your god installation will not support event conditions."
@@ -31,11 +31,11 @@ kqh_event_mask(VALUE klass, VALUE sym)
31
31
  } else {
32
32
  rb_raise(rb_eNotImpError, "Event `%s` not implemented", rb_id2name(id));
33
33
  }
34
-
34
+
35
35
  return Qnil;
36
36
  }
37
37
 
38
-
38
+
39
39
  VALUE
40
40
  kqh_monitor_process(VALUE klass, VALUE pid, VALUE mask)
41
41
  {
@@ -43,18 +43,18 @@ kqh_monitor_process(VALUE klass, VALUE pid, VALUE mask)
43
43
  ID event;
44
44
 
45
45
  (void)event; //!< Silence warning about unused var, should be removed?
46
-
46
+
47
47
  u_int fflags = NUM2UINT(mask);
48
-
48
+
49
49
  EV_SET(&new_event, FIX2UINT(pid), EVFILT_PROC,
50
50
  EV_ADD | EV_ENABLE, fflags, 0, 0);
51
-
51
+
52
52
  if (-1 == kevent(kq, &new_event, 1, NULL, 0, NULL)) {
53
53
  rb_raise(rb_eStandardError, "%s", strerror(errno));
54
54
  }
55
-
55
+
56
56
  num_events = NUM_EVENTS;
57
-
57
+
58
58
  return Qnil;
59
59
  }
60
60
 
@@ -64,23 +64,23 @@ kqh_handle_events()
64
64
  int nevents, i, num_to_fetch;
65
65
  struct kevent *events;
66
66
  fd_set read_set;
67
-
67
+
68
68
  FD_ZERO(&read_set);
69
69
  FD_SET(kq, &read_set);
70
-
70
+
71
71
  // Don't actually run this method until we've got an event
72
- rb_thread_select(kq + 1, &read_set, NULL, NULL, NULL);
73
-
72
+ rb_thread_select(kq + 1, &read_set, NULL, NULL, NULL);
73
+
74
74
  // Grabbing num_events once for thread safety
75
75
  num_to_fetch = num_events;
76
76
  events = (struct kevent*)malloc(num_to_fetch * sizeof(struct kevent));
77
-
77
+
78
78
  if (NULL == events) {
79
79
  rb_raise(rb_eStandardError, "%s", strerror(errno));
80
80
  }
81
-
81
+
82
82
  nevents = kevent(kq, NULL, 0, events, num_to_fetch, NULL);
83
-
83
+
84
84
  if (-1 == nevents) {
85
85
  free(events);
86
86
  rb_raise(rb_eStandardError, "%s", strerror(errno));
@@ -93,7 +93,7 @@ kqh_handle_events()
93
93
  }
94
94
  }
95
95
  }
96
-
96
+
97
97
  free(events);
98
98
 
99
99
  return INT2FIX(nevents);
@@ -103,17 +103,17 @@ void
103
103
  Init_kqueue_handler_ext()
104
104
  {
105
105
  kq = kqueue();
106
-
106
+
107
107
  if (kq == -1) {
108
108
  rb_raise(rb_eStandardError, "kqueue initilization failed");
109
109
  }
110
-
110
+
111
111
  proc_exit = rb_intern("proc_exit");
112
112
  proc_fork = rb_intern("proc_fork");
113
113
  m_call = rb_intern("call");
114
114
  m_size = rb_intern("size");
115
115
  m_deregister = rb_intern("deregister");
116
-
116
+
117
117
  mGod = rb_const_get(rb_cObject, rb_intern("God"));
118
118
  cEventHandler = rb_const_get(mGod, rb_intern("EventHandler"));
119
119
  cKQueueHandler = rb_define_class_under(mGod, "KQueueHandler", rb_cObject);
@@ -28,42 +28,42 @@ nlh_handle_events()
28
28
  char buff[CONNECTOR_MAX_MSG_SIZE];
29
29
  struct nlmsghdr *hdr;
30
30
  struct proc_event *event;
31
-
31
+
32
32
  VALUE extra_data;
33
-
33
+
34
34
  fd_set fds;
35
-
35
+
36
36
  FD_ZERO(&fds);
37
37
  FD_SET(nl_sock, &fds);
38
-
38
+
39
39
  if (0 > rb_thread_select(nl_sock + 1, &fds, NULL, NULL, NULL)) {
40
40
  rb_raise(rb_eStandardError, "%s", strerror(errno));
41
41
  }
42
-
42
+
43
43
  /* If there were no events detected, return */
44
44
  if (! FD_ISSET(nl_sock, &fds)) {
45
45
  return INT2FIX(0);
46
46
  }
47
-
48
- /* if there are events, make calls */
47
+
48
+ /* if there are events, make calls */
49
49
  if (-1 == recv(nl_sock, buff, sizeof(buff), 0)) {
50
50
  rb_raise(rb_eStandardError, "%s", strerror(errno));
51
51
  }
52
-
52
+
53
53
  hdr = (struct nlmsghdr *)buff;
54
-
54
+
55
55
  if (NLMSG_ERROR == hdr->nlmsg_type) {
56
56
  rb_raise(rb_eStandardError, "%s", strerror(errno));
57
- } else if (NLMSG_DONE == hdr->nlmsg_type) {
58
-
57
+ } else if (NLMSG_DONE == hdr->nlmsg_type) {
58
+
59
59
  event = (struct proc_event *)((struct cn_msg *)NLMSG_DATA(hdr))->data;
60
-
60
+
61
61
  switch(event->what) {
62
62
  case PROC_EVENT_EXIT:
63
63
  if (Qnil == rb_funcall(cEventHandler, m_watching_pid, 1, INT2FIX(event->event_data.exit.process_pid))) {
64
64
  return INT2FIX(0);
65
65
  }
66
-
66
+
67
67
  extra_data = rb_hash_new();
68
68
  rb_hash_aset(extra_data, ID2SYM(rb_intern("pid")), INT2FIX(event->event_data.exit.process_pid));
69
69
  rb_hash_aset(extra_data, ID2SYM(rb_intern("exit_code")), INT2FIX(event->event_data.exit.exit_code));
@@ -72,18 +72,18 @@ nlh_handle_events()
72
72
 
73
73
  rb_funcall(cEventHandler, m_call, 3, INT2FIX(event->event_data.exit.process_pid), ID2SYM(proc_exit), extra_data);
74
74
  return INT2FIX(1);
75
-
75
+
76
76
  case PROC_EVENT_FORK:
77
77
  if (Qnil == rb_funcall(cEventHandler, m_watching_pid, 1, INT2FIX(event->event_data.fork.parent_pid))) {
78
78
  return INT2FIX(0);
79
79
  }
80
-
80
+
81
81
  extra_data = rb_hash_new();
82
- rb_hash_aset(extra_data, rb_intern("parent_pid"), INT2FIX(event->event_data.fork.parent_pid));
83
- rb_hash_aset(extra_data, rb_intern("parent_thread_group_id"), INT2FIX(event->event_data.fork.parent_tgid));
84
- rb_hash_aset(extra_data, rb_intern("child_pid"), INT2FIX(event->event_data.fork.child_pid));
85
- rb_hash_aset(extra_data, rb_intern("child_thread_group_id"), INT2FIX(event->event_data.fork.child_tgid));
86
-
82
+ rb_hash_aset(extra_data, ID2SYM(rb_intern("parent_pid")), INT2FIX(event->event_data.fork.parent_pid));
83
+ rb_hash_aset(extra_data, ID2SYM(rb_intern("parent_thread_group_id")), INT2FIX(event->event_data.fork.parent_tgid));
84
+ rb_hash_aset(extra_data, ID2SYM(rb_intern("child_pid")), INT2FIX(event->event_data.fork.child_pid));
85
+ rb_hash_aset(extra_data, ID2SYM(rb_intern("child_thread_group_id")), INT2FIX(event->event_data.fork.child_tgid));
86
+
87
87
  rb_funcall(cEventHandler, m_call, 3, INT2FIX(event->event_data.fork.parent_pid), ID2SYM(proc_fork), extra_data);
88
88
  return INT2FIX(1);
89
89
 
@@ -94,7 +94,7 @@ nlh_handle_events()
94
94
  break;
95
95
  }
96
96
  }
97
-
97
+
98
98
  return Qnil;
99
99
  }
100
100
 
@@ -109,23 +109,23 @@ connect_to_netlink()
109
109
  char buff[NL_MESSAGE_SIZE];
110
110
  struct nlmsghdr *hdr; /* for telling netlink what we want */
111
111
  struct cn_msg *msg; /* the actual connector message */
112
-
112
+
113
113
  /* connect to netlink socket */
114
114
  nl_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
115
-
115
+
116
116
  if (-1 == nl_sock) {
117
117
  rb_raise(rb_eStandardError, "%s", strerror(errno));
118
118
  }
119
-
119
+
120
120
  bzero(&sa_nl, sizeof(sa_nl));
121
121
  sa_nl.nl_family = AF_NETLINK;
122
122
  sa_nl.nl_groups = CN_IDX_PROC;
123
123
  sa_nl.nl_pid = getpid();
124
-
124
+
125
125
  if (-1 == bind(nl_sock, (struct sockaddr *)&sa_nl, sizeof(sa_nl))) {
126
126
  rb_raise(rb_eStandardError, "%s", strerror(errno));
127
127
  }
128
-
128
+
129
129
  /* Fill header */
130
130
  hdr = (struct nlmsghdr *)buff;
131
131
  hdr->nlmsg_len = NL_MESSAGE_SIZE;
@@ -133,7 +133,7 @@ connect_to_netlink()
133
133
  hdr->nlmsg_flags = 0;
134
134
  hdr->nlmsg_seq = 0;
135
135
  hdr->nlmsg_pid = getpid();
136
-
136
+
137
137
  /* Fill message */
138
138
  msg = (struct cn_msg *)NLMSG_DATA(hdr);
139
139
  msg->id.idx = CN_IDX_PROC; /* Connecting to process information */
@@ -143,7 +143,7 @@ connect_to_netlink()
143
143
  msg->flags = 0;
144
144
  msg->len = sizeof(int);
145
145
  *(int*)msg->data = PROC_CN_MCAST_LISTEN;
146
-
146
+
147
147
  if (-1 == send(nl_sock, hdr, hdr->nlmsg_len, 0)) {
148
148
  rb_raise(rb_eStandardError, "%s", strerror(errno));
149
149
  }
@@ -151,17 +151,17 @@ connect_to_netlink()
151
151
 
152
152
  void
153
153
  Init_netlink_handler_ext()
154
- {
154
+ {
155
155
  proc_exit = rb_intern("proc_exit");
156
156
  proc_fork = rb_intern("proc_fork");
157
157
  m_call = rb_intern("call");
158
158
  m_watching_pid = rb_intern("watching_pid?");
159
-
159
+
160
160
  mGod = rb_const_get(rb_cObject, rb_intern("God"));
161
161
  cEventHandler = rb_const_get(mGod, rb_intern("EventHandler"));
162
162
  cNetlinkHandler = rb_define_class_under(mGod, "NetlinkHandler", rb_cObject);
163
163
  rb_define_singleton_method(cNetlinkHandler, "handle_events", nlh_handle_events, 0);
164
-
164
+
165
165
  connect_to_netlink();
166
166
  }
167
167
 
@@ -3,8 +3,8 @@ Gem::Specification.new do |s|
3
3
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
4
4
 
5
5
  s.name = 'god'
6
- s.version = '0.11.0'
7
- s.date = '2010-07-01'
6
+ s.version = '0.12.0'
7
+ s.date = '2012-01-13'
8
8
 
9
9
  s.summary = "Process monitoring framework."
10
10
  s.description = "An easy to configure, easy to extend monitoring framework written in Ruby."
@@ -18,28 +18,35 @@ Gem::Specification.new do |s|
18
18
  s.require_paths = %w[lib ext]
19
19
 
20
20
  s.executables = ["god"]
21
- s.default_executable = 'god'
22
21
  s.extensions = %w[ext/god/extconf.rb]
23
22
 
24
23
  s.rdoc_options = ["--charset=UTF-8"]
25
- s.extra_rdoc_files = %w[README.txt]
24
+ s.extra_rdoc_files = %w[README.md]
26
25
 
27
- s.add_development_dependency('twitter', [">= 0.3.7"])
28
- s.add_development_dependency('prowly', [">= 0.2.1"])
29
- s.add_development_dependency('xmpp4r', [">= 0.4.0"])
30
- s.add_development_dependency('dike', [">= 0.0.3"])
31
- s.add_development_dependency('snapshot', [">= 1.0.0", "< 2.0.0"])
32
- s.add_development_dependency('rcov', [">= 0.9.8"])
33
- s.add_development_dependency('daemons', [">= 1.0.10", "< 2.0.0"])
34
- s.add_development_dependency('mocha', [">= 0.9.1"])
26
+ s.add_development_dependency('json', '~> 1.6')
27
+ s.add_development_dependency('rake', '~> 0.9')
28
+ s.add_development_dependency('rdoc', '~> 3.10')
29
+ s.add_development_dependency('twitter', '~> 2.0')
30
+ s.add_development_dependency('prowly', '~> 0.3')
31
+ s.add_development_dependency('xmpp4r', '~> 0.5')
32
+ s.add_development_dependency('dike', '~> 0.0.3')
33
+ s.add_development_dependency('snapshot', '~> 1.0')
34
+ s.add_development_dependency('rcov', '~> 0.9')
35
+ s.add_development_dependency('daemons', '~> 1.1')
36
+ s.add_development_dependency('mocha', '~> 0.10')
37
+ s.add_development_dependency('gollum', '~> 1.3.1')
35
38
 
36
39
  # = MANIFEST =
37
40
  s.files = %w[
38
41
  Announce.txt
42
+ Gemfile
39
43
  History.txt
40
- README.txt
44
+ LICENSE
45
+ README.md
41
46
  Rakefile
42
47
  bin/god
48
+ doc/god.asciidoc
49
+ doc/intro.asciidoc
43
50
  ext/god/.gitignore
44
51
  ext/god/extconf.rb
45
52
  ext/god/kqueue_handler.c
@@ -67,6 +74,7 @@ Gem::Specification.new do |s|
67
74
  lib/god/conditions/memory_usage.rb
68
75
  lib/god/conditions/process_exits.rb
69
76
  lib/god/conditions/process_running.rb
77
+ lib/god/conditions/socket_responding.rb
70
78
  lib/god/conditions/tries.rb
71
79
  lib/god/configurable.rb
72
80
  lib/god/contact.rb
@@ -77,8 +85,6 @@ Gem::Specification.new do |s|
77
85
  lib/god/contacts/scout.rb
78
86
  lib/god/contacts/twitter.rb
79
87
  lib/god/contacts/webhook.rb
80
- lib/god/dependency_graph.rb
81
- lib/god/diagnostics.rb
82
88
  lib/god/driver.rb
83
89
  lib/god/errors.rb
84
90
  lib/god/event_handler.rb
@@ -115,6 +121,8 @@ Gem::Specification.new do |s|
115
121
  test/configs/daemon_polls/simple_server.rb
116
122
  test/configs/degrading_lambda/degrading_lambda.god
117
123
  test/configs/degrading_lambda/tcp_server.rb
124
+ test/configs/keepalive/keepalive.god
125
+ test/configs/keepalive/keepalive.rb
118
126
  test/configs/lifecycle/lifecycle.god
119
127
  test/configs/matias/matias.god
120
128
  test/configs/real.rb
@@ -134,9 +142,9 @@ Gem::Specification.new do |s|
134
142
  test/test_conditions_disk_usage.rb
135
143
  test/test_conditions_http_response_code.rb
136
144
  test/test_conditions_process_running.rb
145
+ test/test_conditions_socket_responding.rb
137
146
  test/test_conditions_tries.rb
138
147
  test/test_contact.rb
139
- test/test_dependency_graph.rb
140
148
  test/test_driver.rb
141
149
  test/test_email.rb
142
150
  test/test_event_handler.rb
data/lib/god.rb CHANGED
@@ -17,12 +17,12 @@ end
17
17
  require 'god/errors'
18
18
  require 'god/simple_logger'
19
19
  require 'god/logger'
20
+ require 'god/sugar'
20
21
 
21
22
  require 'god/system/process'
22
23
  require 'god/system/portable_poller'
23
24
  require 'god/system/slash_proc_poller'
24
25
 
25
- require 'god/dependency_graph'
26
26
  require 'god/timeline'
27
27
  require 'god/configurable'
28
28
 
@@ -47,6 +47,7 @@ require 'god/conditions/http_response_code'
47
47
  require 'god/conditions/disk_usage'
48
48
  require 'god/conditions/complex'
49
49
  require 'god/conditions/file_mtime'
50
+ require 'god/conditions/socket_responding'
50
51
 
51
52
  require 'god/socket'
52
53
  require 'god/driver'
@@ -59,12 +60,13 @@ require 'god/event_handler'
59
60
  require 'god/registry'
60
61
  require 'god/process'
61
62
 
62
- require 'god/sugar'
63
-
64
63
  require 'god/cli/version'
65
64
  require 'god/cli/command'
66
65
 
67
- require 'god/diagnostics'
66
+ # ruby 1.8 specific configuration
67
+ if RUBY_VERSION < '1.9'
68
+ $KCODE = 'u'
69
+ end
68
70
 
69
71
  CONTACT_DEPS = { }
70
72
  CONTACT_LOAD_SUCCESS = { }
@@ -109,15 +111,15 @@ end
109
111
 
110
112
  module Kernel
111
113
  alias_method :abort_orig, :abort
112
-
114
+
113
115
  def abort(text = nil)
114
116
  $run = false
115
117
  applog(nil, :error, text) if text
116
118
  exit(1)
117
119
  end
118
-
120
+
119
121
  alias_method :exit_orig, :exit
120
-
122
+
121
123
  def exit(code = 0)
122
124
  $run = false
123
125
  exit_orig(code)
@@ -131,15 +133,15 @@ class Module
131
133
  if !self.running && self.inited
132
134
  abort "God.#{arg} must be set before any Tasks are defined"
133
135
  end
134
-
136
+
135
137
  if self.running && self.inited
136
138
  applog(nil, :warn, "God.#{arg} can't be set while god is running")
137
139
  return
138
140
  end
139
-
141
+
140
142
  instance_variable_set(('@' + arg.to_s).intern, other)
141
143
  end
142
-
144
+
143
145
  define_method(arg) do
144
146
  instance_variable_get(('@' + arg.to_s).intern)
145
147
  end
@@ -148,16 +150,36 @@ class Module
148
150
  end
149
151
 
150
152
  module God
151
- VERSION = '0.11.0'
153
+ # The String version number for this package.
154
+ VERSION = '0.12.0'
155
+
156
+ # The Integer number of lines of backlog to keep for the logger.
152
157
  LOG_BUFFER_SIZE_DEFAULT = 100
158
+
159
+ # An Array of directory paths to be used as the default PID file directory.
160
+ # This list will be searched in order and the first one that has write
161
+ # permissions will be used.
153
162
  PID_FILE_DIRECTORY_DEFAULTS = ['/var/run/god', '~/.god/pids']
163
+
164
+ # The default Integer port number for the DRb communcations channel.
154
165
  DRB_PORT_DEFAULT = 17165
166
+
167
+ # The default Array of String IPs that will allow DRb communication access.
155
168
  DRB_ALLOW_DEFAULT = ['127.0.0.1']
169
+
170
+ # The default Symbol log level.
156
171
  LOG_LEVEL_DEFAULT = :info
172
+
173
+ # The default Integer number of seconds to wait for god to terminate when
174
+ # issued the quit command.
157
175
  TERMINATE_TIMEOUT_DEFAULT = 10
176
+
177
+ # The default Integer number of seconds to wait for a process to terminate.
158
178
  STOP_TIMEOUT_DEFAULT = 10
179
+
180
+ # The default String signal to send for the stop command.
159
181
  STOP_SIGNAL_DEFAULT = 'TERM'
160
-
182
+
161
183
  class << self
162
184
  # user configurable
163
185
  safe_attr_accessor :pid,
@@ -173,7 +195,7 @@ module God
173
195
  :socket_user,
174
196
  :socket_group,
175
197
  :socket_perms
176
-
198
+
177
199
  # internal
178
200
  attr_accessor :inited,
179
201
  :running,
@@ -186,8 +208,8 @@ module God
186
208
  :contact_groups,
187
209
  :main
188
210
  end
189
-
190
- # initialize class instance variables
211
+
212
+ # Initialize class instance variables.
191
213
  self.pid = nil
192
214
  self.host = nil
193
215
  self.port = nil
@@ -199,158 +221,150 @@ module God
199
221
  self.socket_user = nil
200
222
  self.socket_group = nil
201
223
  self.socket_perms = 0755
202
-
224
+
203
225
  # Initialize internal data.
204
226
  #
205
- # Returns nothing
227
+ # Returns nothing.
206
228
  def self.internal_init
207
- # only do this once
229
+ # Only do this once.
208
230
  return if self.inited
209
-
210
- # variable init
231
+
232
+ # Variable init.
211
233
  self.watches = {}
212
234
  self.groups = {}
213
235
  self.pending_watches = []
214
236
  self.pending_watch_states = {}
215
237
  self.contacts = {}
216
238
  self.contact_groups = {}
217
-
218
- # set defaults
239
+
240
+ # Set defaults.
219
241
  self.log_buffer_size ||= LOG_BUFFER_SIZE_DEFAULT
220
242
  self.port ||= DRB_PORT_DEFAULT
221
243
  self.allow ||= DRB_ALLOW_DEFAULT
222
244
  self.log_level ||= LOG_LEVEL_DEFAULT
223
245
  self.terminate_timeout ||= TERMINATE_TIMEOUT_DEFAULT
224
-
225
- # additional setup
246
+
247
+ # Additional setup.
226
248
  self.setup
227
-
228
- # log level
249
+
250
+ # Log level.
229
251
  log_level_map = {:debug => Logger::DEBUG,
230
252
  :info => Logger::INFO,
231
253
  :warn => Logger::WARN,
232
254
  :error => Logger::ERROR,
233
255
  :fatal => Logger::FATAL}
234
256
  LOG.level = log_level_map[self.log_level]
235
-
236
- # init has been executed
257
+
258
+ # Init has been executed.
237
259
  self.inited = true
238
-
239
- # not yet running
260
+
261
+ # Not yet running.
240
262
  self.running = false
241
263
  end
242
-
243
- # Instantiate a new, empty Watch object and pass it to the mandatory
244
- # block. The attributes of the watch will be set by the configuration
245
- # file.
246
- #
247
- # Aborts on duplicate watch name
248
- # invalid watch
249
- # conflicting group name
264
+
265
+ # Instantiate a new, empty Watch object and pass it to the mandatory block.
266
+ # The attributes of the watch will be set by the configuration file. Aborts
267
+ # on duplicate watch name, invalid watch, or conflicting group name.
250
268
  #
251
- # Returns nothing
269
+ # Returns nothing.
252
270
  def self.watch(&block)
253
271
  self.task(Watch, &block)
254
272
  end
255
-
256
- # Instantiate a new, empty Task object and yield it to the mandatory
257
- # block. The attributes of the task will be set by the configuration
258
- # file.
259
- #
260
- # Aborts on duplicate task name
261
- # invalid task
262
- # conflicting group name
273
+
274
+ # Instantiate a new, empty Task object and yield it to the mandatory block.
275
+ # The attributes of the task will be set by the configuration file. Aborts
276
+ # on duplicate task name, invalid task, or conflicting group name.
263
277
  #
264
- # Returns nothing
278
+ # Returns nothing.
265
279
  def self.task(klass = Task)
280
+ # Ensure internal init has run.
266
281
  self.internal_init
267
-
282
+
268
283
  t = klass.new
269
284
  yield(t)
270
-
271
- # do the post-configuration
285
+
286
+ # Do the post-configuration.
272
287
  t.prepare
273
-
274
- # if running, completely remove the watch (if necessary) to
275
- # prepare for the reload
288
+
289
+ # If running, completely remove the watch (if necessary) to prepare for
290
+ # the reload
276
291
  existing_watch = self.watches[t.name]
277
292
  if self.running && existing_watch
278
293
  self.pending_watch_states[existing_watch.name] = existing_watch.state
279
294
  self.unwatch(existing_watch)
280
295
  end
281
-
282
- # ensure the new watch has a unique name
296
+
297
+ # Ensure the new watch has a unique name.
283
298
  if self.watches[t.name] || self.groups[t.name]
284
299
  abort "Task name '#{t.name}' already used for a Task or Group"
285
300
  end
286
-
287
- # ensure watch is internally valid
301
+
302
+ # Ensure watch is internally valid.
288
303
  t.valid? || abort("Task '#{t.name}' is not valid (see above)")
289
-
290
- # add to list of watches
304
+
305
+ # Add to list of watches.
291
306
  self.watches[t.name] = t
292
-
293
- # add to pending watches
307
+
308
+ # Add to pending watches.
294
309
  self.pending_watches << t
295
-
296
- # add to group if specified
310
+
311
+ # Add to group if specified.
297
312
  if t.group
298
- # ensure group name hasn't been used for a watch already
313
+ # Ensure group name hasn't been used for a watch already.
299
314
  if self.watches[t.group]
300
315
  abort "Group name '#{t.group}' already used for a Task"
301
316
  end
302
-
317
+
303
318
  self.groups[t.group] ||= []
304
319
  self.groups[t.group] << t
305
320
  end
306
-
307
- # register watch
321
+
322
+ # Register watch.
308
323
  t.register!
309
-
310
- # log
324
+
325
+ # Log.
311
326
  if self.running && existing_watch
312
327
  applog(t, :info, "#{t.name} Reloaded config")
313
328
  elsif self.running
314
329
  applog(t, :info, "#{t.name} Loaded config")
315
330
  end
316
331
  end
317
-
332
+
318
333
  # Unmonitor and remove the given watch from god.
319
- # +watch+ is the Watch to remove
320
334
  #
321
- # Returns nothing
335
+ # watch - The Watch to remove.
336
+ #
337
+ # Returns nothing.
322
338
  def self.unwatch(watch)
323
- # unmonitor
339
+ # Unmonitor.
324
340
  watch.unmonitor unless watch.state == :unmonitored
325
-
326
- # unregister
341
+
342
+ # Unregister.
327
343
  watch.unregister!
328
-
329
- # remove from watches
344
+
345
+ # Remove from watches.
330
346
  self.watches.delete(watch.name)
331
-
332
- # remove from groups
347
+
348
+ # Remove from groups.
333
349
  if watch.group
334
350
  self.groups[watch.group].delete(watch)
335
351
  end
336
-
352
+
337
353
  applog(watch, :info, "#{watch.name} unwatched")
338
354
  end
339
-
355
+
340
356
  # Instantiate a new Contact of the given kind and send it to the block.
341
- # Then prepare, validate, and record the Contact.
342
- # +kind+ is the contact class specifier
357
+ # Then prepare, validate, and record the Contact. Aborts on invalid kind,
358
+ # duplicate contact name, invalid contact, or conflicting group name.
343
359
  #
344
- # Aborts on invalid kind
345
- # duplicate contact name
346
- # invalid contact
347
- # conflicting group name
360
+ # kind - The Symbol contact class specifier.
348
361
  #
349
- # Returns nothing
362
+ # Returns nothing.
350
363
  def self.contact(kind)
364
+ # Ensure internal init has run.
351
365
  self.internal_init
352
-
353
- # verify contact has been loaded
366
+
367
+ # Verify contact has been loaded.
354
368
  if CONTACT_LOAD_SUCCESS[kind] == false
355
369
  applog(nil, :error, "A required dependency for the #{kind} contact is unavailable.")
356
370
  applog(nil, :error, "Run the following commands to install the dependencies:")
@@ -359,82 +373,79 @@ module God
359
373
  end
360
374
  abort
361
375
  end
362
-
363
- # create the contact
376
+
377
+ # Create the contact.
364
378
  begin
365
379
  c = Contact.generate(kind)
366
380
  rescue NoSuchContactError => e
367
381
  abort e.message
368
382
  end
369
-
370
- # send to block so config can set attributes
383
+
384
+ # Send to block so config can set attributes.
371
385
  yield(c) if block_given?
372
-
373
- # call prepare on the contact
386
+
387
+ # Call prepare on the contact.
374
388
  c.prepare
375
-
376
- # remove existing contacts of same name
389
+
390
+ # Remove existing contacts of same name.
377
391
  existing_contact = self.contacts[c.name]
378
392
  if self.running && existing_contact
379
393
  self.uncontact(existing_contact)
380
394
  end
381
-
382
- # warn and noop if the contact has been defined before
395
+
396
+ # Warn and noop if the contact has been defined before.
383
397
  if self.contacts[c.name] || self.contact_groups[c.name]
384
398
  applog(nil, :warn, "Contact name '#{c.name}' already used for a Contact or Contact Group")
385
399
  return
386
400
  end
387
-
388
- # abort if the Contact is invalid, the Contact will have printed
389
- # out its own error messages by now
401
+
402
+ # Abort if the Contact is invalid, the Contact will have printed out its
403
+ # own error messages by now.
390
404
  unless Contact.valid?(c) && c.valid?
391
405
  abort "Exiting on invalid contact"
392
406
  end
393
-
394
- # add to list of contacts
407
+
408
+ # Add to list of contacts.
395
409
  self.contacts[c.name] = c
396
-
397
- # add to contact group if specified
410
+
411
+ # Add to contact group if specified.
398
412
  if c.group
399
- # ensure group name hasn't been used for a contact already
413
+ # Ensure group name hasn't been used for a contact already.
400
414
  if self.contacts[c.group]
401
415
  abort "Contact Group name '#{c.group}' already used for a Contact"
402
416
  end
403
-
417
+
404
418
  self.contact_groups[c.group] ||= []
405
419
  self.contact_groups[c.group] << c
406
420
  end
407
421
  end
408
-
422
+
409
423
  # Remove the given contact from god.
410
- # +contact+ is the Contact to remove
411
424
  #
412
- # Returns nothing
425
+ # contact - The Contact to remove.
426
+ #
427
+ # Returns nothing.
413
428
  def self.uncontact(contact)
414
429
  self.contacts.delete(contact.name)
415
430
  if contact.group
416
431
  self.contact_groups[contact.group].delete(contact)
417
432
  end
418
433
  end
419
-
434
+
420
435
  # Control the lifecycle of the given task(s).
421
- # +name+ is the name of a task/group (String)
422
- # +command+ is the command to run (String)
423
- # one of: "start"
424
- # "monitor"
425
- # "restart"
426
- # "stop"
427
- # "unmonitor"
428
- # "remove"
429
- #
430
- # Returns String[]:task_names
436
+ #
437
+ # name - The String name of a task/group.
438
+ # command - The String command to run. Valid commands are:
439
+ # "start", "monitor", "restart", "stop", "unmonitor", "remove".
440
+ #
441
+ # Returns an Array of String task names affected by the command.
431
442
  def self.control(name, command)
432
- # get the list of items
443
+ # Get the list of items.
433
444
  items = Array(self.watches[name] || self.groups[name]).dup
434
-
445
+
435
446
  jobs = []
436
-
437
- # do the command
447
+
448
+ # Do the command.
438
449
  case command
439
450
  when "start", "monitor"
440
451
  items.each { |w| jobs << Thread.new { w.monitor if w.state != :up } }
@@ -449,16 +460,16 @@ module God
449
460
  else
450
461
  raise InvalidCommandError.new
451
462
  end
452
-
463
+
453
464
  jobs.each { |j| j.join }
454
-
465
+
455
466
  items.map { |x| x.name }
456
467
  end
457
-
468
+
458
469
  # Unmonitor and stop all tasks.
459
470
  #
460
- # Returns true on success
461
- # false if all tasks could not be stopped within 10 seconds
471
+ # Returns true on success, false if all tasks could not be stopped within 10
472
+ # seconds
462
473
  def self.stop_all
463
474
  self.watches.sort.each do |name, w|
464
475
  Thread.new do
@@ -466,19 +477,19 @@ module God
466
477
  w.action(:stop) if w.alive?
467
478
  end
468
479
  end
469
-
480
+
470
481
  terminate_timeout.times do
471
482
  return true unless self.watches.map { |name, w| w.alive? }.any?
472
483
  sleep 1
473
484
  end
474
-
485
+
475
486
  return false
476
487
  end
477
-
488
+
478
489
  # Force the termination of god.
479
- # * Clean up pid file if one exists
480
- # * Stop DRb service
481
- # * Hard exit using exit!
490
+ # * Clean up pid file if one exists
491
+ # * Stop DRb service
492
+ # * Hard exit using exit!
482
493
  #
483
494
  # Never returns because the process will no longer exist!
484
495
  def self.terminate
@@ -486,14 +497,16 @@ module God
486
497
  self.server.stop if self.server
487
498
  exit!(0)
488
499
  end
489
-
500
+
490
501
  # Gather the status of each task.
491
502
  #
492
503
  # Examples
504
+ #
493
505
  # God.status
494
506
  # # => { 'mongrel' => :up, 'nginx' => :up }
495
507
  #
496
- # Returns { String:task_name => Symbol:status, ... }
508
+ # Returns a Hash where the key is the String task name and the value is the
509
+ # Symbol status.
497
510
  def self.status
498
511
  info = {}
499
512
  self.watches.map do |name, w|
@@ -501,12 +514,13 @@ module God
501
514
  end
502
515
  info
503
516
  end
504
-
517
+
505
518
  # Send a signal to each task.
506
- # +name+ is the String name of the task or group
507
- # +signal+ is the signal to send. e.g. HUP, 9
508
519
  #
509
- # Returns String[]:task_names
520
+ # name - The String name of the task or group.
521
+ # signal - The String or integer signal to send. e.g. 'HUP', 9.
522
+ #
523
+ # Returns an Array of String names of the tasks affected.
510
524
  def self.signal(name, signal)
511
525
  items = Array(self.watches[name] || self.groups[name]).dup
512
526
  jobs = []
@@ -514,37 +528,50 @@ module God
514
528
  jobs.each { |j| j.join }
515
529
  items.map { |x| x.name }
516
530
  end
517
-
531
+
518
532
  # Log lines for the given task since the specified time.
519
- # +watch_name+ is the name of the task (may be abbreviated)
520
- # +since+ is the Time since which to report log lines
521
533
  #
522
- # Raises God::NoSuchWatchError if no tasks matched
534
+ # watch_name - The String name of the task (may be abbreviated).
535
+ # since - The Time since which to report log lines.
523
536
  #
524
- # Returns String:joined_log_lines
537
+ # Raises God::NoSuchWatchError if no tasks matched.
538
+ # Returns the String of newline separated log lines.
525
539
  def self.running_log(watch_name, since)
526
540
  matches = pattern_match(watch_name, self.watches.keys)
527
-
541
+
528
542
  unless matches.first
529
543
  raise NoSuchWatchError.new
530
544
  end
531
-
545
+
532
546
  LOG.watch_log_since(matches.first, since)
533
547
  end
534
-
548
+
535
549
  # Load a config file into a running god instance. Rescues any exceptions
536
550
  # that the config may raise and reports these back to the caller.
537
- # +code+ is a String containing the config file
538
- # +filename+ is the filename of the config file
539
551
  #
540
- # Returns [String[]:task_names, String:errors]
541
- def self.running_load(code, filename)
552
+ # code - The String config file contents.
553
+ # filename - The filename of the config file.
554
+ # action - The optional String command specifying how to deal with
555
+ # existing watches. Valid options are: 'stop', 'remove' or
556
+ # 'leave' (default).
557
+ #
558
+ # Returns a three-tuple Array [loaded_names, errors, unloaded_names] where:
559
+ # loaded_names - The Array of String task names that were loaded.
560
+ # errors - The String of error messages produced during the
561
+ # load phase. Will be a blank String if no errors
562
+ # were encountered.
563
+ # unloaded_names - The Array of String task names that were unloaded
564
+ # from the system (if 'remove' or 'stop' was
565
+ # specified as the action).
566
+ def self.running_load(code, filename, action = nil)
542
567
  errors = ""
543
- watches = []
544
-
568
+ loaded_watches = []
569
+ unloaded_watches = []
570
+ jobs = []
571
+
545
572
  begin
546
573
  LOG.start_capture
547
-
574
+
548
575
  Gem.clear_paths
549
576
  eval(code, root_binding, filename)
550
577
  self.pending_watches.each do |w|
@@ -554,39 +581,61 @@ module God
554
581
  w.monitor if w.autostart?
555
582
  end
556
583
  end
557
- watches = self.pending_watches.dup
584
+ loaded_watches = self.pending_watches.map { |w| w.name }
558
585
  self.pending_watches.clear
559
586
  self.pending_watch_states.clear
560
587
 
561
- # make sure we quit capturing when we're done
588
+ self.watches.each do |name, watch|
589
+ next if loaded_watches.include?(name)
590
+
591
+ case action
592
+ when 'stop'
593
+ jobs << Thread.new(watch) { |w| w.action(:stop); self.unwatch(w) }
594
+ unloaded_watches << name
595
+ when 'remove'
596
+ jobs << Thread.new(watch) { |w| self.unwatch(w) }
597
+ unloaded_watches << name
598
+ when 'leave', '', nil
599
+ # Do nothing
600
+ else
601
+ raise InvalidCommandError, "Unknown action: #{action}"
602
+ end
603
+ end
604
+
605
+ # Make sure we quit capturing when we're done.
562
606
  LOG.finish_capture
563
607
  rescue Exception => e
564
- # don't ever let running_load take down god
608
+ # Don't ever let running_load take down god.
565
609
  errors << LOG.finish_capture
566
-
610
+
567
611
  unless e.instance_of?(SystemExit)
568
612
  errors << e.message << "\n"
569
613
  errors << e.backtrace.join("\n")
570
614
  end
571
615
  end
572
-
573
- names = watches.map { |x| x.name }
574
- [names, errors]
616
+
617
+ jobs.each { |t| t.join }
618
+
619
+ [loaded_watches, errors, unloaded_watches]
575
620
  end
576
-
621
+
577
622
  # Load the given file(s) according to the given glob.
578
- # +glob+ is the glob-enabled path to load
579
623
  #
580
- # Returns nothing
624
+ # glob - The glob-enabled String path to load.
625
+ #
626
+ # Returns nothing.
581
627
  def self.load(glob)
582
628
  Dir[glob].each do |f|
583
629
  Kernel.load f
584
630
  end
585
631
  end
586
-
632
+
633
+ # Setup pid file directory and log system.
634
+ #
635
+ # Returns nothing.
587
636
  def self.setup
588
637
  if self.pid_file_directory
589
- # pid file dir was specified, ensure it is created and writable
638
+ # Pid file dir was specified, ensure it is created and writable.
590
639
  unless File.exist?(self.pid_file_directory)
591
640
  begin
592
641
  FileUtils.mkdir_p(self.pid_file_directory)
@@ -594,12 +643,12 @@ module God
594
643
  abort "Failed to create pid file directory: #{e.message}"
595
644
  end
596
645
  end
597
-
646
+
598
647
  unless File.writable?(self.pid_file_directory)
599
648
  abort "The pid file directory (#{self.pid_file_directory}) is not writable by #{Etc.getlogin}"
600
649
  end
601
650
  else
602
- # no pid file dir specified, try defaults
651
+ # No pid file dir specified, try defaults.
603
652
  PID_FILE_DIRECTORY_DEFAULTS.each do |idir|
604
653
  dir = File.expand_path(idir)
605
654
  begin
@@ -611,69 +660,77 @@ module God
611
660
  rescue Errno::EACCES => e
612
661
  end
613
662
  end
614
-
663
+
615
664
  unless self.pid_file_directory
616
665
  dirs = PID_FILE_DIRECTORY_DEFAULTS.map { |x| File.expand_path(x) }
617
666
  abort "No pid file directory exists, could be created, or is writable at any of #{dirs.join(', ')}"
618
667
  end
619
668
  end
620
-
669
+
621
670
  if God::Logger.syslog
622
671
  LOG.info("Syslog enabled.")
623
672
  else
624
673
  LOG.info("Syslog disabled.")
625
674
  end
626
-
675
+
627
676
  applog(nil, :info, "Using pid file directory: #{self.pid_file_directory}")
628
677
  end
629
-
678
+
630
679
  # Initialize and startup the machinery that makes god work.
631
680
  #
632
- # Returns nothing
681
+ # Returns nothing.
633
682
  def self.start
634
683
  self.internal_init
635
-
636
- # instantiate server
684
+
685
+ # Instantiate server.
637
686
  self.server = Socket.new(self.port, self.socket_user, self.socket_group, self.socket_perms)
638
-
639
- # start monitoring any watches set to autostart
687
+
688
+ # Start monitoring any watches set to autostart.
640
689
  self.watches.values.each { |w| w.monitor if w.autostart? }
641
-
642
- # clear pending watches
690
+
691
+ # Clear pending watches.
643
692
  self.pending_watches.clear
644
-
645
- # mark as running
693
+
694
+ # Mark as running.
646
695
  self.running = true
647
-
648
- # don't exit
649
- self.main =
696
+
697
+ # Don't exit.
698
+ self.main =
650
699
  Thread.new do
651
700
  loop do
652
701
  sleep 60
653
702
  end
654
703
  end
655
-
656
- self.main.join
657
704
  end
658
-
705
+
706
+ # Prevent god from exiting.
707
+ #
708
+ # Returns nothing.
709
+ def self.join
710
+ self.main.join if self.main
711
+ end
712
+
713
+ # Returns the version String.
659
714
  def self.version
660
715
  God::VERSION
661
716
  end
662
-
663
- # To be called on program exit to start god
717
+
718
+ # To be called on program exit to start god.
664
719
  #
665
- # Returns nothing
720
+ # Returns nothing.
666
721
  def self.at_exit
667
722
  self.start
723
+ self.join
668
724
  end
669
-
725
+
670
726
  # private
671
-
727
+
672
728
  # Match a shortened pattern against a list of String candidates.
673
- # The pattern is expanded into a regular expression by
729
+ # The pattern is expanded into a regular expression by
674
730
  # inserting .* between each character.
675
- # +pattern+ is the String containing the abbreviation
676
- # +list+ is the Array of Strings to match against
731
+ #
732
+ # pattern - The String containing the abbreviation.
733
+ # list - The Array of Strings to match against.
677
734
  #
678
735
  # Examples
679
736
  #
@@ -682,10 +739,10 @@ module God
682
739
  # God.pattern_match(list, pattern)
683
740
  # # => ['bar', 'bars']
684
741
  #
685
- # Returns String[]:matched_elements
742
+ # Returns the Array of matching name Strings.
686
743
  def self.pattern_match(pattern, list)
687
744
  regex = pattern.split('').join('.*')
688
-
745
+
689
746
  list.select do |item|
690
747
  item =~ Regexp.new(regex)
691
748
  end.sort_by { |x| x.size }
@@ -695,7 +752,7 @@ end
695
752
  # Runs immediately before the program exits. If $run is true,
696
753
  # start god, if $run is false, exit normally.
697
754
  #
698
- # Returns nothing
755
+ # Returns nothing.
699
756
  at_exit do
700
757
  God.at_exit if $run
701
758
  end