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 CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/bin/sh
2
2
 
3
3
  GVF=GIT-VERSION-FILE
4
- DEF_VER=v0.0.0.GIT
4
+ DEF_VER=v0.2.0.GIT
5
5
 
6
6
  LF='
7
7
  '
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
- * Releases the GVL for slow disk operations under Ruby 1.9
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
@@ -7,6 +7,7 @@ dir_config('tdb')
7
7
  have_header('tdb.h') or abort 'tdb.h missing'
8
8
  have_library('tdb') or abort 'libtdb missing'
9
9
  have_func('tdb_jenkins_hash')
10
+ have_func('tdb_repack')
10
11
  have_const('TDB_ERR_NESTING', 'tdb.h')
11
12
 
12
13
  create_makefile('tdb_ext')
data/ext/tdb/lookup3.c CHANGED
@@ -1,4 +1,5 @@
1
1
  #include "rbtdb.h"
2
+ #include <stdint.h>
2
3
 
3
4
  /*
4
5
  * lookup3 implementation copied from tdb.git
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
- rb_define_const(cTDB, "HASHES", hashes);
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
- tdb_CONST(DEFAULT);
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
- tdb_CONST(CLEAR_IF_FIRST);
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
- tdb_CONST(INTERNAL);
703
+ rb_define_const(cTDB, "INTERNAL", UINT2NUM(TDB_INTERNAL));
643
704
 
644
705
  /* don't do any locking */
645
- tdb_CONST(NOLOCK);
706
+ rb_define_const(cTDB, "NOLOCK", UINT2NUM(TDB_NOLOCK));
646
707
 
647
708
  /* don't use mmap */
648
- tdb_CONST(NOMMAP);
709
+ rb_define_const(cTDB, "NOMMAP", UINT2NUM(TDB_NOMMAP));
649
710
 
650
711
  /* convert endian (internal use) */
651
- tdb_CONST(CONVERT);
712
+ rb_define_const(cTDB, "CONVERT", UINT2NUM(TDB_CONVERT));
652
713
 
653
714
  /* header is big-endian (internal use) */
654
- tdb_CONST(BIGENDIAN);
715
+ rb_define_const(cTDB, "BIGENDIAN", UINT2NUM(TDB_BIGENDIAN));
655
716
 
656
717
  /* don't use synchronous transactions */
657
- tdb_CONST(NOSYNC);
718
+ rb_define_const(cTDB, "NOSYNC", UINT2NUM(TDB_NOSYNC));
658
719
 
659
720
  /* maintain a sequence number */
660
- tdb_CONST(SEQNUM);
721
+ rb_define_const(cTDB, "SEQNUM", UINT2NUM(TDB_SEQNUM));
661
722
 
662
723
  /* Activate the per-hashchain freelist, default 5 */
663
- tdb_CONST(VOLATILE);
724
+ rb_define_const(cTDB, "VOLATILE", UINT2NUM(TDB_VOLATILE));
664
725
 
665
726
  #ifdef TDB_ALLOW_NESTING
666
727
  /* Allow transactions to nest */
667
- tdb_CONST(ALLOW_NESTING);
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
- tdb_CONST(DISALLOW_NESTING);
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: can't be opened by tdb < 1.2.6. */
677
- tdb_CONST(INCOMPATIBLE_HASH);
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
@@ -5,7 +5,7 @@ description = File.read("README").split(/\n\n/)[1].strip
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{tdb}
8
- s.version = ENV["VERSION"]
8
+ s.version = ENV["VERSION"].dup
9
9
 
10
10
  s.homepage = 'http://bogomips.org/ruby-tdb/'
11
11
  s.authors = ["Ruby tdb hackers"]
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
@@ -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: 27
4
+ hash: 23
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 1
8
+ - 2
9
9
  - 0
10
- version: 0.1.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-01 00:00:00 +00:00
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