fire_and_forget 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. data/Gemfile.lock +0 -8
  2. data/README.rdoc +75 -14
  3. data/bin/fire_forget +45 -22
  4. data/examples/long_task +27 -16
  5. data/fire_and_forget.gemspec +41 -4
  6. data/lib/fire_and_forget/client.rb +1 -1
  7. data/lib/fire_and_forget/command/fire.rb +23 -4
  8. data/lib/fire_and_forget/command/get_pid.rb +20 -0
  9. data/lib/fire_and_forget/command/set_pid.rb +0 -2
  10. data/lib/fire_and_forget/command/set_status.rb +1 -1
  11. data/lib/fire_and_forget/command.rb +11 -0
  12. data/lib/fire_and_forget/config.rb +3 -8
  13. data/lib/fire_and_forget/daemon.rb +14 -23
  14. data/lib/fire_and_forget/errors.rb +8 -0
  15. data/lib/fire_and_forget/launcher.rb +69 -6
  16. data/lib/fire_and_forget/server.rb +5 -1
  17. data/lib/fire_and_forget/task_description.rb +11 -0
  18. data/lib/fire_and_forget/utilities.rb +4 -4
  19. data/lib/fire_and_forget/version.rb +1 -1
  20. data/lib/fire_and_forget.rb +6 -2
  21. data/test/test_fire_and_forget.rb +59 -26
  22. data/vendor/daemons-1.1.0/LICENSE +29 -0
  23. data/vendor/daemons-1.1.0/README +224 -0
  24. data/vendor/daemons-1.1.0/Rakefile +88 -0
  25. data/vendor/daemons-1.1.0/Releases +152 -0
  26. data/vendor/daemons-1.1.0/TODO +2 -0
  27. data/vendor/daemons-1.1.0/lib/daemons/application.rb +468 -0
  28. data/vendor/daemons-1.1.0/lib/daemons/application_group.rb +194 -0
  29. data/vendor/daemons-1.1.0/lib/daemons/change_privilege.rb +19 -0
  30. data/vendor/daemons-1.1.0/lib/daemons/cmdline.rb +124 -0
  31. data/vendor/daemons-1.1.0/lib/daemons/controller.rb +140 -0
  32. data/vendor/daemons-1.1.0/lib/daemons/daemonize.rb +271 -0
  33. data/vendor/daemons-1.1.0/lib/daemons/etc_extension.rb +12 -0
  34. data/vendor/daemons-1.1.0/lib/daemons/exceptions.rb +28 -0
  35. data/vendor/daemons-1.1.0/lib/daemons/monitor.rb +138 -0
  36. data/vendor/daemons-1.1.0/lib/daemons/pid.rb +109 -0
  37. data/vendor/daemons-1.1.0/lib/daemons/pidfile.rb +116 -0
  38. data/vendor/daemons-1.1.0/lib/daemons/pidmem.rb +19 -0
  39. data/vendor/daemons-1.1.0/lib/daemons.rb +288 -0
  40. data/vendor/daemons-1.1.0/setup.rb +1360 -0
  41. data/vendor/json-1.5.0/COPYING +58 -0
  42. data/vendor/json-1.5.0/GPL +340 -0
  43. data/vendor/json-1.5.0/README +356 -0
  44. data/vendor/json-1.5.0/README-json-jruby.markdown +33 -0
  45. data/vendor/json-1.5.0/Rakefile +397 -0
  46. data/vendor/json-1.5.0/TODO +1 -0
  47. data/vendor/json-1.5.0/VERSION +1 -0
  48. data/vendor/json-1.5.0/lib/json/add/core.rb +147 -0
  49. data/vendor/json-1.5.0/lib/json/add/rails.rb +8 -0
  50. data/vendor/json-1.5.0/lib/json/common.rb +419 -0
  51. data/vendor/json-1.5.0/lib/json/editor.rb +1369 -0
  52. data/vendor/json-1.5.0/lib/json/pure/generator.rb +441 -0
  53. data/vendor/json-1.5.0/lib/json/pure/parser.rb +320 -0
  54. data/vendor/json-1.5.0/lib/json/pure.rb +15 -0
  55. data/vendor/json-1.5.0/lib/json/version.rb +8 -0
  56. data/vendor/json-1.5.0/lib/json.rb +10 -0
  57. metadata +41 -4
  58. data/lib/fire_and_forget/task.rb +0 -11
