moneta 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rubocop.yml +1 -1
  4. data/.travis.yml +10 -4
  5. data/CHANGES +9 -0
  6. data/Gemfile +7 -5
  7. data/README.md +9 -6
  8. data/feature_matrix.yaml +2 -1
  9. data/lib/moneta/adapters/client.rb +56 -19
  10. data/lib/moneta/adapters/couch.rb +5 -0
  11. data/lib/moneta/adapters/mongo/moped.rb +4 -1
  12. data/lib/moneta/builder.rb +2 -2
  13. data/lib/moneta/lock.rb +6 -1
  14. data/lib/moneta/pool.rb +12 -0
  15. data/lib/moneta/proxy.rb +1 -1
  16. data/lib/moneta/server.rb +215 -61
  17. data/lib/moneta/shared.rb +13 -7
  18. data/lib/moneta/transformer.rb +50 -8
  19. data/lib/moneta/transformer/config.rb +59 -40
  20. data/lib/moneta/version.rb +1 -1
  21. data/moneta.gemspec +2 -2
  22. data/script/benchmarks +6 -1
  23. data/script/contributors +1 -2
  24. data/script/start-couchdb +27 -0
  25. data/script/start-hbase +2 -2
  26. data/script/start-services +3 -3
  27. data/spec/features/store.rb +3 -3
  28. data/spec/features/transform_value.rb +27 -21
  29. data/spec/helper.rb +52 -53
  30. data/spec/moneta/adapters/activerecord/standard_activerecord_spec.rb +1 -1
  31. data/spec/moneta/adapters/activerecord/standard_activerecord_with_expires_spec.rb +1 -1
  32. data/spec/moneta/adapters/cassandra/standard_cassandra_spec.rb +1 -1
  33. data/spec/moneta/adapters/client/adapter_client_spec.rb +6 -6
  34. data/spec/moneta/adapters/client/client_helper.rb +24 -0
  35. data/spec/moneta/adapters/client/standard_client_tcp_spec.rb +8 -8
  36. data/spec/moneta/adapters/client/standard_client_unix_spec.rb +23 -7
  37. data/spec/moneta/adapters/couch/adapter_couch_spec.rb +1 -1
  38. data/spec/moneta/adapters/couch/standard_couch_spec.rb +2 -2
  39. data/spec/moneta/adapters/couch/standard_couch_with_expires_spec.rb +2 -2
  40. data/spec/moneta/adapters/daybreak/standard_daybreak_spec.rb +1 -1
  41. data/spec/moneta/adapters/daybreak/standard_daybreak_with_expires_spec.rb +1 -1
  42. data/spec/moneta/adapters/dbm/standard_dbm_spec.rb +1 -1
  43. data/spec/moneta/adapters/dbm/standard_dbm_with_expires_spec.rb +1 -1
  44. data/spec/moneta/adapters/file/standard_file_spec.rb +2 -2
  45. data/spec/moneta/adapters/file/standard_file_with_expires_spec.rb +1 -1
  46. data/spec/moneta/adapters/gdbm/standard_gdbm_spec.rb +1 -1
  47. data/spec/moneta/adapters/gdbm/standard_gdbm_with_expires_spec.rb +1 -1
  48. data/spec/moneta/adapters/kyotocabinet/adapter_kyotocabinet_spec.rb +1 -1
  49. data/spec/moneta/adapters/kyotocabinet/standard_kyotocabinet_spec.rb +2 -2
  50. data/spec/moneta/adapters/kyotocabinet/standard_kyotocabinet_with_expires_spec.rb +2 -2
  51. data/spec/moneta/adapters/leveldb/standard_leveldb_spec.rb +1 -1
  52. data/spec/moneta/adapters/leveldb/standard_leveldb_with_expires_spec.rb +1 -1
  53. data/spec/moneta/adapters/lmdb/standard_lmdb_spec.rb +1 -1
  54. data/spec/moneta/adapters/lmdb/standard_lmdb_with_expires_spec.rb +1 -1
  55. data/spec/moneta/adapters/lruhash/standard_lruhash_spec.rb +1 -1
  56. data/spec/moneta/adapters/lruhash/standard_lruhash_with_expires_spec.rb +1 -1
  57. data/spec/moneta/adapters/memory/standard_memory_spec.rb +1 -1
  58. data/spec/moneta/adapters/memory/standard_memory_with_compress_spec.rb +1 -1
  59. data/spec/moneta/adapters/memory/standard_memory_with_expires_spec.rb +1 -1
  60. data/spec/moneta/adapters/memory/standard_memory_with_json_key_serializer_spec.rb +1 -1
  61. data/spec/moneta/adapters/memory/standard_memory_with_json_serializer_spec.rb +1 -1
  62. data/spec/moneta/adapters/memory/standard_memory_with_json_value_serializer_spec.rb +2 -2
  63. data/spec/moneta/adapters/memory/standard_memory_with_prefix_spec.rb +39 -2
  64. data/spec/moneta/adapters/memory/standard_memory_with_snappy_compress_spec.rb +2 -2
  65. data/spec/moneta/adapters/mongo/adapter_mongo_moped_spec.rb +4 -3
  66. data/spec/moneta/adapters/mongo/adapter_mongo_moped_with_default_expires_spec.rb +5 -3
  67. data/spec/moneta/adapters/mongo/adapter_mongo_official_spec.rb +4 -2
  68. data/spec/moneta/adapters/mongo/adapter_mongo_official_with_default_expires_spec.rb +5 -3
  69. data/spec/moneta/adapters/mongo/adapter_mongo_spec.rb +3 -2
  70. data/spec/moneta/adapters/mongo/adapter_mongo_with_default_expires_spec.rb +5 -3
  71. data/spec/moneta/adapters/mongo/standard_mongo_moped_spec.rb +2 -2
  72. data/spec/moneta/adapters/mongo/standard_mongo_official_spec.rb +2 -2
  73. data/spec/moneta/adapters/mongo/standard_mongo_spec.rb +2 -2
  74. data/spec/moneta/adapters/pstore/standard_pstore_spec.rb +1 -1
  75. data/spec/moneta/adapters/pstore/standard_pstore_with_expires_spec.rb +1 -1
  76. data/spec/moneta/adapters/redis/standard_redis_spec.rb +1 -1
  77. data/spec/moneta/adapters/sdbm/standard_sdbm_spec.rb +1 -1
  78. data/spec/moneta/adapters/sdbm/standard_sdbm_with_expires_spec.rb +1 -1
  79. data/spec/moneta/adapters/sequel/standard_sequel_spec.rb +1 -1
  80. data/spec/moneta/adapters/sequel/standard_sequel_with_expires_spec.rb +1 -1
  81. data/spec/moneta/adapters/sqlite/standard_sqlite_spec.rb +1 -1
  82. data/spec/moneta/adapters/sqlite/standard_sqlite_with_expires_spec.rb +1 -1
  83. data/spec/moneta/adapters/tdb/standard_tdb_spec.rb +1 -1
  84. data/spec/moneta/adapters/tdb/standard_tdb_with_expires_spec.rb +1 -1
  85. data/spec/moneta/adapters/tokyocabinet/standard_tokyocabinet_spec.rb +1 -1
  86. data/spec/moneta/adapters/tokyocabinet/standard_tokyocabinet_with_expires_spec.rb +1 -1
  87. data/spec/moneta/adapters/yaml/standard_yaml_spec.rb +1 -1
  88. data/spec/moneta/adapters/yaml/standard_yaml_with_expires_spec.rb +1 -1
  89. data/spec/moneta/builder_spec.rb +22 -0
  90. data/spec/moneta/proxies/expires/expires_file_spec.rb +1 -1
  91. data/spec/moneta/proxies/shared/shared_tcp_spec.rb +14 -4
  92. data/spec/moneta/proxies/shared/shared_unix_spec.rb +4 -4
  93. data/spec/moneta/proxies/transformer/transformer_bencode_spec.rb +1 -1
  94. data/spec/moneta/proxies/transformer/transformer_bert_spec.rb +3 -3
  95. data/spec/moneta/proxies/transformer/transformer_bson_spec.rb +2 -2
  96. data/spec/moneta/proxies/transformer/transformer_json_spec.rb +1 -1
  97. data/spec/moneta/proxies/transformer/transformer_key_marshal_spec.rb +1 -1
  98. data/spec/moneta/proxies/transformer/transformer_key_yaml_spec.rb +1 -1
  99. data/spec/moneta/proxies/transformer/transformer_marshal_base64_spec.rb +1 -1
  100. data/spec/moneta/proxies/transformer/transformer_marshal_escape_spec.rb +1 -1
  101. data/spec/moneta/proxies/transformer/transformer_marshal_hex_spec.rb +1 -1
  102. data/spec/moneta/proxies/transformer/transformer_marshal_hmac_spec.rb +1 -1
  103. data/spec/moneta/proxies/transformer/transformer_marshal_prefix_base64_spec.rb +33 -0
  104. data/spec/moneta/proxies/transformer/transformer_marshal_prefix_spec.rb +1 -1
  105. data/spec/moneta/proxies/transformer/transformer_marshal_qp_spec.rb +1 -1
  106. data/spec/moneta/proxies/transformer/transformer_marshal_spec.rb +1 -1
  107. data/spec/moneta/proxies/transformer/transformer_marshal_urlsafe_base64_spec.rb +1 -1
  108. data/spec/moneta/proxies/transformer/transformer_marshal_uuencode_spec.rb +1 -1
  109. data/spec/moneta/proxies/transformer/transformer_msgpack_spec.rb +1 -1
  110. data/spec/moneta/proxies/transformer/transformer_ox_spec.rb +1 -1
  111. data/spec/moneta/proxies/transformer/transformer_php_spec.rb +1 -1
  112. data/spec/moneta/proxies/transformer/transformer_tnet_spec.rb +1 -1
  113. data/spec/moneta/proxies/transformer/transformer_yaml_spec.rb +2 -2
  114. data/spec/moneta/proxies/weak_each_key/weak_each_key_spec.rb +0 -2
  115. data/spec/support/mongo_helper.rb +12 -0
  116. metadata +18 -12
  117. data/script/reconfigure-couchdb +0 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 535578909c47f29047f67d577e053809a637f77ea8ba2dde7920198f8b08ac49
