Project

General

Profile

Feature #4464 ยป 0001-add-Fcntl-Lock-object-for-easier-use-of-POSIX-file-l.patch

v3 of the patch - normalperson (Eric Wong), 03/15/2011 04:53 AM

View differences:

ext/.document
7 7
date/lib
8 8
digest/digest.c
9 9
etc/etc.c
10
fcntl/fcntl.c
10
fcntl
11 11
gdbm/gdbm.c
12 12
iconv/iconv.c
13 13
io/wait/wait.c
ext/fcntl/.document
1
fcntl.c
2
lib/fcntl/lock.rb
ext/fcntl/extconf.rb
1 1
require 'mkmf'
2
have_type('struct flock', %w(unistd.h fcntl.h))
2 3
create_makefile('fcntl')
ext/fcntl/fcntl.c
33 33

  
34 34
#include "ruby.h"
35 35
#include <fcntl.h>
36
static VALUE mFcntl;
37
static void Init_FcntlLock(void);
36 38

  
37 39
/* Fcntl loads the constants defined in the system's <fcntl.h> C header
38 40
 * file, and used with both the fcntl(2) and open(2) POSIX system calls.
......
112 114
void
113 115
Init_fcntl()
114 116
{
115
    VALUE mFcntl = rb_define_module("Fcntl");
117
    mFcntl = rb_define_module("Fcntl");
118
    Init_FcntlLock();
119

  
116 120
#ifdef F_DUPFD
117 121
    rb_define_const(mFcntl, "F_DUPFD", INT2NUM(F_DUPFD));
118 122
#endif
......
185 189
    rb_define_const(mFcntl, "O_ACCMODE", INT2FIX(O_RDONLY | O_WRONLY | O_RDWR));
186 190
#endif
187 191
}
192

  
193
#ifdef HAVE_TYPE_STRUCT_FLOCK
194
static short
195
fix2short(VALUE val)
196
{
197
    int i = FIX2INT(val);
198

  
199
    if (i != (short)i) {
200
	const char *s = i > 0 ? "big" : "small";
201
	rb_raise(rb_eRangeError, "Integer %d too %s to for `short'", i, s);
202
    }
203

  
204
    return (short)i;
205
}
206

  
207
static short
208
val2short(short fallback, VALUE klass, VALUE val)
209
{
210
    switch (TYPE(val)) {
211
    case T_NIL: return fallback;
212
    case T_SYMBOL:
213
	val = rb_const_get(klass, SYM2ID(val));
214
	/* fall through */
215
    case T_FIXNUM:
216
	return fix2short(val);
217
    }
218
    rb_raise(rb_eTypeError, "must be a Symbol or Integer");
