moneta 1.1.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (208) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rubocop.yml +194 -0
  4. data/.travis.yml +65 -25
  5. data/CHANGES +36 -0
  6. data/CONTRIBUTORS +4 -2
  7. data/Gemfile +94 -65
  8. data/README.md +50 -25
  9. data/feature_matrix.yaml +3 -11
  10. data/lib/action_dispatch/middleware/session/moneta_store.rb +1 -0
  11. data/lib/active_support/cache/moneta_store.rb +5 -5
  12. data/lib/moneta.rb +20 -10
  13. data/lib/moneta/adapters/activerecord.rb +35 -19
  14. data/lib/moneta/adapters/activesupportcache.rb +3 -7
  15. data/lib/moneta/adapters/cassandra.rb +24 -16
  16. data/lib/moneta/adapters/client.rb +62 -21
  17. data/lib/moneta/adapters/couch.rb +225 -80
  18. data/lib/moneta/adapters/datamapper.rb +1 -0
  19. data/lib/moneta/adapters/file.rb +9 -6
  20. data/lib/moneta/adapters/hbase.rb +1 -1
  21. data/lib/moneta/adapters/kyotocabinet.rb +8 -7
  22. data/lib/moneta/adapters/leveldb.rb +1 -1
  23. data/lib/moneta/adapters/lmdb.rb +3 -4
  24. data/lib/moneta/adapters/lruhash.rb +29 -62
  25. data/lib/moneta/adapters/memcached.rb +1 -0
  26. data/lib/moneta/adapters/memcached/dalli.rb +1 -1
  27. data/lib/moneta/adapters/memcached/native.rb +10 -8
  28. data/lib/moneta/adapters/mongo.rb +256 -6
  29. data/lib/moneta/adapters/null.rb +1 -2
  30. data/lib/moneta/adapters/pstore.rb +3 -2
  31. data/lib/moneta/adapters/redis.rb +8 -4
  32. data/lib/moneta/adapters/restclient.rb +12 -3
  33. data/lib/moneta/adapters/riak.rb +2 -2
  34. data/lib/moneta/adapters/sequel.rb +137 -328
  35. data/lib/moneta/adapters/sequel/mysql.rb +66 -0
  36. data/lib/moneta/adapters/sequel/postgres.rb +80 -0
  37. data/lib/moneta/adapters/sequel/postgres_hstore.rb +240 -0
  38. data/lib/moneta/adapters/sequel/sqlite.rb +57 -0
  39. data/lib/moneta/adapters/sqlite.rb +25 -11
  40. data/lib/moneta/adapters/tokyotyrant.rb +1 -1
  41. data/lib/moneta/builder.rb +2 -3
  42. data/lib/moneta/create_support.rb +21 -0
  43. data/lib/moneta/dbm_adapter.rb +31 -0
  44. data/lib/moneta/{mixins.rb → defaults.rb} +3 -302
  45. data/lib/moneta/each_key_support.rb +27 -0
  46. data/lib/moneta/enumerable.rb +38 -0
  47. data/lib/moneta/expires.rb +12 -12
  48. data/lib/moneta/expires_support.rb +60 -0
  49. data/lib/moneta/fallback.rb +84 -0
  50. data/lib/moneta/hash_adapter.rb +68 -0
  51. data/lib/moneta/increment_support.rb +16 -0
  52. data/lib/moneta/lock.rb +7 -2
  53. data/lib/moneta/logger.rb +2 -2
  54. data/lib/moneta/nil_values.rb +35 -0
  55. data/lib/moneta/option_support.rb +51 -0
  56. data/lib/moneta/optionmerger.rb +0 -1
  57. data/lib/moneta/pool.rb +312 -30
  58. data/lib/moneta/proxy.rb +3 -3
  59. data/lib/moneta/server.rb +216 -65
  60. data/lib/moneta/shared.rb +13 -7
  61. data/lib/moneta/stack.rb +6 -6
  62. data/lib/moneta/synchronize.rb +3 -3
  63. data/lib/moneta/transformer.rb +68 -24
  64. data/lib/moneta/transformer/config.rb +63 -43
  65. data/lib/moneta/transformer/helper.rb +3 -3
  66. data/lib/moneta/transformer/helper/bson.rb +7 -14
  67. data/lib/moneta/utils.rb +3 -9
  68. data/lib/moneta/version.rb +1 -1
  69. data/lib/moneta/weak_each_key.rb +2 -4
  70. data/lib/rack/cache/moneta.rb +13 -11
  71. data/lib/rack/moneta_rest.rb +2 -2
  72. data/lib/rack/session/moneta.rb +3 -4
  73. data/moneta.gemspec +18 -4
  74. data/script/benchmarks +145 -46
  75. data/script/contributors +11 -6
  76. data/script/start-couchdb +27 -0
  77. data/script/start-hbase +3 -2
  78. data/script/start-services +3 -11
  79. data/spec/active_support/cache_moneta_store_spec.rb +30 -30
  80. data/spec/features/concurrent_create.rb +31 -10
  81. data/spec/features/concurrent_increment.rb +26 -19
  82. data/spec/features/create_expires.rb +15 -15
  83. data/spec/features/default_expires.rb +11 -12
  84. data/spec/features/expires.rb +215 -210
  85. data/spec/features/increment.rb +41 -41
  86. data/spec/features/store.rb +3 -3
  87. data/spec/helper.rb +23 -82
  88. data/spec/moneta/adapters/activerecord/standard_activerecord_spec.rb +1 -1
  89. data/spec/moneta/adapters/activerecord/standard_activerecord_with_expires_spec.rb +1 -1
  90. data/spec/moneta/adapters/activesupportcache/adapter_activesupportcache_spec.rb +4 -1
  91. data/spec/moneta/adapters/activesupportcache/adapter_activesupportcache_with_default_expires_spec.rb +4 -1
  92. data/spec/moneta/adapters/activesupportcache/standard_activesupportcache_spec.rb +14 -0
  93. data/spec/moneta/adapters/cassandra/standard_cassandra_spec.rb +1 -1
  94. data/spec/moneta/adapters/client/adapter_client_spec.rb +6 -6
  95. data/spec/moneta/adapters/client/client_helper.rb +24 -0
  96. data/spec/moneta/adapters/client/standard_client_tcp_spec.rb +8 -8
  97. data/spec/moneta/adapters/client/standard_client_unix_spec.rb +23 -7
  98. data/spec/moneta/adapters/couch/adapter_couch_spec.rb +199 -2
  99. data/spec/moneta/adapters/couch/standard_couch_spec.rb +9 -3
  100. data/spec/moneta/adapters/couch/standard_couch_with_expires_spec.rb +8 -2
  101. data/spec/moneta/adapters/daybreak/standard_daybreak_spec.rb +1 -1
  102. data/spec/moneta/adapters/daybreak/standard_daybreak_with_expires_spec.rb +1 -1
  103. data/spec/moneta/adapters/dbm/standard_dbm_spec.rb +1 -1
  104. data/spec/moneta/adapters/dbm/standard_dbm_with_expires_spec.rb +1 -1
  105. data/spec/moneta/adapters/faraday_helper.rb +9 -0
  106. data/spec/moneta/adapters/file/standard_file_spec.rb +2 -2
  107. data/spec/moneta/adapters/file/standard_file_with_expires_spec.rb +1 -1
  108. data/spec/moneta/adapters/gdbm/standard_gdbm_spec.rb +1 -1
  109. data/spec/moneta/adapters/gdbm/standard_gdbm_with_expires_spec.rb +1 -1
  110. data/spec/moneta/adapters/kyotocabinet/adapter_kyotocabinet_spec.rb +1 -1
  111. data/spec/moneta/adapters/kyotocabinet/standard_kyotocabinet_spec.rb +2 -2
  112. data/spec/moneta/adapters/kyotocabinet/standard_kyotocabinet_with_expires_spec.rb +2 -2
  113. data/spec/moneta/adapters/leveldb/standard_leveldb_spec.rb +1 -1
  114. data/spec/moneta/adapters/leveldb/standard_leveldb_with_expires_spec.rb +1 -1
  115. data/spec/moneta/adapters/lmdb/standard_lmdb_spec.rb +1 -1
  116. data/spec/moneta/adapters/lmdb/standard_lmdb_with_expires_spec.rb +1 -1
  117. data/spec/moneta/adapters/lruhash/adapter_lruhash_spec.rb +2 -2
  118. data/spec/moneta/adapters/lruhash/standard_lruhash_spec.rb +1 -1
  119. data/spec/moneta/adapters/lruhash/standard_lruhash_with_expires_spec.rb +1 -1
  120. data/spec/moneta/adapters/memcached/adapter_memcached_spec.rb +1 -1
  121. data/spec/moneta/adapters/memcached/dalli/adapter_memcached_dalli_spec.rb +1 -1
  122. data/spec/moneta/adapters/memcached/dalli/adapter_memcached_dalli_with_default_expires_spec.rb +1 -1
  123. data/spec/moneta/adapters/memcached/dalli/standard_memcached_dalli_spec.rb +1 -1
  124. data/spec/moneta/adapters/memcached/native/adapter_memcached_native_spec.rb +1 -1
  125. data/spec/moneta/adapters/memcached/native/adapter_memcached_native_with_default_expires_spec.rb +1 -1
  126. data/spec/moneta/adapters/memcached/native/standard_memcached_native_spec.rb +1 -1
  127. data/spec/moneta/adapters/memcached/standard_memcached_spec.rb +1 -1
  128. data/spec/moneta/adapters/{memcached/helper.rb → memcached_helper.rb} +0 -0
  129. data/spec/moneta/adapters/memory/standard_memory_spec.rb +1 -1
  130. data/spec/moneta/adapters/memory/standard_memory_with_compress_spec.rb +1 -1
  131. data/spec/moneta/adapters/memory/standard_memory_with_expires_spec.rb +1 -1
  132. data/spec/moneta/adapters/memory/standard_memory_with_json_key_serializer_spec.rb +1 -1
  133. data/spec/moneta/adapters/memory/standard_memory_with_json_serializer_spec.rb +1 -1
  134. data/spec/moneta/adapters/memory/standard_memory_with_json_value_serializer_spec.rb +2 -2
  135. data/spec/moneta/adapters/memory/standard_memory_with_prefix_spec.rb +39 -2
  136. data/spec/moneta/adapters/memory/standard_memory_with_snappy_compress_spec.rb +2 -2
  137. data/spec/moneta/adapters/mongo/adapter_mongo_spec.rb +18 -2
  138. data/spec/moneta/adapters/mongo/adapter_mongo_with_default_expires_spec.rb +5 -3
  139. data/spec/moneta/adapters/mongo/standard_mongo_spec.rb +2 -2
  140. data/spec/moneta/adapters/null/null_adapter_spec.rb +1 -1
  141. data/spec/moneta/adapters/pstore/standard_pstore_spec.rb +1 -1
  142. data/spec/moneta/adapters/pstore/standard_pstore_with_expires_spec.rb +1 -1
  143. data/spec/moneta/adapters/redis/standard_redis_spec.rb +1 -1
  144. data/spec/moneta/adapters/restclient/adapter_restclient_spec.rb +7 -5
  145. data/spec/moneta/adapters/restclient/helper.rb +12 -0
  146. data/spec/moneta/adapters/restclient/standard_restclient_spec.rb +9 -6
  147. data/spec/moneta/adapters/riak/standard_riak_with_expires_spec.rb +4 -0
  148. data/spec/moneta/adapters/sdbm/standard_sdbm_spec.rb +1 -1
  149. data/spec/moneta/adapters/sdbm/standard_sdbm_with_expires_spec.rb +1 -1
  150. data/spec/moneta/adapters/sequel/adapter_sequel_spec.rb +31 -79
  151. data/spec/moneta/adapters/sequel/helper.rb +75 -0
  152. data/spec/moneta/adapters/sequel/standard_sequel_spec.rb +5 -11
  153. data/spec/moneta/adapters/sequel/standard_sequel_with_expires_spec.rb +8 -9
  154. data/spec/moneta/adapters/sqlite/standard_sqlite_spec.rb +1 -1
  155. data/spec/moneta/adapters/sqlite/standard_sqlite_with_expires_spec.rb +1 -1
  156. data/spec/moneta/adapters/tdb/standard_tdb_spec.rb +1 -1
  157. data/spec/moneta/adapters/tdb/standard_tdb_with_expires_spec.rb +1 -1
  158. data/spec/moneta/adapters/tokyocabinet/standard_tokyocabinet_spec.rb +1 -1
  159. data/spec/moneta/adapters/tokyocabinet/standard_tokyocabinet_with_expires_spec.rb +1 -1
  160. data/spec/moneta/adapters/tokyotyrant/adapter_tokyotyrant_spec.rb +6 -2
  161. data/spec/moneta/adapters/tokyotyrant/helper.rb +12 -0
  162. data/spec/moneta/adapters/tokyotyrant/standard_tokyotyrant_spec.rb +5 -2
  163. data/spec/moneta/adapters/tokyotyrant/standard_tokyotyrant_with_expires_spec.rb +5 -1
  164. data/spec/moneta/adapters/yaml/standard_yaml_spec.rb +1 -1
  165. data/spec/moneta/adapters/yaml/standard_yaml_with_expires_spec.rb +1 -1
  166. data/spec/moneta/builder_spec.rb +22 -0
  167. data/spec/moneta/proxies/enumerable/enumerable_spec.rb +26 -0
  168. data/spec/moneta/proxies/expires/expires_file_spec.rb +1 -1
  169. data/spec/moneta/proxies/fallback/fallback_spec.rb +42 -0
  170. data/spec/moneta/proxies/pool/pool_spec.rb +319 -6
  171. data/spec/moneta/proxies/shared/shared_tcp_spec.rb +14 -4
  172. data/spec/moneta/proxies/shared/shared_unix_spec.rb +14 -4
  173. data/spec/moneta/proxies/transformer/transformer_bencode_spec.rb +1 -1
  174. data/spec/moneta/proxies/transformer/transformer_bert_spec.rb +3 -3
  175. data/spec/moneta/proxies/transformer/transformer_bson_spec.rb +2 -2
  176. data/spec/moneta/proxies/transformer/transformer_json_spec.rb +1 -1
  177. data/spec/moneta/proxies/transformer/transformer_key_marshal_spec.rb +1 -1
  178. data/spec/moneta/proxies/transformer/transformer_key_yaml_spec.rb +1 -1
  179. data/spec/moneta/proxies/transformer/transformer_marshal_base64_spec.rb +1 -1
  180. data/spec/moneta/proxies/transformer/transformer_marshal_escape_spec.rb +8 -4
  181. data/spec/moneta/proxies/transformer/transformer_marshal_hex_spec.rb +1 -1
  182. data/spec/moneta/proxies/transformer/transformer_marshal_hmac_spec.rb +1 -1
  183. data/spec/moneta/proxies/transformer/transformer_marshal_prefix_base64_spec.rb +33 -0
  184. data/spec/moneta/proxies/transformer/transformer_marshal_prefix_spec.rb +1 -1
  185. data/spec/moneta/proxies/transformer/transformer_marshal_qp_spec.rb +1 -1
  186. data/spec/moneta/proxies/transformer/transformer_marshal_spec.rb +1 -1
  187. data/spec/moneta/proxies/transformer/transformer_marshal_urlsafe_base64_spec.rb +1 -1
  188. data/spec/moneta/proxies/transformer/transformer_marshal_uuencode_spec.rb +1 -1
  189. data/spec/moneta/proxies/transformer/transformer_msgpack_spec.rb +1 -1
  190. data/spec/moneta/proxies/transformer/transformer_ox_spec.rb +1 -1
  191. data/spec/moneta/proxies/transformer/transformer_php_spec.rb +1 -1
  192. data/spec/moneta/proxies/transformer/transformer_tnet_spec.rb +1 -1
  193. data/spec/moneta/proxies/transformer/transformer_yaml_spec.rb +2 -2
  194. data/spec/moneta/proxies/weak_each_key/weak_each_key_spec.rb +0 -2
  195. data/spec/restserver.rb +55 -0
  196. data/spec/support/mongo_helper.rb +12 -0
  197. metadata +140 -32
  198. data/lib/moneta/adapters/mongo/base.rb +0 -103
  199. data/lib/moneta/adapters/mongo/moped.rb +0 -164
  200. data/lib/moneta/adapters/mongo/official.rb +0 -157
  201. data/script/install-kyotocabinet +0 -17
  202. data/spec/moneta/adapters/mongo/adapter_mongo_moped_spec.rb +0 -25
  203. data/spec/moneta/adapters/mongo/adapter_mongo_moped_with_default_expires_spec.rb +0 -12
  204. data/spec/moneta/adapters/mongo/adapter_mongo_official_spec.rb +0 -25
  205. data/spec/moneta/adapters/mongo/adapter_mongo_official_with_default_expires_spec.rb +0 -12
  206. data/spec/moneta/adapters/mongo/standard_mongo_moped_spec.rb +0 -7
  207. data/spec/moneta/adapters/mongo/standard_mongo_official_spec.rb +0 -7
  208. data/spec/quality_spec.rb +0 -51
