Feature #4464 » 0001-add-Fcntl-Lock-object-for-easier-use-of-POSIX-file-l.patch
| ext/.document | ||
|---|---|---|
| 
     date/lib 
   | 
||
| 
     digest/digest.c 
   | 
||
| 
     etc/etc.c 
   | 
||
| 
     fcntl/fcntl.c 
   | 
||
| 
     fcntl 
   | 
||
| 
     gdbm/gdbm.c 
   | 
||
| 
     iconv/iconv.c 
   | 
||
| 
     io/wait/wait.c 
   | 
||
| ext/fcntl/.document | ||
|---|---|---|
| 
     fcntl.c 
   | 
||
| 
     lib/fcntl/lock.rb 
   | 
||
| ext/fcntl/extconf.rb | ||
|---|---|---|
| 
     require 'mkmf' 
   | 
||
| 
     have_type('struct flock', %w(unistd.h fcntl.h)) 
   | 
||
| 
     create_makefile('fcntl') 
   | 
||
| ext/fcntl/fcntl.c | ||
|---|---|---|
| 
     #include "ruby.h" 
   | 
||
| 
     #include <fcntl.h> 
   | 
||
| 
     static VALUE mFcntl; 
   | 
||
| 
     static void Init_FcntlLock(void); 
   | 
||
| 
     /* Fcntl loads the constants defined in the system's <fcntl.h> C header 
   | 
||
| 
      * file, and used with both the fcntl(2) and open(2) POSIX system calls. 
   | 
||
| ... | ... | |
| 
     void 
   | 
||
| 
     Init_fcntl() 
   | 
||
| 
     { 
   | 
||
| 
         VALUE mFcntl = rb_define_module("Fcntl"); 
   | 
||
| 
         mFcntl = rb_define_module("Fcntl"); 
   | 
||
| 
         Init_FcntlLock(); 
   | 
||
| 
     #ifdef F_DUPFD 
   | 
||
| 
         rb_define_const(mFcntl, "F_DUPFD", INT2NUM(F_DUPFD)); 
   | 
||
| 
     #endif 
   | 
||
| ... | ... | |
| 
         rb_define_const(mFcntl, "O_ACCMODE", INT2FIX(O_RDONLY | O_WRONLY | O_RDWR)); 
   | 
||
| 
     #endif 
   | 
||
| 
     } 
   | 
||
| 
     #ifdef HAVE_TYPE_STRUCT_FLOCK 
   | 
||
| 
     static short 
   | 
||
| 
     fix2short(VALUE val) 
   | 
||
| 
     { 
   | 
||
| 
         int i = FIX2INT(val); 
   | 
||
| 
         if (i != (short)i) { 
   | 
||
| 
     	const char *s = i > 0 ? "big" : "small"; 
   | 
||
| 
     	rb_raise(rb_eRangeError, "Integer %d too %s to for `short'", i, s); 
   | 
||
| 
         } 
   | 
||
| 
         return (short)i; 
   | 
||
| 
     } 
   | 
||
| 
     static short 
   | 
||
| 
     val2short(short fallback, VALUE klass, VALUE val) 
   | 
||
| 
     { 
   | 
||
| 
         switch (TYPE(val)) { 
   | 
||
| 
         case T_NIL: return fallback; 
   | 
||
| 
         case T_SYMBOL: 
   | 
||
| 
     	val = rb_const_get(klass, SYM2ID(val)); 
   | 
||
| 
     	/* fall through */ 
   | 
||
| 
         case T_FIXNUM: 
   | 
||
| 
     	return fix2short(val); 
   | 
||
| 
         } 
   | 
||
| 
         rb_raise(rb_eTypeError, "must be a Symbol or Integer"); 
   | 
||
| 
     } 
   | 
||
| 
     static struct flock * 
   | 
||
| 
     flock_ptr(VALUE self) 
   | 
