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 »