8 /* this protects the global list of tdb objects maintained by libtdb */
9 static pthread_mutex_t big_lock
= PTHREAD_MUTEX_INITIALIZER
;
10 static VALUE cTDB
, cERR
;
11 static VALUE exc_hash
;
14 /* must be a macro to prevent GC from killing converted 'val's */
15 #define TO_TDB_DATA(data,val) do { \
17 (data).dptr = (unsigned char *)RSTRING_PTR(val); \
18 (data).dsize = RSTRING_LEN(val); \
21 static void init_exc(enum TDB_ERROR ecode
, const char *name
)
23 VALUE exc
= rb_define_class_under(cERR
, name
, cERR
);
24 rb_hash_aset(exc_hash
, INT2NUM(ecode
), exc
);
27 static void init_errors(void)
29 cERR
= rb_define_class_under(cTDB
, "ERR", rb_eStandardError
);
30 exc_hash
= rb_hash_new();
31 rb_global_variable(&exc_hash
);
33 init_exc(TDB_ERR_CORRUPT
, "CORRUPT");
34 init_exc(TDB_ERR_IO
, "IO");
35 init_exc(TDB_ERR_LOCK
, "LOCK");
36 init_exc(TDB_ERR_OOM
, "OOM");
37 init_exc(TDB_ERR_EXISTS
, "EXISTS"),
38 init_exc(TDB_ERR_NOLOCK
, "NOLOCK");
39 init_exc(TDB_ERR_LOCK_TIMEOUT
, "LOCK_TIMEOUT");
40 init_exc(TDB_ERR_EINVAL
, "EINVAL");
41 init_exc(TDB_ERR_NOEXIST
, "NOEXIST");
42 init_exc(TDB_ERR_RDONLY
, "RDONLY");
43 #ifdef HAVE_CONST_TDB_ERR_NESTING
44 init_exc(TDB_ERR_NESTING
, "NESTING");
45 #endif /* HAVE_CONST_TDB_ERR_NESTING */
48 static void my_raise(struct tdb_context
*tdb
)
50 enum TDB_ERROR ecode
= tdb_error(tdb
);
51 const char *str
= tdb_errorstr(tdb
);
56 rb_bug("attempted to raise with no error");
63 case TDB_ERR_LOCK_TIMEOUT
:
67 #ifdef HAVE_CONST_TDB_ERR_NESTING
69 #endif /* HAVE_CONST_TDB_ERR_NESTING */
70 exc
= rb_hash_aref(exc_hash
, INT2NUM(ecode
));
73 rb_bug("no-existent exception: %s\n", str
);
74 rb_raise(exc
, "%s", str
);
77 static void init_hashes(void)
80 rb_hash_aset(hashes,ID2SYM(rb_intern(#x)),ULONG2NUM((unsigned long)rbtdb_##x))
97 #if defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL) /* Ruby 2.1+ */
98 # include <ruby/thread.h>
99 # define WITHOUT_GVL(fn,a,ubf,b) \
100 rb_thread_call_without_gvl((fn),(a),(ubf),(b))
101 /* partial emulation of the 1.9 rb_thread_blocking_region under 1.8 */
102 #elif defined(HAVE_RB_THREAD_BLOCKING_REGION) /* Ruby 1.9-2.0 */
103 typedef VALUE (*my_blocking_fn_t
)(void*);
104 # define WITHOUT_GVL(fn,a,ubf,b) \
105 rb_thread_blocking_region((my_blocking_fn_t)(fn),(a),(ubf),(b))
107 # include <rubysig.h>
108 # define RUBY_UBF_IO ((rb_unblock_function_t *)-1)
109 typedef void rb_unblock_function_t(void *);
110 typedef void * rb_blocking_function_t(void *);
111 static void * WITHOUT_GVL(rb_blocking_function_t
*func
, void *data1
,
112 rb_unblock_function_t
*ubf
, void *data2
)
122 #endif /* ! HAVE_RB_THREAD_BLOCKING_REGION */
124 #define my_tbr(fn,data) WITHOUT_GVL((void *)(fn),(data),RUBY_UBF_IO,0)
126 static void gcfree(void *ptr
)
128 struct tdb_context
*tdb
= ptr
;
130 /* no error checking in GC :< */
132 (void)pthread_mutex_lock(&big_lock
);
133 (void)tdb_close(tdb
);
134 (void)pthread_mutex_unlock(&big_lock
);
138 static VALUE
alloc(VALUE klass
)
140 return Data_Wrap_Struct(klass
, NULL
, gcfree
, NULL
);
143 static struct tdb_context
*db(VALUE self
, int check_opened
)
145 struct tdb_context
*tdb
;
147 Data_Get_Struct(self
, struct tdb_context
, tdb
);
149 if (!tdb
&& check_opened
)
150 rb_raise(rb_eIOError
, "closed database");
161 struct tdb_logging_context
*log_ctx
;
162 tdb_hash_func hash_fn
;
165 static void * nogvl_open(void *ptr
)
167 struct open_args
*o
= ptr
;
168 struct tdb_context
*tdb
;
170 pthread_mutex_lock(&big_lock
);
171 tdb
= tdb_open_ex(o
->name
, o
->hash_size
, o
->tdb_flags
,
172 o
->open_flags
, o
->mode
, o
->log_ctx
, o
->hash_fn
);
173 pthread_mutex_unlock(&big_lock
);
178 static void set_args(VALUE self
, struct open_args
*o
, VALUE opts
)
183 o
->hash_size
= 0; /* default */
184 o
->tdb_flags
= TDB_DEFAULT
;
185 o
->open_flags
= O_RDWR
| O_CREAT
;
192 Check_Type(opts
, T_HASH
);
194 tmp
= rb_hash_aref(opts
, ID2SYM(rb_intern("hash_size")));
196 o
->hash_size
= NUM2INT(tmp
);
198 tmp
= rb_hash_aref(opts
, ID2SYM(rb_intern("mode")));
200 o
->mode
= NUM2UINT(tmp
);
202 tmp
= rb_hash_aref(opts
, ID2SYM(rb_intern("open_flags")));
204 o
->open_flags
= NUM2INT(tmp
);
206 tmp
= rb_hash_aref(opts
, ID2SYM(rb_intern("tdb_flags")));
208 o
->tdb_flags
= NUM2INT(tmp
);
210 tmp
= rb_hash_aref(opts
, ID2SYM(rb_intern("hash")));
212 VALUE num
= rb_hash_aref(hashes
, tmp
);
215 tmp
= rb_inspect(tmp
);
216 rb_raise(rb_eArgError
,
217 "`%s' is not a valid hash function",
218 StringValuePtr(tmp
));
221 o
->hash_fn
= (tdb_hash_func
)NUM2ULONG(num
);
224 tmp
= rb_hash_aref(opts
, ID2SYM(rb_intern("threadsafe")));
226 rb_funcall(self
, rb_intern("threadsafe!"), 0);
232 * TDB.new("/path/to/file") -> TDB
233 * TDB.new("/path/to/file", :hash_size => 666) -> TDB
234 * TDB.new("/path/to/file", :hash => :murmur2) -> TDB
235 * TDB.new("/path/to/file", :open_flags => IO::RDONLY) -> TDB
236 * TDB.new("/path/to/file", :tdb_flags => TDB::NOSYNC) -> TDB
238 * Initializes a TDB context. It takes several options.
240 * :hash_size - the number of buckets, this is the most important tuning
241 * parameter when creating large databases. This parameter only affects
242 * the creation of new databases.
244 * :open_flags - a bit mask of IO flags passed directly to open(2),
245 * File.open-compatible flags are accepted.
247 * :hash - any of the hashes described in Hash_Functions.
248 * This must remain the same for all clients.
250 * :tdb_flags - a bitmask of any combination of TDB::CLEAR_IF_FIRST,
251 * TDB::INTERNAL, TDB::NOLOCK, TDB::NOMMAP, TDB::CONVERT,
252 * TDB::BIGENDIAN, TDB::NOSYNC, TDB::SEQNUM, TDB::VOLATILE,
253 * TDB::ALLOW_NESTING, TDB::DISALLOW_NESTING, TDB::INCOMPATIBLE_HASH
255 * :mode - octal mode mask passed to open(2)
257 static VALUE
init(int argc
, VALUE
*argv
, VALUE self
)
259 struct tdb_context
*tdb
= db(self
, 0);
264 rb_raise(rb_eRuntimeError
, "TDB already initialized");
265 rb_scan_args(argc
, argv
, "11", &path
, &opts
);
266 set_args(self
, &o
, opts
);
269 o
.tdb_flags
|= TDB_INTERNAL
;
271 o
.name
= StringValueCStr(path
);
273 tdb
= (struct tdb_context
*)my_tbr(nogvl_open
, &o
);
280 tdb
= (struct tdb_context
*)my_tbr(nogvl_open
, &o
);
283 rb_sys_fail("tdb_open_ex");
285 DATA_PTR(self
) = tdb
;
290 /* tdb_close can do a lot, including cancel transactions an munmap */
291 static void * nogvl_close(void *ptr
)
293 struct tdb_context
*tdb
= ptr
;
296 pthread_mutex_lock(&big_lock
);
298 pthread_mutex_unlock(&big_lock
);
303 static VALUE
tdbclose(VALUE self
)
305 struct tdb_context
*tdb
= db(self
, 1);
307 DATA_PTR(self
) = NULL
;
309 if ((long)my_tbr(nogvl_close
, tdb
) == -1)
310 rb_sys_fail("tdb_close");
315 static VALUE
closed(VALUE self
)
317 struct tdb_context
*tdb
= db(self
, 0);
319 return tdb
? Qfalse
: Qtrue
;
322 #ifdef HAVE_RB_THREAD_CALL_WITH_GVL
323 /* missing prototype in ruby.h: */
324 void *rb_thread_call_with_gvl(void *(*func
)(void *), void *data
);
326 static void * my_rb_thread_call_with_gvl(void *(*func
)(void *), void *data
)
328 return (*func
)(data
);
330 #define rb_thread_call_with_gvl my_rb_thread_call_with_gvl
331 #endif /* !HAVE_RB_THREAD_CALL_WITH_GVL */
334 * We avoid the extra malloc/free pair enforced by tdb_fetch. We
335 * use tdb_parse_record to give us pointers to (hopefully) mmap-ed
336 * regions and create a String object directly off that region.
338 struct fetch_parse_args
{
339 struct tdb_context
*tdb
;
349 static VALUE
str_new_tdb_data(TDB_DATA
*val
)
351 return rb_str_new((const char *)val
->dptr
, val
->dsize
);
354 static void *gvl_str_resize(void *data
)
356 struct fetch_parse_args
*f
= data
;
358 rb_str_resize(f
->value
, f
->as
.value_len
);
359 f
->as
.value_ptr
= RSTRING_PTR(f
->value
);
364 static int fetch_parse(TDB_DATA key
, TDB_DATA val
, void *data
)
366 struct fetch_parse_args
*f
= data
;
368 f
->as
.value_len
= val
.dsize
;
369 (void)rb_thread_call_with_gvl(gvl_str_resize
, data
);
370 memcpy(f
->as
.value_ptr
, val
.dptr
, val
.dsize
);
371 f
->as
.value
= f
->value
;
376 static void * nogvl_parse_record(void *ptr
)
378 struct fetch_parse_args
*f
= ptr
;
380 if (tdb_parse_record(f
->tdb
, f
->as
.key
, fetch_parse
, ptr
) == -1)
383 return (void *)(f
->value
== f
->as
.value
? f
->value
: Qnil
);
386 static VALUE
fetch(int argc
, VALUE
*argv
, VALUE self
)
388 struct fetch_parse_args f
;
391 rb_scan_args(argc
, argv
, "11", &key
, &f
.value
);
392 if (NIL_P(f
.value
)) {
393 f
.value
= rb_str_new(0, 0);
395 StringValue(f
.value
);
396 rb_str_set_len(f
.value
, 0);
400 TO_TDB_DATA(f
.as
.key
, key
);
402 return (VALUE
)my_tbr(nogvl_parse_record
, &f
);
406 struct tdb_context
*tdb
;
412 static void * nogvl_store(void *ptr
)
414 struct store_args
*s
= ptr
;
415 long rc
= tdb_store(s
->tdb
, s
->key
, s
->val
, s
->flag
);
420 static VALUE
rbtdb_store(VALUE self
, VALUE key
, VALUE val
, int flag
, int soft
)
425 TO_TDB_DATA(s
.key
, key
);
426 TO_TDB_DATA(s
.val
, val
);
429 if ((long)my_tbr(nogvl_store
, &s
) == -1) {
431 int ecode
= tdb_error(s
.tdb
);
433 if ((flag
== TDB_INSERT
) && (ecode
== TDB_ERR_EXISTS
))
435 if ((flag
== TDB_MODIFY
) && (ecode
== TDB_ERR_NOEXIST
))
444 static VALUE
store(VALUE self
, VALUE key
, VALUE val
)
446 return rbtdb_store(self
, key
, val
, 0, 0);
449 static VALUE
insert_bang(VALUE self
, VALUE key
, VALUE val
)
451 return rbtdb_store(self
, key
, val
, TDB_INSERT
, 0);
454 static VALUE
insert(VALUE self
, VALUE key
, VALUE val
)
456 return rbtdb_store(self
, key
, val
, TDB_INSERT
, 1);
459 static VALUE
modify_bang(VALUE self
, VALUE key
, VALUE val
)
461 return rbtdb_store(self
, key
, val
, TDB_MODIFY
, 0);
464 static VALUE
modify(VALUE self
, VALUE key
, VALUE val
)
466 return rbtdb_store(self
, key
, val
, TDB_MODIFY
, 1);
470 struct tdb_context
*tdb
;
474 static void * nogvl_exists(void *ptr
)
476 struct exists_args
*e
= ptr
;
478 return (void *)(tdb_exists(e
->tdb
, e
->key
) == 0 ? Qfalse
: Qtrue
);
481 static VALUE
has_key(VALUE self
, VALUE key
)
483 struct exists_args e
;
486 TO_TDB_DATA(e
.key
, key
);
488 return (VALUE
)my_tbr(nogvl_exists
, &e
);
491 struct traverse_args
{
492 struct tdb_context
*tdb
;
498 static VALUE
protected_yield(VALUE val
)
500 VALUE
*kv
= (VALUE
*)val
;
502 return rb_yield_values(2, kv
[0], kv
[1]);
505 static void *my_yield(void *data
)
507 struct traverse_args
*t
= data
;
510 kv
[0] = str_new_tdb_data(&t
->key
);
511 kv
[1] = str_new_tdb_data(&t
->val
);
513 rb_protect(protected_yield
, (VALUE
)kv
, &t
->state
);
519 traverse_fn(struct tdb_context
*tdb
, TDB_DATA key
, TDB_DATA val
, void *data
)
521 struct traverse_args
*t
= data
;
525 (void)rb_thread_call_with_gvl(my_yield
, t
);
530 static void * nogvl_traverse(void *ptr
)
532 struct traverse_args
*t
= ptr
;
534 (void)tdb_traverse(t
->tdb
, traverse_fn
, t
);
536 return (void *)Qfalse
;
539 static VALUE
each(VALUE self
)
541 struct traverse_args t
;
546 my_tbr(nogvl_traverse
, &t
);
548 rb_jump_tag(t
.state
);
553 struct tdb_context
*tdb
;
557 static void * nogvl_delete(void *ptr
)
559 struct delete_args
*d
= ptr
;
561 return (void *)(tdb_delete(d
->tdb
, d
->key
) == 0 ? Qtrue
: Qfalse
);
564 static VALUE
nuke(VALUE self
, VALUE key
)
566 struct delete_args d
;
569 TO_TDB_DATA(d
.key
, key
);
571 return (VALUE
)my_tbr(nogvl_delete
, &d
);
574 static VALUE
aref(VALUE self
, VALUE key
)
576 return fetch(1, &key
, self
);
579 static VALUE
delete(int argc
, VALUE
*argv
, VALUE self
)
581 VALUE rc
= fetch(argc
, argv
, self
);
584 if (nuke(self
, argv
[0]) == Qfalse
)
589 static VALUE
lockall(VALUE self
)
591 struct tdb_context
*tdb
= db(self
, 1);
592 if (my_tbr(tdb_lockall
, tdb
))
598 static VALUE
trylockall(VALUE self
)
600 struct tdb_context
*tdb
= db(self
, 1);
601 void *fn
= tdb_lockall_nonblock
;
603 if (my_tbr(fn
, tdb
)) {
604 if (tdb_error(tdb
) == TDB_ERR_LOCK
)
611 static VALUE
unlockall(VALUE self
)
613 struct tdb_context
*tdb
= db(self
, 1);
614 if (my_tbr(tdb_unlockall
, tdb
))
619 static VALUE
lockall_read(VALUE self
)
621 struct tdb_context
*tdb
= db(self
, 1);
622 if (my_tbr(tdb_lockall_read
, tdb
))
627 static VALUE
trylockall_read(VALUE self
)
629 struct tdb_context
*tdb
= db(self
, 1);
630 void *fn
= tdb_lockall_read_nonblock
;
631 if (my_tbr(fn
, tdb
)) {
632 if (tdb_error(tdb
) == TDB_ERR_LOCK
)
639 static VALUE
unlockall_read(VALUE self
)
641 struct tdb_context
*tdb
= db(self
, 1);
642 if (my_tbr(tdb_unlockall_read
, tdb
))
647 static VALUE
lockall_mark(VALUE self
)
649 struct tdb_context
*tdb
= db(self
, 1);
650 if (my_tbr(tdb_lockall_mark
, tdb
))
655 static VALUE
lockall_unmark(VALUE self
)
657 struct tdb_context
*tdb
= db(self
, 1);
659 if (my_tbr(tdb_lockall_unmark
, tdb
))
665 * clears out the database
667 static VALUE
clear(VALUE self
)
669 struct tdb_context
*tdb
= db(self
, 1);
670 if (my_tbr(tdb_wipe_all
, tdb
))
675 #ifdef HAVE_TDB_REPACK
676 /* repacks a database to reduce fragmentation, available with tdb 1.2.x+ */
677 static VALUE
repack(VALUE self
)
679 struct tdb_context
*tdb
= db(self
, 1);
680 if (my_tbr(tdb_repack
, tdb
))
684 #endif /* HAVE_TDB_REPACK */
686 void Init_tdb_ext(void)
688 cTDB
= rb_define_class("TDB", rb_cObject
);
690 hashes
= rb_hash_new();
693 * Available hash functions, the key is the name of the hash
694 * and the value is a pointer for internal for usage.
696 rb_define_const(cTDB
, "HASHES", hashes
);
698 rb_define_alloc_func(cTDB
, alloc
);
699 rb_include_module(cTDB
, rb_mEnumerable
);
701 rb_define_method(cTDB
, "initialize", init
, -1);
702 rb_define_method(cTDB
, "close", tdbclose
, 0);
703 rb_define_method(cTDB
, "closed?", closed
, 0);
705 rb_define_method(cTDB
, "fetch", fetch
, -1);
706 rb_define_method(cTDB
, "[]", aref
, 1);
707 rb_define_method(cTDB
, "store", store
, 2);
708 rb_define_method(cTDB
, "[]=", store
, 2);
709 rb_define_method(cTDB
, "insert!", insert_bang
, 2);
710 rb_define_method(cTDB
, "modify!", modify_bang
, 2);
711 rb_define_method(cTDB
, "insert", insert
, 2);
712 rb_define_method(cTDB
, "modify", modify
, 2);
714 rb_define_method(cTDB
, "key?", has_key
, 1);
715 rb_define_method(cTDB
, "has_key?", has_key
, 1);
716 rb_define_method(cTDB
, "include?", has_key
, 1);
717 rb_define_method(cTDB
, "member?", has_key
, 1);
718 rb_define_method(cTDB
, "each", each
, 0);
719 rb_define_method(cTDB
, "nuke!", nuke
, 1);
720 rb_define_method(cTDB
, "delete", delete, -1);
722 rb_define_method(cTDB
, "lockall", lockall
, 0);
723 rb_define_method(cTDB
, "trylockall", trylockall
, 0);
724 rb_define_method(cTDB
, "unlockall", unlockall
, 0);
725 rb_define_method(cTDB
, "lockall_read", lockall_read
, 0);
726 rb_define_method(cTDB
, "trylockall_read", trylockall_read
, 0);
727 rb_define_method(cTDB
, "unlockall_read", unlockall_read
, 0);
728 rb_define_method(cTDB
, "lockall_mark", lockall_mark
, 0);
729 rb_define_method(cTDB
, "lockall_unmark", lockall_unmark
, 0);
730 rb_define_method(cTDB
, "clear", clear
, 0);
731 #ifdef HAVE_TDB_REPACK
732 rb_define_method(cTDB
, "repack", repack
, 0);
733 #endif /* HAVE_TDB_REPACK */
738 /* just a readability place holder */
739 rb_define_const(cTDB
, "DEFAULT", UINT2NUM(TDB_DEFAULT
));
741 /* clear database if we are the only one with it open */
742 rb_define_const(cTDB
, "CLEAR_IF_FIRST", UINT2NUM(TDB_CLEAR_IF_FIRST
));
744 /* don't store on disk, use in-memory database */
745 rb_define_const(cTDB
, "INTERNAL", UINT2NUM(TDB_INTERNAL
));
747 /* don't do any locking */
748 rb_define_const(cTDB
, "NOLOCK", UINT2NUM(TDB_NOLOCK
));
751 rb_define_const(cTDB
, "NOMMAP", UINT2NUM(TDB_NOMMAP
));
753 /* convert endian (internal use) */
754 rb_define_const(cTDB
, "CONVERT", UINT2NUM(TDB_CONVERT
));
756 /* header is big-endian (internal use) */
757 rb_define_const(cTDB
, "BIGENDIAN", UINT2NUM(TDB_BIGENDIAN
));
759 /* don't use synchronous transactions */
760 rb_define_const(cTDB
, "NOSYNC", UINT2NUM(TDB_NOSYNC
));
762 /* maintain a sequence number */
763 rb_define_const(cTDB
, "SEQNUM", UINT2NUM(TDB_SEQNUM
));
765 /* Activate the per-hashchain freelist, default 5 */
766 rb_define_const(cTDB
, "VOLATILE", UINT2NUM(TDB_VOLATILE
));
768 #ifdef TDB_ALLOW_NESTING
769 /* Allow transactions to nest */
770 rb_define_const(cTDB
, "ALLOW_NESTING", UINT2NUM(TDB_ALLOW_NESTING
));
773 #ifdef TDB_DISALLOW_NESTING
774 /* Disallow transactions to nest */
775 rb_define_const(cTDB
, "DISALLOW_NESTING", UINT2NUM(TDB_DISALLOW_NESTING
));
778 #ifdef TDB_INCOMPATIBLE_HASH
779 /* Better hashing, but can't be opened by tdb < 1.2.6. */
780 rb_define_const(cTDB
, "INCOMPATIBLE_HASH", UINT2NUM(TDB_INCOMPATIBLE_HASH
));
782 rbtdb_init_tdb_hash_functions();
786 * Document-class: TDB
789 * tdb = TDB.new("/path/to/file", flags => IO::RDWR|IO::CREAT)
790 * tdb.store("HELLO", "world")
791 * tdb.fetch("HELLO") -> "world"
792 * tdb.delete("HELLO") -> "world"