process-daemon-1.0.1/0000755000000000000000000000000013262311314013122 5ustar rootrootprocess-daemon-1.0.1/Gemfile0000644000000000000000000000025113262311314014413 0ustar rootrootsource 'https://rubygems.org' # Specify your gem's dependencies in process-daemon.gemspec gemspec group :test do gem 'simplecov' gem 'coveralls', require: false end process-daemon-1.0.1/lib/0000755000000000000000000000000013262311314013670 5ustar rootrootprocess-daemon-1.0.1/lib/process/0000755000000000000000000000000013262311314015346 5ustar rootrootprocess-daemon-1.0.1/lib/process/daemon/0000755000000000000000000000000013262311314016611 5ustar rootrootprocess-daemon-1.0.1/lib/process/daemon/log_file.rb0000644000000000000000000000625013262311314020721 0ustar rootroot# Copyright, 2014, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. module Process class Daemon # This is a special file instance which provides the ability to read a log file from the end backwards. class LogFile < File # Yields the lines of a log file in reverse order, once the yield statement returns true, stops, and returns the lines in order. def tail_log lines = [] seek_end reverse_each_line do |line| lines << line break if block_given? and yield line end return lines.reverse end private # Seek to the end of the file def seek_end(offset = 0) seek(offset, IO::SEEK_END) end # Read a chunk of data and then move the file pointer backwards. # # Calling this function multiple times will return new data and traverse the file backwards. # def read_reverse(length) offset = tell if offset == 0 return nil end start = [0, offset-length].max seek(start, IO::SEEK_SET) buf = read(offset-start) seek(start, IO::SEEK_SET) return buf end REVERSE_BUFFER_SIZE = 128 # This function is very similar to gets but it works in reverse. # # You can use it to efficiently read a file line by line backwards. # # It returns nil when there are no more lines. def reverse_gets(sep_string=$/) end_pos = tell offset = nil buf = "" while offset == nil chunk = read_reverse(REVERSE_BUFFER_SIZE) return (buf == "" ? nil : buf) if chunk == nil buf = chunk + buf # Don't consider the last newline. offset = buf.rindex(sep_string, -(sep_string.length + 1)) end # Don't include newline: offset += 1 line = buf[offset...buf.size] seek((end_pos - buf.size) + offset, IO::SEEK_SET) return line end # Similar to each_line but works in reverse. Don't forget to call # seek_end before you start! def reverse_each_line(sep_string=$/, &block) return to_enum(:reverse_each_line) unless block_given? line = reverse_gets(sep_string) while line != nil yield line line = reverse_gets(sep_string) end end end end end process-daemon-1.0.1/lib/process/daemon/process_file.rb0000644000000000000000000000470313262311314021617 0ustar rootroot# Copyright, 2014, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'fileutils' module Process class Daemon # This module controls the storage and retrieval of process id files. class ProcessFile # Saves the pid for the given daemon def self.store(daemon, pid) File.write(daemon.process_file_path, pid) end # Retrieves the pid for the given daemon def self.recall(daemon) File.read(daemon.process_file_path).to_i rescue nil end # Removes the pid saved for a particular daemon def self.clear(daemon) if File.exist? daemon.process_file_path FileUtils.rm(daemon.process_file_path) end end # Checks whether the daemon is running by checking the saved pid and checking the corresponding process def self.running(daemon) pid = recall(daemon) return false if pid == nil gpid = Process.getpgid(pid) rescue nil return gpid != nil ? true : false end # Remove the pid file if the daemon is not running def self.cleanup(daemon) clear(daemon) unless running(daemon) end # This function returns the status of the daemon. This can be one of +:running+, +:unknown+ (pid file exists but no # corresponding process can be found) or +:stopped+. def self.status(daemon) if File.exist? daemon.process_file_path return ProcessFile.running(daemon) ? :running : :unknown else return :stopped end end end end end process-daemon-1.0.1/lib/process/daemon/version.rb0000644000000000000000000000224713262311314020630 0ustar rootroot# Copyright, 2014, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. module Process class Daemon VERSION = "1.0.1" end end process-daemon-1.0.1/lib/process/daemon/controller.rb0000644000000000000000000001743113262311314021327 0ustar rootroot# Copyright, 2014, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'rainbow' module Process class Daemon # Daemon startup timeout TIMEOUT = 5 # This module contains functionality related to starting and stopping the @daemon, and code for processing command line input. class Controller # `options[:output]` specifies where to write textual output describing what is going on. def initialize(daemon, options = {}) @daemon = daemon @output = options[:output] || $stdout # How long to wait until sending SIGTERM and eventually SIGKILL to the daemon process group when asking it to stop: @stop_timeout = options[:stop_timeout] || 10.0 end # This function is called from the daemon executable. It processes ARGV and checks whether the user is asking for `start`, `stop`, `restart`, `status`. def daemonize(argv = ARGV) case (argv.shift || :default).to_sym when :start start show_status when :stop stop show_status ProcessFile.cleanup(@daemon) when :restart stop ProcessFile.cleanup(@daemon) start show_status when :status show_status else @output.puts Rainbow("Invalid command. Please specify start, restart, stop or status.").red end end # Fork a child process, detatch it and run the daemon code. def spawn @daemon.prefork @daemon.mark_log fork do Process.setsid exit if fork ProcessFile.store(@daemon, Process.pid) File.umask 0000 Dir.chdir @daemon.working_directory $stdin.reopen '/dev/null' $stdout.reopen @daemon.log_file_path, 'a' $stdout.sync = true $stderr.reopen $stdout $stderr.sync = true begin @daemon.spawn rescue Exception => error $stderr.puts "=== Daemon Exception Backtrace @ #{Time.now.to_s} ===" $stderr.puts "#{error.class}: #{error.message}" $!.backtrace.each { |at| $stderr.puts at } $stderr.puts "=== Daemon Crashed ===" $stderr.flush ensure $stderr.puts "=== Daemon Stopping @ #{Time.now.to_s} ===" $stderr.flush end end end # This function starts the daemon process in the background. def start @output.puts Rainbow("Starting #{@daemon.name} daemon...").blue case self.status when :running @output.puts Rainbow("Daemon already running!").blue return when :stopped # We are good to go... else @output.puts Rainbow("Daemon in unknown state! Will clear previous state and continue.").red ProcessFile.clear(@daemon) end spawn sleep 0.1 timer = TIMEOUT pid = ProcessFile.recall(@daemon) while pid == nil and timer > 0 # Wait a moment for the forking to finish... @output.puts Rainbow("Waiting for daemon to start (#{timer}/#{TIMEOUT})").blue sleep 1 # If the @daemon has crashed, it is never going to start... break if @daemon.crashed? pid = ProcessFile.recall(@daemon) timer -= 1 end end # Prints out the status of the daemon def status ProcessFile.status(@daemon) end def show_status case self.status when :running @output.puts Rainbow("Daemon status: running pid=#{ProcessFile.recall(@daemon)}").green when :unknown if @daemon.crashed? @output.puts Rainbow("Daemon status: crashed").red @output.flush @output.puts Rainbow("Dumping daemon crash log:").red @daemon.tail_log(@output) else @output.puts Rainbow("Daemon status: unknown").red end when :stopped @output.puts Rainbow("Daemon status: stopped").blue end end # The pid of the daemon if it is available. The pid may be invalid if the daemon has crashed. def pid ProcessFile.recall(@daemon) end # How long to wait between checking the daemon process when shutting down: STOP_PERIOD = 0.1 # The number of attempts to stop the daemon using SIGTERM. On the last attempt, SIGKILL is used. STOP_ATTEMPTS = 5 # The factor which controls how long we sleep between attempts to kill the process. Only applies to processes which don't stop immediately. STOP_WAIT_FACTOR = 3.0 # Stops the daemon process. This function initially sends SIGINT. It waits STOP_PERIOD and checks if the daemon is still running. If it is, it sends SIGTERM, and then waits a bit longer. It tries STOP_ATTEMPTS times until it basically assumes the daemon is stuck and sends SIGKILL. def stop @output.puts Rainbow("Stopping #{@daemon.name} daemon...").blue # Check if the pid file exists... unless File.file?(@daemon.process_file_path) @output.puts Rainbow("Pid file not found. Is the daemon running?").red return end pid = ProcessFile.recall(@daemon) # Check if the @daemon is already stopped... unless ProcessFile.running(@daemon) @output.puts Rainbow("Pid #{pid} is not running. Has daemon crashed?").red @daemon.tail_log($stderr) return end pgid = -Process.getpgid(pid) # Stop by interrupt sends a single interrupt and waits for the process to terminate: unless stop_by_interrupt(pgid) # If the process is still running, we try sending SIGTERM followed by SIGKILL: @output.puts Rainbow("** Daemon did not stop gracefully after #{@stop_timeout}s **").red stop_by_terminate_or_kill(pgid) end # If after doing our best the @daemon is still running (pretty odd)... if ProcessFile.running(@daemon) @output.puts Rainbow("Daemon appears to be still running!").red return else @output.puts Rainbow("Daemon has left the building.").green end # Otherwise the @daemon has been stopped. ProcessFile.clear(@daemon) end private # Returns true if the process was stopped. def stop_by_interrupt(pgid) running = true # Interrupt the process group: Process.kill("INT", pgid) (@stop_timeout / STOP_PERIOD).ceil.times do if running = ProcessFile.running(@daemon) sleep STOP_PERIOD end end return !running end def stop_by_terminate_or_kill(pgid) # TERM/KILL loop - if the daemon didn't die easily, shoot it a few more times. (STOP_ATTEMPTS+1).times do |attempt| break unless ProcessFile.running(@daemon) # SIGKILL gets sent on the last attempt. signal_name = (attempt < STOP_ATTEMPTS) ? "TERM" : "KILL" @output.puts Rainbow("Sending #{signal_name} to process group #{pgid}...").red Process.kill(signal_name, pgid) # We iterate quickly to start with, and slow down if the process seems unresponsive. timeout = STOP_PERIOD + (attempt.to_f / STOP_ATTEMPTS) * STOP_WAIT_FACTOR @output.puts Rainbow("Waiting for #{timeout.round(1)}s for daemon to terminate...").blue sleep(timeout) end end end end end process-daemon-1.0.1/lib/process/daemon/notification.rb0000644000000000000000000000400213262311314021620 0ustar rootroot# Copyright, 2015, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. module Process class Daemon # This is a one shot cross-process notification mechanism using pipes. It can also be used in the same process if required, e.g. the self-pipe trick. class Notification def initialize @output, @input = IO.pipe @signalled = false end # Signal the notification. def signal @signalled = true @input.puts end # Was this notification signalled? def signalled? @signalled end # Wait/block until a signal is received. Optional timeout. # @param timeout [Integer] the time to wait in seconds. def wait(timeout: nil) if timeout read_ready, _, _ = IO.select([@output], [], [], timeout) return false unless read_ready and read_ready.any? end @signalled or @output.read(1) # Just in case that this was split across multiple processes. @signalled = true end end end end process-daemon-1.0.1/lib/process/daemon/listen.rb0000644000000000000000000000475313262311314020445 0ustar rootroot# Copyright, 2016, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. module Process class Daemon # Access incoming file descriptors from daemons started by systemd. class Listen LISTEN_PID = 'LISTEN_PID' LISTEN_FDS = 'LISTEN_FDS' LISTEN_FDNAMES = 'LISTEN_FDNAMES' FD_START = 3 SEPERATOR = ':' def self.set_close_at_exec(fd) fd.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) if defined? Fcntl::F_SETFD end def self.open(fd) set_close_at_exec(fd) return IO.for_fd(fd) end # Returns a Array or Hash of file descriptors. If LISTEN_FDNAMES is set, a Hash is returned which includes key => value pairs for named file descriptors. def self.file_descriptors(env = ENV) pid, fds, names = env.values_at(LISTEN_PID, LISTEN_FDS, LISTEN_FDNAMES) # Are the PIDs valid for this process? unless pid and Integer(pid) == Process.pid return nil end files = Integer(fds).times.collect do |i| self.open(FD_START + i) end if names names = names.split(SEPARATOR, -1) end self.new(files, names) end def initialize(files, names) @files = files @names = names @named = {} @unnamed = [] @names.each_with_index do |name, index| if name @named[name] = @files[index] else @unnamed << @files[index] end end end attr :files attr :names attr :named attr :unnamed end end end process-daemon-1.0.1/lib/process/daemon/privileges.rb0000644000000000000000000000316113262311314021310 0ustar rootroot# Copyright, 2014, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'etc' module Process class Daemon # Provides functions for changing the current user. module Privileges # Set the user of the current process. Supply either a user ID # or a user name. def self.change_user(user) if user.kind_of?(String) user = Etc.getpwnam(user).uid end Process::Sys.setuid(user) end # Get the user of the current process. Returns the user name. def self.current_user uid = Process::Sys.getuid Etc.getpwuid(uid).name end end end end process-daemon-1.0.1/lib/process/daemon.rb0000644000000000000000000001235313262311314017142 0ustar rootroot# Copyright, 2014, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'fileutils' require_relative 'daemon/controller' require_relative 'daemon/notification' require_relative 'daemon/log_file' require_relative 'daemon/process_file' module Process # Provides the infrastructure for spawning a daemon. class Daemon # Initialize the daemon in the given working root. def initialize(working_directory = ".") @working_directory = working_directory @shutdown_notification = Notification.new end # Return the name of the daemon def name return self.class.name.gsub(/[^a-zA-Z0-9]+/, '-') end # The directory the daemon will run in. attr :working_directory # Return the directory to store log files in. def log_directory File.join(working_directory, "log") end # Standard log file for stdout and stderr. def log_file_path File.join(log_directory, "#{name}.log") end # Runtime data directory for the daemon. def runtime_directory File.join(working_directory, "run") end # Standard location of process pid file. def process_file_path File.join(runtime_directory, "#{name}.pid") end # Mark the output log. def mark_log File.open(log_file_path, "a") do |log_file| log_file.puts "=== Log Marked @ #{Time.now.to_s} [#{Process.pid}] ===" end end # Prints some information relating to daemon startup problems. def tail_log(output) lines = LogFile.open(log_file_path).tail_log do |line| line.match("=== Log Marked") || line.match("=== Daemon Exception Backtrace") end output.puts lines end # Check the last few lines of the log file to find out if the daemon crashed. def crashed? count = 3 LogFile.open(log_file_path).tail_log do |line| return true if line.match("=== Daemon Crashed") break if (count -= 1) == 0 end return false end # The main function to setup any environment required by the daemon def prefork # Ignore any previously setup signal handler for SIGINT: trap(:INT, :DEFAULT) # We update the working directory to a full path: @working_directory = File.expand_path(working_directory) FileUtils.mkdir_p(log_directory) FileUtils.mkdir_p(runtime_directory) end # The process title of the daemon. attr :title # Set the process title - only works after daemon has forked. def title= title @title = title if Process.respond_to? :setproctitle Process.setproctitle(@title) else $0 = @title end end # Request that the sleep_until_interrupted function call returns. def request_shutdown @shutdown_notification.signal end # Call this function to sleep until the daemon is sent SIGINT. def sleep_until_interrupted trap(:INT) do self.request_shutdown end @shutdown_notification.wait end # This function must setup the daemon quickly and return. def startup end # If you want to implement a long running process you override this method. You may like to call super but it is not necessary to use the supplied interruption machinery. def run sleep_until_interrupted end # This function should terminate any active processes in the daemon and return as quickly as possible. def shutdown end # The entry point from the newly forked process. def spawn self.title = self.name self.startup begin self.run rescue Interrupt $stderr.puts "Daemon interrupted, proceeding to shutdown." end self.shutdown end class << self # A shared instance of the daemon. def instance @instance ||= self.new end # The process controller, responsible for managing the daemon process start, stop, restart, etc. def controller(options = {}) @controller ||= Controller.new(instance, options) end # The main entry point for daemonized scripts. def daemonize(*args, **options) args = ARGV if args.empty? controller(options).daemonize(args) end # Start the shared daemon instance. def start controller.start end # Stop the shared daemon instance. def stop controller.stop end # Check if the shared daemon instance is runnning or not. def status controller.status end end end end process-daemon-1.0.1/process-daemon.gemspec0000644000000000000000000000215613262311314017412 0ustar rootroot# coding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'process/daemon/version' Gem::Specification.new do |spec| spec.name = "process-daemon" spec.version = Process::Daemon::VERSION spec.authors = ["Samuel Williams"] spec.email = ["samuel.williams@oriontransfer.co.nz"] spec.summary = %q{`Process::Daemon` is a stable and helpful base class for long running tasks and daemons. Provides standard `start`, `stop`, `restart`, `status` operations.} spec.homepage = "https://github.com/ioquatix/process-daemon" spec.license = "MIT" spec.has_rdoc = "yard" spec.files = `git ls-files`.split($/) spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ["lib"] spec.required_ruby_version = '>= 1.9.3' spec.add_dependency "rainbow", "~> 2.0" spec.add_development_dependency "bundler", "~> 1.3" spec.add_development_dependency "rspec", "~> 3.4.0" spec.add_development_dependency "rake" end process-daemon-1.0.1/README.md0000644000000000000000000001146613262311314014411 0ustar rootroot# Process::Daemon `Process::Daemon` is a stable and helpful base class for long running tasks and daemons. Provides standard `start`, `stop`, `restart`, `status` operations. [![Build Status](https://travis-ci.org/ioquatix/process-daemon.svg)](https://travis-ci.org/ioquatix/process-daemon) [![Code Climate](https://codeclimate.com/github/ioquatix/process-daemon.svg)](https://codeclimate.com/github/ioquatix/process-daemon) [![Coverage Status](https://coveralls.io/repos/ioquatix/process-daemon/badge.svg)](https://coveralls.io/r/ioquatix/process-daemon) [![Documentation](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/gems/process-daemon) [![Code](http://img.shields.io/badge/github-code-blue.svg)](https://github.com/ioquatix/process-daemon) ## Installation Add this line to your application's Gemfile: gem 'process-daemon' And then execute: $ bundle Or install it yourself as: $ gem install process-daemon ## Usage A process daemon has a specific structure: class MyDaemon < Process::Daemon def startup # Called when the daemon is initialized in it's own process. Should return quickly. end def run # Do the actual work. Does not need to be implemented, e.g. if using threads or other background processing mechanisms which were kicked off in #startup. end def shutdown # Stop everything that was setup in startup. Called as part of main daemon thread/process, not in trap context (e.g. SIGINT). # Asynchronous code can call self.request_shutdown from a trap context to interrupt the main process, provided you aren't doing work in #run. end end # Make this file executable and have a command line interface: MyDaemon.daemonize ### Working directory By default, daemons run in the current working directory. They setup paths according to the following logic: working_directory = "." log_directory = #{working_directory}/log log_file_path = #{log_directory}/#{name}.log runtime_directory = #{working_directory}/run process_file_path = #{runtime_directory}/#{name}.pid After calling `prefork`, the working directory is expanded to a full path and should not be changed. ### WEBRick Server Some servers must run on the main process thread. In this case, the normal interrupt mechanism won't be used and we will handle signals directly. Create a file for your daemon, e.g. `daemon.rb`: #!/usr/bin/env ruby require 'process/daemon' # Very simple XMLRPC daemon class XMLRPCDaemon < Process::Daemon def startup @rpc_server = WEBrick::HTTPServer.new( :Port => 31337, :BindAddress => "0.0.0.0" ) @listener = XMLRPC::WEBrickServlet.new @listener.add_handler("fourty-two") do |amount| "Hello World" end @rpc_server.mount("/RPC2", @listener) end def run # This is the correct way to cleanly shutdown the server: trap(:INT) do @rpc_server.shutdown end @rpc_server.start ensure @rpc_server.shutdown end end XMLRPCDaemon.daemonize Then run `daemon.rb start`. To stop the daemon, run `daemon.rb stop`. ### Celluloid Actor `Process::Daemon` is the perfect place to spawn your [celluloid](https://celluloid.io) actors. require 'celluloid' require 'process/daemon' class MyActor include Celluloid::Actor def long_running sleep 1000 end end class MyDaemon < Process::Daemon def startup @actor = MyActor.new @actor.async.long_running_process end def shutdown @actor.terminate end end MyDaemon.daemonize ## Contributing 1. Fork it 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create new Pull Request ## License Released under the MIT license. Copyright, 2015, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams). Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. process-daemon-1.0.1/Rakefile0000644000000000000000000000030413262311314014564 0ustar rootrootrequire "bundler/gem_tasks" require "rspec/core/rake_task" RSpec::Core::RakeTask.new(:spec) do |task| task.rspec_opts = ["--require", "simplecov"] if ENV['COVERAGE'] end task :default => :spec process-daemon-1.0.1/spec/0000755000000000000000000000000013262311314014054 5ustar rootrootprocess-daemon-1.0.1/spec/process/0000755000000000000000000000000013262311314015532 5ustar rootrootprocess-daemon-1.0.1/spec/process/daemon/0000755000000000000000000000000013262311314016775 5ustar rootrootprocess-daemon-1.0.1/spec/process/daemon/crashed_daemon_spec.rb0000644000000000000000000000363313262311314023275 0ustar rootroot# Copyright, 2007, 2014, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'process/daemon' require 'webrick' require 'webrick/https' require 'xmlrpc/server' require 'xmlrpc/client' module Process::Daemon::DaemonizeSpec class CrashDaemon < Process::Daemon def working_directory File.expand_path("../tmp", __FILE__) end def run and_bobs_your_uncle end end describe Process::Daemon do it "should crash daemon and print exception" do output = StringIO.new controller = Process::Daemon::Controller.new(CrashDaemon.instance, :output => output) controller.start sleep 1 controller.show_status expect(CrashDaemon.status).to be == :unknown expect(output.string).to include("=== Daemon Exception Backtrace", "=== Daemon Crashed") expect(output.string).to include("NameError", "and_bobs_your_uncle") end end end process-daemon-1.0.1/spec/process/daemon/notification_spec.rb0000644000000000000000000000525413262311314023030 0ustar rootroot# Copyright, 2014, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'process/daemon/notification' module Process::Daemon::NotificationSpec describe Process::Daemon::Notification do it "can be signalled multiple times" do notification = Process::Daemon::Notification.new expect(notification.signalled?).to be_falsey notification.signal expect(notification.signalled?).to be_truthy notification.signal notification.signal expect(notification.signalled?).to be_truthy end it "can be signalled within trap context and across processes" do ready = Process::Daemon::Notification.new notification = Process::Daemon::Notification.new pid = fork do trap(:INT) do notification.signal exit(0) end ready.signal sleep end ready.wait(timeout: 5.0) Process.kill(:INT, pid) notification.wait(timeout: 1.0) expect(notification.signalled?).to be_truthy # Clean up zombie process Process.waitpid(pid) end it "should receive signal in child process" do notification = Process::Daemon::Notification.new pid = fork do if notification.wait(timeout: 60) exit(0) else exit(1) end end notification.signal Process.waitpid(pid) expect($?.exitstatus).to be == 0 end it "should not receive signal in child process and time out" do notification = Process::Daemon::Notification.new pid = fork do if notification.wait(timeout: 0.01) exit(0) else exit(1) end end Process.waitpid(pid) expect($?.exitstatus).to be == 1 end end end process-daemon-1.0.1/spec/process/daemon/log_file_spec.rb0000644000000000000000000000274613262311314022125 0ustar rootroot# Copyright, 2014, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'process/daemon' require 'process/daemon/log_file' module Process::Daemon::LogFileSpec describe Process::Daemon::LogFile do let(:daemon) {SleepDaemon.instance} it "should give the several last lines" do lines = File.readlines(__FILE__) log_file = Process::Daemon::LogFile.open(__FILE__) expect(log_file.tail_log).to be == lines end end end process-daemon-1.0.1/spec/process/daemon/terminate_spec.rb0000644000000000000000000000407613262311314022333 0ustar rootroot# Copyright, 2014, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'process/daemon' require 'process/daemon/process_file' module Process::Daemon::TerminateSpec class SleepDaemon < Process::Daemon def working_directory File.expand_path("../tmp", __FILE__) end def startup setup_signals end def setup_signals trap('INT') do puts 'INT' end trap('TERM') do puts 'TERM' end end def run sleep 1 while true end end describe Process::Daemon do let(:daemon) {SleepDaemon.instance} let(:controller) {Process::Daemon::Controller.new(daemon)} # Print out the daemon log file: #after(:each) do # system('cat', daemon.log_file_path) #end it "should be killed" do controller.start expect(controller.status).to be == :running controller.stop expect(controller.status).to be == :stopped output = File.readlines(daemon.log_file_path).last(6) expect(output).to be == ["INT\n", "TERM\n", "TERM\n", "TERM\n", "TERM\n", "TERM\n"] end end end process-daemon-1.0.1/spec/process/daemon/process_file_spec.rb0000755000000000000000000000402413262311314023014 0ustar rootroot# Copyright, 2014, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'process/daemon' require 'process/daemon/process_file' module Process::Daemon::ProcessFileSpec class SleepDaemon < Process::Daemon def working_directory File.expand_path("../tmp", __FILE__) end end describe Process::Daemon::ProcessFile do let(:daemon) {SleepDaemon.instance} it "should save pid" do Process::Daemon::ProcessFile.store(daemon, $$) expect(Process::Daemon::ProcessFile.recall(daemon)).to be == $$ end it "should clear pid" do Process::Daemon::ProcessFile.clear(daemon) expect(Process::Daemon::ProcessFile.recall(daemon)).to be nil end it "should be running" do Process::Daemon::ProcessFile.store(daemon, $$) expect(Process::Daemon::ProcessFile.status(daemon)).to be :running end it "should not be running" do Process::Daemon::ProcessFile.clear(daemon) expect(Process::Daemon::ProcessFile.status(daemon)).to be :stopped end end end process-daemon-1.0.1/spec/process/daemon/daemon_spec.rb0000755000000000000000000000661613262311314021613 0ustar rootroot# Copyright, 2007, 2014, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'process/daemon' require 'webrick' require 'webrick/https' require 'xmlrpc/server' require 'xmlrpc/client' module Process::Daemon::DaemonSpec # Very simple XMLRPC daemon class XMLRPCDaemon < Process::Daemon def working_directory File.expand_path("../tmp", __FILE__) end def startup puts "Starting server..." @rpc_server = WEBrick::HTTPServer.new( :Port => 31337, :BindAddress => "0.0.0.0" ) @listener = XMLRPC::WEBrickServlet.new @listener.add_handler("add") do |amount| @count ||= 0 @count += amount end @listener.add_handler("total") do @count end @rpc_server.mount("/RPC2", @listener) end def run # This is the correct way to cleanly shutdown the server, apparently: trap(:INT) do @rpc_server.shutdown end puts "RPC server starting..." @rpc_server.start ensure puts "Stop accepting new connections to RPC server..." @rpc_server.shutdown end end class DefaultDaemon < Process::Daemon def working_directory File.expand_path("../tmp", __FILE__) end end describe Process::Daemon do before do XMLRPCDaemon.start end after do XMLRPCDaemon.stop end it "should be running" do expect(XMLRPCDaemon.status).to be == :running expect(XMLRPCDaemon.instance.crashed?).to be_falsey end it "should respond to connections" do rpc = XMLRPC::Client.new_from_uri("http://localhost:31337") rpc.call("add", 10) total = rpc.call("total") expect(total).to be == 10 end it "should be a unique instance" do expect(XMLRPCDaemon.instance).to_not be == DefaultDaemon.instance end it "should produce useful output" do output = StringIO.new controller = Process::Daemon::Controller.new(XMLRPCDaemon.instance, :output => output) expect(controller.status).to be == :running controller.show_status expect(output.string).to match /Daemon status: running pid=\d+/ output.rewind controller.stop expect(output.string).to match /Stopping/ output.rewind controller.start expect(output.string).to match /Starting/ end it "should have correct process title" do pid = XMLRPCDaemon.controller.pid title = `ps -p #{pid} -o command=`.strip expect(title).to match /XMLRPCDaemon/ end end end process-daemon-1.0.1/spec/process/daemon/privileges_spec.rb0000755000000000000000000000323513262311314022513 0ustar rootroot# Copyright, 2014, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'process/daemon' require 'process/daemon/privileges' module Process::Daemon::PrivilegesSpec describe Process::Daemon::Privileges do let(:daemon) {SleepDaemon.instance} it "should save report current user" do expect(Process::Daemon::Privileges.current_user).to be == `whoami`.chomp end it "should change current user" do current_user = Process::Daemon::Privileges.current_user Process::Daemon::Privileges.change_user(current_user) expect(Process::Daemon::Privileges.current_user).to be == current_user end end end process-daemon-1.0.1/spec/process/daemon/daemonize_spec.rb0000644000000000000000000000351713262311314022315 0ustar rootroot# Copyright, 2007, 2014, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'process/daemon' require 'webrick' require 'webrick/https' require 'xmlrpc/server' require 'xmlrpc/client' module Process::Daemon::DaemonizeSpec class SleepDaemon < Process::Daemon def working_directory File.expand_path("../tmp", __FILE__) end end describe Process::Daemon do it "should start daemon" do SleepDaemon.daemonize(:start) expect(SleepDaemon.status).to be == :running SleepDaemon.daemonize(:stop) expect(SleepDaemon.status).to be == :stopped end it "should restart daemon" do SleepDaemon.daemonize(:restart) expect(SleepDaemon.status).to be == :running SleepDaemon.daemonize(:stop) expect(SleepDaemon.status).to be == :stopped end end end process-daemon-1.0.1/spec/process/daemon/daemon.rb0000755000000000000000000000354313262311314020575 0ustar rootroot#!/usr/bin/env ruby # # Copyright, 2012, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'process/daemon' require 'webrick' require 'webrick/https' require 'xmlrpc/server' # Very simple XMLRPC daemon class XMLRPCDaemon < Process::Daemon def working_directory File.expand_path("../tmp", __FILE__) end def startup puts "Starting server..." @rpc_server = WEBrick::HTTPServer.new( :Port => 31337, :BindAddress => "0.0.0.0" ) @listener = XMLRPC::WEBrickServlet.new @listener.add_handler("fourty-two") do |amount| "Hello World" end @rpc_server.mount("/RPC2", @listener) end def run # This is the correct way to cleanly shutdown the server: trap(:INT) do @rpc_server.shutdown end @rpc_server.start ensure @rpc_server.shutdown end end XMLRPCDaemon.daemonize process-daemon-1.0.1/.gitignore0000644000000000000000000000023213262311314015107 0ustar rootroot*.gem *.rbc .bundle .config .yardoc Gemfile.lock InstalledFiles _yardoc coverage doc/ lib/bundler/man pkg rdoc spec/reports test/tmp test/version_tmp tmp process-daemon-1.0.1/.travis.yml0000644000000000000000000000016313262311314015233 0ustar rootrootlanguage: ruby sudo: false rvm: - 2.0.0 - 2.1.8 - 2.2.4 - 2.3.0 - ruby-head - rbx-2 env: COVERAGE=true process-daemon-1.0.1/.simplecov0000644000000000000000000000034413262311314015125 0ustar rootroot SimpleCov.start do add_filter "/spec/" end # Work correctly across forks: pid = Process.pid SimpleCov.at_exit do SimpleCov.result.format! if Process.pid == pid end if ENV['TRAVIS'] require 'coveralls' Coveralls.wear! end process-daemon-1.0.1/.rspec0000644000000000000000000000004113262311314014232 0ustar rootroot--color --format documentation