||
| 
     { 
   | 
||
| 
         if (RSTRING_LEN(self) < sizeof(struct flock)) 
   | 
||
| 
     	rb_raise(rb_eRuntimeError, "flock buffer shortened"); 
   | 
||
| 
         rb_str_modify(self); 
   | 
||
| 
         return (struct flock *)RSTRING_PTR(self); 
   | 
||
| 
     } 
   | 
||
| 
     static VALUE 
   | 
||
| 
     lock_init(int argc, VALUE *argv, VALUE self) 
   | 
||
| 
     { 
   | 
||
| 
         struct flock *flock; 
   | 
||
| 
         VALUE type, whence, start, len; 
   | 
||
| 
         rb_str_resize(self, sizeof(struct flock)); 
   | 
||
| 
         flock = flock_ptr(self); 
   | 
||
| 
         rb_scan_args(argc, argv, "04", &type, &whence, &start, &len); 
   | 
||
| 
         flock->l_type = val2short(F_RDLCK, mFcntl, type); 
   | 
||
| 
         flock->l_whence = val2short(SEEK_SET, rb_cIO, whence); 
   | 
||
| 
         flock->l_start = NIL_P(start) ? 0 : NUM2OFFT(start); 
   | 
||
| 
         flock->l_len = NIL_P(len) ? 0 : NUM2OFFT(len); 
   | 
||
| 
         flock->l_pid = 0; 
   | 
||
| 
         return self; 
   | 
||
| 
     } 
   | 
||
| 
     static VALUE 
   | 
||
| 
     l_type(VALUE self) 
   | 
||
| 
     { 
   | 
||
| 
         return INT2NUM(flock_ptr(self)->l_type); 
   | 
||
| 
     } 
   | 
||
| 
     static VALUE 
   | 
||
| 
     set_l_type(VALUE self, VALUE val) 
   | 
||
| 
     { 
   | 
||
| 
         flock_ptr(self)->l_type = val2short(F_RDLCK, mFcntl, val); 
   | 
||
| 
         return val; 
   | 
||
| 
     } 
   | 
||
| 
     static VALUE 
   | 
||
| 
     l_whence(VALUE self) 
   | 
||
| 
     { 
   | 
||
| 
         return INT2NUM(flock_ptr(self)->l_whence); 
   | 
||
| 
     } 
   | 
||
| 
     static VALUE 
   | 
||
| 
     set_l_whence(VALUE self, VALUE val) 
   | 
||
| 
     { 
   | 
||
| 
         flock_ptr(self)->l_whence = val2short(SEEK_SET, rb_cIO, val); 
   | 
||
| 
         return val; 
   | 
||
| 
     } 
   | 
||
| 
     static VALUE 
   | 
||
| 
     l_start(VALUE self) 
   | 
||
| 
     { 
   | 
||
| 
         return OFFT2NUM(flock_ptr(self)->l_start); 
   | 
||
| 
     } 
   | 
||
| 
     static VALUE 
   | 
||
| 
     set_l_start(VALUE self, VALUE val) 
   | 
||
| 
     { 
   | 
||
| 
         flock_ptr(self)->l_start = NUM2OFFT(val); 
   | 
||
| 
         return val; 
   | 
||
| 
     } 
   | 
||
| 
     static VALUE 
   | 
||
| 
     l_len(VALUE self) 
   | 
||
| 
     { 
   | 
||
| 
         return OFFT2NUM(flock_ptr(self)->l_len); 
   | 
||
| 
     } 
   | 
||
| 
     static VALUE 
   | 
||
| 
     set_l_len(VALUE self, VALUE val) 
   | 
||
| 
     { 
   | 
||
| 
         flock_ptr(self)->l_len = NUM2OFFT(val); 
   | 
||
| 
         return val; 
   | 
||
| 
     } 
   | 
||
| 
     static VALUE 
   | 
||
| 
     l_pid(VALUE self) 
   | 
||
| 
     { 
   | 
||
| 
         return PIDT2NUM(flock_ptr(self)->l_pid); 
   | 
||
| 
     } 
   | 
||
| 
     static VALUE 
   | 