219
}
220

  
221
static struct flock *
222
flock_ptr(VALUE self)
223
{
224
    if (RSTRING_LEN(self) < sizeof(struct flock))
225
	rb_raise(rb_eRuntimeError, "flock buffer shortened");
226
    rb_str_modify(self);
227

  
228
    return (struct flock *)RSTRING_PTR(self);
229
}
230

  
231
static VALUE
232
lock_init(int argc, VALUE *argv, VALUE self)
233
{
234
    struct flock *flock;
235
    VALUE type, whence, start, len;
236

  
237
    rb_str_resize(self, sizeof(struct flock));
238
    flock = flock_ptr(self);
239
    rb_scan_args(argc, argv, "04", &type, &whence, &start, &len);
240
    flock->l_type = val2short(F_RDLCK, mFcntl, type);
241
    flock->l_whence = val2short(SEEK_SET, rb_cIO, whence);
242
    flock->l_start = NIL_P(start) ? 0 : NUM2OFFT(start);
243
    flock->l_len = NIL_P(len) ? 0 : NUM2OFFT(len);
244
    flock->l_pid = 0;
245

  
246
    return self;
247
}
248

  
249
static VALUE
250
l_type(VALUE self)
251
{
252
    return INT2NUM(flock_ptr(self)->l_type);
253
}
254

  
255
static VALUE
256
set_l_type(VALUE self, VALUE val)
257
{
258
    flock_ptr(self)->l_type = val2short(F_RDLCK, mFcntl, val);
259

  
260
    return val;
261
}
262

  
263
static VALUE
264
l_whence(VALUE self)
265
{
266
    return INT2NUM(flock_ptr(self)->l_whence);
267
}
268

  
269
static VALUE
270
set_l_whence(VALUE self, VALUE val)
271
{
272
    flock_ptr(self)->l_whence = val2short(SEEK_SET, rb_cIO, val);
273

  
274
    return val;
275
}
276

  
277
static VALUE
278
l_start(VALUE self)
279
{
280
    return OFFT2NUM(flock_ptr(self)->l_start);
281
}
282

  
283
static VALUE
284
set_l_start(VALUE self, VALUE val)
285
{
286
    flock_ptr(self)->l_start = NUM2OFFT(val);
287

  
288
    return val;
289
}
290

  
291
static VALUE
292
l_len(VALUE self)
293
{
294
    return OFFT2NUM(flock_ptr(self)->l_len);
295
}
296

  
297
static VALUE
298
set_l_len(VALUE self, VALUE val)
299
{
300
    flock_ptr(self)->l_len = NUM2OFFT(val);
301

  
302
    return val;
303
}
304

  
305
static VALUE
306
l_pid(VALUE self)
307
{
308
    return PIDT2NUM(flock_ptr(self)->l_pid);
309
}
310

  
311
static VALUE
312
lock_inspect(VALUE self)
313
{
314
    struct flock *flock = flock_ptr(self);
315
    const char fmt[] = "#<%s: type=%s, whence=%s, start=%lld, len=%lld, pid=%ld>";
316
    const char *name = rb_obj_classname(self);
317
    const char *type, *whence;
318

  
319
    switch (flock->l_type) {
320
    case F_RDLCK: type = "F_RDLCK"; break;
321
    case F_WRLCK: type = "F_WRLCK"; break;
322
    case F_UNLCK: type = "F_UNLCK"; break;
323
    default: type = "(unknown)";
324
    }
325

  
326
    switch (flock->l_whence) {
327
    case SEEK_SET: whence = "SEEK_SET"; break;
328
    case SEEK_CUR: whence = "SEEK_CUR"; break;
329
    case SEEK_END: whence = "SEEK_END"; break;
330
    default: whence = "(unknown)";
331
    }
332

  
333
    return rb_sprintf(fmt, name, type, whence,
334
		      (long long)flock->l_start,
335
		      (long long)flock->l_len,
336
		      (long)flock->l_pid);
337
}
338

  
339
static void
340
Init_FcntlLock(void)
341
{
342
    VALUE cFcntlLock = rb_define_class_under(mFcntl, "Lock", rb_cString);
343

  
344
    rb_define_private_method(cFcntlLock, "initialize", lock_init, -1);
345

  
346
    rb_define_method(cFcntlLock, "type", l_type, 0);
347
    rb_define_method(cFcntlLock, "type=", set_l_type, 1);
348

  
349
    rb_define_method(cFcntlLock, "whence", l_whence, 0);
350
    rb_define_method(cFcntlLock, "whence=", set_l_whence, 1);
351

  
352
    rb_define_method(cFcntlLock, "start", l_start, 0);
353
    rb_define_method(cFcntlLock, "start=", set_l_start, 1);
354

  
355
    rb_define_method(cFcntlLock, "len", l_len, 0);
356
    rb_define_method(cFcntlLock, "len=", set_l_len, 1);
357

  
358
    rb_define_method(cFcntlLock, "pid", l_pid, 0);
359

  
360
    rb_define_method(cFcntlLock, "inspect", lock_inspect, 0);
361

  
362
    rb_require("fcntl/lock");
363
}
364
#else /* ! HAVE_TYPE_STRUCT_FLOCK */
365
static void Init_FcntlLock(void) { }
366
#endif /* HAVE_TYPE_STRUCT_FLOCK */
ext/fcntl/lib/fcntl/lock.rb
1
# Fcntl::Lock is used for POSIX advisory record locks.  Unlike File#flock,
2
# POSIX locks provide byte-range granularity and work with some
3
# implementations of NFS.
4
#
5
# Fcntl::Lock objects may be passed as the second argument to IO#fcntl,
6
# but there is also a high-level interface documented in examples
7
# below.
8
#
9
# Fcntl::Lock objects consists of the following readable and writable
10
# attributes:
11
#
12
# - type - the type of lock: :F_RDLCK, :F_WRLCK, :F_UNLCK
13
# - whence - interpretation of +start+: :SEEK_SET, :SEEK_CUR, :SEEK_END
14
# - start - starting offset (in bytes)
15
# - len - number of bytes to lock, zero for the entire file
16
#
17
# The +pid+ attribute is read-only and used for Fcntl::Lock.get operations,
18
# it is the process identifier holding the lock.
19
#
20
# Examples:
21
#
22
#   file = File.open("/path/to/file", "rb+")
23
#   Fcntl::Lock.synchronize(file) do
24
#     # other processes may not lock any part of the file for writing
25
#   end
26
#
27
#   Fcntl::Lock.synchronize(file, :F_RDLCK, :SEEK_SET, 90, 10) do
28
#     # places a shared read lock on the 10 bytes after the first 90 bytes
29
#   end
30
#
31
#   Fcntl::Lock.get(file)
32
#   #=> #<Fcntl::Flock: type=F_RDLCK, whence=SEEK_SET, start=90, len=10, pid=4>
33
#
34
# Example (low level):
35
#
36
#   lock = Fcntl::Lock.new(:F_WRLCK, :SEEK_SET, 0, 100)
37
#   file.fcntl(Fcntl::F_SETLKW, lock)
38
#   # first 100 bytes are now locked for reading or writing
39
#
40
#   lock.type = :F_UNLCK
41
#   file.fcntl(Fcntl::F_SETLKW, lock)
42
#   # first 100 bytes now may be locked by other processes
43
class Fcntl::Lock < String
44
  include Fcntl