@@ -1,17 +1,44 @@
1
1
  module FireAndForget
2
2
  module Launcher
3
- def add_task(task_name, path_to_binary, default_params={}, niceness=0)
4
- if default_params.is_a?(Numeric)
5
- niceness = default_params
6
- default_params = {}
7
- end
8
- tasks[task_name] = Task.new(task_name, path_to_binary, default_params, niceness)
3
+ # Registers a task and makes it available for easy launching using #fire
4
+ #
5
+ # @param [Symbol] task_name
6
+ # the name for the task. This should be unique
7
+ #
8
+ # @param [String] path_to_binary
9
+ # the path to the executable that should be run when this task is launched
10
+ #
11
+ # @param [Fixnum] niceness
12
+ # the niceness value of the process >= 0. The higher this value the 'nicer' the launched
13
+ # process will be (a high nice value results in a low priority task).
14
+ # On UNIX systems the max, nicest, value is 20
15
+ #
16
+ # @param [Hash] default_params
17
+ # A Hash of parameters that should be passed to every invocation of the task.
18
+ # These will be converted to command line parameters
19
+ # { "setting" => "value", "output" => "destination"}
20
+ # gives the parameters
21
+ # --setting=value --output=destination
22
+ # @see FireAndForget::Utilities#to_arguments
23
+ #
24
+ # @param [Hash] env
25
+ # A Hash of values to add to the task's ENV settings
26
+ #
27
+ def add_task(task_name, path_to_binary, niceness=0, default_params={}, env={})
28
+ tasks[task_name] = TaskDescription.new(task_name, path_to_binary, niceness, default_params, env)
9
29
  end
10
30
 
31
+ # Returns the path to the binary for the given task
32
+ #
33
+ # @param [Symbol] task_name the name of the task
34
+ # @return [String] the path of the task's binary
11
35
  def binary(task_name)
12
36
  tasks[task_name].binary
13
37
  end
14
38
 
39
+ # Gets the TaskDescription of a task
40
+ #
41
+ # @param [Symbol] task_name the name of the task to get
15
42
  def [](task_name)
16
43
  tasks[task_name]
17
44
  end
@@ -20,35 +47,65 @@ module FireAndForget
20
47
  @tasks ||= {}
21
48
  end
22
49
 
50
+ # Launches the given task
51
+ #
52
+ # @param [Symbol] task_name the name of the task to launch
53
+ # @param [Hash] params parameters to pass to the executable
23
54
  def fire(task_name, params={})
24
55
  task = tasks[task_name]
25
56
  command = Command::Fire.new(task, params)
26
57
  Client.run(command)
27
58
  end
28
59
 
60
+ # Sets the status of the given task enabling simple interprocess communication through string messages
61
+ #
62
+ # @param [String] task_name the name of the task to set the status for
63
+ # @param [#to_s] status the setting for the given task's status
29
64
  def set_status(task_name, status)
30
65
  command = Command::SetStatus.new(task_name, status)
31
66
  Client.run(command)
32
67
  end
33
68
 
69
+ # Get the status for the given task
70
+ # @see #set_status
71
+ #
72
+ # @param [Symbol] task_name the name of the task
73
+ # @return [String] the current status of the task
34
74
  def get_status(task_name)
35
75
  command = Command::GetStatus.new(task_name)
36
76
  Client.run(command)
37
77
  end
38
78
 
79
+ # Used by the {FireAndForget::Daemon} module to set the correct PID for a given task
39
80
  def map_pid(task_name, pid)
40
81
  command = Command::SetPid.new(task_name, pid)