@@ -21,14 +21,14 @@ module Moneta
21
21
  def key?(key, options = {})
22
22
  # Transformer might raise exception
23
23
  load_entry(key, options) != nil
24
- rescue Exception
24
+ rescue
25
25
  super(key, Utils.without(options, :expires))
26
26
  end
27
27
 
28
28
  # (see Proxy#load)
29
29
  def load(key, options = {})
30
30
  return super if options.include?(:raw)
31
- value, expires = load_entry(key, options)
31
+ value, = load_entry(key, options)
32
32
  value
33
33
  end
34
34
 
@@ -64,8 +64,8 @@ module Moneta
64
64
  entry = invalidate_entry(key, entry, new_expires) do |new_entry|
65
65
  updates[key] = new_entry
66
66
  end
67
- next if entry.nil?
68
- value, _ = entry
67
+ next if entry == nil
68
+ value, = entry
69
69
  value
70
70
  end
71
71
  end
@@ -90,12 +90,12 @@ module Moneta
90
90
  entry = invalidate_entry(key, entry, new_expires) do |new_entry|
91
91
  updates[key] = new_entry
92
92
  end
93
- if entry.nil?
93
+ if entry == nil
94
94
  value = if block_given?
95
95
  yield key
96
96
  end
97
97
  else
98
- value, _ = entry
98
+ value, = entry
99
99
  end
