vinted-memcached_store 2.3.2

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.
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: []