45

  
46
  def self.__fcntl_retry(file, cmd, lock) # :nodoc:
47
    file.fcntl cmd, lock
48
    rescue Errno::EINTR
49
      retry
50
  end
51

  
52
  # Obtains an advisory lock, runs the given block and releases the lock
53
  # when the block completes.  Returns the result of the block.
54
  #
55
  # +type+ is optional and may be +nil+, in which case the lock +type+ is
56
  # determined based on the file access flags.
57
  #
58
  # +whence+, +start+, and +len+ are all optional and documented under
59
  # Fcntl::Lock.
60
  def self.synchronize(file, type=nil, whence=IO::SEEK_SET, start=0, len=0)
61
    type ||= (file.fcntl(F_GETFL) & O_ACCMODE) == O_RDONLY ? F_RDLCK : F_WRLCK
62
    lock = new(type, whence, start, len)
63
    __fcntl_retry(file, F_SETLKW, lock)
64
    begin
65
      yield file
66
    ensure
67
      lock.type = F_UNLCK
68
      __fcntl_retry(file, F_SETLKW, lock)
69
    end
70
  end
71

  
72
  # Describes a type of lock we would like to place on the given +file+
73
  # but does not place it.
74
  #
75
  # Returns a Fcntl::Lock object that either describes a conflicting lock
76
  # that prevents a lock from being placed or one with its type set
77
  # to +Fcntl::F_UNLCK+ with other fields (if specified) unchanged.
78
  #
79
  # +type+ is optional and may be +nil+, in which case the lock +type+ is
80
  # determined based on the file access flags.
81
  #
82
  # +whence+, +start+, and +len+ are all optional and documented under
83
  # Fcntl::Lock.
84
  def self.get(file, type=nil, whence=IO::SEEK_SET, start=0, len=0)
85
    type ||= (file.fcntl(F_GETFL) & O_ACCMODE) == O_RDONLY ? F_RDLCK : F_WRLCK
86
    lock = new(type, whence, start, len)
87
    __fcntl_retry(file, F_GETLK, lock)
88
    lock
89
  end
90
end
test/fcntl/test_fcntl_lock.rb
1
require "test/unit"
2
begin
3
  require "fcntl"
4
rescue LoadError
5
end
6
require "tempfile"
7

  
8
class TestFcntlLock < Test::Unit::TestCase
9

  
10
  def test_initialize_noargs