100
100
  value
101
101
  end
@@ -113,26 +113,26 @@ module Moneta
113
113
  entry = invalidate_entry(key, entry, new_expires) do |new_entry|
114
114
  updates[key] = new_entry
115
115
  end
116
- next if entry.nil?
117
- value, _ = entry
116
+ next if entry == nil
117
+ value, = entry
118
118
  [key, value]
119
119
  end.reject(&:nil?)
120
120
  end
121
121
  end
122
122
 
123
123
  # (see Defaults#merge!)
124
- def merge!(pairs, options={})
124
+ def merge!(pairs, options = {})
125
125
  expires = expires_at(options)
126
126
  options = Utils.without(options, :expires)
127
127
 
128
128
  block = if block_given?
129
129
  lambda do |key, old_entry, entry|
130
130
  old_entry = invalidate_entry(key, old_entry)
131
- if old_entry.nil?
131
+ if old_entry == nil
132
132
  entry # behave as if no replace is happening
133
133
  else
134
- old_value, _ = old_entry
135
- new_value, _ = entry
134
+ old_value, = old_entry
135
+ new_value, = entry
136
136
  new_entry(yield(key, old_value, new_value), expires)
137
137
  end
138
138
  end
@@ -0,0 +1,60 @@
1
+ module Moneta
2
+ # This mixin handles the calculation of expiration times.
3
+ #
4
+ #
5
+ module ExpiresSupport
6
+ attr_accessor :default_expires
7
+
8
+ protected
9
+
10
+ # Calculates the time when something will expire.
11
+ #
12
+ # This method considers false and 0 as "no-expire" and every positive
13
+ # number as a time to live in seconds.
14
+ #
15
+ # @param [Hash] options Options hash
16
+ # @option options [0,false,nil,Numeric] :expires expires value given by user
17
+ # @param [0,false,nil,Numeric] default default expiration time
18
+ #
19
+ # @return [false] if it should not expire
20
+ # @return [Time] the time when something should expire
21
+ # @return [nil] if it is not known
22
+ def expires_at(options, default = @default_expires)
23
+ value = expires_value(options, default)
24
+ Numeric === value ? Time.now + value : value
25
+ end
26
+
27
+ # Calculates the number of seconds something should last.
28
+ #
29
+ # This method considers false and 0 as "no-expire" and every positive
30
+ # number as a time to live in seconds.
31
+ #
32
+ # @param [Hash] options Options hash
33
+ # @option options [0,false,nil,Numeric] :expires expires value given by user
34
+ # @param [0,false,nil,Numeric] default default expiration time
35
+ #
36
+ # @return [false] if it should not expire
37
+ # @return [Numeric] seconds until expiration
38
+ # @return [nil] if it is not known
39
+ def expires_value(options, default = @default_expires)
40
+ case value = options[:expires]
41
+ when 0, false
42
+ false
43
+ when nil
44
+ default ? default.to_r : nil
45
+ when Numeric
46
+ value = value.to_r
47
+ raise ArgumentError, ":expires must be a positive value, got #{value}" if value < 0
48
+ value
49
+ else
50
+ raise ArgumentError, ":expires must be Numeric or false, got #{value.inspect}"
51
+ end
52
+ end
53
+
54
+ class << self
55
+ def included(base)
56
+ base.supports(:expires) if base.respond_to?(:supports)
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,84 @@
1
+ module Moneta
2
+ # Provides a fallback to a second store when an exception is raised
3
+ #
4
+ # @example Basic usage - catches any {IOError} and falls back to {Moneta::Adapters:Null}
5
+ # Moneta.build do
6
+ # use :Fallback
7
+ # adapter :Client
8
+ # end
9
+ #
10
+ # @example Specifying an exception to rescue
11
+ # Moneta.build do
12
+ # use :Fallback, rescue: Redis::CannotConnectError
13
+ # adapter :Redis
14
+ # end
15
+ #
16
+ # @example Specifying a different fallback
17
+ # Moneta.build do
18
+ # use :Fallback do
19
+ # # This is a new builder context
20
+ # adapter :Memory
21
+ # end
22
+ # adapter :File, dir: 'cache'
23
+ # end
24
+ #
25
+ # @api public
26
+ class Fallback < Wrapper
27
+ # @param [Moneta store] adapter The underlying store
28
+ # @param [Hash] options
29
+ # @option options [Moneta store] :fallback (:Null store) The store to fall
30
+ # back on
31
+ # @option options [Class|Array<Class>] :rescue ([IOError]) The list
32
+ # of exceptions that should be rescued
33
+ # @yieldreturn [Moneta store] Moneta store built using the builder API
34
+ def initialize(adapter, options = {}, &block)
35
+ super
36
+
37
+ @fallback =
38
+ if block_given?
39
+ ::Moneta.build(&block)
40
+ elsif options.key?(:fallback)
41
+ options.delete(:fallback)
42
+ else
43
+ ::Moneta::Adapters::Null.new
44
+ end
45
+
46
+ @rescue =
47
+ case options[:rescue]
48
+ when nil
49
+ [::IOError]
50
+ when Array
51
+ options[:rescue]
52
+ else
53
+ [options[:rescue]]
54
+ end
55
+ end
56
+
57
+ protected
58
+
59
+ def wrap(name, *args, &block)
60
+ yield
61
+ rescue => e
62
+ raise unless @rescue.any? { |rescuable| rescuable === e }
63
+ fallback(name, *args, &block)
64
+ end
65
+
66
+ def fallback(name, *args, &block)
67
+ result =
68
+ case name
69
+ when :values_at, :fetch_values, :slice
70
+ keys, options = args
71
+ @fallback.public_send(name, *keys, **options, &block)
72
+ else
73
+ @fallback.public_send(name, *args, &block)
74
+ end
75
+
76
+ # Don't expose the fallback class to the caller
77
+ if result == @fallback
78
+ self
79
+ else
80
+ result
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,68 @@
1
+ module Moneta
2
+ # @api private
3
+ module HashAdapter
4
+ attr_reader :backend
5
+
6
+ # (see Proxy#key?)
7
+ def key?(key, options = {})
8
+ @backend.has_key?(key)
9
+ end
10
+
11
+ # (see Proxy#load)
12
+ def load(key, options = {})
13
+ @backend[key]
14
+ end
15
+
16
+ # (see Proxy#store)
17
+ def store(key, value, options = {})
18
+ @backend[key] = value
19
+ end
20
+
21
+ # (see Proxy#delete)
22
+ def delete(key, options = {})
23
+ @backend.delete(key)
24
+ end
25
+
26
+ # (see Proxy#clear)
27
+ def clear(options = {})
28
+ @backend.clear
29
+ self
30
+ end
31
+
32
+ # (see Defaults#values_at)
33
+ def values_at(*keys, **options)
34
+ return super unless @backend.respond_to? :values_at
35
+ @backend.values_at(*keys)
36
+ end
37
+
38
+ # (see Defaults#fetch_values)
39
+ def fetch_values(*keys, **options, &defaults)
40
+ return super unless @backend.respond_to? :fetch_values
41
+ defaults ||= {} # prevents KeyError
42
+ @backend.fetch_values(*keys, &defaults)
43
+ end
44
+
45
+ # (see Defaults#slice)
46
+ def slice(*keys, **options)
47
+ return super unless @backend.respond_to? :slice
48
+ @backend.slice(*keys)
49
+ end
50
+
51
+ # (see Defaults#merge!)
52
+ def merge!(pairs, options = {}, &block)
53
+ return super unless method = [:merge!, :update].find do |method|
54
+ @backend.respond_to? method
55
+ end
56
+
57
+ hash = Hash === pairs ? pairs : Hash[pairs.to_a]
58
+ case method
59
+ when :merge!
60
+ @backend.merge!(hash, &block)
61
+ when :update
62
+ @backend.update(hash, &block)
63
+ end
64
+
65
+ self
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,16 @@
1
+ module Moneta
2
+ # @api private
3
+ module IncrementSupport
4
+ # (see Defaults#increment)
5
+ def increment(key, amount = 1, options = {})
6
+ existing = load(key, options)
7
+ value = (existing == nil ? 0 : Integer(existing)) + amount
8
+ store(key, value.to_s, options)
9
+ value
10
+ end
11
+
12
+ def self.included(base)
13
+ base.supports(:increment) if base.respond_to?(:supports)
14
+ end
15
+ end
16
+ end
@@ -15,15 +15,20 @@ module Moneta
15
15
  protected