4
- data.tar.gz: 275edd79995139f4918749d01ccdf8129748ea96e1ba4302716dd7cb1ed57bf9
3
+ metadata.gz: 9aebec0b5bcf8b12d295aff540c30c453e88883e5d5d498f92f3666adcbf4eeb
4
+ data.tar.gz: f62cbb548556e6d4a36e0465edb468a7421262620469d7329c34cb7d160553bc
5
5
  SHA512:
6
- metadata.gz: c07c093306f66138db9db041a15103420fdd0e5fef93ca429098de02a732055d441c0968570704ba39dcb08608b7dbfa12b5cd5797e257df1c8be28a7b3b028d
7
- data.tar.gz: a654005281162398bf492fd7fa3e3c59ee40f675d6a187579d4cbfcb0bfbd14e0a78a9f9bb3227e5c64c02e4c97e60bb9b39075457803882aeaa3d585b0e9a51
6
+ metadata.gz: e9544cff0b2b3550776646d83131b8c7db4c3a9e79083ddb56af23a4cffaa9ce5a3de4a8c1db2db3571e768fe00c409692c3a2787ac9e7e8312a3f145d36f270
7
+ data.tar.gz: 96cf85c80a0dbb86c820351e711470f873139e6544ec450e427a5cba92cf23f615bedd7f2ff5fda7eca8f005631e0dbcb5e79550ef001234b6d231fcaa71c924
data/.gitignore CHANGED
@@ -11,3 +11,5 @@ Gemfile.lock
11
11
  logs
