tdb 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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