||
| 
     lock_inspect(VALUE self) 
   | 
||
| 
     { 
   | 
||
| 
         struct flock *flock = flock_ptr(self); 
   | 
||
| 
         const char fmt[] = "#<%s: type=%s, whence=%s, start=%lld, len=%lld, pid=%ld>"; 
   | 
||
| 
         const char *name = rb_obj_classname(self); 
   | 
||
| 
         const char *type, *whence; 
   | 
||
| 
         switch (flock->l_type) { 
   | 
||
| 
         case F_RDLCK: type = "F_RDLCK"; break; 
   | 
||
| 
         case F_WRLCK: type = "F_WRLCK"; break; 
   | 
||
| 
         case F_UNLCK: type = "F_UNLCK"; break; 
   | 
||
| 
         default: type = "(unknown)"; 
   | 
||
| 
         } 
   | 
||
| 
         switch (flock->l_whence) { 
   | 
||
| 
         case SEEK_SET: whence = "SEEK_SET"; break; 
   | 
||
| 
         case SEEK_CUR: whence = "SEEK_CUR"; break; 
   | 
||
| 
         case SEEK_END: whence = "SEEK_END"; break; 
   | 
||
| 
         default: whence = "(unknown)"; 
   | 
||
| 
         } 
   | 
||
| 
         return rb_sprintf(fmt, name, type, whence, 
   | 
||
| 
     		      (long long)flock->l_start, 
   | 
||
| 
     		      (long long)flock->l_len, 
   | 
||
| 
     		      (long)flock->l_pid); 
   | 
||
| 
     } 
   | 
||
| 
     static void 
   | 
||
| 
     Init_FcntlLock(void) 
   | 
||
| 
     { 
   | 
||
| 
         VALUE cFcntlLock = rb_define_class_under(mFcntl, "Lock", rb_cString); 
   | 
||
| 
         rb_define_private_method(cFcntlLock, "initialize", lock_init, -1); 
   | 
||
| 
         rb_define_method(cFcntlLock, "type", l_type, 0); 
   | 
||
| 
         rb_define_method(cFcntlLock, "type=", set_l_type, 1); 
   | 
||
| 
         rb_define_method(cFcntlLock, "whence", l_whence, 0); 
   | 
||
| 
         rb_define_method(cFcntlLock, "whence=", set_l_whence, 1); 
   | 
||
| 
         rb_define_method(cFcntlLock, "start", l_start, 0); 
   | 
||
| 
         rb_define_method(cFcntlLock, "start=", set_l_start, 1); 
   | 
||
| 
         rb_define_method(cFcntlLock, "len", l_len, 0); 
   | 
||
| 
         rb_define_method(cFcntlLock, "len=", set_l_len, 1); 
   | 
||
| 
         rb_define_method(cFcntlLock, "pid", l_pid, 0); 
   | 
||
| 
         rb_define_method(cFcntlLock, "inspect", lock_inspect, 0); 
   | 
||
| 
         rb_require("fcntl/lock"); 
   | 
||
| 
     } 
   | 
||
| 
     #else /* ! HAVE_TYPE_STRUCT_FLOCK */ 
   | 
||
| 
     static void Init_FcntlLock(void) { } 
   | 
||
| 
     #endif /* HAVE_TYPE_STRUCT_FLOCK */ 
   | 
||
| ext/fcntl/lib/fcntl/lock.rb | ||
|---|---|---|
| 
     # Fcntl::Lock is used for POSIX advisory record locks.  Unlike File#flock, 
   | 
||
| 
     # POSIX locks provide byte-range granularity and work with some 
   | 
||
| 
     # implementations of NFS. 
   | 
||
| 
     # 
   | 
||
| 
     # Fcntl::Lock objects may be passed as the second argument to IO#fcntl, 
   | 
||
| 
     # but there is also a high-level interface documented in examples 
   | 
||
| 
     # below. 
   | 
||
| 
     # 
   | 
||
| 
     # Fcntl::Lock objects consists of the following readable and writable 
   | 
