Skip to content

Commit

Permalink
Merge pull request #2912 from ganmacs/enable-combination-daemon-and-n…
Browse files Browse the repository at this point in the history
…o-supervisor

Enable combination daemon and no supervisor
  • Loading branch information
ganmacs authored Mar 31, 2020
2 parents a593786 + 915a022 commit 5e59ec3
Show file tree
Hide file tree
Showing 3 changed files with 193 additions and 1 deletion.
15 changes: 14 additions & 1 deletion lib/fluent/command/fluentd.rb
Original file line number Diff line number Diff line change
Expand Up @@ -335,5 +335,18 @@
end
worker = Fluent::Supervisor.new(opts)
worker.configure
worker.run_worker

if opts[:daemonize]
require 'fluent/daemonizer'
args = ARGV.dup
i = args.index('--daemon')
args.delete_at(i + 1) # value of --daemon
args.delete_at(i) # --daemon itself

Fluent::Daemonizer.daemonize(opts[:daemonize], args) do
worker.run_worker
end
else
worker.run_worker
end
end
88 changes: 88 additions & 0 deletions lib/fluent/daemonizer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#
# Fluentd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

require 'fluent/config/error'

module Fluent
class Daemonizer
def self.daemonize(pid_path, args = [], &block)
new.daemonize(pid_path, args, &block)
end

def daemonize(pid_path, args = [])
pid_fullpath = File.absolute_path(pid_path)
check_pidfile(pid_fullpath)

begin
Process.daemon(false, false)

File.write(pid_fullpath, Process.pid.to_s)

# install signal and set process name are performed by supervisor
install_at_exit_handlers(pid_fullpath)

yield
rescue NotImplementedError
daemonize_with_spawn(pid_fullpath, args)
end
end

private

def daemonize_with_spawn(pid_fullpath, args)
pid = Process.spawn(*['fluentd'].concat(args))

File.write(pid_fullpath, pid.to_s)

pid
end

def check_pidfile(pid_path)
if File.exist?(pid_path)
if !File.readable?(pid_path) || !File.writable?(pid_path)
raise Fluent::ConfigError, "Cannot access pid file: #{pid_path}"
end

pid =
begin
Integer(File.read(pid_path), 10)
rescue TypeError, ArgumentError
return # ignore
end

begin
Process.kill(0, pid)
raise Fluent::ConfigError, "pid(#{pid}) is running"
rescue Errno::EPERM
raise Fluent::ConfigError, "pid(#{pid}) is running"
rescue Errno::ESRCH
end
else
unless File.writable?(File.dirname(pid_path))
raise Fluent::ConfigError, "Cannot access directory for pid file: #{File.dirname(pid_path)}"
end
end
end

def install_at_exit_handlers(pidfile)
at_exit do
if File.exist?(pidfile)
File.delete(pidfile)
end
end
end
end
end
91 changes: 91 additions & 0 deletions test/test_daemonizer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
require_relative 'helper'
require 'fluent/daemonizer'

class DaemonizerTest < ::Test::Unit::TestCase
TMP_DIR = File.join(File.dirname(__FILE__), 'tmp', 'daemonizer')

setup do
FileUtils.mkdir_p(TMP_DIR)
end

teardown do
FileUtils.rm_rf(TMP_DIR) rescue nil
end

test 'makes pid file' do
pid_path = File.join(TMP_DIR, 'file.pid')

mock(Process).daemon(anything, anything).once
r = Fluent::Daemonizer.daemonize(pid_path) { 'ret' }
assert_equal 'ret', r
assert File.exist?(pid_path)
assert Process.pid.to_s, File.read(pid_path).to_s
end

test 'in platforms which do not support fork' do
pid_path = File.join(TMP_DIR, 'file.pid')

mock(Process).daemon(anything, anything) { raise NotImplementedError }
args = ['-c', 'test.conf']
mock(Process).spawn(anything, *args) { Process.pid }

Fluent::Daemonizer.daemonize(pid_path, args) { 'ret' }
assert File.exist?(pid_path)
assert Process.pid.to_s, File.read(pid_path).to_s
end

sub_test_case 'when pid file already exists' do
test 'raise an error when process is running' do
omit 'chmod of file does not affetct root user' if Process.uid.zero?
pid_path = File.join(TMP_DIR, 'file.pid')
File.write(pid_path, '1')

mock(Process).daemon(anything, anything).never
mock(Process).kill(0, 1).once

assert_raise(Fluent::ConfigError.new('pid(1) is running')) do
Fluent::Daemonizer.daemonize(pid_path) { 'ret' }
end
end

test 'raise an error when file is not redable' do
omit 'chmod of file does not affetct root user' if Process.uid.zero?
not_readable_path = File.join(TMP_DIR, 'not_readable.pid')

File.write(not_readable_path, '1')
FileUtils.chmod(0333, not_readable_path)

mock(Process).daemon(anything, anything).never
assert_raise(Fluent::ConfigError.new("Cannot access pid file: #{File.absolute_path(not_readable_path)}")) do
Fluent::Daemonizer.daemonize(not_readable_path) { 'ret' }
end
end

test 'raise an error when file is not writable' do
omit 'chmod of file does not affetct root user' if Process.uid.zero?
not_writable_path = File.join(TMP_DIR, 'not_writable.pid')

File.write(not_writable_path, '1')
FileUtils.chmod(0555, not_writable_path)

mock(Process).daemon(anything, anything).never
assert_raise(Fluent::ConfigError.new("Cannot access pid file: #{File.absolute_path(not_writable_path)}")) do
Fluent::Daemonizer.daemonize(not_writable_path) { 'ret' }
end
end

test 'raise an error when directory is not writable' do
omit 'chmod of file does not affetct root user' if Process.uid.zero?
not_writable_dir = File.join(TMP_DIR, 'not_writable')
pid_path = File.join(not_writable_dir, 'file.pid')

FileUtils.mkdir_p(not_writable_dir)
FileUtils.chmod(0555, not_writable_dir)

mock(Process).daemon(anything, anything).never
assert_raise(Fluent::ConfigError.new("Cannot access directory for pid file: #{File.absolute_path(not_writable_dir)}")) do
Fluent::Daemonizer.daemonize(pid_path) { 'ret' }
end
end
end
end

0 comments on commit 5e59ec3

Please sign in to comment.