couchbase 1.2.1 → 1.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -38,13 +38,11 @@ Gem::Specification.new do |s|
38
38
  s.add_runtime_dependency 'yaji', '~> 0.3.2'
39
39
  s.add_runtime_dependency 'multi_json', '~> 1.0'
40
40
 
41
- s.add_development_dependency 'rake', '~> 0.8.7'
41
+ s.add_development_dependency 'rake'
42
42
  s.add_development_dependency 'minitest'
43
43
  s.add_development_dependency 'rake-compiler', '>= 0.7.5'
44
- s.add_development_dependency 'rdiscount'
45
- s.add_development_dependency 'yard'
46
- s.add_development_dependency 'yard-xml'
47
44
  s.add_development_dependency 'mini_portile'
48
45
  s.add_development_dependency 'yajl-ruby', '~> 1.1.0'
49
46
  s.add_development_dependency 'active_support'
47
+ s.add_development_dependency 'eventmachine'
50
48
  end
@@ -0,0 +1,7 @@
1
+ source :rubygems
2
+
3
+ gem 'eventmachine'
4
+
5
+ # couchbase dependencies
6
+ gem 'yaji'
7
+ gem 'multi_json'
@@ -0,0 +1,45 @@
1
+ # Chat Demo
2
+
3
+ This is simple demo of the chat built on EventMachine, and using
4
+ Couchbase to store logs.
5
+
6
+ # Quick Start and Usage
7
+
8
+ Navigate to the example directory and install dependencies:
9
+
10
+ $ cd examples/chat-em
11
+ $ bundle install
12
+
13
+ Execute the server
14
+
15
+ $ ruby ./server.rb
16
+ Hi, this is simple chat server based on EventMachine.
17
+ To join, just use your telnet or netcat clients to connect to
18
+ port 9999 on this machine. Press Ctrl-C to stop it.
19
+
20
+ Use telnet to join the chat
21
+
22
+ $ telnet localhost 9999
23
+ Trying 127.0.0.1...
24
+ Connected to localhost.
25
+ Escape character is '^]'.
26
+ *** What is your name?
27
+ avsej
28
+ *** Hi, avsej!
29
+ Hi everyone in this chat
30
+
31
+ The server will broadcast all your messages and record any event to
32
+ the Couchbase server. If your server hosted not on the localhost or
33
+ using bucket different from "default" you might want to change the
34
+ connection options at the bottom of the `server.rb`, for example in
35
+ this case it will connect to the bucket "protected" with password
36
+ "secret".
37
+
38
+ Couchbase.connection_options = {
39
+ :async => true,
40
+ :engine => :eventmachine,
41
+ :bucket => "protected",
42
+ :password => "secret"
43
+ }
44
+
45
+ Happy hacking!
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env ruby
2
+ ["/../../lib", "/.."].each do |path|
3
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + path))
4
+ end
5
+
6
+ require 'bundler'
7
+ Bundler.require
8
+
9
+ require 'couchbase'
10
+
11
+ class ChatServer < EM::Connection
12
+
13
+ @@clients = []
14
+
15
+ def log(message, author = nil)
16
+ Couchbase.bucket.incr("log:key", :initial => 1) do |res|
17
+ entry = {
18
+ 'time' => Time.now.utc,
19
+ 'author' => author || "[system]",
20
+ 'message' => message
21
+ }
22
+ Couchbase.bucket.set("log:#{res.value}", entry)
23
+ end
24
+ end
25
+
26
+ def post_init
27
+ @username = nil
28
+ send_data("*** What is your name?\n")
29
+ end
30
+
31
+ def receive_data(data)
32
+ if @username
33
+ broadcast(data.strip, @username)
34
+ else
35
+ name = data.gsub(/\s+|[\[\]]/, '').strip[0..20]
36
+ if name.empty?
37
+ send_data("*** What is your name?\n")
38
+ else
39
+ @username = name
40
+ @@clients.push(self)
41
+ broadcast("#{@username} has joined")
42
+ send_data("*** Hi, #{@username}!\n")
43
+ end
44
+ end
45
+ end
46
+
47
+ def unbind
48
+ @@clients.delete(self)
49
+ broadcast("#{@username} has left") if @username
50
+ end
51
+
52
+ def broadcast(message, author = nil)
53
+ prefix = author ? "<#{@username}>" : "***"
54
+ log(message, author)
55
+ @@clients.each do |client|
56
+ unless client == self
57
+ client.send_data("#{prefix} #{message}\n")
58
+ end
59
+ end
60
+ end
61
+
62
+ end
63
+
64
+ EventMachine.run do
65
+ # hit Control + C to stop
66
+ Signal.trap("INT") { EventMachine.stop }
67
+ Signal.trap("TERM") { EventMachine.stop }
68
+
69
+ Couchbase.connection_options = {:async => true, :engine => :eventmachine}
70
+ Couchbase.bucket.on_connect do |res|
71
+ if res.success?
72
+ puts(<<-MOTD.gsub(/^\s+/, ''))
73
+ Hi, this is simple chat server based on EventMachine.
74
+ To join, just use your telnet or netcat clients to connect to
75
+ port 9999 on this machine. Press Ctrl-C to stop it.
76
+ MOTD
77
+ EventMachine.start_server("0.0.0.0", 9999, ChatServer)
78
+ else
79
+ puts "Cannot connect to Couchbase Server: #{res.error}"
80
+ end
81
+ end
82
+ end
@@ -56,6 +56,7 @@ cb_params_touch_alloc(struct cb_params_st *params, lcb_size_t size)
56
56
  cb_params_touch_init_item(struct cb_params_st *params, lcb_size_t idx, VALUE key_obj, lcb_time_t exptime)
