summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/puppet.rb7
-rw-r--r--lib/puppet/client.rb6
-rwxr-xr-xlib/puppet/daemon.rb3
-rw-r--r--lib/puppet/lock.rb64
-rw-r--r--lib/puppet/storage.rb18
-rw-r--r--lib/puppet/util.rb58
-rwxr-xr-xtest/puppet/utiltest.rb33
-rwxr-xr-xtest/types/schedule.rb2
8 files changed, 169 insertions, 22 deletions
diff --git a/lib/puppet.rb b/lib/puppet.rb
index 926dbb02c..dacaa0f34 100644
--- a/lib/puppet.rb
+++ b/lib/puppet.rb
@@ -122,10 +122,11 @@ module Puppet
directory can be removed without causing harm (although it might
result in spurious service restarts)."],
[:rundir, "$vardir/run", "Where Puppet PID files are kept."],
+ [:lockdir, "$vardir/locks", "Where lock files are kept."],
[:statefile, "$statedir/state.yaml",
- "Where puppetd and puppetmasterd store state associated with the running
- configuration. In the case of puppetmasterd, this file reflects the
- state discovered through interacting with clients."],
+ "Where puppetd and puppetmasterd store state associated with the
+ running configuration. In the case of puppetmasterd, this file
+ reflects the state discovered through interacting with clients."],
[:ssldir, "$confdir/ssl", "Where SSL certificates are kept."],
[:genconfig, false,
"Whether to just print a configuration to stdout and exit. Only makes
diff --git a/lib/puppet/client.rb b/lib/puppet/client.rb
index dcf3dd439..f0f0968bf 100644
--- a/lib/puppet/client.rb
+++ b/lib/puppet/client.rb
@@ -11,9 +11,9 @@ module Puppet
include Puppet
include SignalObserver
- # FIXME the cert stuff should only come up with networking, so it
- # should be in the network client, not the normal client
- # but if i do that, it's hard to tell whether the certs have been initialized
+ # FIXME The cert stuff should only come up with networking, so it
+ # should be in the network client, not the normal client. But if i do
+ # that, it's hard to tell whether the certs have been initialized.
include Puppet::Daemon
attr_reader :local, :secureinit
attr_accessor :schedule, :lastrun
diff --git a/lib/puppet/daemon.rb b/lib/puppet/daemon.rb
index fcedff3d7..9f91e183f 100755
--- a/lib/puppet/daemon.rb
+++ b/lib/puppet/daemon.rb
@@ -221,7 +221,8 @@ module Puppet
begin
File.unlink(@pidfile)
rescue => detail
- Puppet.err "Could not remove PID file %s: %s" % [@pidfile, detail]
+ Puppet.err "Could not remove PID file %s: %s" %
+ [@pidfile, detail]
end
end
diff --git a/lib/puppet/lock.rb b/lib/puppet/lock.rb
new file mode 100644
index 000000000..d873d1bbf
--- /dev/null
+++ b/lib/puppet/lock.rb
@@ -0,0 +1,64 @@
+require 'thread'
+require 'sync'
+
+# Gotten from:
+# http://path.berkeley.edu/~vjoel/ruby/solaris-bug.rb
+
+# Extensions to the File class for exception-safe file locking in a
+# environment with multiple user threads.
+
+# This is here because closing a file on solaris unlocks any locks that
+# other threads might have. So we have to make sure that only the last
+# reader thread closes the file.
+#
+# The hash maps inode number to a count of reader threads
+$reader_count = Hash.new(0)
+
+class File
+ # Get an exclusive (i.e., write) lock on the file, and yield to the block.
+ # If the lock is not available, wait for it without blocking other ruby
+ # threads.
+ def lock_exclusive
+ if Thread.list.size == 1
+ flock(LOCK_EX)
+ else
+ # ugly hack because waiting for a lock in a Ruby thread blocks the
+ # process
+ period = 0.001
+ until flock(LOCK_EX|LOCK_NB)
+ sleep period
+ period *= 2 if period < 1
+ end
+ end
+
+ yield self
+ ensure
+ flush
+ flock(LOCK_UN)
+ end
+
+ # Get a shared (i.e., read) lock on the file, and yield to the block.
+ # If the lock is not available, wait for it without blocking other ruby
+ # threads.
+ def lock_shared
+ if Thread.list.size == 1
+ flock(LOCK_SH)
+ else
+ # ugly hack because waiting for a lock in a Ruby thread blocks the
+ # process
+ period = 0.001
+ until flock(LOCK_SH|LOCK_NB)
+ sleep period
+ period *= 2 if period < 1
+ end
+ end
+
+ yield self
+ ensure
+ Thread.exclusive {flock(LOCK_UN) if $reader_count[self.stat.ino] == 1}
+ ## for solaris, no need to unlock here--closing does it
+ ## but this has no effect on the bug
+ end
+end
+
+# $Id$
diff --git a/lib/puppet/storage.rb b/lib/puppet/storage.rb
index 0c06f5173..59fe4a1fc 100644
--- a/lib/puppet/storage.rb
+++ b/lib/puppet/storage.rb
@@ -1,10 +1,12 @@
require 'yaml'
+require 'sync'
+#require 'puppet/lockfile'
module Puppet
# a class for storing state
class Storage
include Singleton
-
+
def initialize
self.class.load
end
@@ -46,9 +48,7 @@ module Puppet
end
return
end
- #Puppet.debug "Loading statefile %s" % Puppet[:statefile]
- Puppet::Util.lock(Puppet[:statefile]) { |file|
- #@@state = Marshal.load(file)
+ Puppet::Util.readlock(Puppet[:statefile]) do |file|
begin
@@state = YAML.load(file)
rescue => detail
@@ -63,7 +63,7 @@ module Puppet
Puppet[:statefile]
end
end
- }
+ end
unless @@state.is_a?(Hash)
Puppet.err "State got corrupted"
@@ -78,7 +78,7 @@ module Puppet
end
def self.store
- Puppet.debug "Storing state"
+ #Puppet.debug "Storing state"
unless FileTest.directory?(File.dirname(Puppet[:statefile]))
begin
Puppet.recmkdir(File.dirname(Puppet[:statefile]))
@@ -94,11 +94,9 @@ module Puppet
Puppet.info "Creating state file %s" % Puppet[:statefile]
end
- Puppet::Util.lock(
- Puppet[:statefile], "w", 0600
- ) { |file|
+ Puppet::Util.writelock(Puppet[:statefile], 0600) do |file|
file.print YAML.dump(@@state)
- }
+ end
end
end
end
diff --git a/lib/puppet/util.rb b/lib/puppet/util.rb
index 6e74ca602..d84021a46 100644
--- a/lib/puppet/util.rb
+++ b/lib/puppet/util.rb
@@ -1,7 +1,12 @@
# A module to collect utility functions.
+require 'sync'
+require 'puppet/lock'
+
module Puppet
module Util
+ # Create a sync point for any threads
+ @@sync = Sync.new
# Execute a block as a given user or group
def self.asuser(user = nil, group = nil)
require 'etc'
@@ -132,8 +137,43 @@ module Util
end
end
+ # Create a shared lock for reading
+ def self.readlock(file)
+ @@sync.synchronize(Sync::SH) do
+ File.open(file) { |f|
+ f.lock_shared { |lf| yield lf }
+ }
+ end
+ end
+
+ def self.sync
+ end
+
+ # Create an exclusive lock fro writing, and do the writing in a
+ # tmp file.
+ def self.writelock(file, mode = 0600)
+ tmpfile = file + ".tmp"
+ @@sync.synchronize(Sync::EX) do
+ File.open(file, "w", mode) do |rf|
+ rf.lock_exclusive do |lrf|
+ yield lrf
+ File.open(tmpfile, "w", mode) do |tf|
+ yield tf
+ tf.flush
+ end
+ begin
+ File.rename(tmpfile, file)
+ rescue => detail
+ Puppet.err "Could not rename %s to %s: %s" %
+ [file, tmpfile, detail]
+ end
+ end
+ end
+ end
+ end
+
# Create a lock file while something is happening
- def self.lock(*opts)
+ def self.disabledlock(*opts)
lock = opts[0] + ".lock"
while File.exists?(lock)
stamp = File.stat(lock).mtime.to_i
@@ -141,9 +181,11 @@ module Util
Puppet.notice "Lock file %s is %s seconds old; removing" %
[lock, Time.now.to_i - stamp]
File.delete(lock)
+ break
+ else
+ sleep 0.1
end
#Puppet.debug "%s is locked" % opts[0]
- sleep 0.1
end
File.open(lock, "w") { |f| f.print " "; f.flush }
writing = false
@@ -155,8 +197,16 @@ module Util
end
begin
File.open(*opts) { |file| yield file }
- if writing
- File.rename(tmp, orig)
+ begin
+ if writing
+ Puppet.warning "opts were %s" % opts.inspect
+ system("ls -l %s 2>/dev/null" % tmp)
+ system("ls -l %s 2>/dev/null" % orig)
+ File.rename(tmp, orig)
+ end
+ rescue => detail
+ Puppet.err "Could not replace %s: %s" % [orig, detail]
+ File.unlink(tmp)
end
rescue => detail
Puppet.err "Storage error: %s" % detail
diff --git a/test/puppet/utiltest.rb b/test/puppet/utiltest.rb
index 161a831d8..5ca2e4343 100755
--- a/test/puppet/utiltest.rb
+++ b/test/puppet/utiltest.rb
@@ -10,6 +10,39 @@ require 'test/unit'
class TestPuppetUtil < Test::Unit::TestCase
include TestPuppet
+
+ # we're getting corrupt files, probably because multiple processes
+ # are reading or writing the file at once
+ # so we need to test that
+ def test_multiwrite
+ file = tempfile()
+ File.open(file, "w") { |f| f.puts "starting" }
+
+ value = {:a => :b}
+ threads = []
+ sync = Sync.new
+ 9.times { |a|
+ threads << Thread.new {
+ 9.times { |b|
+ assert_nothing_raised {
+ sync.synchronize(Sync::SH) {
+ Puppet::Util.readlock(file) { |f|
+ f.read
+ }
+ }
+ sleep 0.01
+ sync.synchronize(Sync::EX) {
+ Puppet::Util.writelock(file) { |f|
+ f.puts "%s %s" % [a, b]
+ }
+ }
+ }
+ }
+ }
+ }
+ threads.each { |th| th.join }
+ end
+
unless Process.uid == 0
$stderr.puts "Run as root to perform Utility tests"
def test_nothing
diff --git a/test/types/schedule.rb b/test/types/schedule.rb
index 62ab010e3..ded677fd2 100755
--- a/test/types/schedule.rb
+++ b/test/types/schedule.rb
@@ -145,7 +145,7 @@ class TestSchedule < Test::Unit::TestCase
"%s matched %s incorrectly" % [value.inspect, @now])
}
- assert_nothing_raised("Could not parse %s" % values) {
+ assert_nothing_raised("Could not parse %s" % [values]) {
s[:range] = values
}