11
    flock = Fcntl::Lock.new
12
    assert_equal Fcntl::F_RDLCK, flock.type
13
    assert_equal IO::SEEK_SET, flock.whence
14
    assert_equal 0, flock.start
15
    assert_equal 0, flock.len
16
  end
17

  
18
  def test_inspect
19
    expect = "#<Fcntl::Lock: type=F_RDLCK, whence=SEEK_SET, " \
20
             "start=0, len=0, pid=0>"
21
    assert_equal expect, Fcntl::Lock.new.inspect
22
  end
23

  
24
  def test_initialize_write_lock
25
    flock = Fcntl::Lock.new Fcntl::F_WRLCK
26
    assert_equal Fcntl::F_WRLCK, flock.type
27
    assert_equal IO::SEEK_SET, flock.whence
28
    assert_equal 0, flock.start
29
    assert_equal 0, flock.len
30
  end
31

  
32
  def test_accessors
33
    flock = Fcntl::Lock.new
34
    assert_kind_of Integer, flock.pid
35

  
36
    flock.type = Fcntl::F_UNLCK
37
    assert_equal Fcntl::F_UNLCK, flock.type
38

  
39
    flock.whence = IO::SEEK_CUR
40
    assert_equal IO::SEEK_CUR, flock.whence
41

  
42
    flock.len = 2
43
    assert_equal 2, flock.len
44

  
45
    flock.start = 3
46
    assert_equal 3, flock.start
47
  end
48

  
49
  def test_get_lock
50
    lock = Fcntl::Lock.new Fcntl::F_WRLCK
51
    pid = nil
52
    Tempfile.open(self.class.name) do |tmp|
53
      r, w = IO.pipe
54
      pid = fork do
55
        File.open(tmp.path, "wb") do |fp|
56
          fp.fcntl Fcntl::F_SETLKW, lock
57
          r.close
58
          w.syswrite "."
59
          sleep
60
        end
61
      end
62
      w.close
63
      assert_equal ".", r.read(1)
64
      r.close
65
      tmp.fcntl Fcntl::F_GETLK, lock
66
    end
67
    assert_equal pid, lock.pid
68
    Process.kill :TERM, pid
69
    Process.waitpid2(pid)
70
  end
71

  
72
  def test_synchronize
73
    lock = Fcntl::Lock.new :F_WRLCK, :SEEK_CUR, 5, 6
74
    tmp = Tempfile.new(self.class.name)
75
    r, w = IO.pipe
76
    pid = fork do
77
      r.close
78
      Fcntl::Lock.synchronize(tmp, :F_WRLCK, :SEEK_CUR, 5, 6) do
79
        w.syswrite('.')
80
        sleep
81
      end
82
    end
83
    w.close
84
    assert_equal ".", r.read(1)
85
    tmp.fcntl Fcntl::F_GETLK, lock
86
    r.close
87
    Process.kill :TERM, pid
88
    Process.waitpid2(pid)
89
    assert_equal Fcntl::F_WRLCK, lock.type
90
    assert_equal IO::SEEK_SET, lock.whence
91
    assert_equal 5, lock.start
92
    assert_equal 6, lock.len
93
    assert_equal pid, lock.pid
94
    tmp.close!
95
  end
96

  
97
  def test_get_lock
98
    tmp = Tempfile.new(self.class.name)
99
    r, w = IO.pipe
100
    pid = fork do
101
      r.close
102
      Fcntl::Lock.synchronize(tmp) do
103
        w.syswrite('.')
104
        sleep
105
      end
106
    end
107
    w.close
108
    assert_equal ".", r.read(1)
109
    lock = Fcntl::Lock.get(tmp)
110
    r.close
111
    Process.kill :TERM, pid
112
    Process.waitpid2(pid)
113
    assert_equal Fcntl::F_WRLCK, lock.type
114
    assert_equal IO::SEEK_SET, lock.whence
115
    assert_equal 0, lock.start
116
    assert_equal 0, lock.len
117
    assert_equal pid, lock.pid
118
    tmp.close!
119
  end
120
end if defined? Fcntl::Lock
0
-