||
| 
     # attributes: 
   | 
||
| 
     # 
   | 
||
| 
     # - type - the type of lock: :F_RDLCK, :F_WRLCK, :F_UNLCK 
   | 
||
| 
     # - whence - interpretation of +start+: :SEEK_SET, :SEEK_CUR, :SEEK_END 
   | 
||
| 
     # - start - starting offset (in bytes) 
   | 
||
| 
     # - len - number of bytes to lock, zero for the entire file 
   | 
||
| 
     # 
   | 
||
| 
     # The +pid+ attribute is read-only and used for Fcntl::Lock.get operations, 
   | 
||
| 
     # it is the process identifier holding the lock. 
   | 
||
| 
     # 
   | 
||
| 
     # Examples: 
   | 
||
| 
     # 
   | 
||
| 
     #   file = File.open("/path/to/file", "rb+") 
   | 
||
| 
     #   Fcntl::Lock.synchronize(file) do 
   | 
||
| 
     #     # other processes may not lock any part of the file for writing 
   | 
||
| 
     #   end 
   | 
||
| 
     # 
   | 
||
| 
     #   Fcntl::Lock.synchronize(file, :F_RDLCK, :SEEK_SET, 90, 10) do 
   | 
||
| 
     #     # places a shared read lock on the 10 bytes after the first 90 bytes 
   | 
||
| 
     #   end 
   | 
||
| 
     # 
   | 
||
| 
     #   Fcntl::Lock.get(file) 
   | 
||
| 
     #   #=> #<Fcntl::Flock: type=F_RDLCK, whence=SEEK_SET, start=90, len=10, pid=4> 
   | 
||
| 
     # 
   | 
||
| 
     # Example (low level): 
   | 
||
| 
     # 
   | 
||
| 
     #   lock = Fcntl::Lock.new(:F_WRLCK, :SEEK_SET, 0, 100) 
   | 
||
| 
     #   file.fcntl(Fcntl::F_SETLKW, lock) 
   | 
||
| 
     #   # first 100 bytes are now locked for reading or writing 
   | 
||
| 
     # 
   | 
||
| 
     #   lock.type = :F_UNLCK 
   | 
||
| 
     #   file.fcntl(Fcntl::F_SETLKW, lock) 
   | 
||
| 
     #   # first 100 bytes now may be locked by other processes 
   | 
||
| 
     class Fcntl::Lock < String 
   | 
||
| 
       include Fcntl 
   | 
||
| 
       def self.__fcntl_retry(file, cmd, lock) # :nodoc: 
   | 
||
| 
         file.fcntl cmd, lock 
   | 
||
| 
         rescue Errno::EINTR 
   | 
||
| 
           retry 
   | 
||
| 
       end 
   | 
||
| 
       # Obtains an advisory lock, runs the given block and releases the lock 
   | 
||
| 
       # when the block completes.  Returns the result of the block. 
   | 
||
| 
       # 
   | 
||
| 
       # +type+ is optional and may be +nil+, in which case the lock +type+ is 
   | 
||
| 
       # determined based on the file access flags. 
   | 
||
| 
       # 
   | 
||
| 
       # +whence+, +start+, and +len+ are all optional and documented under 
   | 
||
| 
       # Fcntl::Lock. 
   | 
||
| 
       def self.synchronize(file, type=nil, whence=IO::SEEK_SET, start=0, len=0) 
   | 
||
| 
         type ||= (file.fcntl(F_GETFL) & O_ACCMODE) == O_RDONLY ? F_RDLCK : F_WRLCK 
   | 
||
| 
         lock = new(type, whence, start, len) 
   | 
||
| 
         __fcntl_retry(file, F_SETLKW, lock) 
   | 
||
| 
         begin 
   | 
||
| 
           yield file 
   | 
||
| 
         ensure 
   | 
||
| 
           lock.type = F_UNLCK 
   | 
||
| 
           __fcntl_retry(file, F_SETLKW, lock) 
   | 