12
12
  secure
13
13
  .bundle
14
+ .ruby-version
15
+ .byebug_history
data/.rubocop.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  AllCops:
2
- TargetRubyVersion: 2.2.2
2
+ TargetRubyVersion: 2.3.0
3
3
  UseCache: true
4
4
  Exclude:
5
5
  - hbase/**/*
data/.travis.yml CHANGED
@@ -1,12 +1,12 @@
1
1
  dist: xenial
2
2
  language: ruby
3
3
  rvm:
4
+ - 2.7
4
5
  - 2.6
5
6
  - 2.5
6
7
  - 2.4
7
8
  - 2.3
8
- - 2.2
9
- - jruby-9.2
9
+ - jruby-9.2.9.0
10
10
  - rbx-3
11
11
  services:
12
12
  - mysql
@@ -26,7 +26,6 @@ addons:
26
26
  key_url: https://packagecloud.io/basho/riak/gpgkey
27
27
  packages:
28
28
  - cassandra
29
- - couchdb
30
29
  - libkyotocabinet-dev
31
30
  - libleveldb-dev
32
31
  - liblzo2-dev
@@ -36,6 +35,7 @@ addons:
36
35
  - riak
37
36
  postgresql: '9.6'
38
37
  before_install:
38
+ - gem install bundler
39
39
  - script/start-services
40
40
  cache:
41
41
  bundler: true
@@ -47,6 +47,10 @@ before_script:
47
47
  - psql -c 'create database moneta1;' -U postgres
48
48
  - psql -c 'create extension hstore;' -U postgres moneta1
49
49
  - psql -c 'create database moneta2;' -U postgres
50
+ # FIXME: remove this once moneta no longer generates hundreds of warnings
51
+ # in Ruby 2.7
52
+ - if echo $RUBY_VERSION | grep -q 'ruby-2.7'; then export RUBYOPT='-W0'; fi
53
+ - export
50
54
  env:
51
55
  matrix:
52
56
  - SCRIPT='script/parallel-tests ~unstable --
@@ -120,6 +124,8 @@ matrix:
120
124
  - rvm: rbx-3
121
125
  - env: SCRIPT='script/parallel-tests unstable'
122
126
  exclude:
127
+ - rvm: 2.6
128
+ env: SCRIPT='rubocop lib'
123
129
  - rvm: 2.5
124
130
  env: SCRIPT='rubocop lib'
125
131
  - rvm: 2.4
@@ -128,7 +134,7 @@ matrix:
128
134
  env: SCRIPT='rubocop lib'
129
135
  - rvm: 2.2
130
136
  env: SCRIPT='rubocop lib'
131
- - rvm: jruby-9.2
137
+ - rvm: jruby-9.2.9.0
132
138
  env: SCRIPT='rubocop lib'
133
139
  - rvm: rbx-3
134
140
  env: SCRIPT='rubocop lib'