41
82
  Client.run(command)
42
83
  end
84
+ alias_method :set_pid, :map_pid
85
+
86
+ # Retrieve the PID of the running task given by task_name
87
+ def get_pid(task_name)
88
+ command = Command::GetPid.new(task_name)
89
+ Client.run(command)
90
+ end
91
+ alias_method :pid, :get_pid
43
92
 
93
+ # Sends a running task the TERM signal
44
94
  def term(task_name)
45
95
  kill(task_name, "TERM")
46
96
  end
47
97
 
98
+ # Sends a running task the INT signal
48
99
  def int(task_name)
49
100
  kill(task_name, "INT")
50
101
  end
51
102
 
103
+ # Sends a running task an arbitrary signal
104
+ #
105
+ # @param [Symbol] task_name the name of the task to send the signal
106
+ # @param [String] signal the signal to send
107
+ #
108
+ # @see Signal#list for a full list of signals available
52
109
  def kill(task_name, signal="TERM")
53
110
  command = Command::Kill.new(task_name, signal)
54
111
  Client.run(command)
@@ -56,6 +113,12 @@ module FireAndForget
56
113
 
57
114
  protected
58
115
 
116
+ # Catch method missing to enable launching of tasks by direct name
117
+ # e.g.
118
+ # FireAndForget.add_task(:process_things, "/usr/bin/process")
119
+ # launch this task:
120
+ # FireAndForget.process_things
121
+ #
59
122
  def method_missing(method, *args, &block)
60
123
  if tasks.key?(method)
61
124
  fire(method, *args, &block)
@@ -7,7 +7,11 @@ module FireAndForget
7
7
  end
8
8
 
9
9
  def self.run(cmd)
10
- cmd.run
10
+ if Command.allowed?(cmd)
11
+ cmd.run
12
+ else
13
+ raise PermissionsError, "'#{cmd.class}' is not an approved command"
14
+ end
11
15
  end
12
16
 
13
17
  def self.kill(task_name, signal="TERM")
@@ -0,0 +1,11 @@
1
+
2
+
3
+ module FireAndForget
4
+ class TaskDescription
5
+ attr_reader :name, :binary, :niceness, :params, :env
6
+
7
+ def initialize(name, path_to_binary, niceness=0, default_parameters={}, env={})
8
+ @name, @binary, @params, @niceness, @env = name, path_to_binary, default_parameters, niceness, env
9
+ end
10
+ end
11
+ end
@@ -4,16 +4,16 @@ module FireAndForget
4
4
  module Utilities
5
5
  def to_arguments(params={})
6
6
  params.keys.sort { |a, b| a.to_s <=> b.to_s }.map do |key|
7
- %(--#{key}=#{to_json(params[key])})
7
+ %(--#{key}=#{to_parameter(params[key])})
8
8
  end.join(" ")
9
9
  end
10
10
 
11
- def to_json(obj)
11
+ def to_parameter(obj)
12
12
  if obj.is_a?(String)
13
- obj.inspect
13
+ obj
14
14
  else
15
15
  JSON.generate(obj)
16
- end
16
+ end.inspect
17
17
  end
18
18
  end
19
19
  end
@@ -1,4 +1,4 @@
1
1
 
2
2
  module FireAndForget
3
- VERSION = "0.1.2"
3
+ VERSION = "0.2.0"
4
4
  end
@@ -1,16 +1,20 @@
1
1
 
2
2
  module FireAndForget
3
- DEFAULT_PORT = 3001
3
+ DEFAULT_SOCKET = "/tmp/fire_and_forget.sock"
4
+ ENV_SOCKET = "__FAF_SOCKET"
5
+ ENV_TASK_NAME = "__FAF_TASK_NAME"
4
6
 
5
7
  autoload :Config, "fire_and_forget/config"
6
8
  autoload :Utilities, "fire_and_forget/utilities"
7
9
  autoload :Launcher, "fire_and_forget/launcher"
8
- autoload :Task, "fire_and_forget/task"
10
+ autoload :TaskDescription, "fire_and_forget/task_description"
9
11
  autoload :Client, "fire_and_forget/client"
10
12
  autoload :Command, "fire_and_forget/command"
11
13
  autoload :Server, "fire_and_forget/server"
12
14
  autoload :Daemon, "fire_and_forget/daemon"
13
15
 
16
+ require "fire_and_forget/errors"
17
+
14
18
  extend Config
15
19
  extend Utilities
16
20
  extend Launcher
@@ -7,7 +7,7 @@ class TestFireAndForget < Test::Unit::TestCase
7
7
  :param1 => "value1",
8
8
  :param2 => "value2",
9
9
  :array => [1, 2, "3"]
10
- }).should == %(--array=[1,2,"3"] --param1="value1" --param2="value2")
10
+ }).should == %(--array="[1,2,\\"3\\"]" --param1="value1" --param2="value2")
11
11
  end