16
16
 
17
17
  def wrap(name, *args, &block)
18
+ self.locks ||= Set.new
18
19
  if locked?
19
- block.call
20
+ yield
20
21
  else
21
22
  lock!(&block)
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)
@@ -29,7 +29,7 @@ module Moneta
29
29
  def format(entry)
30
30
  args = entry[:args]
31
31
  args.pop if Hash === args.last && args.last.empty?
32
- args = args.map {|a| dump(a) }.join(', ')
32
+ args = args.map { |a| dump(a) }.join(', ')
33
33
  if entry[:error]
34
34
  "#{@prefix}#{entry[:method]}(#{args}) raised error: #{entry[:error].message}\n"
35
35
  else
@@ -66,7 +66,7 @@ module Moneta
66
66
  ret = yield
67
67
  @logger.log(method: method, args: args, return: (method == :clear ? 'self' : ret))
68
68
  ret
69
- rescue Exception => error
69
+ rescue => error
70
70
  @logger.log(method: method, args: args, error: error)
71
71
  raise
72
72
  end
@@ -0,0 +1,35 @@
1
+ module Moneta
2
+ # This contains overrides of methods in Defaults where additional nil
3
+ # checks are required, because nil values are possible in the store.
4
+ # @api private
5
+ module NilValues
6
+ def fetch_values(*keys, **options)
7
+ values = values_at(*keys, **options)
8
+ return values unless block_given?
9
+ keys.zip(values).map do |key, value|
10
+ if value == nil && !key?(key)
11
+ yield key
12
+ else
13
+ value
14
+ end
15
+ end
16
+ end
17
+
18
+ def slice(*keys, **options)
19
+ keys.zip(values_at(*keys, **options)).reject do |key, value|
20
+ value == nil && !key?(key)
21
+ end
22
+ end
23
+
24
+ def merge!(pairs, options = {})
25
+ pairs.each do |key, value|
26
+ if block_given? && key?(key, options)
27
+ existing = load(key, options)
28
+ value = yield(key, existing, value)
29
+ end
30
+ store(key, value, options)
31
+ end
32
+ self
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,51 @@
1
+ module Moneta
2
+ # @api private
3
+ module OptionSupport
4
+ # Return Moneta store with default options or additional proxies
5
+ #
6
+ # @param [Hash] options Options to merge
7
+ # @return [Moneta store]
8
+ #
9
+ # @api public
10
+ def with(options = nil, &block)
11
+ adapter = self
12
+ if block
13
+ builder = Builder.new(&block)
14
+ builder.adapter(adapter)
15
+ adapter = builder.build.last
16
+ end
17
+ options ? OptionMerger.new(adapter, options) : adapter
18
+ end
19
+
20
+ # Return Moneta store with default option raw: true
21
+ #
22
+ # @return [OptionMerger]
23
+ # @api public
24
+ def raw
25
+ @raw ||=
26
+ begin
27
+ store = with(raw: true, only: [:load, :store, :create, :delete])
28
+ store.instance_variable_set(:@raw, store)
29
+ store
30
+ end
31
+ end
32
+
33
+ # Return Moneta store with default prefix option
34
+ #
35
+ # @param [String] prefix Key prefix
36
+ # @return [OptionMerger]
37
+ # @api public
38
+ def prefix(prefix)
39
+ with(prefix: prefix, except: :clear)
40
+ end
41
+
42
+ # Return Moneta store with default expiration time
43
+ #
44
+ # @param [Integer] expires Default expiration time
45
+ # @return [OptionMerger]
46
+ # @api public
47
+ def expires(expires)
48
+ with(expires: expires, only: [:store, :create, :increment])
49
+ end
50
+ end
51
+ end
@@ -40,4 +40,3 @@ module Moneta
40
40
  end