||
| 
         end 
   | 
||
| 
       end 
   | 
||
| 
       # Describes a type of lock we would like to place on the given +file+ 
   | 
||
| 
       # but does not place it. 
   | 
||
| 
       # 
   | 
||
| 
       # Returns a Fcntl::Lock object that either describes a conflicting lock 
   | 
||
| 
       # that prevents a lock from being placed or one with its type set 
   | 
||
| 
       # to +Fcntl::F_UNLCK+ with other fields (if specified) unchanged. 
   | 
||
| 
       # 
   | 
||
| 
       # +type+ is optional and may be +nil+, in which case the lock +type+ is 
   | 
||
| 
       # determined based on the file access flags. 
   | 
||
| 
       # 
   | 
||
| 
       # +whence+, +start+, and +len+ are all optional and documented under 
   | 
||
| 
       # Fcntl::Lock. 
   | 
||
| 
       def self.get(file, type=nil, whence=IO::SEEK_SET, start=0, len=0) 
   | 
||
| 
         type ||= (file.fcntl(F_GETFL) & O_ACCMODE) == O_RDONLY ? F_RDLCK : F_WRLCK 
   | 
||
| 
         lock = new(type, whence, start, len) 
   | 
||
| 
         __fcntl_retry(file, F_GETLK, lock) 
   | 
||
| 
         lock 
   | 
||
| 
       end 
   | 
||
| 
     end 
   | 
||
| test/fcntl/test_fcntl_lock.rb | ||
|---|---|---|
| 
     require "test/unit" 
   | 
||
| 
     begin 
   | 
||
| 
       require "fcntl" 
   | 
||
| 
     rescue LoadError 
   | 
||
| 
     end 
   | 
||
| 
     require "tempfile" 
   | 
||
| 
     class TestFcntlLock < Test::Unit::TestCase 
   | 
||
| 
       def test_initialize_noargs 
   | 
||
| 
         flock = Fcntl::Lock.new 
   | 
||
| 
         assert_equal Fcntl::F_RDLCK, flock.type 
   | 
||
| 
         assert_equal IO::SEEK_SET, flock.whence 
   | 
||
| 
         assert_equal 0, flock.start 
   | 
||
| 
         assert_equal 0, flock.len 
   | 
||
| 
       end 
   | 
||
| 
       def test_inspect 
   | 
||
| 
         expect = "#<Fcntl::Lock: type=F_RDLCK, whence=SEEK_SET, " \ 
   | 
||
| 
                  "start=0, len=0, pid=0>" 
   | 
||
| 
         assert_equal expect, Fcntl::Lock.new.inspect 
   | 
||
| 
       end 
   | 
||
| 
       def test_initialize_write_lock 
   | 
||
| 
         flock = Fcntl::Lock.new Fcntl::F_WRLCK 
   | 
||
| 
         assert_equal Fcntl::F_WRLCK, flock.type 
   | 
||
| 
         assert_equal IO::SEEK_SET, flock.whence 
   | 
||
| 
         assert_equal 0, flock.start 
   | 
||
| 
         assert_equal 0, flock.len 
   | 
||
| 
       end 
   | 
||
| 
       def test_accessors 
   | 
||
| 
         flock = Fcntl::Lock.new 
   | 
||
| 
         assert_kind_of Integer, flock.pid 
   | 
||
| 
         flock.type = Fcntl::F_UNLCK 
   | 
||
| 
         assert_equal Fcntl::F_UNLCK, flock.type 
   | 
||
| 
         flock.whence = IO::SEEK_CUR 
   | 
||
| 
         assert_equal IO::SEEK_CUR, flock.whence 
   | 
||
| 
         flock.len = 2 
   | 
||
| 
         assert_equal 2, flock.len 
   | 
||
| 
         flock.start = 3 
   | 
||
| 
         assert_equal 3, flock.start 
   | 
||
| 
       end 
   | 
||
| 
       def test_get_lock 
   | 
||
| 
         lock = Fcntl::Lock.new Fcntl::F_WRLCK 
   | 
