fire_and_forget 0.1.2 → 0.2.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/Gemfile.lock +0 -8
- data/README.rdoc +75 -14
- data/bin/fire_forget +45 -22
- data/examples/long_task +27 -16
- data/fire_and_forget.gemspec +41 -4
- data/lib/fire_and_forget/client.rb +1 -1
- data/lib/fire_and_forget/command/fire.rb +23 -4
- data/lib/fire_and_forget/command/get_pid.rb +20 -0
- data/lib/fire_and_forget/command/set_pid.rb +0 -2
- data/lib/fire_and_forget/command/set_status.rb +1 -1
- data/lib/fire_and_forget/command.rb +11 -0
- data/lib/fire_and_forget/config.rb +3 -8
- data/lib/fire_and_forget/daemon.rb +14 -23
- data/lib/fire_and_forget/errors.rb +8 -0
- data/lib/fire_and_forget/launcher.rb +69 -6
- data/lib/fire_and_forget/server.rb +5 -1
- data/lib/fire_and_forget/task_description.rb +11 -0
- data/lib/fire_and_forget/utilities.rb +4 -4
- data/lib/fire_and_forget/version.rb +1 -1
- data/lib/fire_and_forget.rb +6 -2
- data/test/test_fire_and_forget.rb +59 -26
- data/vendor/daemons-1.1.0/LICENSE +29 -0
- data/vendor/daemons-1.1.0/README +224 -0
- data/vendor/daemons-1.1.0/Rakefile +88 -0
- data/vendor/daemons-1.1.0/Releases +152 -0
- data/vendor/daemons-1.1.0/TODO +2 -0
- data/vendor/daemons-1.1.0/lib/daemons/application.rb +468 -0
- data/vendor/daemons-1.1.0/lib/daemons/application_group.rb +194 -0
- data/vendor/daemons-1.1.0/lib/daemons/change_privilege.rb +19 -0
- data/vendor/daemons-1.1.0/lib/daemons/cmdline.rb +124 -0
- data/vendor/daemons-1.1.0/lib/daemons/controller.rb +140 -0
- data/vendor/daemons-1.1.0/lib/daemons/daemonize.rb +271 -0
- data/vendor/daemons-1.1.0/lib/daemons/etc_extension.rb +12 -0
- data/vendor/daemons-1.1.0/lib/daemons/exceptions.rb +28 -0
- data/vendor/daemons-1.1.0/lib/daemons/monitor.rb +138 -0
- data/vendor/daemons-1.1.0/lib/daemons/pid.rb +109 -0
- data/vendor/daemons-1.1.0/lib/daemons/pidfile.rb +116 -0
- data/vendor/daemons-1.1.0/lib/daemons/pidmem.rb +19 -0
- data/vendor/daemons-1.1.0/lib/daemons.rb +288 -0
- data/vendor/daemons-1.1.0/setup.rb +1360 -0
- data/vendor/json-1.5.0/COPYING +58 -0
- data/vendor/json-1.5.0/GPL +340 -0
- data/vendor/json-1.5.0/README +356 -0
- data/vendor/json-1.5.0/README-json-jruby.markdown +33 -0
- data/vendor/json-1.5.0/Rakefile +397 -0
- data/vendor/json-1.5.0/TODO +1 -0
- data/vendor/json-1.5.0/VERSION +1 -0
- data/vendor/json-1.5.0/lib/json/add/core.rb +147 -0
- data/vendor/json-1.5.0/lib/json/add/rails.rb +8 -0
- data/vendor/json-1.5.0/lib/json/common.rb +419 -0
- data/vendor/json-1.5.0/lib/json/editor.rb +1369 -0
- data/vendor/json-1.5.0/lib/json/pure/generator.rb +441 -0
- data/vendor/json-1.5.0/lib/json/pure/parser.rb +320 -0
- data/vendor/json-1.5.0/lib/json/pure.rb +15 -0
- data/vendor/json-1.5.0/lib/json/version.rb +8 -0
- data/vendor/json-1.5.0/lib/json.rb +10 -0
- metadata +41 -4
- data/lib/fire_and_forget/task.rb +0 -11
data/Gemfile.lock
CHANGED
@@ -1,10 +1,3 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
fire_and_forget (0.1.0)
|
5
|
-
daemons (~> 1.1.0)
|
6
|
-
json (~> 1.4.6)
|
7
|
-
|
8
1
|
GEM
|
9
2
|
remote: http://rubygems.org/
|
10
3
|
specs:
|
@@ -26,7 +19,6 @@ PLATFORMS
|
|
26
19
|
DEPENDENCIES
|
27
20
|
bundler (~> 1.0.0)
|
28
21
|
daemons (~> 1.1.0)
|
29
|
-
fire_and_forget!
|
30
22
|
jeweler (~> 1.5.1)
|
31
23
|
jnunemaker-matchy (~> 0.4)
|
32
24
|
json (~> 1.4.6)
|
data/README.rdoc
CHANGED
@@ -14,23 +14,26 @@ What it doesn't:
|
|
14
14
|
|
15
15
|
- Have any kind of queue mechanism. If you fire the same task twice then that task will be running twice
|
16
16
|
- Have any persistant state
|
17
|
+
- Work in non-UNIX environments
|
17
18
|
|
18
19
|
== Quick Start
|
19
20
|
|
21
|
+
gem install fire_and_forget
|
20
22
|
|
21
|
-
FAF works by running a simple
|
23
|
+
FAF works by running a simple UNIX socket server so before we want to use it we
|
24
|
+
have to start this server.
|
22
25
|
|
23
|
-
$
|
24
|
-
FAF process 16235 listening on
|
26
|
+
$ fire_forget
|
27
|
+
FAF process 16235 listening on /tmp/fire_and_forget.sock ...
|
25
28
|
|
26
|
-
|
29
|
+
To change the path of the socket file use the '-s' parameter:
|
27
30
|
|
28
|
-
$
|
31
|
+
$ fire_forget -s /path/to/socket
|
32
|
+
FAF process 16240 listening on /path/to/socket ...
|
29
33
|
|
30
|
-
If you do this you will need to set
|
34
|
+
If you do this you will need to set the web-app to use the right values:
|
31
35
|
|
32
|
-
FireAndForget.
|
33
|
-
FireAndForget.bind_address = "192.168.1.22"
|
36
|
+
FireAndForget.socket = "/path/to/socket"
|
34
37
|
|
35
38
|
Now inside our Ruby app:
|
36
39
|
|
@@ -39,13 +42,16 @@ Now inside our Ruby app:
|
|
39
42
|
|
40
43
|
# First set register our task by giving it a name and a path to a script file
|
41
44
|
FireAndForget.add_task(:long_running_task, "/path/to/script")
|
45
|
+
# => #<FireAndForget::TaskDescription @name=:long_running_task ...>
|
42
46
|
|
43
|
-
# when we want to call the script we simple call #fire passing the name of the
|
44
|
-
# we want to pass
|
47
|
+
# when we want to call the script we simple call #fire passing the name of the
|
48
|
+
# task and the options we want to pass
|
45
49
|
FireAndForget.fire(:long_running_task, {:param3 => "value3"})
|
50
|
+
# => "OK"
|
46
51
|
|
47
52
|
# Or, use the name as a method:
|
48
53
|
FireAndForget.long_running_task({:param3 => "value3"})
|
54
|
+
# => "OK"
|
49
55
|
|
50
56
|
This will result in the following command being exec'd in an independent process:
|
51
57
|
|
@@ -53,6 +59,38 @@ This will result in the following command being exec'd in an independent process
|
|
53
59
|
|
54
60
|
It up to the script to parse and deal with the command line options.
|
55
61
|
|
62
|
+
The options for the #add_task call are as follows:
|
63
|
+
|
64
|
+
task_name: A symbol giving the (unique) name for this task
|
65
|
+
path_to_binary: The path on disk of the executable to run when this task is launched
|
66
|
+
niceness: an integer in the range 0-20 giving the 'niceness' of the task process.
|
67
|
+
default_params: Command line parameters to pass to each invocation of this task
|
68
|
+
env: settings to add to this task's environment
|
69
|
+
|
70
|
+
The higher the nice value, the lower the priority of the task. Setting a high 'nice'
|
71
|
+
allows you to launch intensive background tasks without affecting the
|
72
|
+
performance of your front line HTTP services.
|
73
|
+
|
74
|
+
For example:
|
75
|
+
|
76
|
+
FireAndForget.add_task(:long_running_task, "/path/to/script", 12,
|
77
|
+
{:param1 => "value1", :param2 => "value2", :param3 => "value3"},
|
78
|
+
{"ENVIRONMENT_SETTING" => "Something"}
|
79
|
+
)
|
80
|
+
|
81
|
+
this will add some default parameters to all invocations of the task, so now the call
|
82
|
+
|
83
|
+
FireAndForget.fire(:long_running_task, {:param3 => "newvalue3"})
|
84
|
+
|
85
|
+
will issue the following command in the background
|
86
|
+
|
87
|
+
/path/to/script --param1="value1" --param2="value2" --param3="newvalue3"
|
88
|
+
|
89
|
+
as well as setting it's nice value to 12 and updating ENV with the extra parameter "ENVIRONMENT_SETTING".
|
90
|
+
|
91
|
+
Parameter values are JSON encoded so you can pass arrays or hashes. Again, it is
|
92
|
+
up to the script to decode these values.
|
93
|
+
|
56
94
|
Interprocess communication is relatively easy. Take the following as the source of the script "/path/to/script"
|
57
95
|
|
58
96
|
#!/usr/bin/env ruby
|
@@ -60,9 +98,9 @@ Interprocess communication is relatively easy. Take the following as the source
|
|
60
98
|
require 'rubygems'
|
61
99
|
require 'fire_and_forget'
|
62
100
|
|
63
|
-
# this will mix in the
|
64
|
-
#
|
65
|
-
include FireAndForget::Daemon
|
101
|
+
# this will mix in the F&F status methods
|
102
|
+
# the F&F configuration (socket and task-name) is calculated automatically from ENV parameters
|
103
|
+
include FireAndForget::Daemon
|
66
104
|
|
67
105
|
30.times do |i|
|
68
106
|
# update our status. What you put here is up to you, but it should be a String
|
@@ -83,10 +121,33 @@ If we decided we've had enough then we can kill it:
|
|
83
121
|
# Or send any signal (see the Process.kill documentation)
|
84
122
|
FireAndForget.kill(:long_running_task, "HUP")
|
85
123
|
|
124
|
+
== Supporting multiple users
|
125
|
+
|
126
|
+
Tasks will attempt to run as the same user as the originating process. To enable
|
127
|
+
this, and hence enable a single F&F server to launch tasks for any number of
|
128
|
+
separate web-apps, simply run the F&F server as root. See the following section on
|
129
|
+
Security for the implications of this and how to stop unlimited access to the service.
|
130
|
+
|
86
131
|
== Security
|
87
132
|
|
88
|
-
F&F is intended to be run in a relatively trusted environment
|
133
|
+
F&F is intended to be run in a relatively trusted environment. You the UNIX
|
134
|
+
socket file has its permissions set so that only the process owner and a defined
|
135
|
+
group can write to the file. To change the UNIX group that has access pass the
|
136
|
+
group name as a parameter to the server:
|
137
|
+
|
138
|
+
$ fire_forget -s /path/to/socket -g webapp_group
|
139
|
+
|
140
|
+
Only members of the 'webapp_group' will be able to launch processes through
|
141
|
+
the server. Additionally, only scripts belonging to the same user as the F&F task description
|
142
|
+
are executed.
|
143
|
+
|
144
|
+
In the meantime, as I say, this is defined to be run in a trusted environment, for instance on a
|
145
|
+
single non-shared server (potentially running many sites).
|
146
|
+
|
147
|
+
== TODO
|
89
148
|
|
149
|
+
- Improve security restrictions (perhaps by limiting the tasks that each user can launch)
|
150
|
+
- Improve the tests so they are closer to testing the full stack
|
90
151
|
|
91
152
|
== Contributing to fire_and_forget
|
92
153
|
|
data/bin/fire_forget
CHANGED
@@ -6,49 +6,72 @@ Dir.chdir(File.join(File.dirname(__FILE__), '..'))
|
|
6
6
|
faf_lib_path = File.expand_path(File.dirname(__FILE__) + "/../lib")
|
7
7
|
$:.unshift(faf_lib_path) unless $:.include?(faf_lib_path)
|
8
8
|
|
9
|
-
|
9
|
+
# include dependencies without using rubygems (to save memory)
|
10
|
+
$:.unshift('vendor/json-1.5.0/lib')
|
11
|
+
$:.unshift('vendor/daemons-1.1.0/lib')
|
12
|
+
|
13
|
+
require 'vendor/json-1.5.0/lib/json/pure'
|
14
|
+
require 'vendor/daemons-1.1.0/lib/daemons'
|
15
|
+
|
10
16
|
|
11
17
|
require 'ostruct'
|
12
18
|
require 'optparse'
|
13
19
|
require 'socket'
|
20
|
+
require 'etc'
|
14
21
|
require 'fire_and_forget'
|
15
22
|
|
16
23
|
options = OpenStruct.new
|
17
|
-
options.
|
18
|
-
options.
|
24
|
+
options.socket = FireAndForget::DEFAULT_SOCKET
|
25
|
+
options.gid = Process.egid
|
19
26
|
|
20
27
|
OptionParser.new do |opts|
|
21
|
-
opts.on("-
|
22
|
-
opts.on("-
|
28
|
+
opts.on("-s", "--socket SOCKET", "Socket") { |v| options.socket = v }
|
29
|
+
opts.on("-g", "--socket-group GROUPNAME", "Socket owning group") { |v| options.gid = nil; options.group_name = v }
|
23
30
|
end.parse!
|
24
31
|
|
32
|
+
raise ArgumentError, "You must specify a socket file using -s" unless options.socket
|
33
|
+
unless options.gid
|
34
|
+
if options.group_name
|
35
|
+
options.gid = Etc.getgrnam(options.group_name).gid
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
25
39
|
server = nil
|
26
40
|
|
27
41
|
begin
|
28
|
-
|
42
|
+
raise Errno::EADDRINUSE if File.exists?(options.socket) and File.socket?(options.socket)
|
43
|
+
server = UNIXServer.new(options.socket)
|
44
|
+
File.chown(nil, options.gid, options.socket)
|
45
|
+
# make the socket group writable
|
46
|
+
File.chmod(0770, options.socket)
|
29
47
|
rescue Errno::EADDRINUSE
|
30
|
-
puts "FAF unable to
|
48
|
+
puts "FAF unable to create socket #{options.socket}: file exists"
|
31
49
|
exit(1)
|
32
50
|
end
|
33
51
|
|
34
52
|
run = true
|
35
53
|
|
36
54
|
server_thread = Thread.new do
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
55
|
+
begin
|
56
|
+
while run and (session = server.accept)
|
57
|
+
request = response = ""
|
58
|
+
|
59
|
+
while l = session.gets
|
60
|
+
request << l
|
61
|
+
end
|
62
|
+
session.close_read
|
63
|
+
|
64
|
+
begin
|
65
|
+
response = FAF::Server.parse(request)
|
66
|
+
rescue => e
|
67
|
+
response = "ERROR #{e}"
|
68
|
+
end
|
69
|
+
session.write(response)
|
70
|
+
session.close
|
49
71
|
end
|
50
|
-
|
51
|
-
|
72
|
+
ensure
|
73
|
+
server.close
|
74
|
+
File.unlink(options.socket) if File.exists?(options.socket)
|
52
75
|
end
|
53
76
|
end
|
54
77
|
|
@@ -59,7 +82,7 @@ end
|
|
59
82
|
end
|
60
83
|
end
|
61
84
|
|
62
|
-
puts "Fire&Forget process #{$$} listening on #{options.
|
85
|
+
puts "Fire&Forget process #{$$} listening on #{options.socket} ..."
|
63
86
|
|
64
87
|
server_thread.join
|
65
88
|
|
data/examples/long_task
CHANGED
@@ -2,33 +2,44 @@
|
|
2
2
|
|
3
3
|
Dir.chdir(File.join(File.dirname(__FILE__), '..'))
|
4
4
|
|
5
|
-
|
5
|
+
File.open(File.join(File.dirname(__FILE__), "../../long.out"), 'w+') do |file|
|
6
|
+
file.sync = true
|
7
|
+
STDOUT.reopen(file)
|
8
|
+
STDERR.reopen(file)
|
6
9
|
|
7
|
-
|
8
|
-
require 'bundler'
|
10
|
+
$:.unshift(File.dirname(__FILE__) + "/../lib")
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
$stderr.puts e.message
|
14
|
-
$stderr.puts "Run `bundle install` to install missing gems"
|
15
|
-
exit e.status_code
|
16
|
-
end
|
12
|
+
require 'etc'
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler'
|
17
15
|
|
18
|
-
|
16
|
+
begin
|
17
|
+
Bundler.setup(:default)
|
18
|
+
rescue Bundler::BundlerError => e
|
19
|
+
$stderr.puts e.message
|
20
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
21
|
+
exit e.status_code
|
22
|
+
end
|
19
23
|
|
20
|
-
|
24
|
+
require 'fire_and_forget'
|
21
25
|
|
26
|
+
include FAF::Daemon
|
22
27
|
|
23
|
-
|
24
|
-
file.sync = true
|
28
|
+
file.write(("="*40)+"\n")
|
25
29
|
file.write("PID: #{$$}\n")
|
30
|
+
file.write("User: #{Etc.getlogin}\n")
|
31
|
+
file.write("UID: #{Process.uid}\n")
|
26
32
|
file.write(`ps -xO nice | grep '^#{$$}'`)
|
27
33
|
file.write("\n")
|
28
|
-
|
34
|
+
ENV.keys.sort.each do | k |
|
35
|
+
file.write(" #{k}: #{ENV[k].inspect}\n")
|
36
|
+
end
|
37
|
+
file.write("\n")
|
38
|
+
|
39
|
+
30.times do |i|
|
29
40
|
file.write("#{i}\n")
|
30
41
|
begin
|
31
|
-
|
42
|
+
set_task_status(i)
|
32
43
|
rescue Exception => e
|
33
44
|
file.write(e.to_s + "\n")
|
34
45
|
end
|
data/fire_and_forget.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{fire_and_forget}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.2.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Garry Hill"]
|
12
|
-
s.date = %q{
|
12
|
+
s.date = %q{2011-01-19}
|
13
13
|
s.default_executable = %q{fire_forget}
|
14
14
|
s.email = %q{garry@magnetised.info}
|
15
15
|
s.executables = ["fire_forget"]
|
@@ -31,19 +31,56 @@ Gem::Specification.new do |s|
|
|
31
31
|
"lib/fire_and_forget/client.rb",
|
32
32
|
"lib/fire_and_forget/command.rb",
|
33
33
|
"lib/fire_and_forget/command/fire.rb",
|
34
|
+
"lib/fire_and_forget/command/get_pid.rb",
|
34
35
|
"lib/fire_and_forget/command/get_status.rb",
|
35
36
|
"lib/fire_and_forget/command/kill.rb",
|
36
37
|
"lib/fire_and_forget/command/set_pid.rb",
|
37
38
|
"lib/fire_and_forget/command/set_status.rb",
|
38
39
|
"lib/fire_and_forget/config.rb",
|
39
40
|
"lib/fire_and_forget/daemon.rb",
|
41
|
+
"lib/fire_and_forget/errors.rb",
|
40
42
|
"lib/fire_and_forget/launcher.rb",
|
41
43
|
"lib/fire_and_forget/server.rb",
|
42
|
-
"lib/fire_and_forget/
|
44
|
+
"lib/fire_and_forget/task_description.rb",
|
43
45
|
"lib/fire_and_forget/utilities.rb",
|
44
46
|
"lib/fire_and_forget/version.rb",
|
45
47
|
"test/helper.rb",
|
46
|
-
"test/test_fire_and_forget.rb"
|
48
|
+
"test/test_fire_and_forget.rb",
|
49
|
+
"vendor/daemons-1.1.0/LICENSE",
|
50
|
+
"vendor/daemons-1.1.0/README",
|
51
|
+
"vendor/daemons-1.1.0/Rakefile",
|
52
|
+
"vendor/daemons-1.1.0/Releases",
|
53
|
+
"vendor/daemons-1.1.0/TODO",
|
54
|
+
"vendor/daemons-1.1.0/lib/daemons.rb",
|
55
|
+
"vendor/daemons-1.1.0/lib/daemons/application.rb",
|
56
|
+
"vendor/daemons-1.1.0/lib/daemons/application_group.rb",
|
57
|
+
"vendor/daemons-1.1.0/lib/daemons/change_privilege.rb",
|
58
|
+
"vendor/daemons-1.1.0/lib/daemons/cmdline.rb",
|
59
|
+
"vendor/daemons-1.1.0/lib/daemons/controller.rb",
|
60
|
+
"vendor/daemons-1.1.0/lib/daemons/daemonize.rb",
|
61
|
+
"vendor/daemons-1.1.0/lib/daemons/etc_extension.rb",
|
62
|
+
"vendor/daemons-1.1.0/lib/daemons/exceptions.rb",
|
63
|
+
"vendor/daemons-1.1.0/lib/daemons/monitor.rb",
|
64
|
+
"vendor/daemons-1.1.0/lib/daemons/pid.rb",
|
65
|
+
"vendor/daemons-1.1.0/lib/daemons/pidfile.rb",
|
66
|
+
"vendor/daemons-1.1.0/lib/daemons/pidmem.rb",
|
67
|
+
"vendor/daemons-1.1.0/setup.rb",
|
68
|
+
"vendor/json-1.5.0/COPYING",
|
69
|
+
"vendor/json-1.5.0/GPL",
|
70
|
+
"vendor/json-1.5.0/README",
|
71
|
+
"vendor/json-1.5.0/README-json-jruby.markdown",
|
72
|
+
"vendor/json-1.5.0/Rakefile",
|
73
|
+
"vendor/json-1.5.0/TODO",
|
74
|
+
"vendor/json-1.5.0/VERSION",
|
75
|
+
"vendor/json-1.5.0/lib/json.rb",
|
76
|
+
"vendor/json-1.5.0/lib/json/add/core.rb",
|
77
|
+
"vendor/json-1.5.0/lib/json/add/rails.rb",
|
78
|
+
"vendor/json-1.5.0/lib/json/common.rb",
|
79
|
+
"vendor/json-1.5.0/lib/json/editor.rb",
|
80
|
+
"vendor/json-1.5.0/lib/json/pure.rb",
|
81
|
+
"vendor/json-1.5.0/lib/json/pure/generator.rb",
|
82
|
+
"vendor/json-1.5.0/lib/json/pure/parser.rb",
|
83
|
+
"vendor/json-1.5.0/lib/json/version.rb"
|
47
84
|
]
|
48
85
|
s.homepage = %q{http://github.com/magnetised/fire_and_forget}
|
49
86
|
s.licenses = ["MIT"]
|
@@ -13,7 +13,7 @@ module FireAndForget
|
|
13
13
|
def open_connection
|
14
14
|
connection = result = nil
|
15
15
|
begin
|
16
|
-
connection =
|
16
|
+
connection = UNIXSocket.open(FireAndForget.socket)
|
17
17
|
yield(connection)
|
18
18
|
connection.flush
|
19
19
|
connection.close_write
|
@@ -4,7 +4,13 @@ require 'daemons'
|
|
4
4
|
module FireAndForget
|
5
5
|
module Command
|
6
6
|
class Fire < CommandBase
|
7
|
-
attr_reader :niceness
|
7
|
+
attr_reader :niceness, :task_uid, :task_gid
|
8
|
+
|
9
|
+
def initialize(task, params={})
|
10
|
+
super
|
11
|
+
@task_uid = Process.euid
|
12
|
+
@task_gid = Process.egid
|
13
|
+
end
|
8
14
|
|
9
15
|
def niceness
|
10
16
|
@task.niceness
|
@@ -23,24 +29,37 @@ module FireAndForget
|
|
23
29
|
end
|
24
30
|
|
25
31
|
def permitted?
|
26
|
-
raise
|
32
|
+
raise PermissionsError, "'#{binary}' does not belong to user '#{ENV["USER"]}'" unless File.stat(binary).uid == task_uid
|
27
33
|
true
|
28
34
|
end
|
29
35
|
|
30
36
|
def exists?
|
31
|
-
raise
|
37
|
+
raise FileNotFoundError, "'#{binary}'" unless File.exists?(binary)
|
32
38
|
true
|
33
39
|
end
|
34
40
|
|
41
|
+
def env
|
42
|
+
@task.env.merge({
|
43
|
+
FireAndForget::ENV_SOCKET => FireAndForget.socket,
|
44
|
+
FireAndForget::ENV_TASK_NAME => @task.name.to_s
|
45
|
+
})
|
46
|
+
end
|
47
|
+
|
35
48
|
def run
|
36
49
|
if valid?
|
37
50
|
pid = fork do
|
51
|
+
# set up the environment so that the task can access the F&F server
|
52
|
+
env.each { | k, v | ENV[k] = v }
|
38
53
|
Daemons.daemonize(:backtrace => true)
|
39
54
|
Process.setpriority(Process::PRIO_PROCESS, 0, niceness) if niceness > 0
|
55
|
+
# change to the UID of the originating thread if necessary
|
56
|
+
Process::UID.change_privilege(task_uid) unless Process.euid == task_uid
|
40
57
|
exec(cmd)
|
41
58
|
end
|
42
59
|
Process.detach(pid) if pid
|
43
|
-
|
60
|
+
# don't return the PID as it's actually wrong (the daemonize call forks again so our original
|
61
|
+
# PID is at least 1 out)
|
62
|
+
"OK"
|
44
63
|
end
|
45
64
|
end
|
46
65
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module FireAndForget
|
2
|
+
module Command
|
3
|
+
class GetPid < CommandBase
|
4
|
+
|
5
|
+
attr_reader :task_name, :pid
|
6
|
+
|
7
|
+
def initialize(task_name)
|
8
|
+
@task_name = task_name.to_sym
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
FireAndForget::Server.pids[@task_name]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
|
@@ -7,6 +7,16 @@ module FireAndForget
|
|
7
7
|
Marshal.load(command)
|
8
8
|
end
|
9
9
|
|
10
|
+
def self.allowed?(cmd)
|
11
|
+
allowed_commands.include?(cmd.class)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.allowed_commands
|
15
|
+
@allowed_commands ||= self.constants.map { |c| self.const_get(c) }.select do |k|
|
16
|
+
k.respond_to?(:ancestors) && k.ancestors.include?(CommandBase)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
10
20
|
class CommandBase
|
11
21
|
attr_reader :tag, :cmd, :params, :task
|
12
22
|
|
@@ -39,5 +49,6 @@ module FireAndForget
|
|
39
49
|
autoload :SetStatus, "fire_and_forget/command/set_status"
|
40
50
|
autoload :GetStatus, "fire_and_forget/command/get_status"
|
41
51
|
autoload :SetPid, "fire_and_forget/command/set_pid"
|
52
|
+
autoload :GetPid, "fire_and_forget/command/get_pid"
|
42
53
|
end
|
43
54
|
end
|
@@ -1,14 +1,9 @@
|
|
1
1
|
module FireAndForget
|
2
2
|
module Config
|
3
|
-
attr_accessor :
|
4
|
-
attr_accessor :bind_address
|
3
|
+
attr_accessor :socket
|
5
4
|
|
6
|
-
def
|
7
|
-
@
|
8
|
-
end
|
9
|
-
|
10
|
-
def bind_address
|
11
|
-
@bind_address ||= "127.0.0.1"
|
5
|
+
def socket
|
6
|
+
@socket ||= (ENV[FireAndForget::ENV_SOCKET] || FireAndForget::DEFAULT_SOCKET)
|
12
7
|
end
|
13
8
|
end
|
14
9
|
end
|
@@ -1,31 +1,22 @@
|
|
1
1
|
module FireAndForget
|
2
|
-
module Daemon
|
2
|
+
module Daemon
|
3
3
|
|
4
|
-
def self.
|
5
|
-
|
6
|
-
|
7
|
-
FireAndForget.map_pid(self.task_name, $$)
|
8
|
-
rescue Errno::ECONNREFUSED
|
9
|
-
# server isn't running but we don't want this to stop our script
|
10
|
-
end
|
4
|
+
def self.task_name
|
5
|
+
ENV[FireAndForget::ENV_TASK_NAME]
|
6
|
+
end
|
11
7
|
|
12
|
-
|
13
|
-
|
14
|
-
|
8
|
+
def self.included(klass)
|
9
|
+
FireAndForget.set_pid(self.task_name, $$)
|
10
|
+
rescue Errno::ECONNREFUSED
|
11
|
+
# server isn't running but we don't want this to stop our script
|
12
|
+
end
|
15
13
|
|
16
|
-
def self.task_name
|
17
|
-
@@task_name
|
18
|
-
end
|
19
14
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
end
|
25
|
-
end
|
26
|
-
m.task_name = task_name
|
27
|
-
m
|
15
|
+
def set_task_status(status)
|
16
|
+
FireAndForget.set_status(FireAndForget::Daemon.task_name, status)
|
17
|
+
rescue Errno::ECONNREFUSED
|
18
|
+
# server isn't running but we don't want this to stop our script
|
28
19
|
end
|
29
|
-
|
30
20
|
end
|
31
21
|
end
|
22
|
+
|