41
41
  end
42
42
  end
43
-
@@ -1,69 +1,351 @@
1
- require 'thread'
1
+ require 'set'
2
2
 
3
3
  module Moneta
4
- # Creates a pool of stores.
5
- # Each thread gets its own store.
4
+ # Creates a thread-safe pool. Stores are in the pool are transparently
5
+ # checked in and out in order to perform operations.
6
+ #
7
+ # A `max` setting can be specified in order to limit the pool size. If `max`
8
+ # stores are all checked out at once, the next check-out will block until one
9
+ # of the other stores are checked in.
10
+ #
11
+ # A `ttl` setting can be specified, giving the number of seconds to
12
+ # wait without any activity before shrinking the pool size back down to the
13
+ # min size.
14
+ #
15
+ # A `timeout` setting can be specified, giving the number of seconds to wait
16
+ # when checking out a store, before an error is raised. When the pool has a
17
+ # `:max` size, a timeout is highly advisable.
6
18
  #
7
19
  # @example Add `Moneta::Pool` to proxy stack
8
20
  # Moneta.build do
9
21
  # use(:Pool) do
10
- # # Every thread gets its own instance
11
22
  # adapter :MemcachedNative
12
23
  # end
13
24
  # end
14
25
  #
26
+ # @example Add `Moneta::Pool` that contains at least 2 stores, and closes any extras after 60 seconds of inactivity
27
+ # Moneta.build do
28
+ # use(:Pool, min: 2, ttl: 60) do
29
+ # adapter :Sqlite, file: 'test.db'
30
+ # end
31
+ # end
32
+ #
33
+ # @example Add `Moneta::Pool` with a max of 10 stores, and a timeout of 5 seconds for checkout
34
+ # Moneta.build do
35
+ # use(:Pool, max: 10, timeout: 5) do
36
+ # adapter :Sqlite, file: 'test.db'
37
+ # end
38
+ # end
39
+ #
15
40
  # @api public
