vinted-memcached_store 2.3.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a6675c19521b317f6a5a3915a39b7d8ff82efed1f12cbffc5d97ec86f2348e0a
4
+ data.tar.gz: 542e1943df8e5c95a1127b91c2823e490a0a9058a8ae7f9231a04c1209f80588
5
+ SHA512:
6
+ metadata.gz: d3bb0bf65bf86f5269761b9bcfb0adebc1c20aaf896e093f44c8f25a65b9481196299508c7c2fedead98b8a7a1d434b957062f748f0450a222db1dc099be1447
7
+ data.tar.gz: 3a4695a77dea72f8b3fa9db8f9353b30c47ee1ed3c7e6ba247ac1842b8810670bd9ef27c1aeecd759ed33998def06cd23b8bdd3d928f859657a46ed40d8c4af1
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013-2016 Shopify Inc.
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # ActiveSupport MemcachedStore
2
+
3
+ This gem includes two memcached stores.
4
+
5
+ ### MemcachedStore
6
+
7
+ ActiveSupport memcached store. This wraps the memcached gem into a ActiveSupport::Cache::Store, so it could be used inside Rails.
8
+
9
+ ### MemcachedSnappyStore
10
+
11
+ ActiveSupport cache store that adds snappy compression at the cost of making the `incr, decr` operations unavailable.
12
+
13
+ ## Installation
14
+
15
+ Add this line to your application's Gemfile:
16
+
17
+ ```
18
+ gem 'memcached_store'
19
+ ```
20
+
21
+ And then execute:
22
+
23
+ ```bash
24
+ $ bundle
25
+ ```
26
+
27
+ Or install it yourself as:
28
+
29
+ ```bash
30
+ $ gem install memcached_store
31
+ ```
32
+
33
+ ## Usage
34
+
35
+ In your environment file:
36
+
37
+ ```ruby
38
+ # for memcached store
39
+ config.cache_store = :memcached_store,
40
+
41
+ # for snappy store
42
+ config.cache_store = :memcached_snappy_store,
43
+ Memcached.new(['memcached1.foo.com', 'memcached2.foo.com'])
44
+ ```
45
+
46
+ ## Benchmarks
47
+
48
+ For benchmarks please refer to https://github.com/basecamp/memcached_bench.
49
+
50
+ ## Code status
51
+
52
+ [![Build Status](https://travis-ci.org/Shopify/memcached_store.svg?branch=master)](https://travis-ci.org/Shopify/memcached_store)
53
+
54
+ ## Contributing
55
+
56
+ 1. Fork it
57
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
58
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
59
+ 4. Push to the branch (`git push origin my-new-feature`)
60
+ 5. Create new Pull Request
@@ -0,0 +1,45 @@
1
+ begin
2
+ require 'snappy'
3
+ rescue LoadError => e
4
+ $stderr.puts "You don't have snappy installed in your application. Please add `gem \"snappy\"` to your Gemfile and run bundle install"
5
+ raise e
6
+ end
7
+
8
+ require 'active_support/cache/memcached_store'
9
+
10
+ module ActiveSupport
11
+ module Cache
12
+ class MemcachedSnappyStore < MemcachedStore
13
+ class UnsupportedOperation < StandardError; end
14
+
15
+ module SnappyCompressor
16
+ def self.compress(source)
17
+ Snappy.deflate(source)
18
+ end
19
+
20
+ def self.decompress(source)
21
+ Snappy.inflate(source)
22
+ end
23
+ end
24
+
25
+ def increment(*)
26
+ raise UnsupportedOperation, "increment is not supported by: #{self.class.name}"
27
+ end
28
+
29
+ def decrement(*)
30
+ raise UnsupportedOperation, "decrement is not supported by: #{self.class.name}"
31
+ end
32
+
33
+ # IdentityCache has its own handling for read only.
34
+ def read_only
35
+ false
36
+ end
37
+
38
+ def initialize(*addresses, **options)
39
+ options[:codec] ||= ActiveSupport::Cache::MemcachedStore::Codec.new(compressor: SnappyCompressor)
40
+ options[:compress] = false
41
+ super(*addresses, **options)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,392 @@
1
+ # file havily based out off https://github.com/rails/rails/blob/3-2-stable/activesupport/lib/active_support/cache/mem_cache_store.rb
2
+ require 'digest/md5'
3
+ require 'delegate'
4
+
5
+ module ActiveSupport
6
+ module Cache
7
+ # A cache store implementation which stores data in Memcached:
8
+ # http://memcached.org/
9
+ #
10
+ # MemcachedStore uses memcached gem as backend to connect to Memcached server.
11
+ #
12
+ # MemcachedStore implements the Strategy::LocalCache strategy which implements
13
+ # an in-memory cache inside of a block.
14
+ class MemcachedStore < Store
15
+ ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n
16
+
17
+ class Codec
18
+ # use dalli compatible flags
19
+ SERIALIZED_FLAG = 0x1
20
+ COMPRESSED_FLAG = 0x2
21
+
22
+ # Older versions of this gem would use 0 for the flags whether or not
23
+ # the value was marshal dumped. By setting this flag, we can tell if
24
+ # it were set with an older version for backwards compatible decoding.
25
+ RAW_FLAG = 0x10
26
+
27
+ def initialize(serializer: Marshal, compressor: nil)
28
+ @serializer = serializer
29
+ @compressor = compressor
30
+ end
31
+
32
+ def encode(_key, value, flags)
33
+ unless value.is_a?(String)
34
+ flags |= SERIALIZED_FLAG
35
+ value = @serializer.dump(value)
36
+ end
37
+ if @compressor
38
+ flags |= COMPRESSED_FLAG
39
+ value = @compressor.compress(value)
40
+ end
41
+ flags |= RAW_FLAG if flags == 0
42
+ [value, flags]
43
+ end
44
+
45
+ def decode(_key, value, flags)
46
+ if (flags & COMPRESSED_FLAG) != 0
47
+ value = @compressor.decompress(value)
48
+ end
49
+
50
+ if (flags & SERIALIZED_FLAG) != 0
51
+ @serializer.load(value)
52
+ elsif flags == 0 # legacy cache value
53
+ @serializer.load(value) rescue value
54
+ else
55
+ value
56
+ end
57
+ end
58
+ end
59
+
60
+ attr_accessor :read_only, :swallow_exceptions
61
+
62
+ prepend(Strategy::LocalCache)
63
+
64
+ def initialize(*addresses, **options)
65
+ addresses = addresses.flatten
66
+ options[:codec] ||= Codec.new
67
+ @swallow_exceptions = true
68
+ @swallow_exceptions = options.delete(:swallow_exceptions) if options.key?(:swallow_exceptions)
69
+
70
+ if options.key?(:coder)
71
+ raise ArgumentError, "ActiveSupport::Cache::MemcachedStore doesn't support custom coders"
72
+ end
73
+
74
+ # We don't use a coder, so we set it to nil so Active Support don't think we're using
75
+ # a deprecated one.
76
+ super(options.merge(coder: nil))
77
+
78
+ if addresses.first.is_a?(Memcached)
79
+ @connection = addresses.first
80
+ raise "Memcached::Rails is no longer supported, "\
81
+ "use a Memcached instance instead" if @connection.is_a?(Memcached::Rails)
82
+ else
83
+ mem_cache_options = options.dup
84
+ servers = mem_cache_options.delete(:servers)
85
+ UNIVERSAL_OPTIONS.each { |name| mem_cache_options.delete(name) }
86
+ @connection = Memcached.new([*addresses, *servers], mem_cache_options)
87
+ end
88
+ end
89
+
90
+ def append(name, value, options = nil)
91
+ return true if read_only
92
+ options = merged_options(options)
93
+ normalized_key = normalize_key(name, options)
94
+
95
+ handle_exceptions(return_value_on_error: nil, on_miss: false, miss_exceptions: [Memcached::NotStored]) do
96
+ instrument(:append, name) do
97
+ @connection.append(normalized_key, value)
98
+ end
99
+ true
100
+ end
101
+ end
102
+
103
+ def write(*)
104
+ return true if read_only
105
+ super
106
+ end
107
+
108
+ def delete(*)
109
+ return true if read_only
110
+ super
111
+ end
112
+
113
+ def read_multi(*names)
114
+ options = names.extract_options!
115
+ return {} if names.empty?
116
+
117
+ options = merged_options(options)
118
+ keys_to_names = Hash[names.map { |name| [normalize_key(name, options), name] }]
119
+ values = {}
120
+
121
+ handle_exceptions(return_value_on_error: {}) do
122
+ instrument(:read_multi, names, options) do
123
+ if raw_values = @connection.get(keys_to_names.keys)
124
+ raw_values.each do |key, value|
125
+ entry = deserialize_entry(value)
126
+ values[keys_to_names[key]] = entry.value unless entry.expired?
127
+ end
128
+ end
129
+ end
130
+ values
131
+ end
132
+ end
133
+
134
+ def cas(name, options = nil)
135
+ options = merged_options(options)
136
+ key = normalize_key(name, options)
137
+ payload = nil
138
+
139
+ success = handle_exceptions(return_value_on_error: false) do
140
+ instrument(:cas, name, options) do
141
+ @connection.cas(key, expiration(options)) do |raw_value|
142
+ entry = deserialize_entry(raw_value)
143
+ value = yield entry.value
144
+ break true if read_only
145
+ payload = serialize_entry(Entry.new(value, **options), options)
146
+ end
147
+ end
148
+ true
149
+ end
150
+
151
+ if success
152
+ local_cache.write_entry(key, payload) if local_cache
153
+ else
154
+ local_cache.delete_entry(key) if local_cache
155
+ end
156
+
157
+ success
158
+ end
159
+
160
+ def cas_multi(*names, **options)
161
+ return if names.empty?
162
+
163
+ options = merged_options(options)
164
+ keys_to_names = Hash[names.map { |name| [normalize_key(name, options), name] }]
165
+
166
+ sent_payloads = nil
167
+
168
+ handle_exceptions(return_value_on_error: false) do
169
+ instrument(:cas_multi, names, options) do
170
+ written_payloads = @connection.cas(keys_to_names.keys, expiration(options)) do |raw_values|
171
+ values = {}
172
+
173
+ raw_values.each do |key, raw_value|
174
+ entry = deserialize_entry(raw_value)
175
+ values[keys_to_names[key]] = entry.value unless entry.expired?
176
+ end
177
+
178
+ values = yield values
179
+
180
+ break true if read_only
181
+
182
+ serialized_values = values.map do |name, value|
183
+ [normalize_key(name, options), serialize_entry(Entry.new(value, **options), options)]
184
+ end
185
+
186
+ sent_payloads = Hash[serialized_values]
187
+ end
188
+
189
+ if local_cache && sent_payloads
190
+ sent_payloads.each_key do |key|
191
+ if written_payloads.key?(key)
192
+ local_cache.write_entry(key, written_payloads[key])
193
+ else
194
+ local_cache.delete_entry(key)
195
+ end
196
+ end
197
+ end
198
+
199
+ true
200
+ end
201
+ end
202
+ end
203
+
204
+ def increment(name, amount = 1, options = nil) # :nodoc:
205
+ options = merged_options(options)
206
+ handle_exceptions(return_value_on_error: nil) do
207
+ instrument(:increment, name, amount: amount) do
208
+ @connection.incr(normalize_key(name, options), amount)
209
+ end
210
+ end
211
+ end
212
+
213
+ def decrement(name, amount = 1, options = nil) # :nodoc:
214
+ options = merged_options(options)
215
+ handle_exceptions(return_value_on_error: nil) do
216
+ instrument(:decrement, name, amount: amount) do
217
+ @connection.decr(normalize_key(name, options), amount)
218
+ end
219
+ end
220
+ end
221
+
222
+ def clear(options = nil)
223
+ ActiveSupport::Notifications.instrument("cache_clear.active_support", options || {}) do
224
+ @connection.flush
225
+ end
226
+ end
227
+
228
+ def stats
229
+ ActiveSupport::Notifications.instrument("cache_stats.active_support") do
230
+ @connection.stats
231
+ end
232
+ end
233
+
234
+ def exist?(*)
235
+ !!super
236
+ end
237
+
238
+ def reset #:nodoc:
239
+ handle_exceptions(return_value_on_error: false) do
240
+ @connection.reset
241
+ end
242
+ end
243
+
244
+ private
245
+
246
+ if private_method_defined?(:read_serialized_entry)
247
+ class DupLocalStore < DelegateClass(Strategy::LocalCache::LocalStore)
248
+ def write_entry(_key, entry)
249
+ if entry.is_a?(Entry)
250
+ entry.dup_value!
251
+ end
252
+ super
253
+ end
254
+
255
+ def fetch_entry(key)
256
+ entry = super do
257
+ new_entry = yield
258
+ if entry.is_a?(Entry)
259
+ new_entry.dup_value!
260
+ end
261
+ new_entry
262
+ end
263
+ entry = entry.dup
264
+
265
+ if entry.is_a?(Entry)
266
+ entry.dup_value!
267
+ end
268
+
269
+ entry
270
+ end
271
+ end
272
+
273
+ module DupLocalCache
274
+ private
275
+
276
+ def local_cache
277
+ if local_cache = super
278
+ DupLocalStore.new(local_cache)
279
+ end
280
+ end
281
+ end
282
+
283
+ prepend DupLocalCache
284
+
285
+ def read_entry(key, **options) # :nodoc:
286
+ deserialize_entry(read_serialized_entry(key, **options))
287
+ end
288
+
289
+ def read_serialized_entry(key, **)
290
+ handle_exceptions(return_value_on_error: nil) do
291
+ @connection.get(key)
292
+ end
293
+ end
294
+
295
+ def write_entry(key, entry, **options) # :nodoc:
296
+ return true if read_only
297
+
298
+ write_serialized_entry(key, serialize_entry(entry, **options), **options)
299
+ end
300
+
301
+ def write_serialized_entry(key, value, **options)
302
+ method = options && options[:unless_exist] ? :add : :set
303
+ expires_in = expiration(options)
304
+ handle_exceptions(return_value_on_error: false) do
305
+ @connection.send(method, key, value, expires_in)
306
+ true
307
+ end
308
+ end
309
+ else
310
+ def read_entry(key, _options) # :nodoc:
311
+ handle_exceptions(return_value_on_error: nil) do
312
+ deserialize_entry(@connection.get(key))
313
+ end
314
+ end
315
+
316
+ def write_entry(key, entry, options) # :nodoc:
317
+ return true if read_only
318
+ method = options && options[:unless_exist] ? :add : :set
319
+ expires_in = expiration(options)
320
+ value = serialize_entry(entry, options)
321
+ handle_exceptions(return_value_on_error: false) do
322
+ @connection.send(method, key, value, expires_in)
323
+ true
324
+ end
325
+ end
326
+ end
327
+
328
+ def delete_entry(key, _options) # :nodoc:
329
+ return true if read_only
330
+ handle_exceptions(return_value_on_error: false, on_miss: true) do
331
+ @connection.delete(key)
332
+ true
333
+ end
334
+ end
335
+
336
+ private
337
+
338
+ def normalize_key(key, options)
339
+ key = super.dup
340
+ key = key.force_encoding(Encoding::ASCII_8BIT)
341
+ key = key.gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" }
342
+ # When we remove support to Rails 5.1 we can change the code to use ActiveSupport::Digest
343
+ key = "#{key[0, 213]}:md5:#{::Digest::MD5.hexdigest(key)}" if key.size > 250
344
+ key
345
+ end
346
+
347
+ def deserialize_entry(value)
348
+ unless value.nil?
349
+ value.is_a?(Entry) ? value : Entry.new(value, compress: false)
350
+ end
351
+ end
352
+
353
+ def serialize_entry(entry, options)
354
+ if options[:raw]
355
+ entry.value.to_s
356
+ else
357
+ entry
358
+ end
359
+ end
360
+
361
+ def expiration(options)
362
+ expires_in = options[:expires_in].to_i
363
+ if expires_in > 0 && options[:race_condition_ttl] && !options[:raw]
364
+ expires_in += options[:race_condition_ttl].to_i
365
+ end
366
+ expires_in
367
+ end
368
+
369
+ def handle_exceptions(return_value_on_error:, on_miss: return_value_on_error, miss_exceptions: [])
370
+ yield
371
+ rescue Memcached::NotFound, Memcached::ConnectionDataExists, *miss_exceptions
372
+ on_miss
373
+ rescue Memcached::Error => e
374
+ log_warning(e)
375
+ raise unless @swallow_exceptions
376
+ return_value_on_error
377
+ end
378
+
379
+ def log_warning(err)
380
+ return unless logger
381
+ return if err.is_a?(Memcached::NotStored) && @swallow_exceptions
382
+
383
+ logger.warn(
384
+ "[MEMCACHED_ERROR] swallowed=#{@swallow_exceptions}" \
385
+ " exception_class=#{err.class} exception_message=#{err.message}"
386
+ )
387
+ end
388
+
389
+ ActiveSupport.run_load_hooks(:memcached_store)
390
+ end
391
+ end
392
+ end
@@ -0,0 +1,9 @@
1
+ module MemcachedStore
2
+ class Railtie < Rails::Railtie
3
+ initializer 'memcached_store.configuration' do
4
+ ActiveSupport.on_load(:memcached_store) do
5
+ ActiveSupport::Cache::MemcachedStore.logger ||= Rails.logger
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,4 @@
1
+ # encoding: utf-8
2
+ module MemcachedStore
3
+ VERSION = "2.3.2"
4
+ end
@@ -0,0 +1,4 @@
1
+ require "memcached"
2
+ require "active_support"
3
+ require "active_support/cache"
4
+ require "memcached_store/railtie" if defined?(Rails)
metadata ADDED
@@ -0,0 +1,127 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vinted-memcached_store
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.3.2
5
+ platform: ruby
6
+ authors:
7
+ - Camilo Lopez
8
+ - Tom Burns
9
+ - Arthur Neves
10
+ - Francis Bogsanyi
11
+ autorequire:
12
+ bindir: bin
13
+ cert_chain: []
14
+ date: 2023-08-31 00:00:00.000000000 Z
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: activesupport
18
+ requirement: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: '6'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '6'
30
+ - !ruby/object:Gem::Dependency
31
+ name: memcached
32
+ requirement: !ruby/object:Gem::Requirement
33
+ requirements:
34
+ - - "~>"
35
+ - !ruby/object:Gem::Version
36
+ version: '1.8'
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - "~>"
42
+ - !ruby/object:Gem::Version
43
+ version: '1.8'
44
+ - !ruby/object:Gem::Dependency
45
+ name: rake
46
+ requirement: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ type: :development
52
+ prerelease: false
53
+ version_requirements: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ - !ruby/object:Gem::Dependency
59
+ name: mocha
60
+ requirement: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ type: :development
66
+ prerelease: false
67
+ version_requirements: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ - !ruby/object:Gem::Dependency
73
+ name: timecop
74
+ requirement: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ type: :development
80
+ prerelease: false
81
+ version_requirements: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ description: Plugin-able Memcached adapters to add features (compression, safety)
87
+ email:
88
+ - camilo@camilolopez.com
89
+ - tom.burns@shopify.com
90
+ - arthurnn@gmail.com
91
+ - francis.bogsanyi@shopify.com
92
+ executables: []
93
+ extensions: []
94
+ extra_rdoc_files: []
95
+ files:
96
+ - LICENSE
97
+ - README.md
98
+ - lib/active_support/cache/memcached_snappy_store.rb
99
+ - lib/active_support/cache/memcached_store.rb
100
+ - lib/memcached_store.rb
101
+ - lib/memcached_store/railtie.rb
102
+ - lib/memcached_store/version.rb
103
+ homepage: https://github.com/Shopify/memcached_store/
104
+ licenses:
105
+ - MIT
106
+ metadata:
107
+ allowed_push_host: https://rubygems.org
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: 2.6.0
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ requirements: []
123
+ rubygems_version: 3.4.10
124
+ signing_key:
125
+ specification_version: 4
126
+ summary: Plugin-able Memcached adapters to add features (compression, safety)
127
+ test_files: []