||
| 
         pid = nil 
   | 
||
| 
         Tempfile.open(self.class.name) do |tmp| 
   | 
||
| 
           r, w = IO.pipe 
   | 
||
| 
           pid = fork do 
   | 
||
| 
             File.open(tmp.path, "wb") do |fp| 
   | 
||
| 
               fp.fcntl Fcntl::F_SETLKW, lock 
   | 
||
| 
               r.close 
   | 
||
| 
               w.syswrite "." 
   | 
||
| 
               sleep 
   | 
||
| 
             end 
   | 
||
| 
           end 
   | 
||
| 
           w.close 
   | 
||
| 
           assert_equal ".", r.read(1) 
   | 
||
| 
           r.close 
   | 
||
| 
           tmp.fcntl Fcntl::F_GETLK, lock 
   | 
||
| 
         end 
   | 
||
| 
         assert_equal pid, lock.pid 
   | 
||
| 
         Process.kill :TERM, pid 
   | 
||
| 
         Process.waitpid2(pid) 
   | 
||
| 
       end 
   | 
||
| 
       def test_synchronize 
   | 
||
| 
         lock = Fcntl::Lock.new :F_WRLCK, :SEEK_CUR, 5, 6 
   | 
||
| 
         tmp = Tempfile.new(self.class.name) 
   | 
||
| 
         r, w = IO.pipe 
   | 
||
| 
         pid = fork do 
   | 
||
| 
           r.close 
   | 
||
| 
           Fcntl::Lock.synchronize(tmp, :F_WRLCK, :SEEK_CUR, 5, 6) do 
   | 
||
| 
             w.syswrite('.') 
   | 
||
| 
             sleep 
   | 
||
| 
           end 
   | 
||
| 
         end 
   | 
||
| 
         w.close 
   | 
||
| 
         assert_equal ".", r.read(1) 
   | 
||
| 
         tmp.fcntl Fcntl::F_GETLK, lock 
   | 
||
| 
         r.close 
   | 
||
| 
         Process.kill :TERM, pid 
   | 
||
| 
         Process.waitpid2(pid) 
   | 
||
| 
         assert_equal Fcntl::F_WRLCK, lock.type 
   | 
||
| 
         assert_equal IO::SEEK_SET, lock.whence 
   | 
||
| 
         assert_equal 5, lock.start 
   | 
||
| 
         assert_equal 6, lock.len 
   | 
||
| 
         assert_equal pid, lock.pid 
   | 
||
| 
         tmp.close! 
   | 
||
| 
       end 
   | 
||
| 
       def test_get_lock 
   | 
||
| 
         tmp = Tempfile.new(self.class.name) 
   | 
||
| 
         r, w = IO.pipe 
   | 
||
| 
         pid = fork do 
   | 
||
| 
           r.close 
   | 
||
| 
           Fcntl::Lock.synchronize(tmp) do 
   | 
||
| 
             w.syswrite('.') 
   | 
||
| 
             sleep 
   | 
||
| 
           end 
   | 
||
| 
         end 
   | 
||
| 
         w.close 
   | 
||
| 
         assert_equal ".", r.read(1) 
   | 
||
| 
         lock = Fcntl::Lock.get(tmp) 
   | 
||
| 
         r.close 
   | 
||
| 
         Process.kill :TERM, pid 
   | 
||
| 
         Process.waitpid2(pid) 
   | 
||
| 
         assert_equal Fcntl::F_WRLCK, lock.type 
   | 
||
| 
         assert_equal IO::SEEK_SET, lock.whence 
   | 
||
| 
         assert_equal 0, lock.start 
   | 
||
| 
         assert_equal 0, lock.len 
   | 
||
| 
         assert_equal pid, lock.pid 
   | 
||
| 
         tmp.close! 
   | 
||
| 
       end 
   | 
||
| 
     end if defined? Fcntl::Lock 
   | 
||
- « Previous
 - 1
 - 2
 - 3
 - Next »