13 /* this protects the global list of tdb objects maintained by libtdb */
14 static pthread_mutex_t big_lock
= PTHREAD_MUTEX_INITIALIZER
;
15 static VALUE cTDB
, cERR
;
16 static st_table
*exc_hash
;
19 /* must be a macro to prevent GC from killing converted 'val's */
20 #define TO_TDB_DATA(data,val) do { \
22 (data).dptr = (unsigned char *)RSTRING_PTR(val); \
23 (data).dsize = RSTRING_LEN(val); \
26 static void init_exc(enum TDB_ERROR ecode
, const char *name
)
28 VALUE exc
= rb_define_class_under(cERR
, name
, cERR
);
29 st_insert(exc_hash
, (st_data_t
)ecode
, (st_data_t
)exc
);
32 static void init_errors(void)
34 cERR
= rb_define_class_under(cTDB
, "ERR", rb_eStandardError
);
35 exc_hash
= st_init_numtable();
37 init_exc(TDB_ERR_CORRUPT
, "CORRUPT");
38 init_exc(TDB_ERR_IO
, "IO");
39 init_exc(TDB_ERR_LOCK
, "LOCK");
40 init_exc(TDB_ERR_OOM
, "OOM");
41 init_exc(TDB_ERR_EXISTS
, "EXISTS"),
42 init_exc(TDB_ERR_NOLOCK
, "NOLOCK");
43 init_exc(TDB_ERR_LOCK_TIMEOUT
, "LOCK_TIMEOUT");
44 init_exc(TDB_ERR_EINVAL
, "EINVAL");
45 init_exc(TDB_ERR_NOEXIST
, "NOEXIST");
46 init_exc(TDB_ERR_RDONLY
, "RDONLY");
47 #ifdef HAVE_CONST_TDB_ERR_NESTING
48 init_exc(TDB_ERR_NESTING
, "NESTING");
49 #endif /* HAVE_CONST_TDB_ERR_NESTING */
52 static void my_raise(struct tdb_context
*tdb
)
54 enum TDB_ERROR ecode
= tdb_error(tdb
);
55 const char *str
= tdb_errorstr(tdb
);
60 rb_bug("attempted to raise with no error");
67 case TDB_ERR_LOCK_TIMEOUT
:
71 #ifdef HAVE_CONST_TDB_ERR_NESTING
73 #endif /* HAVE_CONST_TDB_ERR_NESTING */
74 if (!st_lookup(exc_hash
, (st_data_t
)ecode
, (st_data_t
*)&exc
))
75 rb_bug("no-existent exception: %s\n", str
);
80 static void init_hashes(void)
83 rb_hash_aset(hashes,ID2SYM(rb_intern(#x)),ULONG2NUM((unsigned long)rbtdb_##x))
98 #ifndef HAVE_RB_THREAD_BLOCKING_REGION
99 /* (very) partial emulation of the 1.9 rb_thread_blocking_region under 1.8 */
100 # include <rubysig.h>
101 typedef VALUE
rb_blocking_function_t(void *);
102 static VALUE
my_tbr(rb_blocking_function_t
*fn
, void *data
)
113 static VALUE
my_tbr(rb_blocking_function_t
*fn
, void *data
)
115 return rb_thread_blocking_region(fn
, data
, RUBY_UBF_IO
, 0);
117 #endif /* HAVE_RUBY_THREAD_BLOCKING_REGION */
119 static void gcfree(void *ptr
)
121 struct tdb_context
*tdb
= ptr
;
123 /* no error checking in GC :< */
125 (void)pthread_mutex_lock(&big_lock
);
126 (void)tdb_close(tdb
);
127 (void)pthread_mutex_unlock(&big_lock
);
131 static VALUE
alloc(VALUE klass
)
133 return Data_Wrap_Struct(klass
, NULL
, gcfree
, NULL
);
136 static struct tdb_context
*db(VALUE self
, int check_opened
)
138 struct tdb_context
*tdb
;
140 Data_Get_Struct(self
, struct tdb_context
, tdb
);
142 if (!tdb
&& check_opened
)
143 rb_raise(rb_eIOError
, "closed database");
154 struct tdb_logging_context
*log_ctx
;
155 tdb_hash_func hash_fn
;
158 static VALUE
nogvl_open(void *ptr
)
160 struct open_args
*o
= ptr
;
161 struct tdb_context
*tdb
;
163 pthread_mutex_lock(&big_lock
);
164 tdb
= tdb_open_ex(o
->name
, o
->hash_size
, o
->tdb_flags
,
165 o
->open_flags
, o
->mode
, o
->log_ctx
, o
->hash_fn
);
166 pthread_mutex_unlock(&big_lock
);
171 static void set_args(VALUE self
, struct open_args
*o
, VALUE opts
)
176 o
->hash_size
= 0; /* default */
177 o
->tdb_flags
= TDB_DEFAULT
;
178 o
->open_flags
= O_RDWR
| O_CREAT
;
185 Check_Type(opts
, T_HASH
);
187 tmp
= rb_hash_aref(opts
, ID2SYM(rb_intern("hash_size")));
189 o
->hash_size
= NUM2INT(tmp
);
191 tmp
= rb_hash_aref(opts
, ID2SYM(rb_intern("mode")));
193 o
->mode
= NUM2UINT(tmp
);
195 tmp
= rb_hash_aref(opts
, ID2SYM(rb_intern("open_flags")));
197 o
->open_flags
= NUM2INT(tmp
);
199 tmp
= rb_hash_aref(opts
, ID2SYM(rb_intern("tdb_flags")));
201 o
->tdb_flags
= NUM2INT(tmp
);
203 tmp
= rb_hash_aref(opts
, ID2SYM(rb_intern("hash")));
205 VALUE num
= rb_hash_aref(hashes
, tmp
);
208 tmp
= rb_inspect(tmp
);
209 rb_raise(rb_eArgError
,
210 "`%s' is not a valid hash function",
211 StringValuePtr(tmp
));
214 o
->hash_fn
= (tdb_hash_func
)NUM2ULONG(num
);
217 tmp
= rb_hash_aref(opts
, ID2SYM(rb_intern("threadsafe")));
219 rb_funcall(self
, rb_intern("threadsafe!"), 0);
225 * TDB.new("/path/to/file") -> TDB
226 * TDB.new("/path/to/file", :hash_size => 666) -> TDB
227 * TDB.new("/path/to/file", :hash => :murmur2) -> TDB
228 * TDB.new("/path/to/file", :open_flags => IO::RDONLY) -> TDB
229 * TDB.new("/path/to/file", :tdb_flags => TDB::NOSYNC) -> TDB
231 * Initializes a TDB context. It takes several options.
233 * :hash_size - the number of buckets, this is the most important tuning
234 * parameter when creating large databases. This parameter only affects
235 * the creation of new databases.
237 * :open_flags - a bit mask of IO flags passed directly to open(2),
238 * File.open-compatible flags are accepted.
240 * :hash - any of the hashes described in Hash_Functions.
241 * This must remain the same for all clients.
243 * :tdb_flags - a bitmask of any combination of TDB::CLEAR_IF_FIRST,
244 * TDB::INTERNAL, TDB::NOLOCK, TDB::NOMMAP, TDB::CONVERT,
245 * TDB::BIGENDIAN, TDB::NOSYNC, TDB::SEQNUM, TDB::VOLATILE,
246 * TDB::ALLOW_NESTING, TDB::DISALLOW_NESTING, TDB::INCOMPATIBLE_HASH
248 * :mode - octal mode mask passed to open(2)
250 static VALUE
init(int argc
, VALUE
*argv
, VALUE self
)
252 struct tdb_context
*tdb
= db(self
, 0);
257 rb_raise(rb_eRuntimeError
, "TDB already initialized");
258 rb_scan_args(argc
, argv
, "11", &path
, &opts
);
259 set_args(self
, &o
, opts
);
262 o
.tdb_flags
|= TDB_INTERNAL
;
264 o
.name
= StringValuePtr(path
);
266 tdb
= (struct tdb_context
*)my_tbr(nogvl_open
, &o
);
273 tdb
= (struct tdb_context
*)my_tbr(nogvl_open
, &o
);
276 rb_sys_fail("tdb_open_ex");
278 DATA_PTR(self
) = tdb
;
283 /* tdb_close can do a lot, including cancel transactions an munmap */
284 static VALUE
nogvl_close(void *ptr
)
286 struct tdb_context
*tdb
= ptr
;
289 pthread_mutex_lock(&big_lock
);
290 rv
= (VALUE
)tdb_close(tdb
);
291 pthread_mutex_unlock(&big_lock
);
296 static VALUE
tdbclose(VALUE self
)
298 struct tdb_context
*tdb
= db(self
, 1);
300 DATA_PTR(self
) = NULL
;
302 if ((int)my_tbr(nogvl_close
, tdb
) == -1)
303 rb_sys_fail("tdb_close");
308 static VALUE
closed(VALUE self
)
310 struct tdb_context
*tdb
= db(self
, 0);
312 return tdb
? Qfalse
: Qtrue
;
315 #ifdef HAVE_RB_THREAD_CALL_WITH_GVL
316 /* missing prototype in ruby.h: */
317 void *rb_thread_call_with_gvl(void *(*func
)(void *), void *data
);
319 static void * my_rb_thread_call_with_gvl(void *(*func
)(void *), void *data
)
321 return (*func
)(data
);
323 #define rb_thread_call_with_gvl my_rb_thread_call_with_gvl
324 #endif /* !HAVE_RB_THREAD_CALL_WITH_GVL */
327 * We avoid the extra malloc/free pair enforced by tdb_fetch. We
328 * use tdb_parse_record to give us pointers to (hopefully) mmap-ed
329 * regions and create a String object directly off that region.
331 struct fetch_parse_args
{
332 struct tdb_context
*tdb
;
342 static VALUE
str_new_tdb_data(TDB_DATA
*val
)
344 return rb_str_new((const char *)val
->dptr
, val
->dsize
);
347 static void *gvl_str_resize(void *data
)
349 struct fetch_parse_args
*f
= data
;
351 rb_str_resize(f
->value
, f
->as
.value_len
);
352 f
->as
.value_ptr
= RSTRING_PTR(f
->value
);
357 static int fetch_parse(TDB_DATA key
, TDB_DATA val
, void *data
)
359 struct fetch_parse_args
*f
= data
;
361 f
->as
.value_len
= val
.dsize
;
362 (void)rb_thread_call_with_gvl(gvl_str_resize
, data
);
363 memcpy(f
->as
.value_ptr
, val
.dptr
, val
.dsize
);
364 f
->as
.value
= f
->value
;
369 static VALUE
nogvl_parse_record(void *ptr
)
371 struct fetch_parse_args
*f
= ptr
;
373 if (tdb_parse_record(f
->tdb
, f
->as
.key
, fetch_parse
, ptr
) == -1)
376 return f
->value
== f
->as
.value
? f
->value
: Qnil
;
379 static VALUE
fetch(int argc
, VALUE
*argv
, VALUE self
)
381 struct fetch_parse_args f
;
384 rb_scan_args(argc
, argv
, "11", &key
, &f
.value
);
385 if (NIL_P(f
.value
)) {
386 f
.value
= rb_str_new(0, 0);
388 StringValue(f
.value
);
389 rb_str_set_len(f
.value
, 0);
393 TO_TDB_DATA(f
.as
.key
, key
);
395 return my_tbr(nogvl_parse_record
, &f
);
399 struct tdb_context
*tdb
;
405 static VALUE
nogvl_store(void *ptr
)
407 struct store_args
*s
= ptr
;
409 return (VALUE
)tdb_store(s
->tdb
, s
->key
, s
->val
, s
->flag
);
412 static VALUE
rbtdb_store(VALUE self
, VALUE key
, VALUE val
, int flag
, int soft
)
417 TO_TDB_DATA(s
.key
, key
);
418 TO_TDB_DATA(s
.val
, val
);
421 if ((int)my_tbr(nogvl_store
, &s
) == -1) {
423 int ecode
= tdb_error(s
.tdb
);
425 if ((flag
== TDB_INSERT
) && (ecode
== TDB_ERR_EXISTS
))
427 if ((flag
== TDB_MODIFY
) && (ecode
== TDB_ERR_NOEXIST
))
436 static VALUE
store(VALUE self
, VALUE key
, VALUE val
)
438 return rbtdb_store(self
, key
, val
, 0, 0);
441 static VALUE
insert_bang(VALUE self
, VALUE key
, VALUE val
)
443 return rbtdb_store(self
, key
, val
, TDB_INSERT
, 0);
446 static VALUE
insert(VALUE self
, VALUE key
, VALUE val
)
448 return rbtdb_store(self
, key
, val
, TDB_INSERT
, 1);
451 static VALUE
modify_bang(VALUE self
, VALUE key
, VALUE val
)
453 return rbtdb_store(self
, key
, val
, TDB_MODIFY
, 0);
456 static VALUE
modify(VALUE self
, VALUE key
, VALUE val
)
458 return rbtdb_store(self
, key
, val
, TDB_MODIFY
, 1);
462 struct tdb_context
*tdb
;
466 static VALUE
nogvl_exists(void *ptr
)
468 struct exists_args
*e
= ptr
;
470 return tdb_exists(e
->tdb
, e
->key
) == 0 ? Qfalse
: Qtrue
;
473 static VALUE
has_key(VALUE self
, VALUE key
)
475 struct exists_args e
;
478 TO_TDB_DATA(e
.key
, key
);
480 return my_tbr(nogvl_exists
, &e
);
483 struct traverse_args
{
484 struct tdb_context
*tdb
;
490 static VALUE
protected_yield(VALUE val
)
492 VALUE
*kv
= (VALUE
*)val
;
494 return rb_yield_values(2, kv
[0], kv
[1]);
497 static void *my_yield(void *data
)
499 struct traverse_args
*t
= data
;
502 kv
[0] = str_new_tdb_data(&t
->key
);
503 kv
[1] = str_new_tdb_data(&t
->val
);
505 rb_protect(protected_yield
, (VALUE
)kv
, &t
->state
);
511 traverse_fn(struct tdb_context
*tdb
, TDB_DATA key
, TDB_DATA val
, void *data
)
513 struct traverse_args
*t
= data
;
517 (void)rb_thread_call_with_gvl(my_yield
, t
);
522 static VALUE
nogvl_traverse(void *ptr
)
524 struct traverse_args
*t
= ptr
;
526 (void)tdb_traverse(t
->tdb
, traverse_fn
, t
);
531 static VALUE
each(VALUE self
)
533 struct traverse_args t
;
538 my_tbr(nogvl_traverse
, &t
);
540 rb_jump_tag(t
.state
);
545 struct tdb_context
*tdb
;
549 static VALUE
nogvl_delete(void *ptr
)
551 struct delete_args
*d
= ptr
;
553 return tdb_delete(d
->tdb
, d
->key
) == 0 ? Qtrue
: Qfalse
;
556 static VALUE
nuke(VALUE self
, VALUE key
)
558 struct delete_args d
;
561 TO_TDB_DATA(d
.key
, key
);
563 return my_tbr(nogvl_delete
, &d
);
566 static VALUE
aref(VALUE self
, VALUE key
)
568 return fetch(1, &key
, self
);
571 static VALUE
delete(int argc
, VALUE
*argv
, VALUE self
)
573 VALUE rc
= fetch(argc
, argv
, self
);
576 if (nuke(self
, argv
[0]) == Qfalse
)
581 static VALUE
lockall(VALUE self
)
583 struct tdb_context
*tdb
= db(self
, 1);
584 if ((int)my_tbr((rb_blocking_function_t
*)tdb_lockall
, tdb
))
590 static VALUE
trylockall(VALUE self
)
592 struct tdb_context
*tdb
= db(self
, 1);
593 void *fn
= tdb_lockall_nonblock
;
595 if ((int)my_tbr((rb_blocking_function_t
*)fn
, tdb
)) {
596 if (tdb_error(tdb
) == TDB_ERR_LOCK
)
603 static VALUE
unlockall(VALUE self
)
605 struct tdb_context
*tdb
= db(self
, 1);
606 if ((int)my_tbr((rb_blocking_function_t
*)tdb_unlockall
, tdb
))
611 static VALUE
lockall_read(VALUE self
)
613 struct tdb_context
*tdb
= db(self
, 1);
614 if ((int)my_tbr((rb_blocking_function_t
*)tdb_lockall_read
, tdb
))
619 static VALUE
trylockall_read(VALUE self
)
621 struct tdb_context
*tdb
= db(self
, 1);
622 void *fn
= tdb_lockall_read_nonblock
;
623 if ((int)my_tbr((rb_blocking_function_t
*)fn
, tdb
)) {
624 if (tdb_error(tdb
) == TDB_ERR_LOCK
)
631 static VALUE
unlockall_read(VALUE self
)
633 struct tdb_context
*tdb
= db(self
, 1);
634 if ((int)my_tbr((rb_blocking_function_t
*)tdb_unlockall_read
, tdb
))
639 static VALUE
lockall_mark(VALUE self
)
641 struct tdb_context
*tdb
= db(self
, 1);
642 if ((int)my_tbr((rb_blocking_function_t
*)tdb_lockall_mark
, tdb
))
647 static VALUE
lockall_unmark(VALUE self
)
649 struct tdb_context
*tdb
= db(self
, 1);
650 if ((int)my_tbr((rb_blocking_function_t
*)tdb_lockall_unmark
, tdb
))
656 * clears out the database
658 static VALUE
clear(VALUE self
)
660 struct tdb_context
*tdb
= db(self
, 1);
661 if ((int)my_tbr((rb_blocking_function_t
*)tdb_wipe_all
, tdb
))
666 #ifdef HAVE_TDB_REPACK
667 /* repacks a database to reduce fragmentation, available with tdb 1.2.x+ */
668 static VALUE
repack(VALUE self
)
670 struct tdb_context
*tdb
= db(self
, 1);
671 if ((int)my_tbr((rb_blocking_function_t
*)tdb_repack
, tdb
))
675 #endif /* HAVE_TDB_REPACK */
677 void Init_tdb_ext(void)
679 cTDB
= rb_define_class("TDB", rb_cObject
);
681 hashes
= rb_hash_new();
684 * Available hash functions, the key is the name of the hash
685 * and the value is a pointer for internal for usage.
687 rb_define_const(cTDB
, "HASHES", hashes
);
689 rb_define_alloc_func(cTDB
, alloc
);
690 rb_include_module(cTDB
, rb_mEnumerable
);
692 rb_define_method(cTDB
, "initialize", init
, -1);
693 rb_define_method(cTDB
, "close", tdbclose
, 0);
694 rb_define_method(cTDB
, "closed?", closed
, 0);
696 rb_define_method(cTDB
, "fetch", fetch
, -1);
697 rb_define_method(cTDB
, "[]", aref
, 1);
698 rb_define_method(cTDB
, "store", store
, 2);
699 rb_define_method(cTDB
, "[]=", store
, 2);
700 rb_define_method(cTDB
, "insert!", insert_bang
, 2);
701 rb_define_method(cTDB
, "modify!", modify_bang
, 2);
702 rb_define_method(cTDB
, "insert", insert
, 2);
703 rb_define_method(cTDB
, "modify", modify
, 2);
705 rb_define_method(cTDB
, "key?", has_key
, 1);
706 rb_define_method(cTDB
, "has_key?", has_key
, 1);
707 rb_define_method(cTDB
, "include?", has_key
, 1);
708 rb_define_method(cTDB
, "member?", has_key
, 1);
709 rb_define_method(cTDB
, "each", each
, 0);
710 rb_define_method(cTDB
, "nuke!", nuke
, 1);
711 rb_define_method(cTDB
, "delete", delete, -1);
713 rb_define_method(cTDB
, "lockall", lockall
, 0);
714 rb_define_method(cTDB
, "trylockall", trylockall
, 0);
715 rb_define_method(cTDB
, "unlockall", unlockall
, 0);
716 rb_define_method(cTDB
, "lockall_read", lockall_read
, 0);
717 rb_define_method(cTDB
, "trylockall_read", trylockall_read
, 0);
718 rb_define_method(cTDB
, "unlockall_read", unlockall_read
, 0);
719 rb_define_method(cTDB
, "lockall_mark", lockall_mark
, 0);
720 rb_define_method(cTDB
, "lockall_unmark", lockall_unmark
, 0);
721 rb_define_method(cTDB
, "clear", clear
, 0);
722 #ifdef HAVE_TDB_REPACK
723 rb_define_method(cTDB
, "repack", repack
, 0);
724 #endif /* HAVE_TDB_REPACK */
729 /* just a readability place holder */
730 rb_define_const(cTDB
, "DEFAULT", UINT2NUM(TDB_DEFAULT
));
732 /* clear database if we are the only one with it open */
733 rb_define_const(cTDB
, "CLEAR_IF_FIRST", UINT2NUM(TDB_CLEAR_IF_FIRST
));
735 /* don't store on disk, use in-memory database */
736 rb_define_const(cTDB
, "INTERNAL", UINT2NUM(TDB_INTERNAL
));
738 /* don't do any locking */
739 rb_define_const(cTDB
, "NOLOCK", UINT2NUM(TDB_NOLOCK
));
742 rb_define_const(cTDB
, "NOMMAP", UINT2NUM(TDB_NOMMAP
));
744 /* convert endian (internal use) */
745 rb_define_const(cTDB
, "CONVERT", UINT2NUM(TDB_CONVERT
));
747 /* header is big-endian (internal use) */
748 rb_define_const(cTDB
, "BIGENDIAN", UINT2NUM(TDB_BIGENDIAN
));
750 /* don't use synchronous transactions */
751 rb_define_const(cTDB
, "NOSYNC", UINT2NUM(TDB_NOSYNC
));
753 /* maintain a sequence number */
754 rb_define_const(cTDB
, "SEQNUM", UINT2NUM(TDB_SEQNUM
));
756 /* Activate the per-hashchain freelist, default 5 */
757 rb_define_const(cTDB
, "VOLATILE", UINT2NUM(TDB_VOLATILE
));
759 #ifdef TDB_ALLOW_NESTING
760 /* Allow transactions to nest */
761 rb_define_const(cTDB
, "ALLOW_NESTING", UINT2NUM(TDB_ALLOW_NESTING
));
764 #ifdef TDB_DISALLOW_NESTING
765 /* Disallow transactions to nest */
766 rb_define_const(cTDB
, "DISALLOW_NESTING", UINT2NUM(TDB_DISALLOW_NESTING
));
769 #ifdef TDB_INCOMPATIBLE_HASH
770 /* Better hashing, but can't be opened by tdb < 1.2.6. */
771 rb_define_const(cTDB
, "INCOMPATIBLE_HASH", UINT2NUM(TDB_INCOMPATIBLE_HASH
));
773 rbtdb_init_tdb_hash_functions();
777 * Document-class: TDB
780 * tdb = TDB.new("/path/to/file", flags => IO::RDWR|IO::CREAT)
781 * tdb.store("HELLO", "world")
782 * tdb.fetch("HELLO") -> "world"
783 * tdb.delete("HELLO") -> "world"