pax_global_header00006660000000000000000000000064141515023460014513gustar00rootroot0000000000000052 comment=aa007815f6ef274fa48001c16b067d9e7c5fe652 dalli-3.0.6/000077500000000000000000000000001415150234600126065ustar00rootroot00000000000000dalli-3.0.6/.github/000077500000000000000000000000001415150234600141465ustar00rootroot00000000000000dalli-3.0.6/.github/workflows/000077500000000000000000000000001415150234600162035ustar00rootroot00000000000000dalli-3.0.6/.github/workflows/codeql-analysis.yml000066400000000000000000000041501415150234600220160ustar00rootroot00000000000000# For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL" on: push: branches: [ master ] pull_request: # The branches below must be a subset of the branches above branches: [ master ] schedule: - cron: '22 14 * * 5' jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: [ 'ruby' ] steps: - name: Checkout repository uses: actions/checkout@v2 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v1 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # queries: ./path/to/local/query, your-org/your-repo/queries@main # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v1 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines # and modify them (or add more) to build your code if your project # uses a compiled language #- run: | # make bootstrap # make release - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v1 dalli-3.0.6/.github/workflows/rubocop.yml000066400000000000000000000005271415150234600204030ustar00rootroot00000000000000name: RuboCop on: [push, pull_request] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: 2.5 bundler-cache: true # 'bundle install' and cache - name: Run RuboCop run: bundle exec rubocop --parallel dalli-3.0.6/.github/workflows/tests.yml000066400000000000000000000013771415150234600201000ustar00rootroot00000000000000name: Tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest strategy: matrix: ruby-version: [2.5, 2.6, 2.7, '3.0', jruby-9.2, jruby-9.3] memcached-version: ['1.5.22', '1.6.12'] steps: - uses: actions/checkout@v2 - name: Install Memcached ${{ matrix.memcached-version }} working-directory: scripts env: MEMCACHED_VERSION: ${{ matrix.memcached-version }} run: | chmod +x ./install_memcached.sh ./install_memcached.sh - name: Set up Ruby ${{ matrix.ruby-version }} uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby-version }} bundler-cache: true # 'bundle install' and cache - name: Run tests run: bundle exec rake dalli-3.0.6/.gitignore000066400000000000000000000011331415150234600145740ustar00rootroot00000000000000*.gem *.rbc /.config /coverage/ /InstalledFiles /pkg/ /spec/reports/ /test/tmp/ /test/version_tmp/ /tmp/ ## Specific to RubyMotion: .dat* .repl_history build/ ## Documentation cache and generated files: /.yardoc/ /_yardoc/ /doc/ /html/ /rdoc/ profile.html ## Environment normalisation: /.bundle/ /lib/bundler/man/ # for a library or gem, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: Gemfile.lock gemfiles/*.lock .ruby-version .ruby-gemset # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: .rvmrc dalli-3.0.6/.rubocop.yml000066400000000000000000000004151415150234600150600ustar00rootroot00000000000000inherit_from: .rubocop_todo.yml require: - rubocop-minitest - rubocop-performance - rubocop-rake AllCops: NewCops: enable TargetRubyVersion: 2.5 Metrics/BlockLength: Max: 50 Exclude: - 'test/**/*' Style/Documentation: Exclude: - 'test/**/*' dalli-3.0.6/.rubocop_todo.yml000066400000000000000000000015241415150234600161070ustar00rootroot00000000000000# This configuration was generated by # `rubocop --auto-gen-config` # on 2021-11-08 17:19:45 UTC using RuboCop version 1.22.3. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. # Offense count: 7 # Configuration parameters: IgnoredMethods, CountRepeatedAttributes. Metrics/AbcSize: Max: 52 # Offense count: 2 # Configuration parameters: CountComments, CountAsOne. Metrics/ClassLength: Max: 406 # Offense count: 11 # Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods. Metrics/MethodLength: Exclude: - 'lib/dalli/client.rb' - 'lib/dalli/protocol/binary.rb' - 'lib/rack/session/dalli.rb' dalli-3.0.6/.standard.yml000066400000000000000000000004201415150234600152030ustar00rootroot00000000000000fix: false # default: false parallel: true # default: false ruby_version: 2.5.1 # default: RUBY_VERSION default_ignores: false # default: true ignore: # default: [] - 'test/**/*': - Style/GlobalVars - Style/Semicolon dalli-3.0.6/3.0-Upgrade.md000066400000000000000000000025751415150234600150260ustar00rootroot00000000000000# Dalli 3.0 This major version update contains several backwards incompatible changes. * **:dalli_store** has been removed. Users should migrate to the official Rails **:mem_cache_store**, documented in the [caching guide](https://guides.rubyonrails.org/caching_with_rails.html#activesupport-cache-memcachestore). * Attempting to store a larger value than allowed by memcached used to print a warning and truncate the value. This now raises an error to prevent silent data corruption. * Compression now defaults to `true` for large values (greater than 4KB). This is intended to minimize errors due to the previous note. * Errors marshalling values now raise rather than just printing an error. * The Rack session adapter has been refactored to remove support for thread-unsafe configurations. You will need to include the `connection_pool` gem in your Gemfile to ensure session operations are thread-safe. * Support for the `kgio` gem has been removed, it is not relevant in Ruby 2.3+. * Removed inline native code, use Ruby 2.3+ support for bsearch instead. * The CAS operations previously in 'dalli/cas/client' have been integrated into 'dalli/client'. ## Future Directions The memcached project has deprecated the binary protocol used by Dalli in favor of a new `meta/text` protocol that is somewhat human readable. Dalli 4.0 will move in this direction and require memcached 1.6+. dalli-3.0.6/Gemfile000066400000000000000000000004061415150234600141010ustar00rootroot00000000000000# frozen_string_literal: true source 'https://rubygems.org' gemspec group :test do gem 'minitest' gem 'rake' gem 'rubocop' gem 'rubocop-minitest' gem 'rubocop-performance' gem 'rubocop-rake' gem 'ruby-prof', platform: :mri gem 'simplecov' end dalli-3.0.6/History.md000066400000000000000000000444761415150234600146100ustar00rootroot00000000000000Dalli Changelog ===================== 3.0.6 ========== - Fix regression in SASL authentication response parsing (petergoldstein) 3.0.5 ========== - Add Rubocop and fix most outstanding issues (petergoldstein) - Extract a number of classes, to simplify the largest classes (petergoldstein) - Ensure against socket corruption if an error occurs in a multi block (petergoldstein) 3.0.4 ========== - Clean connections and retry after NetworkError in get_multi (andrejbl) - Internal refactoring and cleanup (petergoldstein) 3.0.3 ========== - Restore ability for `compress` to be disabled on a per request basis (petergoldstein) - Fix broken image in README (deining) - Use bundler-cache in CI (olleolleolle) - Remove the OpenSSL extensions dependency (petergoldstein) - Add Memcached 1.5.x to the CI matrix - Updated compression documentation (petergoldstein) 3.0.2 ========== - Restore Windows compatibility (petergoldstein) - Add JRuby to CI and make requisite changes (petergoldstein) - Clarify documentation for supported rubies (petergoldstein) 3.0.1 ========== - Fix syntax error that prevented inclusion of Dalli::Server (ryanfb) - Restore with method required by ActiveSupport::Cache::MemCacheStore 3.0.0 ========== - BREAKING CHANGES: * Removes :dalli_store. Use Rails' official :mem_cache_store instead. https://guides.rubyonrails.org/caching_with_rails.html * Attempting to store a larger value than allowed by memcached used to print a warning and truncate the value. This now raises an error to prevent silent data corruption. * Compression now defaults to `true` for large values (greater than 4KB). This is intended to minimize errors due to the previous note. * Errors marshalling values now raise rather than just printing an error. * The Rack session adapter has been refactored to remove support for thread-unsafe configurations. You will need to include the `connection_pool` gem in your Gemfile to ensure session operations are thread-safe. - Raise NetworkError when multi response gets into corrupt state (mervync, #783) - Validate servers argument (semaperepelitsa, petergoldstein, #776) - Enable SSL support (bdunne, #775) - Add gat operation (tbeauvais, #769) - Removes inline native code, use Ruby 2.3+ support for bsearch instead. (mperham) - Switch repo to Github Actions and upgrade Ruby versions (petergoldstein, bdunne, Fryguy) - Update benchmark test for Rubyprof changes (nateberkopec) - Remove support for the `kgio` gem, it is not relevant in Ruby 2.3+. (mperham) - Remove inline native code, use Ruby 2.3+ support for bsearch instead. (mperham) 2.7.11 ========== - DEPRECATION: :dalli_store will be removed in Dalli 3.0. Use Rails' official :mem_cache_store instead. https://guides.rubyonrails.org/caching_with_rails.html - Add new `digest_class` option to Dalli::Client [#724] - Don't treat NameError as a network error [#728] - Handle nested comma separated server strings (sambostock) 2.7.10 ========== - Revert frozen string change (schneems) - Advertise supports_cached_versioning? in DalliStore (schneems) - Better detection of fork support, to allow specs to run under Truffle Ruby (deepj) - Update logging for over max size to log as error (aeroastro) 2.7.9 ========== - Fix behavior for Rails 5.2+ cache_versioning (GriwMF) - Ensure fetch provides the key to the fallback block as an argument (0exp) - Assorted performance improvements (schneems) 2.7.8 ========== - Rails 5.2 compatibility (pbougie) - Fix Session Cache compatibility (pixeltrix) 2.7.7 ========== - Support large cache keys on fetch multi (sobrinho) - Not found checks no longer trigger the result's equality method (dannyfallon) - Use SVG build badges (olleolleolle) - Travis updates (junaruga, tiarly, petergoldstein) - Update default down_retry_delay (jaredhales) - Close kgio socket after IO.select timeouts - Documentation updates (tipair) - Instrument DalliStore errors with instrument_errors configuration option. (btatnall) 2.7.6 ========== - Rails 5.0.0.beta2 compatibility (yui-knk, petergoldstein) - Add cas!, a variant of the #cas method that yields to the block whether or not the key already exist (mwpastore) - Performance improvements (nateberkopec) - Add Ruby 2.3.0 to support matrix (tricknotes) 2.7.5 ========== - Support rcvbuff and sndbuff byte configuration. (btatnall) - Add `:cache_nils` option to support nil values in `DalliStore#fetch` and `Dalli::Client#fetch` (wjordan, #559) - Log retryable server errors with 'warn' instead of 'info' (phrinx) - Fix timeout issue with Dalli::Client#get_multi_yielder (dspeterson) - Escape namespaces with special regexp characters (Steven Peckins) - Ensure LocalCache supports the `:raw` option and Entry unwrapping (sj26) - Ensure bad ttl values don't cause Dalli::RingError (eagletmt, petergoldstein) - Always pass namespaced key to instrumentation API (kaorimatz) - Replace use of deprecated TimeoutError with Timeout::Error (eagletmt) - Clean up gemspec, and use Bundler for loading (grosser) - Dry up local cache testing (grosser) 2.7.4 ========== - Restore Windows compatibility (dfens, #524) 2.7.3 ========== - Assorted spec improvements - README changes to specify defaults for failover and compress options (keen99, #470) - SASL authentication changes to deal with Unicode characters (flypiggy, #477) - Call to_i on ttl to accomodate ActiveSupport::Duration (#494) - Change to implicit blocks for performance (glaucocustodio, #495) - Change to each_key for performance (jastix, #496) - Support stats settings - (dterei, #500) - Raise DallError if hostname canno be parsed (dannyfallon, #501) - Fix instrumentation for falsey values (AlexRiedler, #514) - Support UNIX socket configurations (r-stu31, #515) 2.7.2 ========== - The fix for #423 didn't make it into the released 2.7.1 gem somehow. 2.7.1 ========== - Rack session will check if servers are up on initialization (arthurnn, #423) - Add support for IPv6 addresses in hex form, ie: "[::1]:11211" (dplummer, #428) - Add symbol support for namespace (jingkai #431) - Support expiration intervals longer than 30 days (leonid-shevtsov #436) 2.7.0 ========== - BREAKING CHANGE: Dalli::Client#add and #replace now return a truthy value, not boolean true or false. - Multithreading support with dalli\_store: Use :pool\_size to create a pool of shared, threadsafe Dalli clients in Rails: ```ruby config.cache_store = :dalli_store, "cache-1.example.com", "cache-2.example.com", :compress => true, :pool_size => 5, :expires_in => 300 ``` This will ensure the Rails.cache singleton does not become a source of contention. **PLEASE NOTE** Rails's :mem\_cache\_store does not support pooling as of Rails 4.0. You must use :dalli\_store. - Implement `version` for retrieving version of connected servers [dterei, #384] - Implement `fetch_multi` for batched read/write [sorentwo, #380] - Add more support for safe updates with multiple writers: [philipmw, #395] `require 'dalli/cas/client'` augments Dalli::Client with the following methods: * Get value with CAS: `[value, cas] = get_cas(key)` `get_cas(key) {|value, cas| ...}` * Get multiple values with CAS: `get_multi_cas(k1, k2, ...) {|value, metadata| cas = metadata[:cas]}` * Set value with CAS: `new_cas = set_cas(key, value, cas, ttl, options)` * Replace value with CAS: `replace_cas(key, new_value, cas, ttl, options)` * Delete value with CAS: `delete_cas(key, cas)` - Fix bug with get key with "Not found" value [uzzz, #375] 2.6.4 ======= - Fix ADD command, aka `write(unless_exist: true)` (pitr, #365) - Upgrade test suite from mini\_shoulda to minitest. - Even more performance improvements for get\_multi (xaop, #331) 2.6.3 ======= - Support specific stats by passing `:items` or `:slabs` to `stats` method [bukhamseen] - Fix 'can't modify frozen String' errors in `ActiveSupport::Cache::DalliStore` [dblock] - Protect against objects with custom equality checking [theron17] - Warn if value for key is too large to store [locriani] 2.6.2 ======= - Properly handle missing RubyInline 2.6.1 ======= - Add optional native C binary search for ring, add: gem 'RubyInline' to your Gemfile to get a 10% speedup when using many servers. You will see no improvement if you are only using one server. - More get_multi performance optimization [xaop, #315] - Add lambda support for cache namespaces [joshwlewis, #311] 2.6.0 ======= - read_multi optimization, now checks local_cache [chendo, #306] - Re-implement get_multi to be non-blocking [tmm1, #295] - Add `dalli` accessor to dalli_store to access the underlying Dalli::Client, for things like `get_multi`. - Add `Dalli::GzipCompressor`, primarily for compatibility with nginx's HttpMemcachedModule using `memcached_gzip_flag` 2.5.0 ======= - Don't escape non-ASCII keys, memcached binary protocol doesn't care. [#257] - :dalli_store now implements LocalCache [#236] - Removed lots of old session_store test code, tests now all run without a default memcached server [#275] - Changed Dalli ActiveSupport adapter to always attempt instrumentation [brianmario, #284] - Change write operations (add/set/replace) to return false when value is too large to store [brianmario, #283] - Allowing different compressors per client [naseem] 2.4.0 ======= - Added the ability to swap out the compressed used to [de]compress cache data [brianmario, #276] - Fix get\_multi performance issues with lots of memcached servers [tmm1] - Throw more specific exceptions [tmm1] - Allowing different types of serialization per client [naseem] 2.3.0 ======= - Added the ability to swap out the serializer used to [de]serialize cache data [brianmario, #274] 2.2.1 ======= - Fix issues with ENV-based connections. [#266] - Fix problem with SessionStore in Rails 4.0 [#265] 2.2.0 ======= - Add Rack session with\_lock helper, for Rails 4.0 support [#264] - Accept connection string in the form of a URL (e.g., memcached://user:pass@hostname:port) [glenngillen] - Add touch operation [#228, uzzz] 2.1.0 ======= - Add Railtie to auto-configure Dalli when included in Gemfile [#217, steveklabnik] 2.0.5 ======= - Create proper keys for arrays of objects passed as keys [twinturbo, #211] - Handle long key with namespace [#212] - Add NODELAY to TCP socket options [#206] 2.0.4 ======= - Dalli no longer needs to be reset after Unicorn/Passenger fork [#208] - Add option to re-raise errors rescued in the session and cache stores. [pitr, #200] - DalliStore#fetch called the block if the cached value == false [#205] - DalliStore should have accessible options [#195] - Add silence and mute support for DalliStore [#207] - Tracked down and fixed socket corruption due to Timeout [#146] 2.0.3 ======= - Allow proper retrieval of stored `false` values [laserlemon, #197] - Allow non-ascii and whitespace keys, only the text protocol has those restrictions [#145] - Fix DalliStore#delete error-handling [#196] 2.0.2 ======= - Fix all dalli\_store operations to handle nil options [#190] - Increment and decrement with :initial => nil now return nil (lawrencepit, #112) 2.0.1 ======= - Fix nil option handling in dalli\_store#write [#188] 2.0.0 ======= - Reimplemented the Rails' dalli\_store to remove use of ActiveSupport::Cache::Entry which added 109 bytes overhead to every value stored, was a performance bottleneck and duplicated a lot of functionality already in Dalli. One benchmark went from 4.0 sec to 3.0 sec with the new dalli\_store. [#173] - Added reset\_stats operation [#155] - Added support for configuring keepalive on TCP connections to memcached servers (@bianster, #180) Notes: * data stored with dalli\_store 2.x is NOT backwards compatible with 1.x. Upgraders are advised to namespace their keys and roll out the 2.x upgrade slowly so keys do not clash and caches are warmed. `config.cache_store = :dalli_store, :expires_in => 24.hours.to_i, :namespace => 'myapp2'` * data stored with plain Dalli::Client API is unchanged. * removed support for dalli\_store's race\_condition\_ttl option. * removed support for em-synchrony and unix socket connection options. * removed support for Ruby 1.8.6 * removed memcache-client compability layer and upgrade documentation. 1.1.5 ======= - Coerce input to incr/decr to integer via #to\_i [#165] - Convert test suite to minitest/spec (crigor, #166) - Fix encoding issue with keys [#162] - Fix double namespacing with Rails and dalli\_store. [#160] 1.1.4 ======= - Use 127.0.0.1 instead of localhost as default to avoid IPv6 issues - Extend DalliStore's :expires\_in when :race\_condition\_ttl is also used. - Fix :expires\_in option not propogating from DalliStore to Client, GH-136 - Added support for native Rack session store. Until now, Dalli's session store has required Rails. Now you can use Dalli to store sessions for any Rack application. require 'rack/session/dalli' use Rack::Session::Dalli, :memcache_server => 'localhost:11211', :compression => true 1.1.3 ======= - Support Rails's autoloading hack for loading sessions with objects whose classes have not be required yet, GH-129 - Support Unix sockets for connectivity. Shows a 2x performance increase but keep in mind they only work on localhost. (dfens) 1.1.2 ======= - Fix incompatibility with latest Rack session API when destroying sessions, thanks @twinge! 1.1.1 ======= v1.1.0 was a bad release. Yanked. 1.1.0 ======= - Remove support for Rails 2.3, add support for Rails 3.1 - Fix socket failure retry logic, now you can restart memcached and Dalli won't complain! - Add support for fibered operation via em-synchrony (eliaslevy) - Gracefully handle write timeouts, GH-99 - Only issue bug warning for unexpected StandardErrors, GH-102 - Add travis-ci build support (ryanlecompte) - Gracefully handle errors in get_multi (michaelfairley) - Misc fixes from crash2burn, fphilipe, igreg, raggi 1.0.5 ======= - Fix socket failure retry logic, now you can restart memcached and Dalli won't complain! 1.0.4 ======= - Handle non-ASCII key content in dalli_store - Accept key array for read_multi in dalli_store - Fix multithreaded race condition in creation of mutex 1.0.3 ======= - Better handling of application marshalling errors - Work around jruby IO#sysread compatibility issue 1.0.2 ======= - Allow browser session cookies (blindsey) - Compatibility fixes (mwynholds) - Add backwards compatibility module for memcache-client, require 'dalli/memcache-client'. It makes Dalli more compatible with memcache-client and prints out a warning any time you do something that is no longer supported so you can fix your code. 1.0.1 ======= - Explicitly handle application marshalling bugs, GH-56 - Add support for username/password as options, to allow multiple bucket access from the same Ruby process, GH-52 - Add support for >1MB values with :value_max_bytes option, GH-54 (r-stu31) - Add support for default TTL, :expires_in, in Rails 2.3. (Steven Novotny) config.cache_store = :dalli_store, 'localhost:11211', {:expires_in => 4.hours} 1.0.0 ======= Welcome gucki as a Dalli committer! - Fix network and namespace issues in get_multi (gucki) - Better handling of unmarshalling errors (mperham) 0.11.2 ======= - Major reworking of socket error and failover handling (gucki) - Add basic JRuby support (mperham) 0.11.1 ====== - Minor fixes, doc updates. - Add optional support for kgio sockets, gives a 10-15% performance boost. 0.11.0 ====== Warning: this release changes how Dalli marshals data. I do not guarantee compatibility until 1.0 but I will increment the minor version every time a release breaks compatibility until 1.0. IT IS HIGHLY RECOMMENDED YOU FLUSH YOUR CACHE BEFORE UPGRADING. - multi() now works reentrantly. - Added new Dalli::Client option for default TTLs, :expires_in, defaults to 0 (aka forever). - Added new Dalli::Client option, :compression, to enable auto-compression of values. - Refactor how Dalli stores data on the server. Values are now tagged as "marshalled" or "compressed" so they can be automatically deserialized without the client having to know how they were stored. 0.10.1 ====== - Prefer server config from environment, fixes Heroku session store issues (thanks JoshMcKin) - Better handling of non-ASCII values (size -> bytesize) - Assert that keys are ASCII only 0.10.0 ====== Warning: this release changed how Rails marshals data with Dalli. Unfortunately previous versions double marshalled values. It is possible that data stored with previous versions of Dalli will not work with this version. IT IS HIGHLY RECOMMENDED YOU FLUSH YOUR CACHE BEFORE UPGRADING. - Rework how the Rails cache store does value marshalling. - Rework old server version detection to avoid a socket read hang. - Refactor the Rails 2.3 :dalli\_store to be closer to :mem\_cache\_store. - Better documentation for session store config (plukevdh) 0.9.10 ---- - Better server retry logic (next2you) - Rails 3.1 compatibility (gucki) 0.9.9 ---- - Add support for *_multi operations for add, set, replace and delete. This implements pipelined network operations; Dalli disables network replies so we're not limited by latency, allowing for much higher throughput. dc = Dalli::Client.new dc.multi do dc.set 'a', 1 dc.set 'b', 2 dc.set 'c', 3 dc.delete 'd' end - Minor fix to set the continuum sorted by value (kangster) - Implement session store with Rails 2.3. Update docs. 0.9.8 ----- - Implement namespace support - Misc fixes 0.9.7 ----- - Small fix for NewRelic integration. - Detect and fail on older memcached servers (pre-1.4). 0.9.6 ----- - Patches for Rails 3.0.1 integration. 0.9.5 ----- - Major design change - raw support is back to maximize compatibility with Rails and the increment/decrement operations. You can now pass :raw => true to most methods to bypass (un)marshalling. - Support symbols as keys (ddollar) - Rails 2.3 bug fixes 0.9.4 ----- - Dalli support now in rack-bug (http://github.com/brynary/rack-bug), give it a try! - Namespace support for Rails 2.3 (bpardee) - Bug fixes 0.9.3 ----- - Rails 2.3 support (beanieboi) - Rails SessionStore support - Passenger integration - memcache-client upgrade docs, see Upgrade.md 0.9.2 ---- - Verify proper operation in Heroku. 0.9.1 ---- - Add fetch and cas operations (mperham) - Add incr and decr operations (mperham) - Initial support for SASL authentication via the MEMCACHE_{USERNAME,PASSWORD} environment variables, needed for Heroku (mperham) 0.9.0 ----- - Initial gem release. dalli-3.0.6/LICENSE000066400000000000000000000020561415150234600136160ustar00rootroot00000000000000Copyright (c) Peter M. Goldstein, Mike Perham Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. dalli-3.0.6/Performance.md000066400000000000000000000042611415150234600153740ustar00rootroot00000000000000Performance ==================== Caching is all about performance, so I carefully track Dalli performance to ensure no regressions. You can optionally use kgio to give Dalli a 10-20% performance boost: `gem install kgio`. Note I've added some benchmarks over time to Dalli that the other libraries don't necessarily have. memcache-client --------------- Testing 1.8.5 with ruby 1.9.3p0 (2011-10-30 revision 33570) [x86_64-darwin11.2.0] user system total real set:plain:memcache-client 1.860000 0.310000 2.170000 ( 2.188030) set:ruby:memcache-client 1.830000 0.290000 2.120000 ( 2.130212) get:plain:memcache-client 1.830000 0.340000 2.170000 ( 2.176156) get:ruby:memcache-client 1.900000 0.330000 2.230000 ( 2.235045) multiget:ruby:memcache-client 0.860000 0.120000 0.980000 ( 0.987348) missing:ruby:memcache-client 1.630000 0.320000 1.950000 ( 1.954867) mixed:ruby:memcache-client 3.690000 0.670000 4.360000 ( 4.364469) dalli ----- Testing with Rails 3.2.1 Using kgio socket IO Testing 2.0.0 with ruby 1.9.3p125 (2012-02-16 revision 34643) [x86_64-darwin11.3.0] user system total real mixed:rails:dalli 1.580000 0.570000 2.150000 ( 3.008839) set:plain:dalli 0.730000 0.300000 1.030000 ( 1.567098) setq:plain:dalli 0.520000 0.120000 0.640000 ( 0.634402) set:ruby:dalli 0.800000 0.300000 1.100000 ( 1.640348) get:plain:dalli 0.840000 0.330000 1.170000 ( 1.668425) get:ruby:dalli 0.850000 0.330000 1.180000 ( 1.665716) multiget:ruby:dalli 0.700000 0.260000 0.960000 ( 0.965423) missing:ruby:dalli 0.720000 0.320000 1.040000 ( 1.511720) mixed:ruby:dalli 1.660000 0.640000 2.300000 ( 3.320743) mixedq:ruby:dalli 1.630000 0.510000 2.140000 ( 2.629734) incr:ruby:dalli 0.270000 0.100000 0.370000 ( 0.547618) dalli-3.0.6/README.md000066400000000000000000000052531415150234600140720ustar00rootroot00000000000000Dalli [![Tests](https://github.com/petergoldstein/dalli/actions/workflows/tests.yml/badge.svg)](https://github.com/petergoldstein/dalli/actions/workflows/tests.yml) ===== Dalli is a high performance pure Ruby client for accessing memcached servers. Dalli supports: * Simple and complex memcached configurations * Failover between memcached instances * Fine-grained control of data serialization and compression * Thread-safe operation (either through use of a connection pool, or by using the Dalli client in threadsafe mode) * SSL/TLS connections to memcached * SASL authentication The name is a variant of Salvador Dali for his famous painting [The Persistence of Memory](http://en.wikipedia.org/wiki/The_Persistence_of_Memory). ![Persistence of Memory](https://upload.wikimedia.org/wikipedia/en/d/dd/The_Persistence_of_Memory.jpg) ## Documentation and Information * [User Documentation](https://github.com/petergoldstein/dalli/wiki) - The documentation is maintained in the repository's wiki. * [Announcements](https://github.com/petergoldstein/dalli/discussions/categories/announcements) - Announcements of interest to the Dalli community will be posted here. * [Bug Reports](https://github.com/petergoldstein/dalli/issues) - If you discover a problem with Dalli, please submit a bug report in the tracker. * [Forum](https://github.com/petergoldstein/dalli/discussions/categories/q-a) - If you have questions about Dalli, please post them here. * [Client API](https://www.rubydoc.info/github/petergoldstein/dalli/master/Dalli/Client) - Ruby documentation for the `Dalli::Client` API ## Contributing If you have a fix you wish to provide, please fork the code, fix in your local project and then send a pull request on github. Please ensure that you include a test which verifies your fix and update `History.md` with a one sentence description of your fix so you get credit as a contributor. ## Appreciation Dalli would not exist in its current form without the contributions of many people. But special thanks go to several individuals and organizations: * Mike Perham - for originally authoring the Dalli project and serving as maintainer and primary contributor for many years * Eric Wong - for help using his [kgio](http://bogomips.org/kgio/) library. * Brian Mitchell - for his remix-stash project which was helpful when implementing and testing the binary protocol support. * [CouchBase](http://couchbase.com) - for their sponsorship of the original development ## Authors * [Peter M. Goldstein](https://github.com/petergoldstein) - current maintainer * [Mike Perham](https://github.com/mperham) and contributors ## Copyright Copyright (c) Mike Perham, Peter M. Goldstein. See LICENSE for details. dalli-3.0.6/Rakefile000066400000000000000000000004651415150234600142600ustar00rootroot00000000000000# frozen_string_literal: true require 'bundler/gem_tasks' require 'rake/testtask' Rake::TestTask.new(:test) do |test| test.pattern = 'test/**/test_*.rb' test.warning = true test.verbose = true end task default: :test Rake::TestTask.new(:bench) do |test| test.pattern = 'test/benchmark_test.rb' end dalli-3.0.6/code_of_conduct.md000066400000000000000000000045421415150234600162520ustar00rootroot00000000000000# Contributor Code of Conduct As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery * Personal attacks * Trolling or insulting/derogatory comments * Public or private harassment * Publishing other's private information, such as physical or electronic addresses, without explicit permission * Other unethical or unprofessional conduct Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project maintainer at peter.m.goldstein AT gmail.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Maintainers are obligated to maintain confidentiality with regard to the reporter of an incident. This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.3.0, available at [http://contributor-covenant.org/version/1/3/0/][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/3/0/ dalli-3.0.6/dalli.gemspec000066400000000000000000000015751415150234600152500ustar00rootroot00000000000000# frozen_string_literal: true require './lib/dalli/version' Gem::Specification.new do |s| s.name = 'dalli' s.version = Dalli::VERSION s.license = 'MIT' s.authors = ['Peter M. Goldstein', 'Mike Perham'] s.description = s.summary = 'High performance memcached client for Ruby' s.email = ['peter.m.goldstein@gmail.com', 'mperham@gmail.com'] s.files = Dir.glob('lib/**/*') + [ 'LICENSE', 'README.md', 'History.md', 'Gemfile' ] s.homepage = 'https://github.com/petergoldstein/dalli' s.required_ruby_version = '>= 2.5' s.add_development_dependency 'connection_pool' s.add_development_dependency 'rack' s.add_development_dependency 'rubocop' s.add_development_dependency 'rubocop-minitest' s.add_development_dependency 'rubocop-performance' s.add_development_dependency 'rubocop-rake' s.metadata = { 'rubygems_mfa_required' => 'true' } end dalli-3.0.6/lib/000077500000000000000000000000001415150234600133545ustar00rootroot00000000000000dalli-3.0.6/lib/dalli.rb000066400000000000000000000035171415150234600147740ustar00rootroot00000000000000# frozen_string_literal: true ## # Namespace for all Dalli code. ## module Dalli autoload :Server, 'dalli/server' # generic error class DalliError < RuntimeError; end # socket/server communication error class NetworkError < DalliError; end # no server available/alive error class RingError < DalliError; end # application error in marshalling serialization class MarshalError < DalliError; end # application error in marshalling deserialization or decompression class UnmarshalError < DalliError; end # payload too big for memcached class ValueOverMaxSize < DalliError; end # Implements the NullObject pattern to store an application-defined value for 'Key not found' responses. class NilObject; end # rubocop:disable Lint/EmptyClass NOT_FOUND = NilObject.new def self.logger @logger ||= (rails_logger || default_logger) end def self.rails_logger (defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger) || (defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER.respond_to?(:debug) && RAILS_DEFAULT_LOGGER) end def self.default_logger require 'logger' l = Logger.new($stdout) l.level = Logger::INFO l end def self.logger=(logger) @logger = logger end end require_relative 'dalli/version' require_relative 'dalli/compressor' require_relative 'dalli/client' require_relative 'dalli/key_manager' require_relative 'dalli/ring' require_relative 'dalli/protocol' require_relative 'dalli/protocol/binary' require_relative 'dalli/protocol/server_config_parser' require_relative 'dalli/protocol/ttl_sanitizer' require_relative 'dalli/protocol/value_compressor' require_relative 'dalli/protocol/value_marshaller' require_relative 'dalli/protocol/value_serializer' require_relative 'dalli/servers_arg_normalizer' require_relative 'dalli/socket' require_relative 'dalli/options' dalli-3.0.6/lib/dalli/000077500000000000000000000000001415150234600144415ustar00rootroot00000000000000dalli-3.0.6/lib/dalli/cas/000077500000000000000000000000001415150234600152075ustar00rootroot00000000000000dalli-3.0.6/lib/dalli/cas/client.rb000066400000000000000000000002211415150234600170050ustar00rootroot00000000000000# frozen_string_literal: true puts "You can remove `require 'dalli/cas/client'` as this code has been rolled into the standard 'dalli/client'." dalli-3.0.6/lib/dalli/client.rb000066400000000000000000000427711415150234600162570ustar00rootroot00000000000000# frozen_string_literal: true require 'digest/md5' require 'set' # encoding: ascii module Dalli ## # Dalli::Client is the main class which developers will use to interact with # Memcached. ## class Client ## # Dalli::Client is the main class which developers will use to interact with # the memcached server. Usage: # # Dalli::Client.new(['localhost:11211:10', # 'cache-2.example.com:11211:5', # '192.168.0.1:22122:5', # '/var/run/memcached/socket'], # failover: true, expires_in: 300) # # servers is an Array of "host:port:weight" where weight allows you to distribute cache unevenly. # Both weight and port are optional. If you pass in nil, Dalli will use the MEMCACHE_SERVERS # environment variable or default to 'localhost:11211' if it is not present. Dalli also supports # the ability to connect to Memcached on localhost through a UNIX socket. To use this functionality, # use a full pathname (beginning with a slash character '/') in place of the "host:port" pair in # the server configuration. # # Options: # - :namespace - prepend each key with this value to provide simple namespacing. # - :failover - if a server is down, look for and store values on another server in the ring. Default: true. # - :threadsafe - ensure that only one thread is actively using a socket at a time. Default: true. # - :expires_in - default TTL in seconds if you do not pass TTL as a parameter to an individual operation, defaults # to 0 or forever. # - :compress - if true Dalli will compress values larger than compression_min_size bytes before sending them # to memcached. Default: true. # - :compression_min_size - the minimum size (in bytes) for which Dalli will compress values sent to Memcached. # Defaults to 4K. # - :serializer - defaults to Marshal # - :compressor - defaults to Dalli::Compressor, a Zlib-based implementation # - :cache_nils - defaults to false, if true Dalli will not treat cached nil values as 'not found' for # #fetch operations. # - :digest_class - defaults to Digest::MD5, allows you to pass in an object that responds to the hexdigest method, # useful for injecting a FIPS compliant hash object. # - :protocol_implementation - defaults to Dalli::Protocol::Binary which uses the binary protocol. Allows you to # pass an alternative implementation using another protocol. # def initialize(servers = nil, options = {}) @servers = ::Dalli::ServersArgNormalizer.normalize_servers(servers) @options = normalize_options(options) @key_manager = ::Dalli::KeyManager.new(options) @ring = nil end # # The standard memcached instruction set # ## # Turn on quiet aka noreply support. # All relevant operations within this block will be effectively # pipelined as Dalli will use 'quiet' operations where possible. # Currently supports the set, add, replace and delete operations. def multi old = Thread.current[:dalli_multi] Thread.current[:dalli_multi] = true yield ensure @ring&.flush_multi_responses Thread.current[:dalli_multi] = old end ## # Get the value associated with the key. # If a value is not found, then +nil+ is returned. def get(key, options = nil) perform(:get, key, options) end ## # Fetch multiple keys efficiently. # If a block is given, yields key/value pairs one at a time. # Otherwise returns a hash of { 'key' => 'value', 'key2' => 'value1' } def get_multi(*keys) keys.flatten! keys.compact! return {} if keys.empty? if block_given? get_multi_yielder(keys) { |k, data| yield k, data.first } else {}.tap do |hash| get_multi_yielder(keys) { |k, data| hash[k] = data.first } end end end CACHE_NILS = { cache_nils: true }.freeze # Fetch the value associated with the key. # If a value is found, then it is returned. # # If a value is not found and no block is given, then nil is returned. # # If a value is not found (or if the found value is nil and :cache_nils is false) # and a block is given, the block will be invoked and its return value # written to the cache and returned. def fetch(key, ttl = nil, req_options = nil) req_options = req_options.nil? ? CACHE_NILS : req_options.merge(CACHE_NILS) if cache_nils val = get(key, req_options) if not_found?(val) && block_given? val = yield add(key, val, ttl_or_default(ttl), req_options) end val end def not_found?(val) cache_nils ? val == ::Dalli::NOT_FOUND : val.nil? end def cache_nils @options[:cache_nils] end ## # compare and swap values using optimistic locking. # Fetch the existing value for key. # If it exists, yield the value to the block. # Add the block's return value as the new value for the key. # Add will fail if someone else changed the value. # # Returns: # - nil if the key did not exist. # - false if the value was changed by someone else. # - true if the value was successfully updated. def cas(key, ttl = nil, options = nil, &block) cas_core(key, false, ttl, options, &block) end ## # like #cas, but will yield to the block whether or not the value # already exists. # # Returns: # - false if the value was changed by someone else. # - true if the value was successfully updated. def cas!(key, ttl = nil, options = nil, &block) cas_core(key, true, ttl, options, &block) end def set(key, value, ttl = nil, options = nil) perform(:set, key, value, ttl_or_default(ttl), 0, options) end ## # Conditionally add a key/value pair, if the key does not already exist # on the server. Returns truthy if the operation succeeded. def add(key, value, ttl = nil, options = nil) perform(:add, key, value, ttl_or_default(ttl), options) end ## # Conditionally add a key/value pair, only if the key already exists # on the server. Returns truthy if the operation succeeded. def replace(key, value, ttl = nil, options = nil) perform(:replace, key, value, ttl_or_default(ttl), 0, options) end def delete(key) perform(:delete, key, 0) end ## # Append value to the value already stored on the server for 'key'. # Appending only works for values stored with :raw => true. def append(key, value) perform(:append, key, value.to_s) end ## # Prepend value to the value already stored on the server for 'key'. # Prepending only works for values stored with :raw => true. def prepend(key, value) perform(:prepend, key, value.to_s) end def flush(delay = 0) time = -delay ring.servers.map { |s| s.request(:flush, time += delay) } end alias flush_all flush ## # Incr adds the given amount to the counter on the memcached server. # Amt must be a positive integer value. # # If default is nil, the counter must already exist or the operation # will fail and will return nil. Otherwise this method will return # the new value for the counter. # # Note that the ttl will only apply if the counter does not already # exist. To increase an existing counter and update its TTL, use # #cas. def incr(key, amt = 1, ttl = nil, default = nil) raise ArgumentError, "Positive values only: #{amt}" if amt.negative? perform(:incr, key, amt.to_i, ttl_or_default(ttl), default) end ## # Decr subtracts the given amount from the counter on the memcached server. # Amt must be a positive integer value. # # memcached counters are unsigned and cannot hold negative values. Calling # decr on a counter which is 0 will just return 0. # # If default is nil, the counter must already exist or the operation # will fail and will return nil. Otherwise this method will return # the new value for the counter. # # Note that the ttl will only apply if the counter does not already # exist. To decrease an existing counter and update its TTL, use # #cas. def decr(key, amt = 1, ttl = nil, default = nil) raise ArgumentError, "Positive values only: #{amt}" if amt.negative? perform(:decr, key, amt.to_i, ttl_or_default(ttl), default) end ## # Touch updates expiration time for a given key. # # Returns true if key exists, otherwise nil. def touch(key, ttl = nil) resp = perform(:touch, key, ttl_or_default(ttl)) resp.nil? ? nil : true end ## # Gat (get and touch) fetch an item and simultaneously update its expiration time. # # If a value is not found, then +nil+ is returned. def gat(key, ttl = nil) perform(:gat, key, ttl_or_default(ttl)) end ## # Collect the stats for each server. # You can optionally pass a type including :items, :slabs or :settings to get specific stats # Returns a hash like { 'hostname:port' => { 'stat1' => 'value1', ... }, 'hostname2:port' => { ... } } def stats(type = nil) type = nil unless [nil, :items, :slabs, :settings].include? type values = {} ring.servers.each do |server| values[server.name.to_s] = server.alive? ? server.request(:stats, type.to_s) : nil end values end ## # Reset stats for each server. def reset_stats ring.servers.map do |server| server.alive? ? server.request(:reset_stats) : nil end end ## ## Make sure memcache servers are alive, or raise an Dalli::RingError def alive! ring.server_for_key('') end ## ## Version of the memcache servers. def version values = {} ring.servers.each do |server| values[server.name.to_s] = server.alive? ? server.request(:version) : nil end values end ## # Get the value and CAS ID associated with the key. If a block is provided, # value and CAS will be passed to the block. def get_cas(key) (value, cas) = perform(:cas, key) value = nil if !value || value == 'Not found' if block_given? yield value, cas else [value, cas] end end ## # Fetch multiple keys efficiently, including available metadata such as CAS. # If a block is given, yields key/data pairs one a time. Data is an array: # [value, cas_id] # If no block is given, returns a hash of # { 'key' => [value, cas_id] } def get_multi_cas(*keys) if block_given? get_multi_yielder(keys) { |*args| yield(*args) } else {}.tap do |hash| get_multi_yielder(keys) { |k, data| hash[k] = data } end end end ## # Set the key-value pair, verifying existing CAS. # Returns the resulting CAS value if succeeded, and falsy otherwise. def set_cas(key, value, cas, ttl = nil, options = nil) ttl ||= @options[:expires_in].to_i perform(:set, key, value, ttl, cas, options) end ## # Conditionally add a key/value pair, verifying existing CAS, only if the # key already exists on the server. Returns the new CAS value if the # operation succeeded, or falsy otherwise. def replace_cas(key, value, cas, ttl = nil, options = nil) ttl ||= @options[:expires_in].to_i perform(:replace, key, value, ttl, cas, options) end # Delete a key/value pair, verifying existing CAS. # Returns true if succeeded, and falsy otherwise. def delete_cas(key, cas = 0) perform(:delete, key, cas) end ## # Close our connection to each server. # If you perform another operation after this, the connections will be re-established. def close return unless @ring @ring.servers.each(&:close) @ring = nil end alias reset close # Stub method so a bare Dalli client can pretend to be a connection pool. def with yield self end private def cas_core(key, always_set, ttl = nil, options = nil) (value, cas) = perform(:cas, key) value = nil if !value || value == 'Not found' return if value.nil? && !always_set newvalue = yield(value) perform(:set, key, newvalue, ttl_or_default(ttl), cas, options) end def ttl_or_default(ttl) (ttl || @options[:expires_in]).to_i rescue NoMethodError raise ArgumentError, "Cannot convert ttl (#{ttl}) to an integer" end def ring # TODO: This server initialization should probably be pushed down # to the Ring @ring ||= Dalli::Ring.new( @servers.map do |s| protocol_implementation.new(s, @options) end, @options ) end def protocol_implementation @protocol_implementation ||= @options.fetch(:protocol_implementation, Dalli::Protocol::Binary) end # Chokepoint method for instrumentation def perform(*all_args) return yield if block_given? op, key, *args = all_args key = key.to_s key = @key_manager.validate_key(key) server = ring.server_for_key(key) server.request(op, key, *args) rescue NetworkError => e Dalli.logger.debug { e.inspect } Dalli.logger.debug { 'retrying request with new server' } retry end def normalize_options(opts) begin opts[:expires_in] = opts[:expires_in].to_i if opts[:expires_in] rescue NoMethodError raise ArgumentError, "cannot convert :expires_in => #{opts[:expires_in].inspect} to an integer" end opts end # TODO: Look at extracting below into separate MultiYielder class ## # Yields, one at a time, keys and their values+attributes. # def get_multi_yielder(keys, &block) return {} if keys.empty? ring.lock do groups = groups_for_keys(keys) if (unfound_keys = groups.delete(nil)) Dalli.logger.debug do "unable to get keys for #{unfound_keys.length} keys "\ 'because no matching server was found' end end make_multi_get_requests(groups) servers = groups.keys return if servers.empty? # TODO: How does this exit on a NetworkError servers = perform_multi_response_start(servers) timeout = servers.first.options[:socket_timeout] start_time = Time.now loop do # remove any dead servers # TODO: Is this well behaved in a multi-threaded environment? # Accessing the server socket like this seems problematic servers.delete_if { |s| s.sock.nil? } break if servers.empty? servers = multi_yielder_loop(servers, start_time, timeout, &block) end end rescue NetworkError => e Dalli.logger.debug { e.inspect } Dalli.logger.debug { 'retrying multi yielder because of timeout' } retry end def make_multi_get_requests(groups) groups.each do |server, keys_for_server| server.request(:send_multiget, keys_for_server) rescue DalliError, NetworkError => e Dalli.logger.debug { e.inspect } Dalli.logger.debug { "unable to get keys for server #{server.name}" } end end # raises Dalli::NetworkError def perform_multi_response_start(servers) deleted = [] servers.each do |server| next unless server.alive? begin server.multi_response_start rescue Dalli::NetworkError abort_multi_response(servers) raise rescue Dalli::DalliError => e Dalli.logger.debug { e.inspect } Dalli.logger.debug { 'results from this server will be missing' } deleted.append(server) end end servers.delete_if { |server| deleted.include?(server) } end # Swallows Dalli::NetworkError def abort_multi_response(servers) servers.each(&:multi_response_abort) end def multi_yielder_loop(servers, start_time, timeout, &block) time_left = remaining_time(start_time, timeout) readable_servers = servers_with_data(servers, time_left) if readable_servers.empty? abort_multi_connections_w_timeout(servers) return readable_servers end readable_servers.each do |server| servers.delete(server) if respond_to_readable_server(server, &block) end servers rescue NetworkError abort_multi_response(servers) raise end def remaining_time(start, timeout) elapsed = Time.now - start return 0 if elapsed > timeout timeout - elapsed end # Swallows Dalli::NetworkError def abort_multi_connections_w_timeout(servers) abort_multi_response(servers) servers.each do |server| Dalli.logger.debug { "memcached at #{server.name} did not response within timeout" } end true # Required to simplify caller end def respond_to_readable_server(server) server.multi_response_nonblock.each_pair do |key, value_list| yield @key_manager.key_without_namespace(key), value_list end server.multi_response_completed? end def servers_with_data(servers, timeout) readable, = IO.select(servers.map(&:sock), nil, nil, timeout) return [] if readable.nil? readable.map(&:server) end def groups_for_keys(*keys) keys.flatten! keys.map! { |a| @key_manager.validate_key(a.to_s) } ring.keys_grouped_by_server(keys) end end end dalli-3.0.6/lib/dalli/compressor.rb000066400000000000000000000013711415150234600171640ustar00rootroot00000000000000# frozen_string_literal: true require 'zlib' require 'stringio' module Dalli ## # Default compressor used by Dalli, that uses # Zlib DEFLATE to compress data. ## class Compressor def self.compress(data) Zlib::Deflate.deflate(data) end def self.decompress(data) Zlib::Inflate.inflate(data) end end ## # Alternate compressor for Dalli, that uses # Gzip. Gzip adds a checksum to each compressed # entry. ## class GzipCompressor def self.compress(data) io = StringIO.new(+'', 'w') gz = Zlib::GzipWriter.new(io) gz.write(data) gz.close io.string end def self.decompress(data) io = StringIO.new(data, 'rb') Zlib::GzipReader.new(io).read end end end dalli-3.0.6/lib/dalli/key_manager.rb000066400000000000000000000064401415150234600172540ustar00rootroot00000000000000# frozen_string_literal: true require 'digest/md5' module Dalli ## # This class manages and validates keys sent to Memcached, ensuring # that they meet Memcached key length requirements, and supporting # the implementation of optional namespaces on a per-Dalli client # basis. ## class KeyManager MAX_KEY_LENGTH = 250 NAMESPACE_SEPARATOR = ':' # This is a hard coded md5 for historical reasons TRUNCATED_KEY_SEPARATOR = ':md5:' # This is 249 for historical reasons TRUNCATED_KEY_TARGET_SIZE = 249 DEFAULTS = { digest_class: ::Digest::MD5 }.freeze OPTIONS = %i[digest_class namespace].freeze attr_reader :namespace def initialize(client_options) @key_options = DEFAULTS.merge(client_options.select { |k, _| OPTIONS.include?(k) }) validate_digest_class_option(@key_options) @namespace = namespace_from_options end ## # Validates the key, and transforms as needed. # # If the key is nil or empty, raises ArgumentError. Whitespace # characters are allowed for historical reasons, but likely shouldn't # be used. # If the key (with namespace) is shorter than the memcached maximum # allowed key length, just returns the argument key # Otherwise computes a "truncated" key that uses a truncated prefix # combined with a 32-byte hex digest of the whole key. ## def validate_key(key) raise ArgumentError, 'key cannot be blank' unless key&.length&.positive? key = key_with_namespace(key) key.length > MAX_KEY_LENGTH ? truncated_key(key) : key end ## # Returns the key with the namespace prefixed, if a namespace is # defined. Otherwise just returns the key ## def key_with_namespace(key) return key if namespace.nil? "#{namespace}#{NAMESPACE_SEPARATOR}#{key}" end def key_without_namespace(key) return key if namespace.nil? key.sub(namespace_regexp, '') end def digest_class @digest_class ||= @key_options[:digest_class] end def namespace_regexp @namespace_regexp ||= /\A#{Regexp.escape(namespace)}:/.freeze unless namespace.nil? end def validate_digest_class_option(opts) return if opts[:digest_class].respond_to?(:hexdigest) raise ArgumentError, 'The digest_class object must respond to the hexdigest method' end def namespace_from_options raw_namespace = @key_options[:namespace] return nil unless raw_namespace return raw_namespace.call.to_s if raw_namespace.is_a?(Proc) raw_namespace.to_s end ## # Produces a truncated key, if the raw key is longer than the maximum allowed # length. The truncated key is produced by generating a hex digest # of the key, and appending that to a truncated section of the key. ## def truncated_key(key) digest = digest_class.hexdigest(key) "#{key[0, prefix_length(digest)]}#{TRUNCATED_KEY_SEPARATOR}#{digest}" end def prefix_length(digest) return TRUNCATED_KEY_TARGET_SIZE - (TRUNCATED_KEY_SEPARATOR.length + digest.length) if namespace.nil? # For historical reasons, truncated keys with namespaces had a length of 250 rather # than 249 TRUNCATED_KEY_TARGET_SIZE + 1 - (TRUNCATED_KEY_SEPARATOR.length + digest.length) end end end dalli-3.0.6/lib/dalli/options.rb000066400000000000000000000016741415150234600164710ustar00rootroot00000000000000# frozen_string_literal: true require 'monitor' module Dalli # Make Dalli threadsafe by using a lock around all # public server methods. # # Dalli::Protocol::Binary.extend(Dalli::Threadsafe) # module Threadsafe def self.extended(obj) obj.init_threadsafe end def request(opcode, *args) @lock.synchronize do super end end def alive? @lock.synchronize do super end end def close @lock.synchronize do super end end def multi_response_start @lock.synchronize do super end end def multi_response_nonblock @lock.synchronize do super end end def multi_response_abort @lock.synchronize do super end end def lock! @lock.mon_enter end def unlock! @lock.mon_exit end def init_threadsafe @lock = Monitor.new end end end dalli-3.0.6/lib/dalli/protocol.rb000066400000000000000000000002621415150234600166270ustar00rootroot00000000000000# frozen_string_literal: true module Dalli module Protocol # Preserved for backwards compatibility. Should be removed in 4.0 NOT_FOUND = ::Dalli::NOT_FOUND end end dalli-3.0.6/lib/dalli/protocol/000077500000000000000000000000001415150234600163025ustar00rootroot00000000000000dalli-3.0.6/lib/dalli/protocol/binary.rb000066400000000000000000000403471415150234600201230ustar00rootroot00000000000000# frozen_string_literal: true require 'English' require 'forwardable' require 'socket' require 'timeout' require_relative 'binary/request_formatter' require_relative 'binary/response_processor' require_relative 'binary/sasl_authentication' module Dalli module Protocol ## # Access point for a single Memcached server, accessed via Memcached's binary # protocol. Contains logic for managing connection state to the server (retries, etc), # formatting requests to the server, and unpacking responses. ## class Binary extend Forwardable attr_accessor :hostname, :port, :weight, :options attr_reader :sock, :socket_type def_delegators :@value_marshaller, :serializer, :compressor, :compression_min_size, :compress_by_default? DEFAULTS = { # seconds between trying to contact a remote server down_retry_delay: 30, # connect/read/write timeout for socket operations socket_timeout: 1, # times a socket operation may fail before considering the server dead socket_max_failures: 2, # amount of time to sleep between retries when a failure occurs socket_failure_delay: 0.1, username: nil, password: nil }.freeze def initialize(attribs, options = {}) @hostname, @port, @weight, @socket_type, options = ServerConfigParser.parse(attribs, options) @options = DEFAULTS.merge(options) @value_marshaller = ValueMarshaller.new(@options) @response_processor = ResponseProcessor.new(self, @value_marshaller) reset_down_info @sock = nil @pid = nil @request_in_progress = false end def name if socket_type == :unix hostname else "#{hostname}:#{port}" end end # Chokepoint method for error handling and ensuring liveness def request(opcode, *args) verify_state # The alive? call has the side effect of connecting the underlying # socket if it is not connected, or there's been a disconnect # because of timeout or other error. Method raises an error # if it can't connect raise_memcached_down_err unless alive? begin send(opcode, *args) rescue Dalli::MarshalError => e log_marshall_err(args.first, e) raise rescue Dalli::DalliError, Dalli::NetworkError, Dalli::ValueOverMaxSize, Timeout::Error raise rescue StandardError => e log_unexpected_err(e) down! end end def raise_memcached_down_err raise Dalli::NetworkError, "#{name} is down: #{@error} #{@msg}. If you are sure it is running, "\ "ensure memcached version is > #{::Dalli::MIN_SUPPORTED_MEMCACHED_VERSION}." end def log_marshall_err(key, err) Dalli.logger.error "Marshalling error for key '#{key}': #{err.message}" Dalli.logger.error 'You are trying to cache a Ruby object which cannot be serialized to memcached.' end def log_unexpected_err(err) Dalli.logger.error "Unexpected exception during Dalli request: #{err.class.name}: #{err.message}" Dalli.logger.error err.backtrace.join("\n\t") end # The socket connection to the underlying server is initialized as a side # effect of this call. In fact, this is the ONLY place where that # socket connection is initialized. def alive? return true if @sock return false unless reconnect_down_server? connect !!@sock rescue Dalli::NetworkError false end def reconnect_down_server? return true unless @last_down_at time_to_next_reconnect = @last_down_at + options[:down_retry_delay] - Time.now return true unless time_to_next_reconnect.positive? Dalli.logger.debug do format('down_retry_delay not reached for %s (%