tdb 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/GIT-VERSION-GEN +1 -1
- data/README +34 -2
- data/ext/tdb/extconf.rb +1 -0
- data/ext/tdb/lookup3.c +1 -0
- data/ext/tdb/tdb.c +80 -19
- data/lib/tdb.rb +14 -0
- data/lib/tdb/mt.rb +41 -0
- data/tdb.gemspec +1 -1
- data/test/test_tdb.rb +24 -0
- data/test/test_tdb_mt.rb +85 -0
- metadata +8 -4
data/GIT-VERSION-GEN
CHANGED
data/README
CHANGED
@@ -8,17 +8,40 @@ write to the same databases used by Samba!
|
|
8
8
|
== Features
|
9
9
|
|
10
10
|
* Concurrent reader and writer processes may safely operate on the
|
11
|
-
same file.
|
11
|
+
same file. This is great for MRI 1.8 and 1.9 where multi-core
|
12
|
+
performance is easiest to achieve with processes and not threads.
|
12
13
|
|
13
|
-
*
|
14
|
+
* Fork-safe, you may fork and share the same TDB object in your parent
|
15
|
+
and child processes.
|
16
|
+
|
17
|
+
* Releases the GVL for slow disk operations under Ruby 1.9 so
|
18
|
+
other threads can run (but not other TDB operations on the same file)
|
14
19
|
|
15
20
|
* Includes several {hash functions}[link:Hash_Functions.html]
|
16
21
|
not included by upstream TDB.
|
17
22
|
|
23
|
+
== Caveats
|
24
|
+
|
25
|
+
These caveats will be addressed upstream in
|
26
|
+
{TDB2}[http://mid.gmane.org/201008021002.47351.rusty@rustcorp.com.au]
|
27
|
+
|
28
|
+
* NOT native thread-safe by default, you MUST initialize your TDB
|
29
|
+
objects with <code>:threadsafe => true</code> or call
|
30
|
+
TDB#threadsafe! on each TDB object if you run with threads
|
31
|
+
under Ruby 1.9 (but not 1.8).
|
32
|
+
|
33
|
+
* Database size is limited to 4G, even on 64-bit systems.
|
34
|
+
|
35
|
+
* TDB should be created with an appropriate :hash_size for large databases
|
36
|
+
or performance will suffer.
|
37
|
+
|
18
38
|
== Install
|
19
39
|
|
20
40
|
The original tdb library from the {main site}[http://tdb.samba.org/] is
|
21
41
|
required. Debian users can just <code>apt-get install tdb-dev</code>.
|
42
|
+
Non-Debian users: building against upstream tdb 1.2.2 and 1.2.7 are
|
43
|
+
known to be broken, so installing tdb from the latest git is
|
44
|
+
recommended.
|
22
45
|
|
23
46
|
The library consists of a C extension so you'll need a C compiler
|
24
47
|
and Ruby development libraries/headers.
|
@@ -33,6 +56,15 @@ You may also install it via RubyGems on RubyGems.org:
|
|
33
56
|
|
34
57
|
gem install tdb
|
35
58
|
|
59
|
+
If you have a tdb installation in a non-standard prefix, you
|
60
|
+
will have to use:
|
61
|
+
|
62
|
+
gem install tdb -- --with-tdb-dir=$PFX
|
63
|
+
|
64
|
+
Or if you have a non-standard prefix that linkers normally do not search:
|
65
|
+
|
66
|
+
gem install tdb -- --with-tdb-dir=$PFX --with-dldflags=-Wl,-rpath=$PFX/lib
|
67
|
+
|
36
68
|
You can get the latest source via git from the following locations
|
37
69
|
(these versions may not be stable):
|
38
70
|
|
data/ext/tdb/extconf.rb
CHANGED
data/ext/tdb/lookup3.c
CHANGED
data/ext/tdb/tdb.c
CHANGED
@@ -158,7 +158,7 @@ static VALUE nogvl_open(void *ptr)
|
|
158
158
|
return (VALUE)tdb;
|
159
159
|
}
|
160
160
|
|
161
|
-
static void set_args(struct open_args *o, VALUE opts)
|
161
|
+
static void set_args(VALUE self, struct open_args *o, VALUE opts)
|
162
162
|
{
|
163
163
|
VALUE tmp;
|
164
164
|
|
@@ -203,8 +203,40 @@ static void set_args(struct open_args *o, VALUE opts)
|
|
203
203
|
|
204
204
|
o->hash_fn = (tdb_hash_func)NUM2ULONG(num);
|
205
205
|
}
|
206
|
+
|
207
|
+
tmp = rb_hash_aref(opts, ID2SYM(rb_intern("threadsafe")));
|
208
|
+
if (RTEST(tmp))
|
209
|
+
rb_funcall(self, rb_intern("threadsafe!"), 0);
|
206
210
|
}
|
207
211
|
|
212
|
+
/*
|
213
|
+
* :call-seq:
|
214
|
+
*
|
215
|
+
* TDB.new("/path/to/file") -> TDB
|
216
|
+
* TDB.new("/path/to/file", :hash_size => 666) -> TDB
|
217
|
+
* TDB.new("/path/to/file", :hash => :murmur2) -> TDB
|
218
|
+
* TDB.new("/path/to/file", :open_flags => IO::RDONLY) -> TDB
|
219
|
+
* TDB.new("/path/to/file", :tdb_flags => TDB::NOSYNC) -> TDB
|
220
|
+
*
|
221
|
+
* Initializes a TDB context. It takes several options.
|
222
|
+
*
|
223
|
+
* :hash_size - the number of buckets, this is the most important tuning
|
224
|
+
* parameter when creating large databases. This parameter only affects
|
225
|
+
* the creation of new databases.
|
226
|
+
*
|
227
|
+
* :open_flags - a bit mask of IO flags passed directly to open(2),
|
228
|
+
* File.open-compatible flags are accepted.
|
229
|
+
*
|
230
|
+
* :hash - any of the hashes described in Hash_Functions.
|
231
|
+
* This must remain the same for all clients.
|
232
|
+
*
|
233
|
+
* :tdb_flags - a bitmask of any combination of TDB::CLEAR_IF_FIRST,
|
234
|
+
* TDB::INTERNAL, TDB::NOLOCK, TDB::NOMMAP, TDB::CONVERT,
|
235
|
+
* TDB::BIGENDIAN, TDB::NOSYNC, TDB::SEQNUM, TDB::VOLATILE,
|
236
|
+
* TDB::ALLOW_NESTING, TDB::DISALLOW_NESTING, TDB::INCOMPATIBLE_HASH
|
237
|
+
*
|
238
|
+
* :mode - octal mode mask passed to open(2)
|
239
|
+
*/
|
208
240
|
static VALUE init(int argc, VALUE *argv, VALUE self)
|
209
241
|
{
|
210
242
|
struct tdb_context *tdb = db(self, 0);
|
@@ -214,7 +246,7 @@ static VALUE init(int argc, VALUE *argv, VALUE self)
|
|
214
246
|
if (tdb)
|
215
247
|
rb_raise(rb_eRuntimeError, "TDB already initialized");
|
216
248
|
rb_scan_args(argc, argv, "11", &path, &opts);
|
217
|
-
set_args(&o, opts);
|
249
|
+
set_args(self, &o, opts);
|
218
250
|
|
219
251
|
if (NIL_P(path))
|
220
252
|
o.tdb_flags |= TDB_INTERNAL;
|
@@ -587,12 +619,39 @@ static VALUE lockall_unmark(VALUE self)
|
|
587
619
|
return Qtrue;
|
588
620
|
}
|
589
621
|
|
622
|
+
/*
|
623
|
+
* clears out the database
|
624
|
+
*/
|
625
|
+
static VALUE clear(VALUE self)
|
626
|
+
{
|
627
|
+
struct tdb_context *tdb = db(self, 1);
|
628
|
+
if ((int)my_tbr((rb_blocking_function_t *)tdb_wipe_all, tdb))
|
629
|
+
my_raise(tdb);
|
630
|
+
return self;
|
631
|
+
}
|
632
|
+
|
633
|
+
#ifdef HAVE_TDB_REPACK
|
634
|
+
/* repacks a database to reduce fragmentation, available with tdb 1.2.x+ */
|
635
|
+
static VALUE repack(VALUE self)
|
636
|
+
{
|
637
|
+
struct tdb_context *tdb = db(self, 1);
|
638
|
+
if ((int)my_tbr((rb_blocking_function_t *)tdb_repack, tdb))
|
639
|
+
my_raise(tdb);
|
640
|
+
return self;
|
641
|
+
}
|
642
|
+
#endif /* HAVE_TDB_REPACK */
|
643
|
+
|
590
644
|
void Init_tdb_ext(void)
|
591
645
|
{
|
592
646
|
cTDB = rb_define_class("TDB", rb_cObject);
|
593
647
|
|
594
648
|
hashes = rb_hash_new();
|
595
|
-
|
649
|
+
|
650
|
+
/*
|
651
|
+
* Available hash functions, the key is the name of the hash
|
652
|
+
* and the value is a pointer for internal for usage.
|
653
|
+
*/
|
654
|
+
rb_define_const(cTDB, "HASHES", hashes);
|
596
655
|
|
597
656
|
rb_define_alloc_func(cTDB, alloc);
|
598
657
|
rb_include_module(cTDB, rb_mEnumerable);
|
@@ -626,55 +685,57 @@ void Init_tdb_ext(void)
|
|
626
685
|
rb_define_method(cTDB, "unlockall_read", unlockall_read, 0);
|
627
686
|
rb_define_method(cTDB, "lockall_mark", lockall_mark, 0);
|
628
687
|
rb_define_method(cTDB, "lockall_unmark", lockall_unmark, 0);
|
688
|
+
rb_define_method(cTDB, "clear", clear, 0);
|
689
|
+
#ifdef HAVE_TDB_REPACK
|
690
|
+
rb_define_method(cTDB, "repack", repack, 0);
|
691
|
+
#endif /* HAVE_TDB_REPACK */
|
629
692
|
|
630
693
|
init_errors();
|
631
694
|
init_hashes();
|
632
695
|
|
633
|
-
#define tdb_CONST(x) rb_define_const(cTDB, #x, UINT2NUM(TDB_##x))
|
634
|
-
|
635
696
|
/* just a readability place holder */
|
636
|
-
|
697
|
+
rb_define_const(cTDB, "DEFAULT", UINT2NUM(TDB_DEFAULT));
|
637
698
|
|
638
699
|
/* clear database if we are the only one with it open */
|
639
|
-
|
700
|
+
rb_define_const(cTDB, "CLEAR_IF_FIRST", UINT2NUM(TDB_CLEAR_IF_FIRST));
|
640
701
|
|
641
702
|
/* don't store on disk, use in-memory database */
|
642
|
-
|
703
|
+
rb_define_const(cTDB, "INTERNAL", UINT2NUM(TDB_INTERNAL));
|
643
704
|
|
644
705
|
/* don't do any locking */
|
645
|
-
|
706
|
+
rb_define_const(cTDB, "NOLOCK", UINT2NUM(TDB_NOLOCK));
|
646
707
|
|
647
708
|
/* don't use mmap */
|
648
|
-
|
709
|
+
rb_define_const(cTDB, "NOMMAP", UINT2NUM(TDB_NOMMAP));
|
649
710
|
|
650
711
|
/* convert endian (internal use) */
|
651
|
-
|
712
|
+
rb_define_const(cTDB, "CONVERT", UINT2NUM(TDB_CONVERT));
|
652
713
|
|
653
714
|
/* header is big-endian (internal use) */
|
654
|
-
|
715
|
+
rb_define_const(cTDB, "BIGENDIAN", UINT2NUM(TDB_BIGENDIAN));
|
655
716
|
|
656
717
|
/* don't use synchronous transactions */
|
657
|
-
|
718
|
+
rb_define_const(cTDB, "NOSYNC", UINT2NUM(TDB_NOSYNC));
|
658
719
|
|
659
720
|
/* maintain a sequence number */
|
660
|
-
|
721
|
+
rb_define_const(cTDB, "SEQNUM", UINT2NUM(TDB_SEQNUM));
|
661
722
|
|
662
723
|
/* Activate the per-hashchain freelist, default 5 */
|
663
|
-
|
724
|
+
rb_define_const(cTDB, "VOLATILE", UINT2NUM(TDB_VOLATILE));
|
664
725
|
|
665
726
|
#ifdef TDB_ALLOW_NESTING
|
666
727
|
/* Allow transactions to nest */
|
667
|
-
|
728
|
+
rb_define_const(cTDB, "ALLOW_NESTING", UINT2NUM(TDB_ALLOW_NESTING));
|
668
729
|
#endif
|
669
730
|
|
670
731
|
#ifdef TDB_DISALLOW_NESTING
|
671
732
|
/* Disallow transactions to nest */
|
672
|
-
|
733
|
+
rb_define_const(cTDB, "DISALLOW_NESTING", UINT2NUM(TDB_DISALLOW_NESTING));
|
673
734
|
#endif
|
674
735
|
|
675
736
|
#ifdef TDB_INCOMPATIBLE_HASH
|
676
|
-
/* Better hashing
|
677
|
-
|
737
|
+
/* Better hashing, but can't be opened by tdb < 1.2.6. */
|
738
|
+
rb_define_const(cTDB, "INCOMPATIBLE_HASH", UINT2NUM(TDB_INCOMPATIBLE_HASH));
|
678
739
|
#endif
|
679
740
|
}
|
680
741
|
|
data/lib/tdb.rb
CHANGED
@@ -1,2 +1,16 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
2
|
require 'tdb_ext'
|
3
|
+
class TDB
|
4
|
+
autoload :MT, 'tdb/mt'
|
5
|
+
|
6
|
+
# makes the current TDB object thread-safe
|
7
|
+
def threadsafe!
|
8
|
+
extend MT
|
9
|
+
end
|
10
|
+
|
11
|
+
# will return true when TDB::MT is included in TDB or the TDB
|
12
|
+
# object is extended by TDB
|
13
|
+
def threadsafe?
|
14
|
+
false
|
15
|
+
end
|
16
|
+
end
|
data/lib/tdb/mt.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
module TDB::MT
|
3
|
+
def initialize
|
4
|
+
super
|
5
|
+
@lock = Mutex.new
|
6
|
+
end
|
7
|
+
|
8
|
+
wrap_methods = %w(
|
9
|
+
close closed? fetch [] store []= insert! modify! insert modify
|
10
|
+
key? has_key? include? member?
|
11
|
+
nuke! delete
|
12
|
+
lockall trylockall unlockall
|
13
|
+
lockall_read trylockall_read unlockall_read
|
14
|
+
lockall_mark lockall_unmark
|
15
|
+
clear
|
16
|
+
)
|
17
|
+
wrap_methods << :repack if TDB.method_defined?(:repack)
|
18
|
+
wrap_methods.each do |meth|
|
19
|
+
eval "def #{meth}(*args); @lock.synchronize { super }; end"
|
20
|
+
end
|
21
|
+
|
22
|
+
def each(&block)
|
23
|
+
@lock.synchronize do
|
24
|
+
super { |k,v| @lock.exclusive_unlock { yield(k,v) } }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def threadsafe?
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.extended(obj)
|
33
|
+
obj.instance_eval { @lock = Mutex.new unless defined?(@lock) }
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.included(klass)
|
37
|
+
ObjectSpace.each_object(klass) { |obj|
|
38
|
+
obj.instance_eval { @lock = Mutex.new unless defined?(@lock) }
|
39
|
+
}
|
40
|
+
end
|
41
|
+
end
|
data/tdb.gemspec
CHANGED
data/test/test_tdb.rb
CHANGED
@@ -257,4 +257,28 @@ class TestTdb < Test::Unit::TestCase
|
|
257
257
|
assert Process.waitpid2(pid)[1].success?
|
258
258
|
assert_equal true, @tdb.trylockall
|
259
259
|
end
|
260
|
+
|
261
|
+
def test_check_constant_typos
|
262
|
+
names = {}
|
263
|
+
TDB.constants.each { |const| names[const] = TDB.const_get(const) }
|
264
|
+
assert_equal TDB.constants.size, names.size
|
265
|
+
|
266
|
+
values = {}
|
267
|
+
TDB.constants.each { |const| values[TDB.const_get(const)] = const }
|
268
|
+
assert_equal TDB.constants.size, values.size
|
269
|
+
end
|
270
|
+
|
271
|
+
def test_clear
|
272
|
+
@tdb = TDB.new(nil)
|
273
|
+
@tdb["hello"] = "world"
|
274
|
+
assert_equal @tdb, @tdb.clear
|
275
|
+
assert ! @tdb.include?("hello")
|
276
|
+
end
|
277
|
+
|
278
|
+
def test_repack
|
279
|
+
@tdb = TDB.new(nil)
|
280
|
+
@tdb["hello"] = "world"
|
281
|
+
assert_equal @tdb, @tdb.repack
|
282
|
+
assert_equal "world", @tdb["hello"]
|
283
|
+
end if TDB.method_defined?(:repack)
|
260
284
|
end
|
data/test/test_tdb_mt.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
$stdout.sync = $stderr.sync = true
|
3
|
+
require 'test/unit'
|
4
|
+
require 'tempfile'
|
5
|
+
$-w = true
|
6
|
+
require 'tdb'
|
7
|
+
|
8
|
+
class Test_TDB_MT < Test::Unit::TestCase
|
9
|
+
def setup
|
10
|
+
@tdb = @tmp = nil
|
11
|
+
@start_pid = $$
|
12
|
+
end
|
13
|
+
|
14
|
+
def teardown
|
15
|
+
return if @start_pid != $$
|
16
|
+
@tmp.close! if @tmp.respond_to?(:close!)
|
17
|
+
@tdb.close if @tdb && ! @tdb.closed?
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_make_threadsafe
|
21
|
+
@tdb = TDB.new(nil)
|
22
|
+
assert_kind_of TDB, @tdb
|
23
|
+
assert ! @tdb.threadsafe?
|
24
|
+
assert_nothing_raised { @tdb.threadsafe! }
|
25
|
+
assert @tdb.threadsafe?
|
26
|
+
@tdb.each { |k,v| assert_equal v, @tdb[k] }
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_init_threadsafe
|
30
|
+
@tdb = TDB.new(nil, :threadsafe => true)
|
31
|
+
assert @tdb.threadsafe?
|
32
|
+
@tdb.close
|
33
|
+
@tdb = TDB.new(nil, :threadsafe => false)
|
34
|
+
assert ! @tdb.threadsafe?
|
35
|
+
@tdb.close
|
36
|
+
@tdb = TDB.new(nil)
|
37
|
+
assert ! @tdb.threadsafe?
|
38
|
+
@tdb.close
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_thread_safe_torture_test
|
42
|
+
@tdb = TDB.new(nil)
|
43
|
+
assert_nothing_raised { @tdb.threadsafe! }
|
44
|
+
pid = fork do
|
45
|
+
Thread.abort_on_exception = true
|
46
|
+
threads = []
|
47
|
+
blob = 'foo' * 1000
|
48
|
+
nr = 10000
|
49
|
+
t = Thread.new do
|
50
|
+
while true
|
51
|
+
Thread.pass
|
52
|
+
@tdb.to_a
|
53
|
+
end
|
54
|
+
end
|
55
|
+
threads << Thread.new { nr.times { |i| @tdb[i.to_s] = blob } }
|
56
|
+
threads << Thread.new { nr.times { |i| @tdb[i.to_s] = blob } }
|
57
|
+
threads << Thread.new { nr.times { |i| @tdb[i.to_s] = blob } }
|
58
|
+
threads << Thread.new { nr.times { |i| @tdb[i.to_s] = blob } }
|
59
|
+
threads << t
|
60
|
+
|
61
|
+
t.kill
|
62
|
+
threads.each { |t| t.join }
|
63
|
+
end
|
64
|
+
_, status = Process.waitpid2(pid)
|
65
|
+
assert status.success?, status.inspect
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_check_methods
|
69
|
+
m = TDB.instance_methods.sort
|
70
|
+
m -= Object.instance_methods
|
71
|
+
m -= Enumerable.instance_methods
|
72
|
+
m.map! { |x| x.to_sym }
|
73
|
+
mt = TDB::MT.instance_methods.sort
|
74
|
+
m -= [ :threadsafe! ]
|
75
|
+
m += [ :include?, :member? ]
|
76
|
+
m.sort!
|
77
|
+
unwrapped = ( (m - mt) | (mt - m)).uniq
|
78
|
+
assert unwrapped.empty?, "unwrapped methods: #{unwrapped.inspect}"
|
79
|
+
@tdb = TDB.new(nil)
|
80
|
+
respond_to?(:refute_match) and
|
81
|
+
m.each { |meth| refute_match(/\bTDB::MT\b/, @tdb.method(meth).to_s) }
|
82
|
+
@tdb.threadsafe!
|
83
|
+
m.each { |meth| assert_match(/\bTDB::MT\b/, @tdb.method(meth).to_s) }
|
84
|
+
end
|
85
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tdb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 2
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.2.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Ruby tdb hackers
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-12-
|
18
|
+
date: 2010-12-08 00:00:00 +00:00
|
19
19
|
default_executable:
|
20
20
|
dependencies: []
|
21
21
|
|
@@ -37,6 +37,7 @@ extra_rdoc_files:
|
|
37
37
|
- NEWS
|
38
38
|
- ChangeLog
|
39
39
|
- lib/tdb.rb
|
40
|
+
- lib/tdb/mt.rb
|
40
41
|
- ext/tdb/tdb.c
|
41
42
|
files:
|
42
43
|
- .document
|
@@ -62,9 +63,11 @@ files:
|
|
62
63
|
- ext/tdb/rbtdb.h
|
63
64
|
- ext/tdb/tdb.c
|
64
65
|
- lib/tdb.rb
|
66
|
+
- lib/tdb/mt.rb
|
65
67
|
- setup.rb
|
66
68
|
- tdb.gemspec
|
67
69
|
- test/test_tdb.rb
|
70
|
+
- test/test_tdb_mt.rb
|
68
71
|
has_rdoc: true
|
69
72
|
homepage: http://bogomips.org/ruby-tdb/
|
70
73
|
licenses: []
|
@@ -102,4 +105,5 @@ signing_key:
|
|
102
105
|
specification_version: 3
|
103
106
|
summary: Trivial Database bindings for Ruby
|
104
107
|
test_files:
|
108
|
+
- test/test_tdb_mt.rb
|
105
109
|
- test/test_tdb.rb
|