16
41
  class Pool < Wrapper
42
+ # @api private
43
+ class ShutdownError < ::RuntimeError; end
44
+ class TimeoutError < ::RuntimeError; end
45
+
46
+ # @api private
47
+ class Reply
48
+ attr_reader :resource
49
+
50
+ def initialize(mutex)
51
+ @mutex = mutex
52
+ @resource = ::ConditionVariable.new
53
+ @value = nil
54
+ end
55
+
56
+ def resolve(value)
57
+ @mutex.synchronize do
58
+ raise "Already resolved" if @value
59
+ @value = value
60
+ @resource.signal
61
+ end
62
+ nil
63
+ end
64
+
65
+ def wait
66
+ @resource.wait(@mutex)
67
+ @value
68
+ end
69
+ end
70
+
71
+ # @api private
72
+ class PoolManager
73
+ def initialize(builder, min: 0, max: nil, ttl: nil, timeout: nil)
74
+ @builder = builder
75
+ @min = min
76
+ @max = max
77
+ @ttl = ttl
78
+ @timeout = timeout
79
+
80
+ @inbox = []
81
+ @mutex = ::Mutex.new
82
+ @resource = ::ConditionVariable.new
83
+
84
+ @stores = Set.new
85
+ @available = []
86
+ @waiting = []
87
+ @waiting_since = [] if @timeout
88
+ @last_checkout = nil
89
+ @stopping = false
90
+
91
+ # Launch the manager thread
92
+ @thread = run
93
+ end
94
+
95
+ def stats
96
+ push(:stats, reply: true)
97
+ end
98
+
99
+ def stop
100
+ push(:stop)
101
+ nil
102
+ ensure
103
+ @thread.value
104
+ end
105
+
106
+ def kill!
107
+ @thread.kill
108
+ nil
109
+ end
110
+
111
+ def check_out
112
+ reply = push(:check_out, reply: true)
113
+ raise reply if Exception === reply
114
+ reply
115
+ end
116
+
117
+ def check_in(store)
118
+ push(:check_in, store)
119
+ end
120
+
121
+ private
122
+
123
+ def run
124
+ Thread.new do
125
+ begin
126
+ populate_stores
127
+
128
+ until @stopping && @stores.empty?
129
+ # Block until a message arrives, or until we time out for some reason
130
+ if request = pop
131
+ handle_request(request)
132
+ end
133
+
134
+ # Handle any stale checkout requests
135
+ handle_timed_out_requests
136
+ # Drop any stores that are no longer needed
137
+ remove_unneeded_stores
138
+ end
139
+ rescue => e
140
+ reject_waiting(e.message)
141
+ raise
142
+ end
143
+ end
144
+ end
145
+
146
+ def populate_stores
147
+ return if @stopping
148
+ @available.push(add_store) while @stores.length < @min
149
+ end
150
+
151
+ # If the last checkout was more than timeout ago, drop any available stores
152
+ def remove_unneeded_stores
153
+ return unless @stopping || (@ttl && @last_checkout && Time.now - @last_checkout >= @ttl)
154
+ while (@stopping || @stores.length > @min) and store = @available.pop
155
+ store.close rescue nil
156
+ @stores.delete(store)
157
+ end
158
+ end
159
+
160
+ # If there are checkout requests that have been waiting too long,
161
+ # feed them timeout errors.
162
+ def handle_timed_out_requests
163
+ while @timeout && !@waiting.empty? && (Time.now - @waiting_since.first) >= @timeout
164
+ waiting_since = @waiting_since.shift
165
+ @waiting.shift.resolve(TimeoutError.new("Waited %<secs>f seconds" % { secs: Time.now - waiting_since }))
166
+ end
167
+ end
168
+
169
+ # This is called from outside the loop thread
170
+ def push(message, what = nil, reply: nil)
171
+ @mutex.synchronize do
172
+ raise ShutdownError, "Pool has been shutdown" if reply && !@thread.alive?
173
+ reply &&= Reply.new(@mutex)
174
+ @inbox.push([message, what, reply])
175
+ @resource.signal
176
+ reply.wait if reply
177
+ end
178
+ end
179
+
180
+ # This method calculates the number of seconds to wait for a signal on
181
+ # the condition variable, or `nil` if there is no need to time out.
182
+ #
183
+ # Calculated based on the `:ttl` and `:timeout` options used during
184
+ # construction.
185
+ #
186
+ # @return [Integer, nil]
187
+ def timeout
188
+ # Time to wait before there will be stores that should be closed
189
+ ttl = if @ttl && @last_checkout && !@available.empty?
190
+ [@ttl - (Time.now - @last_checkout), 0].max
191
+ end
192
+
193
+ # Time to wait
194
+ timeout = if @timeout && !@waiting_since.empty?
195
+ longest_waiting = @waiting_since.first
196
+ [@timeout - (Time.now - longest_waiting), 0].max
197
+ end
198
+
199
+ [ttl, timeout].compact.min
200
+ end
201
+
202
+ def pop
203
+ @mutex.synchronize do
204
+ @resource.wait(@mutex, timeout) if @inbox.empty?
205
+ @inbox.shift
206
+ end
207
+ end
208
+
209
+ def add_store
210
+ store = @builder.build.last
211
+ @stores.add(store)
212
+ store
213
+ end
214
+
215
+ def handle_check_out(reply)
216
+ @last_checkout = Time.now
217
+ if @stopping
218
+ reply.resolve(ShutdownError.new("Shutting down"))
219
+ elsif !@available.empty?
220
+ reply.resolve(@available.pop)
221
+ elsif !@max || @stores.length < @max
222
+ begin
223
+ reply.resolve(add_store)
224
+ rescue => e
225
+ reply.resolve(e)
226
+ end
227
+ else
228
+ @waiting.push(reply)
229
+ @waiting_since.push(Time.now) if @timeout
230
+ end
231
+ end
232
+
233
+ def handle_stop
234
+ @stopping = true
235
+ # Reject anyone left waiting
236
+ reject_waiting "Shutting down"
237
+ end
238
+
239
+ def reject_waiting(reason)
240
+ while reply = @waiting.shift
241
+ reply.resolve(ShutdownError.new(reason))
242
+ end
243
+ @waiting_since = [] if @timeout
244
+ end
245
+
246
+ def handle_check_in(store)
247
+ if !@waiting.empty?
248
+ @waiting.shift.resolve(store)
249
+ @waiting_since.shift if @timeout
250
+ else
251
+ @available.push(store)
252
+ end
253
+ end
254
+
255
+ def handle_stats(reply)
256
+ reply.resolve(stores: @stores.length,
257
+ available: @available.length,
258
+ waiting: @waiting.length,
259
+ longest_wait: @timeout && !@waiting_since.empty? ? @waiting_since.first.dup : nil,
260
+ stopping: @stopping,
261
+ last_checkout: @last_checkout && @last_checkout.dup)
262
+ end
263
+
264
+ def handle_request(request)
265
+ cmd, what, reply = request
266
+ case cmd
267
+ when :check_out
268
+ handle_check_out(reply)
269
+ when :check_in
270
+ # A checkin request
271
+ handle_check_in(what)
272
+ when :stats
273
+ handle_stats(reply)
274
+ when :stop
275
+ # Graceful exit
276
+ handle_stop
277
+ end
278
+ end
279
+ end
280
+
17
281
  # @param [Hash] options
