couchbase 1.2.1-x86-mingw32 → 1.2.2-x86-mingw32

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/couchbase.gemspec CHANGED
@@ -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");