From 30a23ac94c99e1a44fa6b5f7cdcfe896906a53fc Mon Sep 17 00:00:00 2001 From: Daniel Pittman Date: Tue, 21 Feb 2012 17:58:02 -0800 Subject: [PATCH 1/4] (#4862) Stop using EventLoop in the Puppet daemon. This eliminates the places in the code that use the EventLoop model directly: the two timers established to check configuration changes and run the agent, and the invocation of the main loop in daemon mode. Signed-off-by: Daniel Pittman --- lib/puppet/agent.rb | 12 ------ lib/puppet/daemon.rb | 73 +++++++++++++++++++++++++++++++-- lib/puppet/util/settings.rb | 10 ----- spec/unit/agent_spec.rb | 32 --------------- spec/unit/daemon_spec.rb | 18 +------- spec/unit/util/settings_spec.rb | 53 ------------------------ 6 files changed, 71 insertions(+), 127 deletions(-) diff --git a/lib/puppet/agent.rb b/lib/puppet/agent.rb index a16b75688..7d6a6e996 100644 --- a/lib/puppet/agent.rb +++ b/lib/puppet/agent.rb @@ -77,18 +77,6 @@ class Puppet::Agent @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 diff --git a/lib/puppet/daemon.rb b/lib/puppet/daemon.rb index a6945c840..5a0d4d5c3 100755 --- a/lib/puppet/daemon.rb +++ b/lib/puppet/daemon.rb @@ -120,10 +120,77 @@ class Puppet::Daemon create_pidfile raise Puppet::DevError, "Daemons must have an agent, server, or both" unless agent or server - server.start if server - agent.start if agent - EventLoop.current.run + # Start the listening server, if required. + server.start if server + + # 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/util/settings.rb b/lib/puppet/util/settings.rb index aaa4f44c7..7406af8c6 100644 --- a/lib/puppet/util/settings.rb +++ b/lib/puppet/util/settings.rb @@ -296,10 +296,6 @@ class Puppet::Util::Settings @sync.synchronize do unsafe_parse(self[:config]) end - - # Create a timer so that this file will get checked automatically - # and reparsed if necessary. - set_filetimeout_timer end # Unsafely parse the file -- this isn't thread-safe and causes plenty of problems if used directly. @@ -549,12 +545,6 @@ class Puppet::Util::Settings call.each { |setting| setting.handle(self.value(setting.name)) } end - # Create a timer to check whether the file should be reparsed. - def set_filetimeout_timer - return unless timeout = self[:filetimeout] and timeout = Integer(timeout) and timeout > 0 - timer = EventLoop::Timer.new(:interval => timeout, :tolerance => 1, :start? => true) { self.reparse } - end - # Convert the settings we manage into a catalog full of resources that model those settings. def to_catalog(*sections) sections = nil if sections.empty? diff --git a/spec/unit/agent_spec.rb b/spec/unit/agent_spec.rb index f5342f882..e83313a3d 100755 --- a/spec/unit/agent_spec.rb +++ b/spec/unit/agent_spec.rb @@ -260,36 +260,4 @@ describe Puppet::Agent do end end end - - describe "when starting" do - before do - @agent.stubs(:observe_signal) - end - - it "should create a timer with the runinterval, a tolerance of 1, and :start? set to true" do - Puppet.settings.expects(:value).with(:runinterval).returns 5 - timer = stub 'timer', :sound_alarm => nil - EventLoop::Timer.expects(:new).with(:interval => 5, :start? => true, :tolerance => 1).returns timer - - @agent.stubs(:run) - @agent.start - end - - it "should run once immediately" do - timer = mock 'timer' - EventLoop::Timer.expects(:new).returns timer - - timer.expects(:sound_alarm) - - @agent.start - end - - it "should run within the block passed to the timer" do - timer = stub 'timer', :sound_alarm => nil - EventLoop::Timer.expects(:new).returns(timer).yields - @agent.expects(:run) - - @agent.start - end - end end diff --git a/spec/unit/daemon_spec.rb b/spec/unit/daemon_spec.rb index 3788a0a27..526fd88d1 100755 --- a/spec/unit/daemon_spec.rb +++ b/spec/unit/daemon_spec.rb @@ -49,7 +49,7 @@ describe Puppet::Daemon do before do @daemon.stubs(:create_pidfile) @daemon.stubs(:set_signal_traps) - EventLoop.current.stubs(:run) + @daemon.stubs(:run_event_loop) end it "should fail if it has neither agent nor server" do @@ -58,19 +58,10 @@ describe Puppet::Daemon do it "should create its pidfile" do @daemon.stubs(:agent).returns stub('agent', :start => nil) - @daemon.expects(:create_pidfile) @daemon.start end - it "should start the agent if the agent is configured" do - agent = mock 'agent' - agent.expects(:start) - @daemon.stubs(:agent).returns agent - - @daemon.start - end - it "should start its server if one is configured" do server = mock 'server' server.expects(:start) @@ -78,13 +69,6 @@ describe Puppet::Daemon do @daemon.start end - - it "should let the current EventLoop run" do - @daemon.stubs(:agent).returns stub('agent', :start => nil) - EventLoop.current.expects(:run) - - @daemon.start - end end describe "when stopping" do diff --git a/spec/unit/util/settings_spec.rb b/spec/unit/util/settings_spec.rb index 26e69a3ac..7af43c000 100755 --- a/spec/unit/util/settings_spec.rb +++ b/spec/unit/util/settings_spec.rb @@ -411,13 +411,6 @@ describe Puppet::Util::Settings do @settings.parse end - it "should set a timer that triggers reparsing, even if the file does not exist" do - FileTest.expects(:exist?).returns false - @settings.expects(:set_filetimeout_timer) - - @settings.parse - end - it "should return values set in the configuration file" do text = "[main] one = fileval @@ -559,12 +552,6 @@ describe Puppet::Util::Settings do File.expects(:expand_path).with(somefile).returns somefile @settings[:config] = somefile end - - it "should not set a timer" do - EventLoop::Timer.expects(:new).never - - @settings.parse - end end end @@ -1057,46 +1044,6 @@ describe Puppet::Util::Settings do end end - describe "when setting a timer to trigger configuration file reparsing" do - before do - @settings = Puppet::Util::Settings.new - @settings.setdefaults :foo, :filetimeout => [5, "eh"] - end - - it "should do nothing if no filetimeout setting is available" do - @settings.expects(:value).with(:filetimeout).returns nil - EventLoop::Timer.expects(:new).never - @settings.set_filetimeout_timer - end - - it "should always convert the timer interval to an integer" do - @settings.expects(:value).with(:filetimeout).returns "10" - EventLoop::Timer.expects(:new).with(:interval => 10, :start? => true, :tolerance => 1) - @settings.set_filetimeout_timer - end - - it "should do nothing if the filetimeout setting is not greater than 0" do - @settings.expects(:value).with(:filetimeout).returns -2 - EventLoop::Timer.expects(:new).never - @settings.set_filetimeout_timer - end - - it "should create a timer with its interval set to the filetimeout, start? set to true, and a tolerance of 1" do - @settings.expects(:value).with(:filetimeout).returns 5 - EventLoop::Timer.expects(:new).with(:interval => 5, :start? => true, :tolerance => 1) - - @settings.set_filetimeout_timer - end - - it "should reparse when the timer goes off" do - EventLoop::Timer.expects(:new).with(:interval => 5, :start? => true, :tolerance => 1).yields - - @settings.expects(:reparse) - - @settings.set_filetimeout_timer - end - end - describe "when determining if the service user is available" do it "should return false if there is no user setting" do Puppet::Util::Settings.new.should_not be_service_user_available From 11501a7c8db9e7547bfa94a79dc60160a20825a7 Mon Sep 17 00:00:00 2001 From: Daniel Pittman Date: Wed, 22 Feb 2012 13:46:50 -0800 Subject: [PATCH 2/4] (#4862) `returning` is not a standard Ruby method. The `returning(foo) do ... end` idiom used in this method isn't a standard part of Ruby; it comes from the event-loop better definers code. Since this is the one occurrence in the source tree, might as well open-code it. Signed-off-by: Daniel Pittman --- lib/puppet/module_tool/modulefile.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/puppet/module_tool/modulefile.rb b/lib/puppet/module_tool/modulefile.rb index 35c1e9973..4e91b2ab9 100644 --- a/lib/puppet/module_tool/modulefile.rb +++ b/lib/puppet/module_tool/modulefile.rb @@ -10,13 +10,13 @@ module Puppet::Module::Tool # Read the +filename+ and eval its Ruby code to set values in the Metadata # +metadata+ instance. def self.evaluate(metadata, filename) - returning(new(metadata)) do |builder| - if File.file?(filename) - builder.instance_eval(File.read(filename.to_s), filename.to_s, 1) - else - Puppet.warning "No Modulefile: #{filename}" - end + builder = new(metadata) + if File.file?(filename) + builder.instance_eval(File.read(filename.to_s), filename.to_s, 1) + else + Puppet.warning "No Modulefile: #{filename}" end + return builder end # Instantiate with the Metadata +metadata+ instance. From 8e1176639afb1110d671b73dfd3571ff5d5ac905 Mon Sep 17 00:00:00 2001 From: Daniel Pittman Date: Wed, 22 Feb 2012 13:52:37 -0800 Subject: [PATCH 3/4] (#4862) define_method is not a public method in Ruby. The `better-definers` part of the EventLoop library change `define_method` to be public on all objects, rather than private. This isn't standard in Ruby, and we only had one place that wasn't respecting this to start with. Rather than retaining this divergence we just use `send` to trigger the behaviour externally in the one place that is needed. Signed-off-by: Daniel Pittman --- lib/puppet/interface/action.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 @@ -212,7 +212,7 @@ WRAPPER if @face.is_a?(Class) @face.class_eval do eval wrapper, nil, file, line end - @face.define_method(internal_name, &block) + @face.send(:define_method, internal_name, &block) @when_invoked = @face.instance_method(name) else @face.instance_eval do eval wrapper, nil, file, line end From dba27c9809d5af26cdf31c7de9cb3b55fa24faba Mon Sep 17 00:00:00 2001 From: Daniel Pittman Date: Wed, 22 Feb 2012 13:58:13 -0800 Subject: [PATCH 4/4] (#4862) Finally remove the event-loop library. Now that all the consumers of the event-loop abstraction have been removed, delete the obsolete code from Puppet. Signed-off-by: Daniel Pittman --- lib/puppet/agent.rb | 1 - lib/puppet/daemon.rb | 1 - lib/puppet/external/event-loop.rb | 1 - .../external/event-loop/better-definers.rb | 367 ------------------ lib/puppet/external/event-loop/event-loop.rb | 355 ----------------- .../external/event-loop/signal-system.rb | 218 ----------- lib/puppet/util/settings.rb | 1 - test/other/puppet.rb | 1 - 8 files changed, 945 deletions(-) delete mode 100644 lib/puppet/external/event-loop.rb delete mode 100644 lib/puppet/external/event-loop/better-definers.rb delete mode 100644 lib/puppet/external/event-loop/event-loop.rb delete mode 100644 lib/puppet/external/event-loop/signal-system.rb diff --git a/lib/puppet/agent.rb b/lib/puppet/agent.rb index 7d6a6e996..c34535200 100644 --- a/lib/puppet/agent.rb +++ b/lib/puppet/agent.rb @@ -1,5 +1,4 @@ require 'sync' -require 'puppet/external/event-loop' require 'puppet/application' # A general class for triggering a run of another diff --git a/lib/puppet/daemon.rb b/lib/puppet/daemon.rb index 5a0d4d5c3..d43da6fd5 100755 --- a/lib/puppet/daemon.rb +++ b/lib/puppet/daemon.rb @@ -1,6 +1,5 @@ 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 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/util/settings.rb b/lib/puppet/util/settings.rb index 7406af8c6..e41bf5fa7 100644 --- a/lib/puppet/util/settings.rb +++ b/lib/puppet/util/settings.rb @@ -1,7 +1,6 @@ require 'puppet' require 'sync' require 'getoptlong' -require 'puppet/external/event-loop' require 'puppet/util/loadedfile' # The class for handling configuration files. diff --git a/test/other/puppet.rb b/test/other/puppet.rb index 9fb53ddba..871860f08 100755 --- a/test/other/puppet.rb +++ b/test/other/puppet.rb @@ -8,7 +8,6 @@ require 'puppettest' # Test the different features of the main puppet module class TestPuppetModule < Test::Unit::TestCase include PuppetTest - include SignalObserver def mkfakeclient Class.new(Puppet::Network::Client) do