18
- # @option options [String] :mutex (::Mutex.new) Mutex object
282
+ # @option options [Integer] :min (0) The minimum pool size
283
+ # @option options [Integer] :max The maximum pool size. If not specified,
284
+ # there is no maximum.
285
+ # @option options [Numeric] :ttl The number of seconds to keep
286
+ # stores above the minumum number around for without activity. If
287
+ # not specified, stores will never be removed.
288
+ # @option options [Numeric] :timeout The number of seconds to wait for a
289
+ # store to become available. If not specified, will wait forever.
290
+ # @yield A builder context for speciying how to construct stores
19
291
  def initialize(options = {}, &block)
20
292
  super(nil)
21
- @mutex = options[:mutex] || ::Mutex.new
22
293
  @id = "Moneta::Pool(#{object_id})"
23
- @builder = Builder.new(&block)
24
- @pool, @all = [], []
294
+ @manager = PoolManager.new(Builder.new(&block), **options)
25
295
  end
26
296
 
27
- def close
28
- @mutex.synchronize do
29
- raise '#close can only be called when no thread is using the pool' if @all.size != @pool.size
30
- @all.each(&:close)
31
- @all = @pool = nil
297
+ # Closing has no effect on the pool, as stores are closed in the background
298
+ # by the manager after the ttl
299
+ def close; end
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
32
310
  end
