diff --git a/lib/puppet/agent.rb b/lib/puppet/agent.rb index 6b254c0cd..3e993f6ba 100644 --- a/lib/puppet/agent.rb +++ b/lib/puppet/agent.rb @@ -1,112 +1,99 @@ require 'sync' -require 'puppet/external/event-loop' require 'puppet/application' # A general class for triggering a run of another # class. class Puppet::Agent require 'puppet/agent/locker' include Puppet::Agent::Locker attr_reader :client_class, :client, :splayed # Just so we can specify that we are "the" instance. def initialize(client_class) @splayed = false @client_class = client_class end def lockfile_path client_class.lockfile_path end def needing_restart? Puppet::Application.restart_requested? end # Perform a run with our client. def run(*args) if running? Puppet.notice "Run of #{client_class} already in progress; skipping" return end if disabled? Puppet.notice "Skipping run of #{client_class}; administratively disabled; use 'puppet #{client_class} --enable' to re-enable." return end result = nil block_run = Puppet::Application.controlled_run do splay with_client do |client| begin sync.synchronize { lock { result = client.run(*args) } } rescue SystemExit,NoMemoryError raise rescue Exception => detail puts detail.backtrace if Puppet[:trace] Puppet.err "Could not run #{client_class}: #{detail}" end end true end Puppet.notice "Shutdown/restart in progress (#{Puppet::Application.run_status.inspect}); skipping run" unless block_run result end def stopping? Puppet::Application.stop_requested? end # Have we splayed already? def splayed? splayed end # Sleep when splay is enabled; else just return. def splay return unless Puppet[:splay] return if splayed? time = rand(Integer(Puppet[:splaylimit]) + 1) Puppet.info "Sleeping for #{time} seconds (splay is enabled)" sleep(time) @splayed = true end - # Start listening for events. We're pretty much just listening for - # timer events here. - def start - # Create our timer. Puppet will handle observing it and such. - timer = EventLoop::Timer.new(:interval => Puppet[:runinterval], :tolerance => 1, :start? => true) do - run - end - - # Run once before we start following the timer - timer.sound_alarm - end - def sync @sync ||= Sync.new end private # Create and yield a client instance, keeping a reference # to it during the yield. def with_client begin @client = client_class.new rescue SystemExit,NoMemoryError raise rescue Exception => detail puts detail.backtrace if Puppet[:trace] Puppet.err "Could not create instance of #{client_class}: #{detail}" return end yield @client ensure @client = nil end end diff --git a/lib/puppet/daemon.rb b/lib/puppet/daemon.rb index 5092bdf2e..b97f2c361 100755 --- a/lib/puppet/daemon.rb +++ b/lib/puppet/daemon.rb @@ -1,129 +1,195 @@ require 'puppet' require 'puppet/util/pidlock' -require 'puppet/external/event-loop' require 'puppet/application' # A module that handles operations common to all daemons. This is included # into the Server and Client base classes. class Puppet::Daemon attr_accessor :agent, :server, :argv def daemonname Puppet[:name] end # Put the daemon into the background. def daemonize if pid = fork Process.detach(pid) exit(0) end create_pidfile # Get rid of console logging Puppet::Util::Log.close(:console) Process.setsid Dir.chdir("/") begin $stdin.reopen "/dev/null" $stdout.reopen "/dev/null", "a" $stderr.reopen $stdout Puppet::Util::Log.reopen rescue => detail Puppet.err "Could not start #{Puppet[:name]}: #{detail}" Puppet::Util::replace_file("/tmp/daemonout", 0644) do |f| f.puts "Could not start #{Puppet[:name]}: #{detail}" end exit(12) end end # Create a pidfile for our daemon, so we can be stopped and others # don't try to start. def create_pidfile Puppet::Util.synchronize_on(Puppet[:name],Sync::EX) do raise "Could not create PID file: #{pidfile}" unless Puppet::Util::Pidlock.new(pidfile).lock end end # Provide the path to our pidfile. def pidfile Puppet[:pidfile] end def reexec raise Puppet::DevError, "Cannot reexec unless ARGV arguments are set" unless argv command = $0 + " " + argv.join(" ") Puppet.notice "Restarting with '#{command}'" stop(:exit => false) exec(command) end def reload return unless agent if agent.running? Puppet.notice "Not triggering already-running agent" return end agent.run end # Remove the pid file for our daemon. def remove_pidfile Puppet::Util.synchronize_on(Puppet[:name],Sync::EX) do Puppet::Util::Pidlock.new(pidfile).unlock end end def restart Puppet::Application.restart! reexec unless agent and agent.running? end def reopen_logs Puppet::Util::Log.reopen end # Trap a couple of the main signals. This should probably be handled # in a way that anyone else can register callbacks for traps, but, eh. def set_signal_traps signals = {:INT => :stop, :TERM => :stop } # extended signals not supported under windows signals.update({:HUP => :restart, :USR1 => :reload, :USR2 => :reopen_logs }) unless Puppet.features.microsoft_windows? signals.each do |signal, method| Signal.trap(signal) do Puppet.notice "Caught #{signal}; calling #{method}" send(method) end end end # Stop everything def stop(args = {:exit => true}) Puppet::Application.stop! server.stop if server remove_pidfile Puppet::Util::Log.close_all exit if args[:exit] end def start set_signal_traps create_pidfile raise Puppet::DevError, "Daemons must have an agent, server, or both" unless agent or server + + # Start the listening server, if required. server.start if server - agent.start if agent - EventLoop.current.run + # Finally, loop forever running events - or, at least, until we exit. + run_event_loop + end + + def run_event_loop + # Now, we loop waiting for either the configuration file to change, or the + # next agent run to be due. Fun times. + # + # We want to trigger the reparse if 15 seconds passed since the previous + # wakeup, and the agent run if Puppet[:runinterval] seconds have passed + # since the previous wakeup. + # + # We always want to run the agent on startup, so it was always before now. + # Because 0 means "continuously run", `to_i` does the right thing when the + # input is strange or badly formed by returning 0. Integer will raise, + # which we don't want, and we want to protect against -1 or below. + next_agent_run = 0 + agent_run_interval = [Puppet[:runinterval].to_i, 0].max + + # We may not want to reparse; that can be disable. Fun times. + next_reparse = 0 + reparse_interval = Puppet[:filetimeout].to_i + + loop do + now = Time.now.to_i + + # Handle reparsing of configuration files, if desired and required. + # `reparse` will just check if the action is required, and would be + # better named `reparse_if_changed` instead. + if reparse_interval > 0 and now >= next_reparse + Puppet.settings.reparse + + # The time to the next reparse might have changed, so recalculate + # now. That way we react dynamically to reconfiguration. + reparse_interval = Puppet[:filetimeout].to_i + next_reparse = now + reparse_interval + + # We should also recalculate the agent run interval, and adjust the + # next time it is scheduled to run, just in case. In the event that + # we made no change the result will be a zero second adjustment. + new_run_interval = [Puppet[:runinterval].to_i, 0].max + next_agent_run += agent_run_interval - new_run_interval + agent_run_interval = new_run_interval + end + + # Handle triggering another agent run. This will block the next check + # for configuration reparsing, which is a desired and deliberate + # behaviour. You should not change that. --daniel 2012-02-21 + if agent and now >= next_agent_run + agent.run + next_agent_run = now + agent_run_interval + end + + # Finally, an interruptable able sleep until the next scheduled event. + # We also set a default wakeup of "one hour from now", which will + # recheck everything at a minimum every hour. Just in case something in + # the math messes up or something; it should be inexpensive enough to + # wake once an hour, then go back to sleep after doing nothing, if + # someone only wants listen mode. + next_event = now + 60 * 60 + next_event > next_reparse and next_event = next_reparse + next_event > next_agent_run and next_event = next_agent_run + + how_long = next_event - now + + how_long > 0 and select([], [], [], how_long) + end end end diff --git a/lib/puppet/external/event-loop.rb b/lib/puppet/external/event-loop.rb deleted file mode 100644 index 476fb0ba3..000000000 --- a/lib/puppet/external/event-loop.rb +++ /dev/null @@ -1 +0,0 @@ -require "puppet/external/event-loop/event-loop" diff --git a/lib/puppet/external/event-loop/better-definers.rb b/lib/puppet/external/event-loop/better-definers.rb deleted file mode 100644 index ef1d44c53..000000000 --- a/lib/puppet/external/event-loop/better-definers.rb +++ /dev/null @@ -1,367 +0,0 @@ -## better-definers.rb --- better attribute and method definers -# Copyright (C) 2005 Daniel Brockman - -# This program is free software; you can redistribute it -# and/or modify it under the terms of the GNU General Public -# License as published by the Free Software Foundation; -# either version 2 of the License, or (at your option) any -# later version. - -# This file is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty -# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License for more details. - -# You should have received a copy of the GNU General Public -# License along with this program; if not, write to the Free -# Software Foundation, 51 Franklin Street, Fifth Floor, -# Boston, MA 02110-1301, USA. - -class Symbol - def predicate? - to_s.include? "?" end - def imperative? - to_s.include? "!" end - def writer? - to_s.include? "=" end - - def punctuated? - predicate? or imperative? or writer? end - def without_punctuation - to_s.delete("?!=").to_sym end - - def predicate - without_punctuation.to_s + "?" end - def imperative - without_punctuation.to_s + "!" end - def writer - without_punctuation.to_s + "=" end -end - -class Hash - def collect! (&block) - replace Hash[*collect(&block).flatten] - end - - def flatten - to_a.flatten - end -end - -module Kernel - def returning (value) - yield value ; value - end -end - -class Module - def define_hard_aliases (name_pairs) - for new_aliases, existing_name in name_pairs do - new_aliases.kind_of? Array or new_aliases = [new_aliases] - for new_alias in new_aliases do - alias_method(new_alias, existing_name) - end - end - end - - def define_soft_aliases (name_pairs) - for new_aliases, existing_name in name_pairs do - new_aliases.kind_of? Array or new_aliases = [new_aliases] - for new_alias in new_aliases do - class_eval %{def #{new_alias}(*args, &block) - #{existing_name}(*args, &block) end} - end - end - end - - define_soft_aliases \ - :define_hard_alias => :define_hard_aliases, - :define_soft_alias => :define_soft_aliases - - # This method lets you define predicates like :foo?, - # which will be defined to return the value of @foo. - def define_readers (*names) - for name in names.map { |x| x.to_sym } do - if name.punctuated? - # There's no way to define an efficient reader whose - # name is different from the instance variable. - class_eval %{def #{name} ; @#{name.without_punctuation} end} - else - # Use `attr_reader' to define an efficient method. - attr_reader(name) - end - end - end - - def writer_defined? (name) - method_defined?(name.to_sym.writer) - end - - # If you pass a predicate symbol :foo? to this method, it'll first - # define a regular writer method :foo, without a question mark. - # Then it'll define an imperative writer method :foo! as a shorthand - # for setting the property to true. - def define_writers (*names, &body) - for name in names.map { |x| x.to_sym } do - if block_given? - define_method(name.writer, &body) - else - attr_writer(name.without_punctuation) - end - if name.predicate? - class_eval %{def #{name.imperative} - self.#{name.writer} true end} - end - end - end - - define_soft_aliases \ - :define_reader => :define_readers, - :define_writer => :define_writers - - # We don't need a singular alias for `define_accessors', - # because it always defines at least two methods. - - def define_accessors (*names) - define_readers(*names) - define_writers(*names) - end - - def define_opposite_readers (name_pairs) - name_pairs.collect! { |k, v| [k.to_sym, v.to_sym] } - for opposite_name, name in name_pairs do - define_reader(name) unless method_defined?(name) - class_eval %{def #{opposite_name} ; not #{name} end} - end - end - - def define_opposite_writers (name_pairs) - name_pairs.collect! { |k, v| [k.to_sym, v.to_sym] } - for opposite_name, name in name_pairs do - define_writer(name) unless writer_defined?(name) - class_eval %{def #{opposite_name.writer} x - self.#{name.writer} !x end} - class_eval %{def #{opposite_name.imperative} - self.#{name.writer} false end} - end - end - - define_soft_aliases \ - :define_opposite_reader => :define_opposite_readers, - :define_opposite_writer => :define_opposite_writers - - def define_opposite_accessors (name_pairs) - define_opposite_readers name_pairs - define_opposite_writers name_pairs - end - - def define_reader_with_opposite (name_pair, &body) - name, opposite_name = name_pair.flatten.collect { |x| x.to_sym } - define_method(name, &body) - define_opposite_reader(opposite_name => name) - end - - def define_writer_with_opposite (name_pair, &body) - name, opposite_name = name_pair.flatten.collect { |x| x.to_sym } - define_writer(name, &body) - define_opposite_writer(opposite_name => name) - end - - public :define_method - - def define_methods (*names, &body) - names.each { |name| define_method(name, &body) } - end - - def define_private_methods (*names, &body) - define_methods(*names, &body) - names.each { |name| private name } - end - - def define_protected_methods (*names, &body) - define_methods(*names, &body) - names.each { |name| protected name } - end - - def define_private_method (name, &body) - define_method(name, &body) - private name - end - - def define_protected_method (name, &body) - define_method(name, &body) - protected name - end -end - -class ImmutableAttributeError < StandardError - def initialize (attribute=nil, message=nil) - super message - @attribute = attribute - end - - define_accessors :attribute - - def to_s - if @attribute and @message - "cannot change the value of `#@attribute': #@message" - elsif @attribute - "cannot change the value of `#@attribute'" - elsif @message - "cannot change the value of attribute: #@message" - else - "cannot change the value of attribute" - end - end -end - -class Module - # Guard each of the specified attributes by replacing the writer - # method with a proxy that asks the supplied block before proceeding - # with the change. - # - # If it's okay to change the attribute, the block should return - # either nil or the symbol :mutable. If it isn't okay, the block - # should return a string saying why the attribute can't be changed. - # If you don't want to provide a reason, you can have the block - # return just the symbol :immutable. - def guard_writers(*names, &predicate) - for name in names.map { |x| x.to_sym } do - define_hard_alias("__unguarded_#{name.writer}" => name.writer) - define_method(name.writer) do |new_value| - case result = predicate.call - when :mutable, nil - __send__("__unguarded_#{name.writer}", new_value) - when :immutable - raise ImmutableAttributeError.new(name) - else - raise ImmutableAttributeError.new(name, result) - end - end - end - end - - def define_guarded_writers (*names, &block) - define_writers(*names) - guard_writers(*names, &block) - end - - define_soft_alias :guard_writer => :guard_writers - define_soft_alias :define_guarded_writer => :define_guarded_writers -end - -if __FILE__ == $0 - require "test/unit" - - class DefineAccessorsTest < Test::Unit::TestCase - def setup - @X = Class.new - @Y = Class.new @X - @x = @X.new - @y = @Y.new - end - - def test_define_hard_aliases - @X.define_method(:foo) { 123 } - @X.define_method(:baz) { 321 } - @X.define_hard_aliases :bar => :foo, :quux => :baz - assert_equal @x.foo, 123 - assert_equal @x.bar, 123 - assert_equal @y.foo, 123 - assert_equal @y.bar, 123 - assert_equal @x.baz, 321 - assert_equal @x.quux, 321 - assert_equal @y.baz, 321 - assert_equal @y.quux, 321 - @Y.define_method(:foo) { 456 } - assert_equal @y.foo, 456 - assert_equal @y.bar, 123 - @Y.define_method(:quux) { 654 } - assert_equal @y.baz, 321 - assert_equal @y.quux, 654 - end - - def test_define_soft_aliases - @X.define_method(:foo) { 123 } - @X.define_method(:baz) { 321 } - @X.define_soft_aliases :bar => :foo, :quux => :baz - assert_equal @x.foo, 123 - assert_equal @x.bar, 123 - assert_equal @y.foo, 123 - assert_equal @y.bar, 123 - assert_equal @x.baz, 321 - assert_equal @x.quux, 321 - assert_equal @y.baz, 321 - assert_equal @y.quux, 321 - @Y.define_method(:foo) { 456 } - assert_equal @y.foo, @y.bar, 456 - @Y.define_method(:quux) { 654 } - assert_equal @y.baz, 321 - assert_equal @y.quux, 654 - end - - def test_define_readers - @X.define_readers :foo, :bar - assert !@x.respond_to?(:foo=) - assert !@x.respond_to?(:bar=) - @x.instance_eval { @foo = 123 ; @bar = 456 } - assert_equal @x.foo, 123 - assert_equal @x.bar, 456 - @X.define_readers :baz?, :quux? - assert !@x.respond_to?(:baz=) - assert !@x.respond_to?(:quux=) - @x.instance_eval { @baz = false ; @quux = true } - assert !@x.baz? - assert @x.quux? - end - - def test_define_writers - assert !@X.writer_defined?(:foo) - assert !@X.writer_defined?(:bar) - @X.define_writers :foo, :bar - assert @X.writer_defined?(:foo) - assert @X.writer_defined?(:bar) - assert @X.writer_defined?(:foo=) - assert @X.writer_defined?(:bar=) - assert @X.writer_defined?(:foo?) - assert @X.writer_defined?(:bar?) - assert !@x.respond_to?(:foo) - assert !@x.respond_to?(:bar) - @x.foo = 123 - @x.bar = 456 - assert_equal @x.instance_eval { @foo }, 123 - assert_equal @x.instance_eval { @bar }, 456 - @X.define_writers :baz?, :quux? - assert !@x.respond_to?(:baz?) - assert !@x.respond_to?(:quux?) - @x.baz = true - @x.quux = false - assert_equal @x.instance_eval { @baz }, true - assert_equal @x.instance_eval { @quux }, false - end - - def test_define_accessors - @X.define_accessors :foo, :bar - @x.foo = 123 ; @x.bar = 456 - assert_equal @x.foo, 123 - assert_equal @x.bar, 456 - end - - def test_define_opposite_readers - @X.define_opposite_readers :foo? => :bar?, :baz? => :quux? - assert !@x.respond_to?(:foo=) - assert !@x.respond_to?(:bar=) - assert !@x.respond_to?(:baz=) - assert !@x.respond_to?(:quux=) - @x.instance_eval { @bar = true ; @quux = false } - assert !@x.foo? - assert @x.bar? - assert @x.baz? - assert !@x.quux? - end - - def test_define_opposite_writers - @X.define_opposite_writers :foo? => :bar?, :baz => :quux - end - end -end diff --git a/lib/puppet/external/event-loop/event-loop.rb b/lib/puppet/external/event-loop/event-loop.rb deleted file mode 100644 index 3b40f6e71..000000000 --- a/lib/puppet/external/event-loop/event-loop.rb +++ /dev/null @@ -1,355 +0,0 @@ -## event-loop.rb --- high-level IO multiplexer -# Copyright (C) 2005 Daniel Brockman - -# This program is free software; you can redistribute it -# and/or modify it under the terms of the GNU General Public -# License as published by the Free Software Foundation; -# either version 2 of the License, or (at your option) any -# later version. - -# This file is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty -# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License for more details. - -# You should have received a copy of the GNU General Public -# License along with this program; if not, write to the Free -# Software Foundation, 51 Franklin Street, Fifth Floor, -# Boston, MA 02110-1301, USA. - -require "puppet/external/event-loop/better-definers" -require "puppet/external/event-loop/signal-system" - -require "fcntl" - -class EventLoop - include SignalEmitter - - IO_STATES = [:readable, :writable, :exceptional] - - class << self - def default ; @default ||= new end - def default= x ; @default = x end - - def current - Thread.current["event-loop::current"] || default end - def current= x - Thread.current["event-loop::current"] = x end - - def with_current (new) - if current == new - yield - else - begin - old = self.current - self.current = new - yield - ensure - self.current = old - end - end - end - - def method_missing (name, *args, &block) - if current.respond_to? name - current.__send__(name, *args, &block) - else - super - end - end - end - - define_signals :before_sleep, :after_sleep - - def initialize - @running = false - @awake = false - @wakeup_time = nil - @timers = [] - - @io_arrays = [[], [], []] - @ios = Hash.new do |h, k| raise ArgumentError, - "invalid IO event: #{k}", caller(2) end - IO_STATES.each_with_index { |x, i| @ios[x] = @io_arrays[i] } - - @notify_src, @notify_snk = IO.pipe - - # prevent file descriptor leaks - if @notify_src.respond_to?(:fcntl) and defined?(Fcntl) and defined?(Fcntl::F_SETFD) and defined?(Fcntl::FD_CLOEXEC) - @notify_src.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) - @notify_snk.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) - end - - @notify_src.will_block = false - @notify_snk.will_block = false - - # Each time a byte is sent through the notification pipe - # we need to read it, or IO.select will keep returning. - monitor_io(@notify_src, :readable) - @notify_src.extend(Watchable) - @notify_src.on_readable do - begin - @notify_src.sysread(256) - rescue Errno::EAGAIN - # The pipe wasn't readable after all. - end - end - end - - define_opposite_accessors \ - :stopped? => :running?, - :sleeping? => :awake? - - def run - if block_given? - thread = Thread.new { run } - yield ; quit ; thread.join - else - running! - iterate while running? - end - ensure - quit - end - - def iterate (user_timeout=nil) - t1, t2 = user_timeout, max_timeout - timeout = t1 && t2 ? [t1, t2].min : t1 || t2 - select(timeout).zip(IO_STATES) do |ios, state| - ios.each { |x| x.signal(state) } if ios - end - end - - private - - def select (timeout) - @wakeup_time = timeout ? Time.now + timeout : nil - # puts "waiting: #{timeout} seconds" - signal :before_sleep ; sleeping! - IO.select(*@io_arrays + [timeout]) || [] - ensure - awake! ; signal :after_sleep - @timers.each { |x| x.sound_alarm if x.ready? } - end - - public - - def quit ; stopped! ; wake_up ; self end - - def monitoring_io? (io, event) - @ios[event].include? io end - def monitoring_timer? (timer) - @timers.include? timer end - - def monitor_io (io, *events) - for event in events do - @ios[event] << io ; wake_up unless monitoring_io?(io, event) - end - end - - def monitor_timer (timer) - @timers << timer unless monitoring_timer? timer - end - - def check_timer (timer) - wake_up if timer.end_time < @wakeup_time - end - - def ignore_io (io, *events) - events = IO_STATES if events.empty? - for event in events do - wake_up if @ios[event].delete(io) - end - end - - def ignore_timer (timer) - # Don't need to wake up for this. - @timers.delete(timer) - end - - def max_timeout - return nil if @timers.empty? - [@timers.collect { |x| x.time_left }.min, 0].max - end - - def wake_up - @notify_snk.write('.') if sleeping? - end -end - -class Symbol - def io_state? - EventLoop::IO_STATES.include? self - end -end - -module EventLoop::Watchable - include SignalEmitter - - define_signals :readable, :writable, :exceptional - - def monitor_events (*events) - EventLoop.monitor_io(self, *events) end - def ignore_events (*events) - EventLoop.ignore_io(self, *events) end - - define_soft_aliases \ - :monitor_event => :monitor_events, - :ignore_event => :ignore_events - - def close ; super - ignore_events end - def close_read ; super - ignore_event :readable end - def close_write ; super - ignore_event :writable end - - module Automatic - include EventLoop::Watchable - - def add_signal_handler (name, &handler) super - monitor_event(name) if name.io_state? - end - - def remove_signal_handler (name, handler) super - if @signal_handlers[name].empty? - ignore_event(name) if name.io_state? - end - end - end -end - -class IO - def on_readable &block - extend EventLoop::Watchable::Automatic - on_readable(&block) - end - - def on_writable &block - extend EventLoop::Watchable::Automatic - on_writable(&block) - end - - def on_exceptional &block - extend EventLoop::Watchable::Automatic - on_exceptional(&block) - end - - def will_block? - if respond_to?(:fcntl) and defined?(Fcntl) and defined?(Fcntl::F_GETFL) and defined?(Fcntl::O_NONBLOCK) - fcntl(Fcntl::F_GETFL, 0) & Fcntl::O_NONBLOCK == 0 - end - end - - def will_block= (wants_blocking) - if respond_to?(:fcntl) and defined?(Fcntl) and defined?(Fcntl::F_GETFL) and defined?(Fcntl::O_NONBLOCK) - flags = fcntl(Fcntl::F_GETFL, 0) - if wants_blocking - flags &= ~Fcntl::O_NONBLOCK - else - flags |= Fcntl::O_NONBLOCK - end - fcntl(Fcntl::F_SETFL, flags) - end - end -end - -class EventLoop::Timer - include SignalEmitter - - DEFAULT_INTERVAL = 0.0 - DEFAULT_TOLERANCE = 0.001 - - def initialize (options={}, &handler) - @running = false - @start_time = nil - - options = { :interval => options } if options.kind_of? Numeric - - if options[:interval] - @interval = options[:interval].to_f - else - @interval = DEFAULT_INTERVAL - end - - if options[:tolerance] - @tolerance = options[:tolerance].to_f - elsif DEFAULT_TOLERANCE < @interval - @tolerance = DEFAULT_TOLERANCE - else - @tolerance = 0.0 - end - - @event_loop = options[:event_loop] || EventLoop.current - - if block_given? - add_signal_handler(:alarm, &handler) - start unless options[:start?] == false - else - start if options[:start?] - end - end - - define_readers :interval, :tolerance - define_signal :alarm - - def stopped? ; @start_time == nil end - def running? ; @start_time != nil end - - def interval= (new_interval) - old_interval = @interval - @interval = new_interval - @event_loop.check_timer(self) if new_interval < old_interval - end - - def end_time - @start_time + @interval end - def time_left - end_time - Time.now end - def ready? - time_left <= @tolerance end - - def restart - @start_time = Time.now - end - - def sound_alarm - signal :alarm - restart if running? - end - - def start - @start_time = Time.now - @event_loop.monitor_timer(self) - end - - def stop - @start_time = nil - @event_loop.ignore_timer(self) - end -end - -if __FILE__ == $0 - require "test/unit" - - class TimerTest < Test::Unit::TestCase - def setup - @timer = EventLoop::Timer.new(:interval => 0.001) - end - - def test_timer - @timer.on_alarm do - puts "[#{@timer.time_left} seconds left after alarm]" - EventLoop.quit - end - 8.times do - t0 = Time.now - @timer.start ; EventLoop.run - t1 = Time.now - assert(t1 - t0 > @timer.interval - @timer.tolerance) - end - end - end -end - -## event-loop.rb ends here. diff --git a/lib/puppet/external/event-loop/signal-system.rb b/lib/puppet/external/event-loop/signal-system.rb deleted file mode 100644 index d3c924bf8..000000000 --- a/lib/puppet/external/event-loop/signal-system.rb +++ /dev/null @@ -1,218 +0,0 @@ -## signal-system.rb --- simple intra-process signal system -# Copyright (C) 2005 Daniel Brockman - -# This program is free software; you can redistribute it -# and/or modify it under the terms of the GNU General Public -# License as published by the Free Software Foundation; -# either version 2 of the License, or (at your option) any -# later version. - -# This file is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty -# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License for more details. - -# You should have received a copy of the GNU General Public -# License along with this program; if not, write to the Free -# Software Foundation, 51 Franklin Street, Fifth Floor, -# Boston, MA 02110-1301, USA. - -require "puppet/external/event-loop/better-definers" - -module SignalEmitterModule - def self.extended (object) - if object.kind_of? Module and not object < SignalEmitter - if object.respond_to? :fcall - # This is the way to call private methods - # in Ruby 1.9 as of November 16. - object.fcall :include, SignalEmitter - else - object.__send__ :include, SignalEmitter - end - end - end - - def define_signal (name, slot=:before, &body) - # Can't use `define_method' and take a block pre-1.9. - class_eval %{ def on_#{name} &block - add_signal_handler(:#{name}, &block) end } - define_signal_handler(name, :before, &lambda {|*a|}) - define_signal_handler(name, :after, &lambda {|*a|}) - define_signal_handler(name, slot, &body) if block_given? - end - - def define_signals (*names, &body) - names.each { |x| define_signal(x, &body) } - end - - def define_signal_handler (name, slot=:before, &body) - case slot - when :before - define_protected_method "handle_#{name}", &body - when :after - define_protected_method "after_handle_#{name}", &body - else - raise ArgumentError, "invalid slot `#{slot.inspect}'; " + - "should be `:before' or `:after'", caller(1) - end - end -end - -# This is an old name for the same thing. -SignalEmitterClass = SignalEmitterModule - -module SignalEmitter - def self.included (includer) - includer.extend SignalEmitterClass if not includer.kind_of? SignalEmitterClass - end - - def __maybe_initialize_signal_emitter - @signal_handlers ||= Hash.new { |h, k| h[k] = Array.new } - @allow_dynamic_signals ||= false - end - - define_accessors :allow_dynamic_signals? - - def add_signal_handler (name, &handler) - __maybe_initialize_signal_emitter - @signal_handlers[name] << handler - handler - end - - define_soft_aliases [:on, :on_signal] => :add_signal_handler - - def remove_signal_handler (name, handler) - __maybe_initialize_signal_emitter - @signal_handlers[name].delete(handler) - end - - def __signal__ (name, *args, &block) - __maybe_initialize_signal_emitter - respond_to? "on_#{name}" or allow_dynamic_signals? or - fail "undefined signal `#{name}' for #{self}:#{self.class}" - __send__("handle_#{name}", *args, &block) if - respond_to? "handle_#{name}" - @signal_handlers[name].each { |x| x.call(*args, &block) } - __send__("after_handle_#{name}", *args, &block) if - respond_to? "after_handle_#{name}" - end - - define_soft_alias :signal => :__signal__ -end - -# This module is indended to be a convenience mixin to be used by -# classes whose objects need to observe foreign signals. That is, -# if you want to observe some signals coming from an object, *you* -# should mix in this module. -# -# You cannot use this module at two different places of the same -# inheritance chain to observe signals coming from the same object. -# -# XXX: This has not seen much use, and I'd like to provide a -# better solution for the problem in the future. -module SignalObserver - def __maybe_initialize_signal_observer - @observed_signals ||= Hash.new do |signals, object| - signals[object] = Hash.new do |handlers, name| - handlers[name] = Array.new - end - end - end - - def observe_signal (subject, name, &handler) - __maybe_initialize_signal_observer - @observed_signals[subject][name] << handler - subject.add_signal_handler(name, &handler) - end - - def map_signals (source, pairs={}) - pairs.each do |src_name, dst_name| - observe_signal(source, src_name) do |*args| - __signal__(dst_name, *args) - end - end - end - - def absorb_signals (subject, *names) - names.each do |name| - observe_signal(subject, name) do |*args| - __signal__(name, *args) - end - end - end - - define_soft_aliases \ - :map_signal => :map_signals, - :absorb_signal => :absorb_signals - - def ignore_signal (subject, name) - __maybe_initialize_signal_observer - __ignore_signal_1(subject, name) - @observed_signals.delete(subject) if - @observed_signals[subject].empty? - end - - def ignore_signals (subject, *names) - __maybe_initialize_signal_observer - names = @observed_signals[subject] if names.empty? - names.each { |x| __ignore_signal_1(subject, x) } - end - - private - - def __ignore_signal_1(subject, name) - @observed_signals[subject][name].each do |handler| - subject.remove_signal_handler(name, handler) end - @observed_signals[subject].delete(name) - end -end - -if __FILE__ == $0 - require "test/unit" - class SignalEmitterTest < Test::Unit::TestCase - class X - include SignalEmitter - define_signal :foo - end - - def setup - @x = X.new - end - - def test_on_signal - moomin = 0 - @x.on_signal(:foo) { moomin = 1 } - @x.signal :foo - assert moomin == 1 - end - - def test_on_foo - moomin = 0 - @x.on_foo { moomin = 1 } - @x.signal :foo - assert moomin == 1 - end - - def test_multiple_on_signal - moomin = 0 - @x.on_signal(:foo) { moomin += 1 } - @x.on_signal(:foo) { moomin += 2 } - @x.on_signal(:foo) { moomin += 4 } - @x.on_signal(:foo) { moomin += 8 } - @x.signal :foo - assert moomin == 15 - end - - def test_multiple_on_foo - moomin = 0 - @x.on_foo { moomin += 1 } - @x.on_foo { moomin += 2 } - @x.on_foo { moomin += 4 } - @x.on_foo { moomin += 8 } - @x.signal :foo - assert moomin == 15 - end - end -end - -## application-signals.rb ends here. diff --git a/lib/puppet/interface/action.rb b/lib/puppet/interface/action.rb index 3f2f41273..5f23f55db 100644 --- a/lib/puppet/interface/action.rb +++ b/lib/puppet/interface/action.rb @@ -1,322 +1,322 @@ require 'puppet/interface' require 'puppet/interface/documentation' require 'prettyprint' class Puppet::Interface::Action extend Puppet::Interface::DocGen include Puppet::Interface::FullDocs def initialize(face, name, attrs = {}) raise "#{name.inspect} is an invalid action name" unless name.to_s =~ /^[a-z]\w*$/ @face = face @name = name.to_sym # The few bits of documentation we actually demand. The default license # is a favour to our end users; if you happen to get that in a core face # report it as a bug, please. --daniel 2011-04-26 @authors = [] @license = 'All Rights Reserved' attrs.each do |k, v| send("#{k}=", v) end # @options collects the added options in the order they're declared. # @options_hash collects the options keyed by alias for quick lookups. @options = [] @options_hash = {} @when_rendering = {} end # This is not nice, but it is the easiest way to make us behave like the # Ruby Method object rather than UnboundMethod. Duplication is vaguely # annoying, but at least we are a shallow clone. --daniel 2011-04-12 def __dup_and_rebind_to(to) bound_version = self.dup bound_version.instance_variable_set(:@face, to) return bound_version end def to_s() "#{@face}##{@name}" end attr_reader :name attr_reader :face attr_accessor :default def default? !!@default end ######################################################################## # Documentation... attr_doc :returns attr_doc :arguments def synopsis build_synopsis(@face.name, default? ? nil : name, arguments) end ######################################################################## # Support for rendering formats and all. def when_rendering(type) unless type.is_a? Symbol raise ArgumentError, "The rendering format must be a symbol, not #{type.class.name}" end # Do we have a rendering hook for this name? return @when_rendering[type].bind(@face) if @when_rendering.has_key? type # How about by another name? alt = type.to_s.sub(/^to_/, '').to_sym return @when_rendering[alt].bind(@face) if @when_rendering.has_key? alt # Guess not, nothing to run. return nil end def set_rendering_method_for(type, proc) unless proc.is_a? Proc msg = "The second argument to set_rendering_method_for must be a Proc" msg += ", not #{proc.class.name}" unless proc.nil? raise ArgumentError, msg end if proc.arity != 1 and proc.arity != (@positional_arg_count + 1) msg = "the when_rendering method for the #{@face.name} face #{name} action " msg += "takes either just one argument, the result of when_invoked, " msg += "or the result plus the #{@positional_arg_count} arguments passed " msg += "to the when_invoked block, not " if proc.arity < 0 then msg += "a variable number" else msg += proc.arity.to_s end raise ArgumentError, msg end unless type.is_a? Symbol raise ArgumentError, "The rendering format must be a symbol, not #{type.class.name}" end if @when_rendering.has_key? type then raise ArgumentError, "You can't define a rendering method for #{type} twice" end # Now, the ugly bit. We add the method to our interface object, and # retrieve it, to rotate through the dance of getting a suitable method # object out of the whole process. --daniel 2011-04-18 @when_rendering[type] = @face.__send__( :__add_method, __render_method_name_for(type), proc) end def __render_method_name_for(type) :"#{name}_when_rendering_#{type}" end private :__render_method_name_for attr_accessor :render_as def render_as=(value) @render_as = value.to_sym end ######################################################################## # Initially, this was defined to allow the @action.invoke pattern, which is # a very natural way to invoke behaviour given our introspection # capabilities. Heck, our initial plan was to have the faces delegate to # the action object for invocation and all. # # It turns out that we have a binding problem to solve: @face was bound to # the parent class, not the subclass instance, and we don't pass the # appropriate context or change the binding enough to make this work. # # We could hack around it, by either mandating that you pass the context in # to invoke, or try to get the binding right, but that has probably got # subtleties that we don't instantly think of – especially around threads. # # So, we are pulling this method for now, and will return it to life when we # have the time to resolve the problem. For now, you should replace... # # @action = @face.get_action(name) # @action.invoke(arg1, arg2, arg3) # # ...with... # # @action = @face.get_action(name) # @face.send(@action.name, arg1, arg2, arg3) # # I understand that is somewhat cumbersome, but it functions as desired. # --daniel 2011-03-31 # # PS: This code is left present, but commented, to support this chunk of # documentation, for the benefit of the reader. # # def invoke(*args, &block) # @face.send(name, *args, &block) # end # We need to build an instance method as a wrapper, using normal code, to be # able to expose argument defaulting between the caller and definer in the # Ruby API. An extra method is, sadly, required for Ruby 1.8 to work since # it doesn't expose bind on a block. # # Hopefully we can improve this when we finally shuffle off the last of Ruby # 1.8 support, but that looks to be a few "enterprise" release eras away, so # we are pretty stuck with this for now. # # Patches to make this work more nicely with Ruby 1.9 using runtime version # checking and all are welcome, provided that they don't change anything # outside this little ol' bit of code and all. # # Incidentally, we though about vendoring evil-ruby and actually adjusting # the internal C structure implementation details under the hood to make # this stuff work, because it would have been cleaner. Which gives you an # idea how motivated we were to make this cleaner. Sorry. # --daniel 2011-03-31 attr_reader :positional_arg_count attr_accessor :when_invoked def when_invoked=(block) internal_name = "#{@name} implementation, required on Ruby 1.8".to_sym arity = @positional_arg_count = block.arity if arity == 0 then # This will never fire on 1.8.7, which treats no arguments as "*args", # but will on 1.9.2, which treats it as "no arguments". Which bites, # because this just begs for us to wind up in the horrible situation # where a 1.8 vs 1.9 error bites our end users. --daniel 2011-04-19 raise ArgumentError, "when_invoked requires at least one argument (options) for action #{@name}" elsif arity > 0 then range = Range.new(1, arity - 1) decl = range.map { |x| "arg#{x}" } << "options = {}" optn = "" args = "[" + (range.map { |x| "arg#{x}" } << "options").join(", ") + "]" else range = Range.new(1, arity.abs - 1) decl = range.map { |x| "arg#{x}" } << "*rest" optn = "rest << {} unless rest.last.is_a?(Hash)" if arity == -1 then args = "rest" else args = "[" + range.map { |x| "arg#{x}" }.join(", ") + "] + rest" end end file = __FILE__ + "+eval[wrapper]" line = __LINE__ + 2 # <== points to the same line as 'def' in the wrapper. wrapper = <