57
57
  {
58
58
  key_obj = cb_unify_key(params->bucket, key_obj, 1);
59
+ rb_ary_push(params->ensurance, key_obj);
59
60
  params->cmd.touch.items[idx].v.v0.key = RSTRING_PTR(key_obj);
60
61
  params->cmd.touch.items[idx].v.v0.nkey = RSTRING_LEN(key_obj);
61
62
  params->cmd.touch.items[idx].v.v0.exptime = exptime;
@@ -140,6 +141,7 @@ cb_params_remove_alloc(struct cb_params_st *params, lcb_size_t size)
140
141
  cb_params_remove_init_item(struct cb_params_st *params, lcb_size_t idx, VALUE key_obj, lcb_cas_t cas)
141
142
  {
142
143
  key_obj = cb_unify_key(params->bucket, key_obj, 1);
144
+ rb_ary_push(params->ensurance, key_obj);
143
145
  params->cmd.remove.items[idx].v.v0.key = RSTRING_PTR(key_obj);
144
146
  params->cmd.remove.items[idx].v.v0.nkey = RSTRING_LEN(key_obj);
145
147
  params->cmd.remove.items[idx].v.v0.cas = cas;
@@ -239,6 +241,8 @@ cb_params_store_init_item(struct cb_params_st *params, lcb_size_t idx,
239
241
  VALUE val = rb_any_to_s(value_obj);
240
242
  rb_raise(cb_eValueFormatError, "unable to convert value for key '%s' to string: %s", RSTRING_PTR(key_obj), RSTRING_PTR(val));
241
243
  }
244
+ rb_ary_push(params->ensurance, key_obj);
245
+ rb_ary_push(params->ensurance, value_obj);
242
246
  params->cmd.store.items[idx].v.v0.datatype = params->cmd.store.datatype;
243
247
  params->cmd.store.items[idx].v.v0.operation = params->cmd.store.operation;
244
248
  params->cmd.store.items[idx].v.v0.key = RSTRING_PTR(key_obj);
@@ -347,6 +351,7 @@ cb_params_get_init_item(struct cb_params_st *params, lcb_size_t idx,
347
351
  VALUE key_obj, lcb_time_t exptime)
348
352
  {
349
353
  key_obj = cb_unify_key(params->bucket, key_obj, 1);
354
+ rb_ary_push(params->ensurance, key_obj);
350
355
  if (params->cmd.get.replica) {
351
356
  params->cmd.get.items_gr[idx].v.v0.key = RSTRING_PTR(key_obj);
352
357
  params->cmd.get.items_gr[idx].v.v0.nkey = RSTRING_LEN(key_obj);
@@ -461,6 +466,7 @@ cb_params_arith_init_item(struct cb_params_st *params, lcb_size_t idx,
461
466
  VALUE key_obj, lcb_int64_t delta)
462
467
  {
463
468
  key_obj = cb_unify_key(params->bucket, key_obj, 1);
469
+ rb_ary_push(params->ensurance, key_obj);
464
470
  params->cmd.arith.items[idx].v.v0.key = RSTRING_PTR(key_obj);
465
471
  params->cmd.arith.items[idx].v.v0.nkey = RSTRING_LEN(key_obj);
466
472
  params->cmd.arith.items[idx].v.v0.delta = delta * params->cmd.arith.sign;
@@ -565,6 +571,7 @@ cb_params_stats_init_item(struct cb_params_st *params, lcb_size_t idx,
565
571
  VALUE key_obj)
566
572
  {
567
573
  key_obj = cb_unify_key(params->bucket, key_obj, 1);
574
+ rb_ary_push(params->ensurance, key_obj);
568
575
  params->cmd.stats.items[idx].v.v0.name = RSTRING_PTR(key_obj);
569
576
  params->cmd.stats.items[idx].v.v0.nname = RSTRING_LEN(key_obj);
570
577
  params->npayload += RSTRING_LEN(key_obj);
@@ -616,6 +623,7 @@ cb_params_observe_alloc(struct cb_params_st *params, lcb_size_t size)
616
623
  cb_params_observe_init_item(struct cb_params_st *params, lcb_size_t idx, VALUE key_obj)
617
624
  {
618
625
  key_obj = cb_unify_key(params->bucket, key_obj, 1);
626
+ rb_ary_push(params->ensurance, key_obj);
619
627
  params->cmd.observe.items[idx].v.v0.key = RSTRING_PTR(key_obj);
620
628
  params->cmd.observe.items[idx].v.v0.nkey = RSTRING_LEN(key_obj);
621
629
  params->npayload += RSTRING_LEN(key_obj);
@@ -666,6 +674,7 @@ cb_params_unlock_alloc(struct cb_params_st *params, lcb_size_t size)
666
674
  cb_params_unlock_init_item(struct cb_params_st *params, lcb_size_t idx, VALUE key_obj, lcb_cas_t cas)
667
675
  {
668
676
  key_obj = cb_unify_key(params->bucket, key_obj, 1);
677
+ rb_ary_push(params->ensurance, key_obj);
669
678
  params->cmd.unlock.items[idx].v.v0.key = RSTRING_PTR(key_obj);
670
679
  params->cmd.unlock.items[idx].v.v0.nkey = RSTRING_LEN(key_obj);
671
680
  params->cmd.unlock.items[idx].v.v0.cas = cas;
@@ -733,6 +742,9 @@ cb_params_version_alloc(struct cb_params_st *params)
733
742
  void
734
743
  cb_params_destroy(struct cb_params_st *params)
735
744
  {
745
+ rb_ary_clear(params->ensurance);
746
+ params->ensurance = Qfalse;
747
+ params->args = Qfalse;
736
748
  switch (params->type) {
737
749
  case cb_cmd_get:
738
750
  _release_data_for(get);
@@ -765,22 +777,14 @@ cb_params_destroy(struct cb_params_st *params)
765
777
  }
766
778
  }
767
779
 
768
- struct build_params_st
769
- {
770
- struct cb_params_st *params;
771
- int argc;
772
- VALUE argv;
773
- };
774
-
775
780
  static VALUE
776
781
  do_params_build(VALUE ptr)
777
782
  {
778
783
  VALUE opts;
779
784
  /* unpack arguments */
780
- struct build_params_st *p = (struct build_params_st *)ptr;
781
- struct cb_params_st *params = p->params;
782
- int argc = p->argc;
783
- VALUE argv = p->argv;
785
+ struct cb_params_st *params = (struct cb_params_st*)ptr;
786
+ int argc = RARRAY_LEN(params->args);
787
+ VALUE argv = params->args;
784
788
 
785
789
  /* extract options */
786
790
  if (argc > 1 && TYPE(RARRAY_PTR(argv)[argc-1]) == T_HASH) {
@@ -873,15 +877,12 @@ do_params_build(VALUE ptr)
873
877
  }
874
878
 
875
879
  void
876
- cb_params_build(struct cb_params_st *params, int argc, VALUE argv)
880
+ cb_params_build(struct cb_params_st *params)
877
881
  {
878
882
  int fail = 0;
879
- struct build_params_st args;
883
+ params->ensurance = rb_ary_new();
880
884
 
881
- args.params = params;
882
- args.argc = argc;
883
- args.argv = argv;
884
- rb_protect(do_params_build, (VALUE)&args, &fail);
885
+ rb_protect(do_params_build, (VALUE)params, &fail);
885
886
  if (fail) {
886
887
  cb_params_destroy(params);
887
888
  /* raise exception from protected block */
@@ -22,7 +22,7 @@ cb_arithmetic_callback(lcb_t handle, const void *cookie, lcb_error_t error, cons
22
22
  {
23
23
  struct cb_context_st *ctx = (struct cb_context_st *)cookie;
24
24
  struct cb_bucket_st *bucket = ctx->bucket;
25
- VALUE cas, key, val, *rv = ctx->rv, exc, res;
25
+ VALUE cas, key, val, exc, res;
26
26
  ID o;
27
27
 
28
28
  ctx->nqueries--;
@@ -35,7 +35,7 @@ cb_arithmetic_callback(lcb_t handle, const void *cookie, lcb_error_t error, cons
35
35
  if (exc != Qnil) {
36
36
  rb_ivar_set(exc, cb_id_iv_cas, cas);
37
37
  rb_ivar_set(exc, cb_id_iv_operation, o);
38
- ctx->exception = cb_gc_protect(bucket, exc);
38
+ ctx->exception = exc;
39
39
  }
40
40
  val = ULL2NUM(resp->v.v0.value);
41
41
  if (bucket->async) { /* asynchronous */
@@ -51,16 +51,16 @@ cb_arithmetic_callback(lcb_t handle, const void *cookie, lcb_error_t error, cons
51
51
  } else { /* synchronous */
52
52
  if (NIL_P(exc)) {
53
53
  if (ctx->extended) {
54
- rb_hash_aset(*rv, key, rb_ary_new3(2, val, cas));
54
+ rb_hash_aset(ctx->rv, key, rb_ary_new3(2, val, cas));
55
55
  } else {
56
- rb_hash_aset(*rv, key, val);
56
+ rb_hash_aset(ctx->rv, key, val);
57
57
  }
58
58
  }
59
59
  }
60
60
  if (ctx->nqueries == 0) {
61
- cb_gc_unprotect(bucket, ctx->proc);
61
+ ctx->proc = Qnil;
62
62
  if (bucket->async) {
63
- free(ctx);
63
+ cb_context_free(ctx);
64
64
  }
65
65
  }
66
66
  (void)handle;
@@ -71,38 +71,30 @@ cb_bucket_arithmetic(int sign, int argc, VALUE *argv, VALUE self)
71
71
  {
72
72
  struct cb_bucket_st *bucket = DATA_PTR(self);
73
73
  struct cb_context_st *ctx;
74
- VALUE args, rv, proc, exc;
74
+ VALUE rv, proc, exc;
75
75
  lcb_error_t err;
76
76
  struct cb_params_st params;
77
77
 
78
- if (bucket->handle == NULL) {
79
- rb_raise(cb_eConnectError, "closed connection");
78
+ if (!cb_bucket_connected_bang(bucket, sign > 0 ? cb_sym_increment : cb_sym_decrement)) {
79
+ return Qnil;
80
80
  }
81
- rb_scan_args(argc, argv, "0*&", &args, &proc);
81
+
82
+ memset(&params, 0, sizeof(struct cb_params_st));
83
+ rb_scan_args(argc, argv, "0*&", &params.args, &proc);
82
84
  if (!bucket->async && proc != Qnil) {
83
85
  rb_raise(rb_eArgError, "synchronous mode doesn't support callbacks");
84
86
  }
85
- memset(&params, 0, sizeof(struct cb_params_st));
86
87
  params.type = cb_cmd_arith;
87
88
  params.bucket = bucket;
88
89
  params.cmd.arith.sign = sign;
89
- cb_params_build(&params, RARRAY_LEN(args), args);
90
- ctx = calloc(1, sizeof(struct cb_context_st));
91
- if (ctx == NULL) {
92
- rb_raise(cb_eClientNoMemoryError, "failed to allocate memory for context");
93
- }
94
- rv = rb_hash_new();
95
- ctx->rv = &rv;
96
- ctx->bucket = bucket;
97
- ctx->proc = cb_gc_protect(bucket, proc);
98
- ctx->exception = Qnil;
99
- ctx->nqueries = params.cmd.arith.num;
90
+ cb_params_build(&params);
91
+ ctx = cb_context_alloc_common(bucket, proc, params.cmd.arith.num);
100
92
  err = lcb_arithmetic(bucket->handle, (const void *)ctx,
101
93
  params.cmd.arith.num, params.cmd.arith.ptr);
102
94
  cb_params_destroy(&params);
103
95
  exc = cb_check_error(err, "failed to schedule arithmetic request", Qnil);
104
96
  if (exc != Qnil) {
105
- free(ctx);
97
+ cb_context_free(ctx);
106
98
  rb_exc_raise(exc);
107
99
  }
108
100
  bucket->nbytes += params.npayload;
@@ -115,9 +107,9 @@ cb_bucket_arithmetic(int sign, int argc, VALUE *argv, VALUE self)
115
107
  lcb_wait(bucket->handle);
116
108
  }
117
109
  exc = ctx->exception;
118
- free(ctx);
110
+ rv = ctx->rv;
111
+ cb_context_free(ctx);
119
112
  if (exc != Qnil) {
120
- cb_gc_unprotect(bucket, exc);
121
113
  rb_exc_raise(exc);
122
114
  }
123
115
  if (params.cmd.store.num > 1) {
@@ -17,6 +17,24 @@
17
17
 
18
18
  #include "couchbase_ext.h"
19
19
 
20
+ static VALUE
21
+ trigger_on_connect_callback(VALUE self)
22
+ {
23
+ struct cb_bucket_st *bucket = DATA_PTR(self);
24
+ VALUE on_connect_proc = bucket->on_connect_proc;
25
+ if (RTEST(on_connect_proc)) {
26
+ VALUE res = rb_class_new_instance(0, NULL, cb_cResult);
27
+ rb_ivar_set(res, cb_id_iv_error, bucket->exception);
28
+ bucket->exception = Qnil;
29
+ rb_ivar_set(res, cb_id_iv_operation, cb_sym_connect);
30
+ rb_ivar_set(res, cb_id_iv_value, self);
31
+ return rb_funcall(on_connect_proc, cb_id_call, 1, res);
32
+ } else {
33
+ bucket->trigger_connect_cb_on_set = 1;
34
+ return Qnil;
35
+ }
36
+ }
37
+
20
38
  static void
21
39
  error_callback(lcb_t handle, lcb_error_t error, const char *errinfo)
22
40
  {
@@ -24,6 +42,20 @@ error_callback(lcb_t handle, lcb_error_t error, const char *errinfo)
24
42
 
25
43
  lcb_breakout(handle);
26
44
  bucket->exception = cb_check_error(error, errinfo, Qnil);
45
+ if (bucket->async && !bucket->connected) {
46
+ (void)trigger_on_connect_callback(bucket->self);
47
+ }
48
+ }
49
+
50
+ static void
51
+ configuration_callback(lcb_t handle, lcb_configuration_t config)
52
+ {
53
+ struct cb_bucket_st *bucket = (struct cb_bucket_st *)lcb_get_cookie(handle);
54
+
55
+ if (config == LCB_CONFIGURATION_NEW) {
56
+ bucket->connected = 1;
57
+ (void)trigger_on_connect_callback(bucket->self);
58
+ }
27
59
  }
28
60
 
29
61
  void
@@ -32,12 +64,21 @@ cb_bucket_free(void *ptr)
32
64
  struct cb_bucket_st *bucket = ptr;
33
65
 
34
66
  if (bucket) {
67
+ bucket->destroying = 1;
35
68
  if (bucket->handle) {
36
69
  lcb_destroy(bucket->handle);
37
70
  lcb_destroy_io_ops(bucket->io);
38
71
  }
72
+ st_free_table(bucket->object_space);
73
+ xfree(bucket);
39
74
  }
40
- xfree(bucket);
75
+ }
76
+
77
+ static int
78
+ cb_bucket_mark_object_i(st_index_t key, st_data_t value, st_data_t arg)
79
+ {
80
+ ((mark_f)value)((void*)key, (struct cb_bucket_st*)arg);
81
+ return ST_CONTINUE;
41
82
  }
42
83
 
43
84
  void
@@ -54,8 +95,9 @@ cb_bucket_mark(void *ptr)
54
95
  rb_gc_mark(bucket->password);
55
96
  rb_gc_mark(bucket->exception);
56
97
  rb_gc_mark(bucket->on_error_proc);
98
+ rb_gc_mark(bucket->on_connect_proc);
57
99
  rb_gc_mark(bucket->key_prefix_val);
58
- rb_gc_mark(bucket->object_space);
100
+ st_foreach(bucket->object_space, cb_bucket_mark_object_i, (st_data_t)bucket);
59
101
  }
60
102
  }
61
103
 
@@ -207,6 +249,26 @@ do_scan_connection_options(struct cb_bucket_st *bucket, int argc, VALUE *argv)
207
249
  bucket->default_arith_init = NUM2ULL(arg);
208
250
  }
209
251
  }
252
+ arg = rb_hash_aref(opts, cb_sym_engine);
253
+ if (arg != Qnil) {
254
+ if (arg == cb_sym_default) {
255
+ bucket->engine = cb_sym_default;
256
+ #ifndef _WIN32
257
+ } else if (arg == cb_sym_libev) {
258
+ bucket->engine = cb_sym_libev;
259
+ } else if (arg == cb_sym_libevent) {
260
+ bucket->engine = cb_sym_libevent;
261
+ #ifdef BUILD_EVENTMACHINE_PLUGIN
262
+ } else if (arg == cb_sym_eventmachine) {
263
+ bucket->engine = cb_sym_eventmachine;
264
+ #endif
265
+ #endif
266
+ } else {
267
+ VALUE ins = rb_funcall(arg, rb_intern("inspect"), 0);
268
+ rb_raise(rb_eArgError, "Couchbase: unknown engine %s", RSTRING_PTR(ins));
269
+ }
270
+ }
271
+ bucket->async = RTEST(rb_hash_aref(opts, cb_sym_async));
210
272
  } else {
211
273
  opts = Qnil;
212
274
  }
@@ -223,6 +285,17 @@ do_scan_connection_options(struct cb_bucket_st *bucket, int argc, VALUE *argv)
223
285
  rb_str_freeze(bucket->authority);
224
286
  }
225
287
 
288
+ static VALUE
289
+ em_disconnect_block(VALUE unused, VALUE self)
290
+ {
291
+ struct cb_bucket_st *bucket = DATA_PTR(self);
292
+ if (bucket->handle) {
293
+ return cb_bucket_disconnect(self);
294
+ }
295
+ (void)unused;
296
+ return Qnil;
297
+ }
298
+
226
299
  static void
227
300
  do_connect(struct cb_bucket_st *bucket)
228
301
  {
@@ -234,24 +307,37 @@ do_connect(struct cb_bucket_st *bucket)
234
307
  lcb_destroy_io_ops(bucket->io);
235
308
  bucket->handle = NULL;
236
309
  bucket->io = NULL;
310
+ bucket->connected = 0;
237
311
  }
238
312
 
239
- #ifndef _WIN32
240
313
  {
241
314
  struct lcb_create_io_ops_st ciops;
242
315
  memset(&ciops, 0, sizeof(ciops));
243
- ciops.version = 1;
244
- ciops.v.v1.sofile = NULL;
245
- ciops.v.v1.symbol = "cb_create_ruby_mt_io_opts";
246
- ciops.v.v1.cookie = NULL;
247
-
248
- err = lcb_create_io_ops(&bucket->io, &ciops);
249
- }
316
+ ciops.version = 0;
317
+
318
+ if (bucket->engine == cb_sym_libevent) {
319
+ ciops.v.v0.type = LCB_IO_OPS_LIBEVENT;
320
+ } else if (bucket->engine == cb_sym_libev) {
321
+ ciops.v.v0.type = LCB_IO_OPS_LIBEV;
322
+ } else if (bucket->engine == cb_sym_eventmachine) {
323
+ ciops.version = 1;
324
+ ciops.v.v1.sofile = NULL;
325
+ ciops.v.v1.symbol = "cb_create_ruby_em_io_opts";
326
+ ciops.v.v1.cookie = bucket;
327
+ } else {
328
+ #ifdef _WIN32
329
+ ciops.v.v0.type = LCB_IO_OPS_DEFAULT;
250
330
  #else
251
- err = lcb_create_io_ops(&bucket->io, NULL);
331
+ ciops.version = 1;
332
+ ciops.v.v1.sofile = NULL;
333
+ ciops.v.v1.symbol = "cb_create_ruby_mt_io_opts";
334
+ ciops.v.v1.cookie = NULL;
252
335
  #endif
253
- if (err != LCB_SUCCESS) {
254
- rb_exc_raise(cb_check_error(err, "failed to create IO instance", Qnil));
336
+ }
337
+ err = lcb_create_io_ops(&bucket->io, &ciops);
338
+ if (err != LCB_SUCCESS) {
339
+ rb_exc_raise(cb_check_error(err, "failed to create IO instance", Qnil));
340
+ }
255
341
  }
256
342
 
257
343
  memset(&create_opts, 0, sizeof(struct lcb_create_st));
@@ -279,6 +365,7 @@ do_connect(struct cb_bucket_st *bucket)
279
365
  (void)lcb_set_http_data_callback(bucket->handle, cb_http_data_callback);
280
366
  (void)lcb_set_observe_callback(bucket->handle, cb_observe_callback);
281
367
  (void)lcb_set_unlock_callback(bucket->handle, cb_unlock_callback);
368
+ (void)lcb_set_configuration_callback(bucket->handle, configuration_callback);
282
369
 
283
370
  if (bucket->timeout > 0) {
284
371
  lcb_set_timeout(bucket->handle, bucket->timeout);
@@ -294,13 +381,19 @@ do_connect(struct cb_bucket_st *bucket)
294
381
  rb_exc_raise(cb_check_error(err, "failed to connect libcouchbase instance to server", Qnil));
295
382
  }
296
383
  bucket->exception = Qnil;
297
- lcb_wait(bucket->handle);
298
- if (bucket->exception != Qnil) {
299
- lcb_destroy(bucket->handle);
300
- lcb_destroy_io_ops(bucket->io);
301
- bucket->handle = NULL;
302
- bucket->io = NULL;
303
- rb_exc_raise(bucket->exception);
384
+ if (bucket->engine == cb_sym_eventmachine && !bucket->async_disconnect_hook_set) {
385
+ bucket->async_disconnect_hook_set = 1;
386
+ rb_block_call(em_m, cb_id_add_shutdown_hook, 0, NULL, em_disconnect_block, bucket->self);
387
+ }
388
+ if (!bucket->async) {
389
+ lcb_wait(bucket->handle);
390
+ if (bucket->exception != Qnil) {
391
+ lcb_destroy(bucket->handle);
392
+ lcb_destroy_io_ops(bucket->io);
393
+ bucket->handle = NULL;
394
+ bucket->io = NULL;
395
+ rb_exc_raise(bucket->exception);
396
+ }
304
397
  }
305
398
  }
306
399
 
@@ -374,6 +467,17 @@ cb_bucket_alloc(VALUE klass)
374
467
  * non positive number forces creation missing keys with given default
375
468
  * value. Setting it to +true+ will use zero as initial value. (see
376
469
  * {Bucket#incr} and {Bucket#decr}).
470
+ * @option options [Symbol] :engine (:default) the IO engine to use
471
+ * Currently following engines are supported:
472
+ * :default :: Built-in engine (multi-thread friendly)
473
+ * :libevent :: libevent IO plugin from libcouchbase (optional)
474
+ * :libev :: libev IO plugin from libcouchbase (optional)
475
+ * :eventmachine :: EventMachine plugin (builtin, but requires EM gem and ruby 1.9+)
476
+ * @option options [true, false] :async (false) If true, the
477
+ * connection instance will be considered always asynchronous and
478
+ * IO interaction will be occured only when {Couchbase::Bucket#run}
479
+ * called. See {Couchbase::Bucket#on_connect} to hook your code
480
+ * after the instance will be connected.
377
481
  *
378
482
  * @example Initialize connection using default options
379
483
  * Couchbase.new
@@ -406,14 +510,13 @@ cb_bucket_init(int argc, VALUE *argv, VALUE self)
406
510
  bucket->self = self;
407
511
  bucket->exception = Qnil;
408
512
  bucket->type = LCB_TYPE_BUCKET;
409
- bucket->hostname = rb_str_new2("localhost");
513
+ bucket->hostname = cb_vStrLocalhost;
410
514
  bucket->port = 8091;
411
515
  bucket->pool = cb_vStrDefault;
412
- rb_str_freeze(bucket->pool);
413
516
  bucket->bucket = cb_vStrDefault;
414
- rb_str_freeze(bucket->bucket);
415
517
  bucket->username = Qnil;
416
518
  bucket->password = Qnil;
519
+ bucket->engine = cb_sym_default;
417
520
  bucket->async = 0;
418
521
  bucket->quiet = 0;
419
522
  bucket->default_ttl = 0;
@@ -421,11 +524,16 @@ cb_bucket_init(int argc, VALUE *argv, VALUE self)
421
524
  bucket->default_format = cb_sym_document;
422
525
  bucket->default_observe_timeout = 2500000;
423
526
  bucket->on_error_proc = Qnil;
527
+ bucket->on_connect_proc = Qnil;
424
528
  bucket->timeout = 0;
425
529
  bucket->environment = cb_sym_production;
426
530
  bucket->key_prefix_val = Qnil;
427
531
  bucket->node_list = Qnil;
428
- bucket->object_space = rb_hash_new();
532
+ bucket->object_space = st_init_numtable();
533
+ bucket->destroying = 0;
534
+ bucket->connected = 0;
535
+ bucket->on_connect_proc = Qnil;
536
+ bucket->async_disconnect_hook_set = 0;
429
537
 
430
538
  do_scan_connection_options(bucket, argc, argv);
431
539
  do_connect(bucket);
@@ -467,6 +575,7 @@ cb_bucket_init_copy(VALUE copy, VALUE orig)
467
575
  copy_b->bucket = orig_b->bucket;
468
576
  copy_b->username = orig_b->username;
469
577
  copy_b->password = orig_b->password;
578
+ copy_b->engine = orig_b->engine;
470
579
  copy_b->async = orig_b->async;
471
580
  copy_b->quiet = orig_b->quiet;
472
581
  copy_b->default_format = orig_b->default_format;
@@ -475,10 +584,17 @@ cb_bucket_init_copy(VALUE copy, VALUE orig)
475
584
  copy_b->environment = orig_b->environment;
476
585
  copy_b->timeout = orig_b->timeout;
477
586
  copy_b->exception = Qnil;
587
+ copy_b->async_disconnect_hook_set = 0;
478
588
  if (orig_b->on_error_proc != Qnil) {
479
589
  copy_b->on_error_proc = rb_funcall(orig_b->on_error_proc, cb_id_dup, 0);
480
590
  }
591
+ if (orig_b->on_connect_proc != Qnil) {
592
+ copy_b->on_connect_proc = rb_funcall(orig_b->on_connect_proc, cb_id_dup, 0);
593
+ }
481
594
  copy_b->key_prefix_val = orig_b->key_prefix_val;
595
+ copy_b->object_space = st_init_numtable();
596
+ copy_b->destroying = 0;
597
+ copy_b->connected = 0;
482
598
 
483
599
  do_connect(copy_b);
484
600
 
@@ -529,7 +645,7 @@ cb_bucket_reconnect(int argc, VALUE *argv, VALUE self)
529
645
  cb_bucket_connected_p(VALUE self)
530
646
  {
531
647
  struct cb_bucket_st *bucket = DATA_PTR(self);
532
- return bucket->handle ? Qtrue : Qfalse;
648
+ return (bucket->handle && bucket->connected) ? Qtrue : Qfalse;
533
649
  }
534
650
 
535
651
  /* Document-method: async?
@@ -654,6 +770,51 @@ cb_bucket_on_error_get(VALUE self)
654
770
  }
655
771
  }
656
772
 
773
+ static
774
+ VALUE trigger_on_connect_callback_block(VALUE nil, VALUE self)
775
+ {
776
+ (void)nil;
777
+ return trigger_on_connect_callback(self);
778
+ }
779
+
780
+ VALUE
781
+ cb_bucket_on_connect_set(VALUE self, VALUE val)
782
+ {
783
+ struct cb_bucket_st *bucket = DATA_PTR(self);
784
+
785
+ if (rb_respond_to(val, cb_id_call)) {
786
+ bucket->on_connect_proc = val;
787
+ if (bucket->trigger_connect_cb_on_set) {
788
+ bucket->trigger_connect_cb_on_set = 0;
789
+ if (bucket->async) {
790
+ VALUE args[] = {INT2FIX(0)};
791
+ /* setup timer with zero interval to call on_connect
792
+ * callback on the next tick */
793
+ rb_block_call(bucket->self, cb_id_create_timer, 1,
794
+ args, trigger_on_connect_callback_block, bucket->self);
795
+ } else {
796
+ trigger_on_connect_callback(self);
797
+ }
798
+ }
799
+ } else {
800
+ bucket->on_connect_proc = Qnil;
801
+ }
802
+
803
+ return bucket->on_connect_proc;
804
+ }
805
+
806
+ VALUE
807
+ cb_bucket_on_connect_get(VALUE self)
808
+ {
809
+ struct cb_bucket_st *bucket = DATA_PTR(self);
810
+
811
+ if (rb_block_given_p()) {
812
+ return cb_bucket_on_connect_set(self, rb_block_proc());
813
+ } else {
814
+ return bucket->on_connect_proc;
815
+ }
816
+ }
817
+
657
818
  VALUE
658
819
  cb_bucket_timeout_get(VALUE self)
659
820
  {
@@ -947,7 +1108,7 @@ cb_bucket_inspect(VALUE self)
947
1108
  rb_id2name(SYM2ID(bucket->default_format)),
948
1109
  bucket->default_flags,
949
1110
  bucket->quiet ? "true" : "false",
950
- bucket->handle ? "true" : "false",
1111
+ (bucket->handle && bucket->connected) ? "true" : "false",
951
1112
  bucket->timeout);
952
1113
  rb_str_buf_cat2(str, buf);
953
1114
  if (RTEST(bucket->key_prefix_val)) {
@@ -978,12 +1139,14 @@ cb_maybe_do_loop(struct cb_bucket_st *bucket)
978
1139
  do_run(VALUE *args)
979
1140
  {
980
1141
  VALUE self = args[0], opts = args[1], proc = args[2], exc;
1142
+ VALUE was_async = args[3];
981
1143
  struct cb_bucket_st *bucket = DATA_PTR(self);
982
1144
 
983
1145
  if (bucket->handle == NULL) {
984
1146
  rb_raise(cb_eConnectError, "closed connection");
985
1147
  }
986
- if (bucket->async) {
1148
+
1149
+ if (bucket->running) {
987
1150
  rb_raise(cb_eInvalidError, "nested #run");
988
1151
  }
989
1152
  bucket->threshold = 0;
@@ -996,12 +1159,29 @@ do_run(VALUE *args)
996
1159
  }
997
1160
  }
998
1161
  bucket->async = 1;
999
- cb_proc_call(bucket, proc, 1, self);
1162
+ bucket->running = 1;
1163
+ if (proc != Qnil) {
1164
+ cb_proc_call(bucket, proc, 1, self);
1165
+ }
1166
+ if (bucket->exception != Qnil) {
1167
+ exc = bucket->exception;
1168
+ bucket->exception = Qnil;
1169
+ if (was_async) {
1170
+ cb_async_error_notify(bucket, exc);
1171
+ /* XXX return here? */
1172
+ } else {
1173
+ rb_exc_raise(exc);
1174
+ }
1175
+ }
1000
1176
  do_loop(bucket);
1001
1177
  if (bucket->exception != Qnil) {
1002
1178
  exc = bucket->exception;
1003
1179
  bucket->exception = Qnil;
1004
- rb_exc_raise(exc);
1180
+ if (!was_async) {
1181
+ rb_exc_raise(exc);
1182
+ }
1183
+ /* async connections notified immediately from the callbacks
1184
+ * via cb_async_error_notify() */
1005
1185
  }
1006
1186
  return Qnil;
1007
1187
  }
@@ -1012,7 +1192,9 @@ ensure_run(VALUE *args)
1012
1192
  VALUE self = args[0];
1013
1193
  struct cb_bucket_st *bucket = DATA_PTR(self);
1014
1194
 
1015
- bucket->async = 0;
1195
+ bucket->running = 0;
1196
+ bucket->async = args[3];
1197
+ bucket->running = args[4];
1016
1198
  return Qnil;
1017
1199
  }
1018
1200
 
@@ -1054,6 +1236,12 @@ ensure_run(VALUE *args)
1054
1236
  * end
1055
1237
  * # all commands were executed and sent is 3 now
1056
1238
  *
1239
+ * @example Use {Couchbase::Bucket#run} without block for async connection
1240
+ * c = Couchbase.new(:async => true)
1241
+ * c.run # ensure that instance connected
1242
+ * c.set("foo", "bar"){|r| puts r.cas}
1243
+ * c.run
1244
+ *
1057
1245
  * @return [nil]
1058
1246
  *
1059
1247
  * @raise [Couchbase::Error::Connect] if connection closed (see {Bucket#reconnect})
@@ -1061,11 +1249,17 @@ ensure_run(VALUE *args)
1061
1249
  VALUE
1062
1250
  cb_bucket_run(int argc, VALUE *argv, VALUE self)
1063
1251
  {
1064
- VALUE args[3];
1252
+ struct cb_bucket_st *bucket = DATA_PTR(self);
1253
+ VALUE args[5];
1065
1254
 
1066
- rb_need_block();
1255
+ /* it is allowed to omit block for async connections */
1256
+ if (!bucket->async) {
1257
+ rb_need_block();
1258
+ }
1067
1259
  args[0] = self;
1068
1260
  rb_scan_args(argc, argv, "01&", &args[1], &args[2]);
1261
+ args[3] = bucket->async;
1262
+ args[4] = bucket->running;
1069
1263
  rb_ensure(do_run, (VALUE)args, ensure_run, (VALUE)args);
1070
1264
  return Qnil;
1071
1265
  }
@@ -1115,6 +1309,7 @@ cb_bucket_disconnect(VALUE self)
1115
1309
  lcb_destroy_io_ops(bucket->io);
1116
1310
  bucket->handle = NULL;
1117
1311
  bucket->io = NULL;
1312
+ bucket->connected = 0;
1118
1313
  return Qtrue;
1119
1314
  } else {
1120
1315
  rb_raise(cb_eConnectError, "closed connection");