33
311
  end
34
312
 
313
+ # Tells the manager to close all stores. It will not be possible to use
314
+ # the store after this.
315
+ def stop
316
+ @manager.stop
317
+ nil
318
+ end
319
+
320
+ def stats
321
+ @manager.stats
322
+ end
323
+
35
324
  protected
36
325
 
37
326
  def adapter
38
- Thread.current[@id]
327
+ Thread.current.thread_variable_get(@id)
328
+ end
329
+
330
+ def adapter=(store)
331
+ Thread.current.thread_variable_set(@id, store)
39
332
  end
40
333
 
41
334
  def wrap(*args, &block)
42
- if checked_out?
335
+ if adapter
43
336
  yield
44
337
  else
45
338
  check_out!(&block)
46
339
  end
47
340
  end
48
341
 
49
- def checked_out?
50
- Thread.current.key?(@id) && Thread.current[@id] != nil
51
- end
52
-
53
342
  def check_out!
54
- store = Thread.current[@id] = pop
343
+ store = @manager.check_out
344
+ self.adapter = store
55
345
  yield
56
346
  ensure
57
- Thread.current[@id] = nil
58
- @mutex.synchronize { @pool << store }
59
- end
60
-
61
- def pop
62
- unless store = @mutex.synchronize { @pool.pop }
63
- store = @builder.build.last
64
- @mutex.synchronize { @all << store }
65
- end
66
- store
347
+ self.adapter = nil
348
+ @manager.check_in store if store
67
349
  end
68
350
  end
69
351
  end