12
12
  end
13
13
  context "configuration" do
@@ -29,20 +29,24 @@ class TestFireAndForget < Test::Unit::TestCase
29
29
  FAF.publish(args)
30
30
  end
31
31
 
32
- should "enable setting of port for server" do
33
- FAF.port = 3007
34
- FAF.port.should == 3007
35
- end
32
+ should "enable setting of path to socket" do
33
+ FAF.socket = "/tmp/something"
34
+ FAF.socket.should == "/tmp/something"
35
+ end
36
+ # should "enable setting of port for server" do
37
+ # FAF.port = 3007
38
+ # FAF.port.should == 3007
39
+ # end
36
40
 
37
- should "enable setting an address for the server" do
38
- FAF.bind_address = "10.0.1.10"
39
- FAF.bind_address.should == "10.0.1.10"
40
- end
41
+ # should "enable setting an address for the server" do
42
+ # FAF.bind_address = "10.0.1.10"
43
+ # FAF.bind_address.should == "10.0.1.10"
44
+ # end
41
45
  end
42
46
 
43
47
  context "commands" do
44
48
  should "serialize and deserialize correctly" do
45
- task = FAF::Task.new(:publish, "/publish", {:param1 => "value1", :param2 => "value2"}, 9)
49
+ task = FAF::TaskDescription.new(:publish, "/publish", 9, {:param1 => "value1", :param2 => "value2"})
46
50
  cmd = FAF::Command::CommandBase.new(task, {"param2" => "newvalue2", :param3 => "value3"})
47
51
  cmd2 = FAF::Command.load(cmd.dump)
48
52
  task2 = cmd2.task
@@ -55,28 +59,56 @@ class TestFireAndForget < Test::Unit::TestCase
55
59
 
56
60
  context "actions" do
57
61
  setup do
58
- @task = FAF::Task.new(:publish, "/publish", {:param1 => "value1", :param2 => "value2"}, 9)
62
+ @task = FAF::TaskDescription.new(:publish, "/publish", 9, {:param1 => "value1", :param2 => "value2"})
59
63
  end
60
64
  should "set status for a task" do
61
65
  cmd = FAF::Command::SetStatus.new(:publish, :doing)
62
66
  FAF::Server.run(cmd)
63
67
  cmd = FAF::Command::GetStatus.new(:publish)
64
68
  status = FAF::Server.run(cmd)
65
- status.should == :doing
69
+ status.should == "doing"
66
70
  end
71
+
67
72
  should "only run scripts belonging to the same user as the ruby process" do
68
73
  stub(File).exist?("/publish") { true }
69
74
  stub(File).exists?("/publish") { true }
70
- stub(File).owned?("/publish") { false }
75
+ stat = Object.new
76
+ stub(stat).uid { Process.uid + 1 }
77
+ stub(File).stat("/publish") { stat }
71
78
  cmd = FAF::Command::Fire.new(@task)
72
- lambda { cmd.run }.should raise_error(Errno::EACCES)
79
+ lambda { cmd.run }.should raise_error(FAF::PermissionsError)
73
80
  end