data/CHANGES CHANGED
@@ -1,3 +1,12 @@
1
+ 1.3.0
2
+
3
+ * Transformer - add :each_key support (#170)
4
+ * Server - add :each_key support, use non-blocking IO (#165)
5
+ * Builder - dup options before passing to adapter/proxy (#174)
6
+ * Adapter::Couch - add HTTP basic auth support
7
+ * Support MRI 2.7.0 (#172)
8
+ * Minimum required MRI version is now 2.3.0 (#172)
9
+
1
10
  1.2.1
2
11
 
3
12
  * Transformer - fix :escape transformer deserialize implementation (#168)
data/Gemfile CHANGED
@@ -23,7 +23,7 @@ group :msgpack do
23
23
  end
24
24
 
25
25
  # Compressors used by Transformer
26
- gem 'rbzip2', '~> 0.3.0', group: :bzip2
26
+ gem 'rbzip2', '>= 0.3.0', group: :bzip2
27
27
  gem 'lz4-ruby', platforms: :ruby, group: :lz4
28
28
  gem 'ruby-lzma', platforms: :ruby, group: :lzma
29
29
  gem 'lzoruby', platforms: :ruby, group: :lzo
@@ -37,8 +37,8 @@ gem 'cityhash', platforms: :ruby, group: :city
37
37
  gem 'daybreak', group: :daybreak
38
38
  gem 'activerecord', '~> 5.2', group: :activerecord
39
39
  gem 'redis', '~> 4.0.0', group: :redis
40
- gem 'mongo', '~> 2.1.0', group: :mongo_official
41
- gem 'moped', '>= 2.0.0', group: :mongo_moped
40
+ gem 'mongo', '>= 2', group: :mongo_official
41
+ gem 'moped', '>= 2', group: :mongo_moped
42
42
  gem 'sequel', group: :sequel
43
43
  gem 'dalli', group: :memcached_dalli
44
44
  gem 'riak-client', group: :riak
@@ -50,7 +50,7 @@ gem 'tdb', platforms: :ruby, group: :tdb
50
50
  gem 'leveldb-ruby', platforms: :ruby, group: :leveldb
51
51
  gem 'lmdb', platforms: :mri, group: :lmdb
52
52
  gem 'tokyocabinet', platforms: :ruby, group: :tokyocabinet
53
- gem 'kyotocabinet-ruby-reanimated', platforms: :ruby, group: :kyotocabinet
53
+ gem 'kyotocabinet-ruby-reanimated', platforms: [:ruby_23, :ruby_24, :ruby_25, :ruby_26], group: :kyotocabinet
54
54
  gem 'memcached', platforms: :ruby, group: :memcached_native
55
55
  gem 'jruby-memcached', platforms: :jruby, group: :memcached_native
56
56
  gem 'activerecord-jdbch2-adapter', platforms: :jruby, group: :h2, github: 'jruby/activerecord-jdbc-adapter', glob: 'activerecord-jdbch2-adapter/*.gemspec', branch: '52-stable'
@@ -94,7 +94,9 @@ end
94
94
 
95
95
  # Rails integration testing
96
96
  group :rails do
97
- gem 'actionpack', '~> 5.0'
97
+ git 'https://github.com/rails/rails.git', branch: '5-2-stable' do
98
+ gem 'actionpack'
99
+ end
98
100
  gem 'minitest', '~> 5.0'
99
101
  end
100
102
 
data/README.md CHANGED
@@ -214,7 +214,7 @@ __NOTE:__ <a name="backend-matrix"></a>The backend matrix is much more readable
214
214
 
215
215
  <tr><th colspan="2">Network clients</th><th colspan="12"></th></tr>
216
216
 
217
- <tr><td>Client</td><td>-</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#F44">✗</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#55F">?<sup>13</sup></td><td style="text-align:center;background:#55F">?<sup>13</sup></td><td style="text-align:center;background:#55F">?<sup>13</sup></td><td style="text-align:center;background:#55F">?<sup>13</sup></td><td style="text-align:center;background:#F44">✗</td><td style="text-align:center;background:#F44">✗</td><td style="text-align:center;background:#F44">✗</td><td>Moneta client adapter</td></tr>
217
+ <tr><td>Client</td><td>-</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#F44">✗</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#55F">?<sup>13</sup></td><td style="text-align:center;background:#55F">?<sup>13</sup></td><td style="text-align:center;background:#55F">?<sup>13</sup></td><td style="text-align:center;background:#55F">?<sup>13</sup></td><td style="text-align:center;background:#55F">?<sup>13</sup></td><td style="text-align:center;background:#F44">✗</td><td style="text-align:center;background:#F44">✗</td><td>Moneta client adapter</td></tr>
218
218
 
219
219
  <tr><td>RestClient</td><td>-</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#F44">✗</td><td style="text-align:center;background:#5F5">✓</td><td style="text-align:center;background:#F44">✗</td><td style="text-align:center;background:#F44">✗</td><td style="text-align:center;background:#F44">✗</td><td style="text-align:center;background:#55F">?<sup>13</sup></td><td style="text-align:center;background:#F44">✗</td><td style="text-align:center;background:#F44">✗</td><td style="text-align:center;background:#F44">✗</td><td>Moneta REST client adapter</td></tr>
220
220
 
@@ -913,12 +913,15 @@ Person.adapter :memory, Moneta.new(:Redis)
913
913
 
914
914
  ## Testing and Benchmarks
915
915
 
916
- Testing is done using [Travis-CI](http://travis-ci.org/moneta-rb/moneta). Currently we support MRI Ruby >= 2.2.2 and JRuby 9.2.x.
916
+ Testing is done using [Travis-CI](http://travis-ci.org/moneta-rb/moneta).
917
+ Currently we support MRI Ruby >= 2.3.0 and the JRuby >= 9.2.9.0.
917
918
 
918
- Benchmarks for each store are done on [Travis-CI](http://travis-ci.org/moneta-rb/moneta) for each build. Take a look there
919
- to compare the speed of the different key value stores for different key/value sizes and size distributions.
920
- Feel free to add your own configurations! The impact of Moneta should be minimal since it is only a thin layer
921
- on top of the different stores.
919
+ Benchmarks for each store are done on
920
+ [Travis-CI](http://travis-ci.org/moneta-rb/moneta) for each build. Take a look
921
+ there to compare the speed of the different key value stores for different
922
+ key/value sizes and size distributions. Feel free to add your own
923
+ configurations! The impact of Moneta should be minimal since it is only a thin
924
+ layer on top of the different stores.
922
925
 
923
926
 
924
927
  ------
data/feature_matrix.yaml CHANGED
@@ -220,13 +220,14 @@ backends:
220
220
  - adapter: Client
221
221
  platforms: [ MRI, JRuby ]
222
222
  features: [multiprocess]
223
- unknown: [ increment, create, expires, persist ]
223
+ unknown: [ increment, create, expires, persist, each_key ]
224
224
  description: "Moneta client adapter"
225
225
  notes:
226
226
  increment: depends on server
227
227
  create: depends on server
228
228
  expires: depends on server
229
229
  persist: depends on server
230
+ each_key: depends on server
230
231
  - adapter: RestClient
231
232
  platforms: [ MRI, JRuby ]
232
233
  features: [ multiprocess ]
@@ -23,44 +23,44 @@ module Moneta
23
23
  # (see Proxy#key?)
24
24
  def key?(key, options = {})
25
25
  write(:key?, key, options)
26
- read
26
+ read_msg
27
27
  end
28
28
 
29
29
  # (see Proxy#load)
30
30
  def load(key, options = {})
31
31
  write(:load, key, options)
32
- read
32
+ read_msg
33
33
  end
34
34
 
35
35
  # (see Proxy#store)
36
36
  def store(key, value, options = {})
37
37
  write(:store, key, value, options)
38
- read
38
+ read_msg
39
39
  value
40
40
  end
41
41
 
42
42
  # (see Proxy#delete)
43
43
  def delete(key, options = {})
44
44
  write(:delete, key, options)
45
- read
45
+ read_msg
46
46
  end
47
47
 
48
48
  # (see Proxy#increment)
49
49
  def increment(key, amount = 1, options = {})
50
50
  write(:increment, key, amount, options)
51
- read
51
+ read_msg
52
52
  end
53
53
 
54
54
  # (see Proxy#create)
55
55
  def create(key, value, options = {})
56
56
  write(:create, key, value, options)
57
- read
57
+ read_msg
58
58
  end
59
59
 
60
60
  # (see Proxy#clear)
61
61
  def clear(options = {})
62
62
  write(:clear, options)
63
- read
63
+ read_msg
64
64
  self
65
65
  end
66
66
 
@@ -70,12 +70,43 @@ module Moneta
70
70
  nil
71
71
  end
72
72
 
73
+ # (see Proxy#each_key)
74
+ def each_key
75
+ raise NotImplementedError, 'each_key is not supported' unless supports?(:each_key)
76
+ return enum_for(:each_key) unless block_given?
77
+
78
+ begin
79
+ write(:each_key)
80
+ yield_break = false
81
+
82
+ loop do
83
+ write('NEXT')
84
+
85
+ # A StopIteration error will be raised by this call if the server
86
+ # reached the end of the enumeration. This will stop the loop
87
+ # automatically.
88
+ result = read_msg
89
+
90
+ # yield_break will be true in the ensure block (below) if anything
91
+ # happened during the yield to stop further enumeration.
92
+ yield_break = true
93
+ yield result
94
+ yield_break = false
95
+ end
96
+ ensure
97
+ write('BREAK') if yield_break
98
+ read_msg # nil return from each_key
99
+ end
100
+
101
+ self
102
+ end
103
+
73
104
  # (see Default#features)
74
105
  def features
75
106
  @features ||=
76
107
  begin
77
108
  write(:features)
78
- read.freeze
109
+ read_msg.freeze
79
110
  end
80
111
  end
81
112
 
@@ -86,18 +117,24 @@ module Moneta
86
117
  @socket.write([s.bytesize].pack('N') << s)
87
118
  end
88
119
 
89
- def read
90
- size = @socket
91
- .recv(4).tap do |bytes|
92
- raise EOFError, 'failed to read size' unless bytes.bytesize == 4
93
- end
94
- .unpack('N')
95
- .first
96
-
97
- result = Marshal.load(@socket.recv(size).tap do |bytes|
98
- raise EOFError, 'Not enough bytes read' unless bytes.bytesize == size
99
- end)
120
+ # JRuby doesn't support socket#recv with flags
121
+ if defined?(JRUBY_VERSION)
122
+ def read(bytes)
123
+ received = @socket.read(bytes)
124
+ raise EOFError, "Server closed socket" unless received && received.bytesize == bytes
125
+ received
126
+ end
127
+ else
128
+ def read(bytes)
129
+ received = @socket.recv(bytes, Socket::MSG_WAITALL)
130
+ raise EOFError, "Server closed socket" unless received && received.bytesize == bytes
131
+ received
132
+ end
133
+ end
100
134
 
135
+ def read_msg
136
+ size = read(4).unpack('N').first
137
+ result = Marshal.load(read(size))
101
138
  raise result if Exception === result
102
139
  result
103
140
  end
@@ -39,6 +39,8 @@ module Moneta
39
39
  # @option options [String] :scheme ('http') HTTP scheme to use
40
40
  # @option options [String] :value_field ('value') Document field to store value
41
41
  # @option options [String] :type_field ('type') Document field to store value type
42
+ # @option options [String] :login Login name to use for HTTP basic authentication
43
+ # @option options [String] :password Password to use for HTTP basic authentication
42
44
  # @option options [Symbol] :adapter Adapter to use with Faraday
43
45
  # @option options [Faraday::Connecton] :backend Use existing backend instance
44
46
  # @option options Other options passed to {Faraday::new} (unless
@@ -46,6 +48,8 @@ module Moneta
46
48
  def initialize(options = {})
47
49
  @value_field = options.delete(:value_field) || 'value'
48
50
  @type_field = options.delete(:type_field) || 'type'
51
+ login = options.delete(:login)
52
+ password = options.delete(:password)
49
53
  @backend = options.delete(:backend) || begin
50
54
  host = options.delete(:host) || '127.0.0.1'
51
55
  port = options.delete(:port) || 5984
@@ -56,6 +60,7 @@ module Moneta
56
60
  end
57
61
  ::Faraday.new("#{scheme}://#{host}:#{port}/#{db}", options, &block)
58
62
  end
63
+ @backend.basic_auth(login, password) if login && password
59
64
  @rev_cache = Moneta.build do
60
65
  use :Lock
61
66
  adapter :LRUHash
@@ -44,7 +44,10 @@ module Moneta
44
44
  @backend.use(db)
45
45
  @backend.login(user, password) if user && password
46
46
  @collection = @backend[collection]
47
- if @backend.command(buildinfo: 1)['version'] >= '2.2'
47
+ if @backend.command(buildinfo: 1)['version'] >= '3.0'
48
+ # Moped creates indexes in the system.indexes collection which is not writable anymore since Mongo v3
49
+ warn 'Moneta::Adapters::MongoMoped - You are using the unmaintained Moped gem, expired documents will not be deleted'
50
+ elsif @backend.command(buildinfo: 1)['version'] >= '2.2'
48
51
  @collection.indexes.create({ @expires_field => 1 }, expireAfterSeconds: 0)
49
52
  else
50
53
  warn 'Moneta::Adapters::Mongo - You are using MongoDB version < 2.2, expired documents will not be deleted'
@@ -17,12 +17,12 @@ module Moneta
17
17
  adapter = @proxies.first
18
18
  if Array === adapter
19
19
  klass, options, block = adapter
20
- adapter = new_proxy(klass, options, &block)
20
+ adapter = new_proxy(klass, options.dup, &block)
21
21
  check_arity(klass, adapter, 1)
22
22
  end
23
23
  @proxies[1..-1].inject([adapter]) do |result, proxy|
24
24
  klass, options, block = proxy
25
- proxy = new_proxy(klass, result.last, options, &block)
25
+ proxy = new_proxy(klass, result.last, options.dup, &block)
26
26
  check_arity(klass, proxy, 2)
27
27
  result << proxy
28
28
  end
data/lib/moneta/lock.rb CHANGED
@@ -15,6 +15,7 @@ module Moneta
15
15
  protected
16
16
 
17
17
  def wrap(name, *args, &block)
18
+ self.locks ||= Set.new
18
19
  if locked?
19
20
  yield
20
21
  else
@@ -22,8 +23,12 @@ module Moneta
22
23
  end
23
24
  end
24
25
 
26
+ def locks=(locks)
27
+ Thread.current.thread_variable_set('Moneta::Lock', locks)
28
+ end
29
+
25
30
  def locks
26
- Thread.current['Moneta::Lock'] ||= Set.new
31
+ Thread.current.thread_variable_get('Moneta::Lock')
27
32
  end
28
33
 
29
34
  def lock!(&block)
data/lib/moneta/pool.rb CHANGED
@@ -298,6 +298,18 @@ module Moneta
298
298
  # by the manager after the ttl
299
299
  def close; end
300
300
 
301
+ def each_key(&block)
302
+ wrap(:each_key) do
303
+ raise NotImplementedError, "each_key is not supported on this proxy" \
304
+ unless supports? :each_key
305
+
306
+ return enum_for(:each_key) { adapter ? adapter.each_key.size : check_out! { adapter.each_key.size } } unless block_given?
307
+
308
+ adapter.each_key(&block)
309
+ self
310
+ end
311
+ end
312
+
301
313
  # Tells the manager to close all stores. It will not be possible to use
302
314
  # the store after this.
303
315
  def stop
data/lib/moneta/proxy.rb CHANGED
@@ -22,7 +22,7 @@ module Moneta
22
22
  raise NotImplementedError, "each_key is not supported on this proxy" \
23
23
  unless supports? :each_key
24
24
 
25
- return enum_for(:each_key) unless block_given?
25
+ return enum_for(:each_key) { adapter.each_key.size } unless block_given?
26
26
  adapter.each_key(&block)
27
27
  self
28
28
  end
data/lib/moneta/server.rb CHANGED
@@ -4,6 +4,154 @@ module Moneta
4
4
  # Moneta server to be used together with Moneta::Adapters::Client
5
5
  # @api public
6
6
  class Server
7
+ TIMEOUT = 1
8
+ MAXSIZE = 0x100000
9
+
10
+ # @api private
11
+ class Connection
12
+ def initialize(io, store)
13
+ @io = io
14
+ @store = store
15
+ @fiber = Fiber.new { run }
16
+ end
17
+
18
+ def resume(result = nil)
19
+ @fiber.resume result
20
+ end
21
+
22
+ private
23
+
24
+ # The return value of this function will be sent to the reactor.
25
+ #
26
+ # @return [:closed,Exception]
27
+ def run
28
+ catch :closed do
29
+ loop { write_dispatch(read_msg) }
30
+ end
31
+ :closed
32
+ rescue => ex
33
+ ex
34
+ ensure
35
+ @io.close unless @io.closed?
36
+ end
37
+
38
+ def dispatch(method, args)
39
+ case method
40
+ when :key?, :load, :delete, :increment, :create, :features
41
+ @store.public_send(method, *args)
42
+ when :store, :clear
43
+ @store.public_send(method, *args)
44
+ nil
45
+ when :each_key
46
+ yield_each(@store.each_key)
47
+ nil
48
+ end
49
+ rescue => ex
50
+ ex
51
+ end
52
+
53
+ def write_dispatch(msg)
54
+ method, *args = msg
55
+ result = dispatch(method, args)
56
+ write(result)
57
+ end
58
+
59
+ def read_msg
60
+ size = read(4).unpack('N').first
61
+ throw :closed, 'Message too big' if size > MAXSIZE
62
+ Marshal.load(read(size))
63
+ end
64
+
65
+ def read(len)
66
+ buffer = ''
67
+ loop do
68
+ begin
69
+ case received = @io.recv_nonblock(len)
70
+ when '', nil
71
+ throw :closed, 'Closed during read'
72
+ else
73
+ buffer << received
74
+ len -= received.bytesize
75
+ end
76
+ rescue IO::WaitReadable, IO::WaitWritable
77
+ yield_to_reactor(:read)
78
+ rescue Errno::ECONNRESET
79
+ throw :closed, 'Closed during read'
80
+ rescue IOError => ex
81
+ if ex.message =~ /closed stream/
82
+ throw :closed, 'Closed during read'
83
+ else
84
+ raise
85
+ end
86
+ end
87
+ break if len == 0
88
+ end
89
+ buffer
90
+ end
91
+
92
+ def write(obj)
93
+ buffer = pack(obj)
94
+ until buffer.empty?
95
+ begin
96
+ len = sendmsg(buffer)
97
+ buffer = buffer.byteslice(len...buffer.length)
98
+ rescue IO::WaitWritable, Errno::EINTR
99
+ yield_to_reactor(:write)
100
+ end
101
+ end
102
+ nil
103
+ end
104
+
105
+ # Detect support for socket#sendmsg_nonblock
106
+ Socket.new(Socket::AF_INET, Socket::SOCK_STREAM).tap do |socket|
107
+ begin
108
+ socket.sendmsg_nonblock('probe')
109
+ rescue Errno::EPIPE, Errno::ENOTCONN
110
+ def sendmsg(msg)
111
+ @io.sendmsg_nonblock(msg)
112
+ end
113
+ rescue NotImplementedError
114
+ def sendmsg(msg)
115
+ @io.write_nonblock(msg)
116
+ end
117
+ end
118
+ end
119
+
120
+ def yield_to_reactor(mode = :read)
121
+ if Fiber.yield(mode) == :close
122
+ throw :closed, 'Closed by reactor'
123
+ end
124
+ end
125
+
126
+ def pack(obj)
127
+ s = Marshal.dump(obj)
128
+ [s.bytesize].pack('N') << s
129
+ end
130
+
131
+ def yield_each(enumerator)
132
+ received_break = false
133
+ loop do
134
+ case msg = read_msg
135
+ when %w{NEXT}
136
+ # This will raise a StopIteration at the end of the enumeration,
137
+ # which will exit the loop.
138
+ write(enumerator.next)
139
+ when %w{BREAK}
140
+ # This is received when the client wants to stop the enumeration.
141
+ received_break = true
142
+ break
143
+ else
144
+ # Otherwise, the client is attempting to call another method within
145
+ # an `each` block.
146
+ write_dispatch(msg)
147
+ end
148
+ end
149
+ ensure
150
+ # This tells the client to stop enumerating
151
+ write(StopIteration.new("Server initiated stop")) unless received_break
152
+ end
153
+ end
154
+
7
155
  # @param [Hash] options
8
156
  # @option options [Integer] :port (9000) TCP port
9
157
  # @option options [String] :socket Alternative Unix socket file name
@@ -11,7 +159,9 @@ module Moneta
11
159
  @store = store
12
160
  @server = start(options)
13
161
  @ios = [@server]
14
- @clients = {}
162
+ @reads = @ios.dup
163
+ @writes = []
164
+ @connections = {}
15
165
  @running = false
16
166
  end
17
167
 
@@ -26,94 +176,89 @@ module Moneta
26
176
  #
27
177
  # @note This method blocks!
28
178
  def run
29
- raise 'Already running' if @running
179
+ raise 'Already running' if running?
30
180
  @stop = false
31
181
  @running = true
32
182
  begin
33
183
  mainloop until @stop
34
184
  ensure
35
- File.unlink(@socket) if @socket
36
- @ios.each { |io| io.close rescue nil }
185
+ @running = false
186
+ @server.close unless @server.closed?
187
+ @ios
188
+ .reject { |io| io == @server }
189
+ .each { |io| close_connection(io) }
190
+ File.unlink(@socket) if @socket rescue nil
37
191
  end
38
192
  end
39
193
 
40
194
  # Stop the server
41
195
  def stop
42
- raise 'Not running' unless @running
196
+ raise 'Not running' unless running?
43
197
  @stop = true
44
198
  @server.close
45
- @server = nil
199
+ nil
46
200
  end
47
201
 
48
202
  private
49
203
 
50
- TIMEOUT = 1
51
- MAXSIZE = 0x100000
52
-
53
204
  def mainloop
54
- if ios = IO.select(@ios, nil, @ios, TIMEOUT)
55
- ios[2].each do |io|
56
- io.close
57
- delete_client(io)
58
- end
59
- ios[0].each do |io|
60
- if io == @server
61
- if client = @server.accept
62
- @ios << client
63
- @clients[client] = ''
64
- end
65
- elsif io.closed? || io.eof?
66
- delete_client(io)
67
- else
68
- handle(io, @clients[io] << io.readpartial(0xFFFF))
69
- end
205
+ if ready = IO.select(@reads, @writes, @ios, TIMEOUT)
206
+ reads, writes, errors = ready
207
+ errors.each { |io| close_connection(io) }
208
+
209
+ @reads -= reads
210
+ reads.each do |io|
211
+ io == @server ? accept_connection : resume(io)
70
212
  end
213
+
214
+ @writes -= writes
215
+ writes.each { |io| resume(io) }
71
216
  end
72
- rescue SignalException => ex
73
- warn "Moneta::Server - #{ex.message}"
74
- raise if ex.signo == 15 || ex.signo == 2 # SIGTERM or SIGINT
75
- rescue => ex
217
+ rescue SignalException => signal
218
+ warn "Moneta::Server - received #{signal}"
219
+ case signal.signo
220
+ when Signal.list['INT'], Signal.list['TERM']
221
+ @stop = true # graceful exit
222
+ end
223
+ rescue IOError => ex
224
+ # We get a lot of these "closed stream" errors, which we ignore
225
+ raise unless ex.message =~ /closed stream/
226
+ rescue Errno::EBADF => ex
76
227
  warn "Moneta::Server - #{ex.message}"
77
228
  end
78
229
 
79
- def delete_client(io)
230
+ def accept_connection
231
+ io = @server.accept
232
+ @connections[io] = Connection.new(io, @store)
233
+ @ios << io
234
+ resume(io)
235
+ ensure
236
+ @reads << @server
237
+ end
238
+
239
+ def delete_connection(io)
80
240
  @ios.delete(io)
81
- @clients.delete(io)
241
+ @reads.delete(io)
242
+ @writes.delete(io)
82
243
  end
83
244
 
84
- def pack(obj)
85
- s = Marshal.dump(obj)
86
- [s.bytesize].pack('N') << s
245
+ def close_connection(io)
246
+ delete_connection(io)
247
+ @connections.delete(io).resume(:close)
87
248
  end
88
249
 
89
- def handle(io, buffer)
90
- return if buffer.bytesize < 8 # At least 4 bytes for the marshalled array
91
- size = buffer[0, 4].unpack('N').first
92
- if size > MAXSIZE
93
- delete_client(io)
94
- return
95
- end
96
- return if buffer.bytesize < 4 + size
97
- buffer.slice!(0, 4)
98
- method, *args = Marshal.load(buffer.slice!(0, size))
99
- case method
100
- when :key?, :load, :delete, :increment, :create
101
- io.write(pack(@store.send(method, *args)))
102
- when :features
103
- # all features except each_key are supported
104
- io.write(pack(@store.features - [:each_key]))
105
- when :store, :clear
106
- @store.send(method, *args)
107
- io.write(@nil ||= pack(nil))
108
- else
109
- raise 'Invalid method call'
250
+ def resume(io)
251
+ case result = @connections[io].resume
252
+ when :closed # graceful exit
253
+ delete_connection(io)
254
+ when Exception # messy exit
255
+ delete_connection(io)
256
+ raise result
257
+ when :read
258
+ @reads << io
259
+ when :write
260
+ @writes << io
110
261
  end
111
- rescue IOError => ex
112
- warn "Moneta::Server - #{ex.message}" unless ex.message =~ /closed/
113
- delete_client(io)
114
- rescue => ex
115
- warn "Moneta::Server - #{ex.message}"
116
- io.write(pack(Exception.new(ex.message)))
117
262
  end
118
263
 
119
264
  def start(options)
@@ -133,5 +278,14 @@ module Moneta
133
278
  TCPServer.open(options[:host] || '127.0.0.1', options[:port] || 9000)
134
279
  end
135
280
  end
281
+
282
+ def stats
283
+ {
284
+ connections: @connections.length,
285
+ reading: @reads.length,
286
+ writing: @writes.length,
287
+ total: @ios.length
288
+ }
289
+ end
136
290
  end
137
291
  end