lifeline 0.3.0
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.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +201 -0
- data/README.rdoc +67 -0
- data/Rakefile +54 -0
- data/VERSION +1 -0
- data/lib/lifeline.rb +132 -0
- data/test/helper.rb +11 -0
- data/test/test_lifeline.rb +104 -0
- metadata +109 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright (c) 2009 The New York Times
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
data/README.rdoc
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
= lifeline
|
|
2
|
+
|
|
3
|
+
Methods for creating lifeline tasks. This allows you to write cron jobs that
|
|
4
|
+
restart processes that die while ensuring the code is only executing only once
|
|
5
|
+
(avoiding the duplication that sometimes happens using the 'daemons') gem.
|
|
6
|
+
Both a generic lifeline method is provided as well as methods to create 3
|
|
7
|
+
lifeline rake tasks for managing lifelines.
|
|
8
|
+
|
|
9
|
+
== The Lifeline Approach
|
|
10
|
+
|
|
11
|
+
For all their history, daemons can be problematic. For starters, mechanisms
|
|
12
|
+
for restarting daemons that have failed often rely on a third-party
|
|
13
|
+
application like monit which is itself a daemon. In addition, PID-based
|
|
14
|
+
mechanisms to ensure only one daemon is running at a time fail in both ways,
|
|
15
|
+
sometimes preventing a daemon from starting up and other times not preventing
|
|
16
|
+
multiple instances from running.
|
|
17
|
+
|
|
18
|
+
The lifeline pattern (originally presented to me by Matt Todd of Highgroove
|
|
19
|
+
Studios) is a different approach that uses a cron job that runs every minute
|
|
20
|
+
as a lifeline to restart the daemon process if it has died. This lifeline
|
|
21
|
+
calls ps to see if another instance of the same command is already running. If
|
|
22
|
+
it is, the lifeline exits immediately. Otherwise, it stays running
|
|
23
|
+
indefinitely (Cron starts all jobs via a fork, so it can run forever without
|
|
24
|
+
stalling out cron). The trick is figuring out the right invocation to ps to
|
|
25
|
+
ensure you are only checking for apps with the same name. But the lifeline gem
|
|
26
|
+
handles all this to you.
|
|
27
|
+
|
|
28
|
+
Although lifelines can work well for processes that run forever, the technique
|
|
29
|
+
can also be used to safeguard any code you only want to run a single instance
|
|
30
|
+
of at a time (eg, if you have a cron job that runs every 5 minutes and you
|
|
31
|
+
want to ensure it doesn't run 2 processes should the first cron job get
|
|
32
|
+
delayed.)
|
|
33
|
+
|
|
34
|
+
== How a Lifeline Works
|
|
35
|
+
|
|
36
|
+
More specifically, the lifeline method (and rake task that calls it) does the following steps:
|
|
37
|
+
|
|
38
|
+
* Get a list of all running processes
|
|
39
|
+
* Find the name of the current process by looking for the command associated with the current process id (the $$ variable)
|
|
40
|
+
* If there is another process with the same command string, return and exit
|
|
41
|
+
* Otherwise yield to run the passed block.
|
|
42
|
+
|
|
43
|
+
== Examples
|
|
44
|
+
|
|
45
|
+
Lifeline.lifeline do
|
|
46
|
+
# some code you want to run in only a single process
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
Lifeline.define_lifeline_tasks("appname") do
|
|
50
|
+
# some code you want to run in a single lifeline
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
> rake -T appname
|
|
54
|
+
|
|
55
|
+
== Note on Patches/Pull Requests
|
|
56
|
+
|
|
57
|
+
* Fork the project.
|
|
58
|
+
* Make your feature addition or bug fix.
|
|
59
|
+
* Add tests for it. This is important so I don't break it in a
|
|
60
|
+
future version unintentionally.
|
|
61
|
+
* Commit, do not mess with rakefile, version, or history.
|
|
62
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
|
63
|
+
* Send me a pull request. Bonus points for topic branches.
|
|
64
|
+
|
|
65
|
+
== Copyright
|
|
66
|
+
|
|
67
|
+
Copyright (c) 2010 The New York Times. See LICENSE for details.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'rake'
|
|
3
|
+
|
|
4
|
+
begin
|
|
5
|
+
require 'jeweler'
|
|
6
|
+
Jeweler::Tasks.new do |gem|
|
|
7
|
+
gem.name = "lifeline"
|
|
8
|
+
gem.summary = %Q{A cron-based alternative to running daemon scripts}
|
|
9
|
+
gem.description = %Q{A cron-based alternative to running daemon scripts}
|
|
10
|
+
gem.email = "open@nytimes.com"
|
|
11
|
+
gem.homepage = "http://github.com/harrisj/lifeline"
|
|
12
|
+
gem.authors = ["Jacob Harris", "Ben Koski", "Matt Todd"]
|
|
13
|
+
gem.add_development_dependency "shoulda", ">= 0"
|
|
14
|
+
gem.add_development_dependency "yard", ">= 0"
|
|
15
|
+
gem.add_development_dependency "mocha", ">= 0"
|
|
16
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
|
17
|
+
end
|
|
18
|
+
Jeweler::GemcutterTasks.new
|
|
19
|
+
rescue LoadError
|
|
20
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
require 'rake/testtask'
|
|
24
|
+
Rake::TestTask.new(:test) do |test|
|
|
25
|
+
test.libs << 'lib' << 'test'
|
|
26
|
+
test.pattern = 'test/**/test_*.rb'
|
|
27
|
+
test.verbose = true
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
begin
|
|
31
|
+
require 'rcov/rcovtask'
|
|
32
|
+
Rcov::RcovTask.new do |test|
|
|
33
|
+
test.libs << 'test'
|
|
34
|
+
test.pattern = 'test/**/test_*.rb'
|
|
35
|
+
test.verbose = true
|
|
36
|
+
end
|
|
37
|
+
rescue LoadError
|
|
38
|
+
task :rcov do
|
|
39
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
task :test => :check_dependencies
|
|
44
|
+
|
|
45
|
+
task :default => :test
|
|
46
|
+
|
|
47
|
+
begin
|
|
48
|
+
require 'yard'
|
|
49
|
+
YARD::Rake::YardocTask.new
|
|
50
|
+
rescue LoadError
|
|
51
|
+
task :yardoc do
|
|
52
|
+
abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
|
|
53
|
+
end
|
|
54
|
+
end
|
data/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.3.0
|
data/lib/lifeline.rb
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
require 'rake'
|
|
2
|
+
require 'rake/tasklib'
|
|
3
|
+
|
|
4
|
+
module Lifeline
|
|
5
|
+
##
|
|
6
|
+
# @private
|
|
7
|
+
def get_process_list
|
|
8
|
+
processes = %x{ps ax -o pid,command}
|
|
9
|
+
|
|
10
|
+
return nil if processes.nil?
|
|
11
|
+
|
|
12
|
+
processes.split(/\n/).map do |p|
|
|
13
|
+
if p =~ /^(\d+)\s(.+)$/
|
|
14
|
+
{:pid => $1.to_i, :command => $2.strip}
|
|
15
|
+
end
|
|
16
|
+
end.compact
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
##
|
|
20
|
+
# A method for executing a block of code only if there is no other process with the same command running. This is useful if you want
|
|
21
|
+
# to have a perpetually running daemon that only executes once at a time. It uses the process name returned by ps ax to see if there is
|
|
22
|
+
# a process with the same command name but a different PID already executing. If so, it terminates without running the block. NOTE: since it
|
|
23
|
+
# uses the command name returned from <tt>ps ax</tt>, it us up to to you to name the process containing this code with a distinctly unique name. If two
|
|
24
|
+
# separate Rails projects both have a <tt>rake lifeline</tt> task they WILL interfere with each other. I'd suggest prefixing with the project name (ie,
|
|
25
|
+
# <tt>doc_viewer:lifeline</tt>) to be sure
|
|
26
|
+
#
|
|
27
|
+
# @param &block a block which is executed if there is not already a lifeline running.
|
|
28
|
+
# @raise [ArgumentError] if you do not pass in a block argument
|
|
29
|
+
def lifeline
|
|
30
|
+
if !block_given?
|
|
31
|
+
raise ArgumentError, "You must pass in a block to be the body of the run rake task"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
my_pid = $$
|
|
35
|
+
processes = get_process_list
|
|
36
|
+
|
|
37
|
+
if processes.nil? || processes.empty?
|
|
38
|
+
raise "No processes being returned by get_process_list. Aborting!"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
myself = processes.detect {|p| p[:pid] == my_pid}
|
|
42
|
+
if myself.nil?
|
|
43
|
+
raise "Unable to find self in process list. This is bizarre to say the least. Exiting"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# there isn't already another process running with the same command
|
|
47
|
+
if !processes.any? {|p| p[:pid] != my_pid && p[:command] == myself[:command]}
|
|
48
|
+
yield
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Define rake tasks for running, starting, and terminating
|
|
53
|
+
class LifelineRakeTask < ::Rake::TaskLib
|
|
54
|
+
# The namespace to define the tasks in
|
|
55
|
+
# @return [String] the namespace for the tasks
|
|
56
|
+
attr_accessor :namespace
|
|
57
|
+
|
|
58
|
+
##
|
|
59
|
+
# Creates 3 new tasks for the lifeline in the namespace specified. These
|
|
60
|
+
# tasks are
|
|
61
|
+
# * run - a task for running the code provided in the block
|
|
62
|
+
# * lifeline - a lifeline task for running the run task if it's not already running
|
|
63
|
+
# * terminate - a task for terminating all lifelines.
|
|
64
|
+
#
|
|
65
|
+
# @param [String, Symbol] name the namespace of the rake tasks
|
|
66
|
+
# @param [optional, Hash] opts Additional options for the method
|
|
67
|
+
# @option opts [Array<String, Symbol>] :prereqs ([]) If there any any rake tasks that should be prerequisites of the :run task, specify them here (For Rails, you would do :prereqs => :environment)
|
|
68
|
+
# @param a block that defines the body of the run task
|
|
69
|
+
def initialize(namespace, opts={}, &block)
|
|
70
|
+
if !block_given?
|
|
71
|
+
raise ArgumentError, "You must pass in a block to be the body of the run rake task"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
@namespace = namespace
|
|
75
|
+
|
|
76
|
+
define_run_task(opts, &block)
|
|
77
|
+
define_lifeline_task
|
|
78
|
+
define_terminate_task
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
protected
|
|
82
|
+
|
|
83
|
+
def run_task_name
|
|
84
|
+
"#{namespace}:run"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def define_run_task(opts={}, &block)
|
|
88
|
+
desc "Runs the #{namespace}:run task"
|
|
89
|
+
|
|
90
|
+
task_arg = if opts[:prereqs]
|
|
91
|
+
{run_task_name => opts[:prereqs]}
|
|
92
|
+
else
|
|
93
|
+
run_task_name
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
task(task_arg, &block)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def define_lifeline_task
|
|
100
|
+
desc "A lifeline task for executing only one process of #{namespace}:run at a time"
|
|
101
|
+
task("#{namespace}:lifeline") do
|
|
102
|
+
lifeline do
|
|
103
|
+
Rake::Task["#{namespace}:run"].invoke
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def define_terminate_task
|
|
109
|
+
desc "Terminates any running #{namespace}:lifeline tasks"
|
|
110
|
+
task("#{namespace}:terminate") do
|
|
111
|
+
unless (process = %x{ps aux | grep "#{namespace}:lifeline" | grep ruby | grep -v grep}.chomp).empty?
|
|
112
|
+
runner_pid = process.gsub(/(\s+)/, ' ').split(' ')[1]
|
|
113
|
+
puts %x{kill -9 #{runner_pid}}
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
##
|
|
120
|
+
# A method that defines 3 rake tasks for doing lifelines:
|
|
121
|
+
# * <tt>namespace:run</tt> runs the specified block
|
|
122
|
+
# * <tt>namespace:lifeline</tt> a lifeline for executing only a single copy of <tt>namespace:run</tt> at a time
|
|
123
|
+
# * <tt>namespace:terminate</tt> a task for terminating the lifelines
|
|
124
|
+
#
|
|
125
|
+
# @param [String,Symbol] namespace the namespace to define the 3 tasks in
|
|
126
|
+
# @param &block a block which defines the body of the namespace:run method
|
|
127
|
+
#
|
|
128
|
+
# @raise [ArgumentError] if you do not pass in a block argument
|
|
129
|
+
def define_lifeline_tasks(namespace, &block)
|
|
130
|
+
LifelineRakeTask.new(namespace, &block)
|
|
131
|
+
end
|
|
132
|
+
end
|
data/test/helper.rb
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
require 'helper'
|
|
2
|
+
|
|
3
|
+
class TestBlockExecutionException < StandardError
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
class TestLifeline < Test::Unit::TestCase
|
|
7
|
+
include Lifeline
|
|
8
|
+
|
|
9
|
+
context "get_process_list" do
|
|
10
|
+
should "return an array of process hashes" do
|
|
11
|
+
processes = get_process_list
|
|
12
|
+
assert_kind_of(Array, processes)
|
|
13
|
+
processes.all? do |p|
|
|
14
|
+
assert_kind_of(Hash, p)
|
|
15
|
+
assert_not_nil p[:pid]
|
|
16
|
+
assert_kind_of(Integer, p[:pid])
|
|
17
|
+
assert_not_nil p[:command]
|
|
18
|
+
assert_kind_of(String, p[:command])
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
context "lifeline" do
|
|
24
|
+
should "raise an ArgumentError when not passed a block" do
|
|
25
|
+
assert_raise(ArgumentError) { lifeline }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
context "when the process list is empty" do
|
|
29
|
+
setup do
|
|
30
|
+
self.expects(:get_process_list).returns(nil)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
should "raise a RuntimeError and exit without executing the block" do
|
|
34
|
+
assert_raise(RuntimeError) do
|
|
35
|
+
lifeline do
|
|
36
|
+
flunk "This block should not be executed"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
context "when the process list does not contain the PID for this process" do
|
|
43
|
+
setup do
|
|
44
|
+
self.expects(:get_process_list).returns([{:pid => $$+1, :command => "random commmand"}])
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
should "raise a RuntimeError and exit" do
|
|
48
|
+
assert_raise(RuntimeError) do
|
|
49
|
+
lifeline do
|
|
50
|
+
flunk "This block should not be executed"
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
context "when there is no other process with the same command (different pid) as this one" do
|
|
57
|
+
should "execute the provided block" do
|
|
58
|
+
assert_raise(TestBlockExecutionException) do
|
|
59
|
+
lifeline do
|
|
60
|
+
raise TestBlockExecutionException, "This block throws an exception to show the block was run"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
context "when there is another process with the same command (different pid)" do
|
|
67
|
+
setup do
|
|
68
|
+
@processes = get_process_list
|
|
69
|
+
my_process = @processes.detect {|p| p[:pid] == $$}
|
|
70
|
+
assert_not_nil my_process
|
|
71
|
+
@processes << {:pid => $$ + 10, :command => my_process[:command]}
|
|
72
|
+
self.expects(:get_process_list).returns(@processes)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
should "not execute the provided block" do
|
|
76
|
+
lifeline do
|
|
77
|
+
flunk "This block should not be executed"
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
context "define_lifeline_tasks" do
|
|
84
|
+
setup do
|
|
85
|
+
define_lifeline_tasks("awesome:namespace") do
|
|
86
|
+
raise TestBlockExecutionException, "This block throws an exception to show the block was run"
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
should "create an awesome:namespace:run task" do
|
|
91
|
+
assert_raise(TestBlockExecutionException) do
|
|
92
|
+
Rake::Task["awesome:namespace:run"].invoke
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
should "create an awesome:namespace:lifeline task" do
|
|
97
|
+
assert_not_nil Rake::Task["awesome:namespace:lifeline"]
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
should "create an awesome:namespace:terminate task" do
|
|
101
|
+
assert_not_nil Rake::Task["awesome:namespace:terminate"]
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: lifeline
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
prerelease: false
|
|
5
|
+
segments:
|
|
6
|
+
- 0
|
|
7
|
+
- 3
|
|
8
|
+
- 0
|
|
9
|
+
version: 0.3.0
|
|
10
|
+
platform: ruby
|
|
11
|
+
authors:
|
|
12
|
+
- Jacob Harris
|
|
13
|
+
- Ben Koski
|
|
14
|
+
- Matt Todd
|
|
15
|
+
autorequire:
|
|
16
|
+
bindir: bin
|
|
17
|
+
cert_chain: []
|
|
18
|
+
|
|
19
|
+
date: 2010-04-13 00:00:00 -07:00
|
|
20
|
+
default_executable:
|
|
21
|
+
dependencies:
|
|
22
|
+
- !ruby/object:Gem::Dependency
|
|
23
|
+
name: shoulda
|
|
24
|
+
prerelease: false
|
|
25
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
|
26
|
+
requirements:
|
|
27
|
+
- - ">="
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
segments:
|
|
30
|
+
- 0
|
|
31
|
+
version: "0"
|
|
32
|
+
type: :development
|
|
33
|
+
version_requirements: *id001
|
|
34
|
+
- !ruby/object:Gem::Dependency
|
|
35
|
+
name: yard
|
|
36
|
+
prerelease: false
|
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
|
38
|
+
requirements:
|
|
39
|
+
- - ">="
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
segments:
|
|
42
|
+
- 0
|
|
43
|
+
version: "0"
|
|
44
|
+
type: :development
|
|
45
|
+
version_requirements: *id002
|
|
46
|
+
- !ruby/object:Gem::Dependency
|
|
47
|
+
name: mocha
|
|
48
|
+
prerelease: false
|
|
49
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
segments:
|
|
54
|
+
- 0
|
|
55
|
+
version: "0"
|
|
56
|
+
type: :development
|
|
57
|
+
version_requirements: *id003
|
|
58
|
+
description: A cron-based alternative to running daemon scripts
|
|
59
|
+
email: open@nytimes.com
|
|
60
|
+
executables: []
|
|
61
|
+
|
|
62
|
+
extensions: []
|
|
63
|
+
|
|
64
|
+
extra_rdoc_files:
|
|
65
|
+
- LICENSE
|
|
66
|
+
- README.rdoc
|
|
67
|
+
files:
|
|
68
|
+
- .document
|
|
69
|
+
- .gitignore
|
|
70
|
+
- LICENSE
|
|
71
|
+
- README.rdoc
|
|
72
|
+
- Rakefile
|
|
73
|
+
- VERSION
|
|
74
|
+
- lib/lifeline.rb
|
|
75
|
+
- test/helper.rb
|
|
76
|
+
- test/test_lifeline.rb
|
|
77
|
+
has_rdoc: true
|
|
78
|
+
homepage: http://github.com/harrisj/lifeline
|
|
79
|
+
licenses: []
|
|
80
|
+
|
|
81
|
+
post_install_message:
|
|
82
|
+
rdoc_options:
|
|
83
|
+
- --charset=UTF-8
|
|
84
|
+
require_paths:
|
|
85
|
+
- lib
|
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
87
|
+
requirements:
|
|
88
|
+
- - ">="
|
|
89
|
+
- !ruby/object:Gem::Version
|
|
90
|
+
segments:
|
|
91
|
+
- 0
|
|
92
|
+
version: "0"
|
|
93
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
94
|
+
requirements:
|
|
95
|
+
- - ">="
|
|
96
|
+
- !ruby/object:Gem::Version
|
|
97
|
+
segments:
|
|
98
|
+
- 0
|
|
99
|
+
version: "0"
|
|
100
|
+
requirements: []
|
|
101
|
+
|
|
102
|
+
rubyforge_project:
|
|
103
|
+
rubygems_version: 1.3.6
|
|
104
|
+
signing_key:
|
|
105
|
+
specification_version: 3
|
|
106
|
+
summary: A cron-based alternative to running daemon scripts
|
|
107
|
+
test_files:
|
|
108
|
+
- test/helper.rb
|
|
109
|
+
- test/test_lifeline.rb
|