81
+
82
+ should "not raise an error if the binary belongs to this process" do
83
+ stat = Object.new
84
+ stub(stat).uid { Process.uid }
85
+ stub(File).stat("/publish") { stat }
86
+ cmd = FAF::Command::Fire.new(@task)
87
+ lambda { cmd.permitted? }.should_not raise_error(FAF::PermissionsError)
88
+ end
89
+
90
+ should "raise an error if the binary belongs to this process" do
91
+ stat = Object.new
92
+ uid = Process.uid
93
+ stub(stat).uid { uid }
94
+ stub(File).stat("/publish") { stat }
95
+ stub(Process).euid { uid + 1 }
96
+ cmd = FAF::Command::Fire.new(@task)
97
+ lambda { cmd.permitted? }.should raise_error(FAF::PermissionsError)
98
+ end
99
+
74
100
  should "give error if binary doesn't exist" do
75
101
  stub(File).exist?("/publish") { false }
76
102
  stub(File).exists?("/publish") { false }
77
103
  stub(File).owned?("/publish") { true }
78
104
  cmd = FAF::Command::Fire.new(@task)
79
- lambda { cmd.run }.should raise_error(Errno::ENOENT )
105
+ lambda { cmd.run }.should raise_error(FAF::FileNotFoundError)
106
+ end
107
+
108
+ should "raise error if command isn't one of the approved list" do
109
+ cmd = Object.new
110
+ mock(cmd).run.times(0)
111
+ lambda { FAF::Server.run(cmd) }.should raise_error(FAF::PermissionsError)
80
112
  end
81
113
  end
82
114
 
@@ -92,9 +124,8 @@ class TestFireAndForget < Test::Unit::TestCase
92
124
  stub(connection).close_write
93
125
  mock(connection).read { "99999" }
94
126
  mock(connection).close
95
- FAF.bind_address = "10.0.1.10"
96
- FAF.port = 9007
97
- mock(TCPSocket).open("10.0.1.10", 9007) { connection }
127
+ FAF.socket = "/tmp/faf.sock"
128
+ mock(UNIXSocket).open("/tmp/faf.sock") { connection }
98
129
  pid = FAF.publish({:param2 => "value3"})
99
130
  pid.should == "99999"
100
131
  end
@@ -107,6 +138,7 @@ class TestFireAndForget < Test::Unit::TestCase
107
138
 
108
139
  should "run any command sent to it" do
109
140
  command = Object.new
141
+ stub(FAF::Command).allowed? { true }
110
142
  mock(FAF::Command).load(is_a(String)) { command }
111
143
  mock(command).run { "666" }
112
144
  result = FAF::Server.parse("object")
@@ -115,17 +147,18 @@ class TestFireAndForget < Test::Unit::TestCase
115
147
  end
116
148
  context "daemon methods" do
117
149
  setup do
118
- class ::TaskClass; end
150
+ class ::TaskDescriptionClass; end
119
151
  end
120
152
  teardown do
121
- Object.send(:remove_const, :TaskClass) rescue nil
153
+ Object.send(:remove_const, :TaskDescriptionClass) rescue nil
122
154
  end
123
155
 
124
- should "map a taskname to a pid when included" do
125
- mock(FAF::Client).run(satisfy { |cmd|
126
- (cmd.pid == $$) && (cmd.task_name == :tasking)
127
- })
128
- TaskClass.send(:include, FAF::Daemon[:tasking])
129
- end
156
+ ## not sure how to test this
157
+ # should "map a taskname to a pid when included" do
158
+ # mock(FAF::Client).run(satisfy { |cmd|
159
+ # (cmd.pid == $$) && (cmd.task_name == :tasking)
160
+ # })
161
+ # TaskDescriptionClass.send(:include, FAF::Daemon)
162
+ # end
130
163
  end
131
164
  end
@@ -0,0 +1,29 @@
1
+ Copyright (c) 2005-2007 Thomas Uehlinger
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ This license does not apply to daemonize.rb, which is was written by
25
+ Travis Whitton und published under the following license:
26
+
27
+ The Daemonize extension module is copywrited free software by Travis Whitton
28
+ <whitton@atlantic.net>. You can redistribute it under the terms specified in
29
+ the COPYING file of the Ruby distribution.
@@ -0,0 +1,224 @@
1
+ = Daemons Version 1.1.0
2
+
3
+ (See Releases for release-specific information)
4
+
5
+ == What is Daemons?
6
+
7
+ Daemons provides an easy way to wrap existing ruby scripts (for example a self-written server)
8
+ to be <i>run as a daemon</i> and to be <i>controlled by simple start/stop/restart commands</i>.
9
+
10
+ If you want, you can also use daemons to <i>run blocks of ruby code in a daemon process</i> and to control
11
+ these processes from the main application.
12
+
13
+ Besides this basic functionality, daemons offers many advanced features like <i>exception backtracing</i>
14
+ and logging (in case your ruby script crashes) and <i>monitoring</i> and automatic restarting of your processes
15
+ if they crash.
16
+
17
+ Daemons includes the <tt>daemonize.rb</tt> script written by <i>Travis Whitton</i> to do the daemonization
18
+ process.
19
+
20
+ == Basic Usage
21
+
22
+ You can use Daemons in four different ways:
23
+
24
+ === 1. Create wrapper scripts for your server scripts or applications
25
+
26
+ Layout: suppose you have your self-written server <tt>myserver.rb</tt>:
27
+
28
+ # this is myserver.rb
29
+ # it does nothing really useful at the moment
30
+
31
+ loop do
32
+ sleep(5)
33
+ end
34
+
35
+ To use <tt>myserver.rb</tt> in a production environment, you need to be able to
36
+ run <tt>myserver.rb</tt> in the _background_ (this means detach it from the console, fork it
37
+ in the background, release all directories and file descriptors).
38
+
39
+ Just create <tt>myserver_control.rb</tt> like this:
40
+
41
+ # this is myserver_control.rb
42
+
43
+ require 'rubygems' # if you use RubyGems
44
+ require 'daemons'
45
+
46
+ Daemons.run('myserver.rb')
47
+
48
+ And use it like this from the console:
49
+
50
+ $ ruby myserver_control.rb start
51
+ (myserver.rb is now running in the background)
52
+ $ ruby myserver_control.rb restart
53
+ (...)
54
+ $ ruby myserver_control.rb stop
55
+
56
+ For testing purposes you can even run <tt>myserver.rb</tt> <i>without forking</i> in the background:
57
+
58
+ $ ruby myserver_control.rb run
59
+
60
+ An additional nice feature of Daemons is that you can pass <i>additional arguments</i> to the script that
61
+ should be daemonized by seperating them by two _hyphens_:
62
+
63
+ $ ruby myserver_control.rb start -- --file=anyfile --a_switch another_argument
64
+
65
+
66
+ === 2. Create wrapper scripts that include your server procs
67
+
68
+ Layout: suppose you have some code you want to run in the background and control that background process
69
+ from a script:
70
+
71
+ # this is your code
72
+ # it does nothing really useful at the moment
73
+
74
+ loop do
75
+ sleep(5)
76
+ end
77
+
78
+ To run this code as a daemon create <tt>myproc_control.rb</tt> like this and include your code:
79
+
80
+ # this is myproc_control.rb
81
+
82
+ require 'rubygems' # if you use RubyGems
83
+ require 'daemons'
84
+
85
+ Daemons.run_proc('myproc.rb') do
86
+ loop do
87
+ sleep(5)
88
+ end
89
+ end
90
+
91
+ And use it like this from the console:
92
+
93
+ $ ruby myproc_control.rb start
94
+ (myproc.rb is now running in the background)
95
+ $ ruby myproc_control.rb restart
96
+ (...)
97
+ $ ruby myproc_control.rb stop
98
+
99
+ For testing purposes you can even run <tt>myproc.rb</tt> <i>without forking</i> in the background:
100
+
101
+ $ ruby myproc_control.rb run
102
+
103
+ === 3. Control a bunch of daemons from another application
104
+
105
+ Layout: you have an application <tt>my_app.rb</tt> that wants to run a bunch of
106
+ server tasks as daemon processes.
107
+
108
+ # this is my_app.rb
109
+
110
+ require 'rubygems' # if you use RubyGems
111
+ require 'daemons'
112
+
113
+ task1 = Daemons.call(:multiple => true) do
114
+ # first server task
115
+
116
+ loop {
117
+ conn = accept_conn()
118
+ serve(conn)
119
+ }
120
+ end
121
+
122
+ task2 = Daemons.call do
123
+ # second server task
124
+
125
+ loop {
126
+ something_different()
127
+ }
128
+ end
129
+
130
+ # the parent process continues to run
131
+
132
+ # we can even control our tasks, for example stop them
133
+ task1.stop
134
+ task2.stop
135
+
136
+ exit
137
+
138
+ === 4. Daemonize the currently running process
139
+
140
+ Layout: you have an application <tt>my_daemon.rb</tt> that wants to run as a daemon
141
+ (but without the ability to be controlled by daemons via start/stop commands)
142
+
143
+ # this is my_daemons.rb
144
+
145
+ require 'rubygems' # if you use RubyGems
146
+ require 'daemons'
147
+
148
+ # Initialize the app while we're not a daemon
149
+ init()
150
+
151
+ # Become a daemon
152
+ Daemons.daemonize
153
+
154
+ # The server loop
155
+ loop {
156
+ conn = accept_conn()
157
+ serve(conn)
158
+ }
159
+
160
+
161
+ <b>For further documentation, refer to the module documentation of Daemons.</b>
162
+
163
+
164
+ == Download and Installation
165
+
166
+ *Download*: just go to http://rubyforge.org/projects/daemons/
167
+
168
+ Installation *with* RubyGems:
169
+ $ su
170
+ # gem install daemons
171
+
172
+ Installation *without* RubyGems:
173
+ $ tar xfz daemons-x.x.x.tar.gz
174
+ $ cd daemons-x.x.x
175
+ $ su
176
+ # ruby setup.rb
177
+
178
+ == Documentation
179
+
180
+ For further documentation, refer to the module documentation of Daemons (click on Daemons).
181
+
182
+ The RDoc documentation is also online at http://daemons.rubyforge.org
183
+
184
+
185
+ == Author
186
+
187
+ Written 2005-2010 by Thomas Uehlinger <mailto:th.uehlinger@gmx.ch>.
188
+ Anonymous SVN checkout is available with "svn checkout http://daemons.rubyforge.org/svn/".
189
+
190
+ == License
191
+
192
+ Copyright (c) 2005-2010 Thomas Uehlinger
193
+
194
+ Permission is hereby granted, free of charge, to any person
195
+ obtaining a copy of this software and associated documentation
196
+ files (the "Software"), to deal in the Software without
197
+ restriction, including without limitation the rights to use,
198
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
199
+ copies of the Software, and to permit persons to whom the
200
+ Software is furnished to do so, subject to the following
201
+ conditions:
202
+
203
+ The above copyright notice and this permission notice shall be
204
+ included in all copies or substantial portions of the Software.
205
+
206
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
207
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
208
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
209
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
210
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
211
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
212
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
213
+ OTHER DEALINGS IN THE SOFTWARE.
214
+
215
+ This license does not apply to daemonize.rb, which is was written by
216
+ Travis Whitton und published under the following license:
217
+
218
+ The Daemonize extension module is copywrited free software by Travis Whitton
219
+ <whitton@atlantic.net>. You can redistribute it under the terms specified in
220
+ the COPYING file of the Ruby distribution.
221
+
222
+ == Feedback and other resources
223
+
224
+ At http://rubyforge.org/projects/daemons.