Skip to content

GarbageCollection hangs because #scan doesn't honor last_key argument #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ Ruby file using the following method:

require 'aws-sessionstore-dynamodb'

AWS::SessionStore::DynamoDB::Table.create_table
options = { :secret_key => 'SECRET_KEY' }

Aws::SessionStore::DynamoDB::Table.create_table(options)

Run the session store as a Rack middleware in the following way:

Expand All @@ -46,7 +48,7 @@ Run the session store as a Rack middleware in the following way:

options = { :secret_key => 'SECRET_KEY' }

use AWS::SessionStore::DynamoDB::RackMiddleware.new(options)
use Aws::SessionStore::DynamoDB::RackMiddleware, options
run SomeRackApp

Note that `:secret_key` is a mandatory configuration option that must be set.
Expand All @@ -71,7 +73,7 @@ Full API documentation of the library can be found on [RubyDoc.info][1].
### Configuration Options

A number of options are available to be set in
`AWS::SessionStore::DynamoDB::Configuration`, which is used by the
`Aws::SessionStore::DynamoDB::Configuration`, which is used by the
`RackMiddleware` class. These options can be set in the YAML configuration
file in a Rails application (located in `config/sessionstore/dynamodb.yml`),
directly by Ruby code, or through environment variables.
Expand Down Expand Up @@ -126,7 +128,7 @@ You can create your own Rake task for garbage collection similar to below:
desc 'Perform Garbage Collection'
task :garbage_collect do |t|
options = {:max_age => 3600*24, max_stale => 5*3600 }
AWS::SessionStore::DynamoDB::GarbageCollection.collect_garbage(options)
Aws::SessionStore::DynamoDB::GarbageCollection.collect_garbage(options)
end

The above example will clear sessions older than one day or that have been
Expand Down Expand Up @@ -164,8 +166,8 @@ locking strategy according to your needs:

You can pass in your own error handler for raised exceptions or you can allow
the default error handler to them for you. See the API documentation
on the {AWS::SessionStore::DynamoDB::Errors::BaseHandler} class for more
on the {Aws::SessionStore::DynamoDB::Errors::BaseHandler} class for more
details.

[1]: http://rubydoc.org/gems/aws-sessionstore-dynamodb/frames
[2]: http://rubydoc.org/gems/aws-sessionstore-dynamodb/AWS/SessionStore/DynamoDB/Configuration#initialize-instance_method
[2]: http://rubydoc.org/gems/aws-sessionstore-dynamodb/Aws/SessionStore/DynamoDB/Configuration#initialize-instance_method
4 changes: 2 additions & 2 deletions aws-sessionstore-dynamodb.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ require File.dirname(__FILE__) + '/lib/aws/session_store/dynamo_db/version'

Gem::Specification.new do |spec|
spec.name = "aws-sessionstore-dynamodb"
spec.version = AWS::SessionStore::DynamoDB::VERSION
spec.version = Aws::SessionStore::DynamoDB::VERSION
spec.authors = ["Ruby Robinson"]
spec.summary = "The Amazon DynamoDB Session Store handles sessions " +
"for Ruby web applications using a DynamoDB backend."
Expand All @@ -13,6 +13,6 @@ Gem::Specification.new do |spec|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
spec.require_paths = ["lib"]

spec.add_dependency 'aws-sdk', '~> 1.0'
spec.add_dependency 'aws-sdk', '~> 2.1.2'
spec.add_dependency 'rack', '~> 1.0'
end
2 changes: 1 addition & 1 deletion lib/aws-sessionstore-dynamodb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# language governing permissions and limitations under the License.


module AWS
module Aws
module SessionStore
module DynamoDB; end
end
Expand Down
50 changes: 34 additions & 16 deletions lib/aws/session_store/dynamo_db/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
require 'yaml'
require 'aws-sdk'

module AWS::SessionStore::DynamoDB
module Aws::SessionStore::DynamoDB
# This class provides a Configuration object for all DynamoDB transactions
# by pulling configuration options from Runtime, a YAML file, the ENV and
# default settings.
Expand Down Expand Up @@ -56,6 +56,7 @@ class Configuration
DEFAULTS = {
:table_name => "sessions",
:table_key => "session_id",
:user_key => nil,
:consistent_read => true,
:read_capacity => 10,
:write_capacity => 5,
Expand All @@ -67,7 +68,7 @@ class Configuration
:lock_retry_delay => 500,
:lock_max_wait_time => 1,
:secret_key => nil,
:api_version => '2012-08-10'
:verbose => false
}

# @return [String] Session table name.
Expand All @@ -76,19 +77,22 @@ class Configuration
# @return [String] Session table hash key name.
attr_reader :table_key

# @return [String] Name of user key in session data.
attr_reader :user_key

# @return [true] If a strongly consistent read is used
# @return [false] If an eventually consistent read is used.
# See AWS DynamoDB documentation for table consistent_read for more
# See Aws DynamoDB documentation for table consistent_read for more
# information on this setting.
attr_reader :consistent_read

# @return [Integer] Maximum number of reads consumed per second before
# DynamoDB returns a ThrottlingException. See AWS DynamoDB documentation
# DynamoDB returns a ThrottlingException. See Aws DynamoDB documentation
# for table read_capacity for more information on this setting.
attr_reader :read_capacity

# @return [Integer] Maximum number of writes consumed per second before
# DynamoDB returns a ThrottlingException. See AWS DynamoDB documentation
# DynamoDB returns a ThrottlingException. See Aws DynamoDB documentation
# for table write_capacity for more information on this setting.
attr_reader :write_capacity

Expand All @@ -102,7 +106,7 @@ class Configuration
attr_reader :dynamo_db_client

# @return [Error Handler] An error handling object that handles all exceptions
# thrown during execution of the AWS DynamoDB Session Store Rack Middleware.
# thrown during execution of the Aws DynamoDB Session Store Rack Middleware.
# For more information see the Handling Errors Section.
attr_reader :error_handler

Expand Down Expand Up @@ -134,6 +138,9 @@ class Configuration
# before giving up.
attr_reader :lock_max_wait_time

# @return [true] Info printed to stdout
# @return [false] Run silent, run deep
attr_reader :verbose

# Provides configuration object that allows access to options defined
# during Runtime, in a YAML file, in the ENV and by default.
Expand All @@ -142,26 +149,27 @@ class Configuration
# table.
# @option options [String] :table_key ("id") The hash key of the sesison
# table.
# @option options [String] :user_key ("id") Name of user key in session.
# @option options [Boolean] :consistent_read (true) If true, a strongly
# consistent read is used. If false, an eventually consistent read is
# used.
# @option options [Integer] :read_capacity (10) The maximum number of
# strongly consistent reads consumed per second before
# DynamoDB raises a ThrottlingException. See AWS DynamoDB documentation
# DynamoDB raises a ThrottlingException. See Aws DynamoDB documentation
# for table read_capacity for more information on this setting.
# @option options [Integer] :write_capacity (5) The maximum number of writes
# consumed per second before DynamoDB returns a ThrottlingException.
# See AWS DynamoDB documentation for table write_capacity for more
# See Aws DynamoDB documentation for table write_capacity for more
# information on this setting.
# @option options [DynamoDB Client] :dynamo_db_client
# (AWS::DynamoDB::ClientV2) DynamoDB client used to perform database
# (Aws::DynamoDB::ClientV2) DynamoDB client used to perform database
# operations inside of middleware application.
# @option options [Boolean] :raise_errors (false) If true, all errors are
# raised up the stack when default ErrorHandler. If false, Only specified
# errors are raised up the stack when default ErrorHandler is used.
# @option options [Error Handler] :error_handler (DefaultErrorHandler)
# An error handling object that handles all exceptions thrown during
# execution of the AWS DynamoDB Session Store Rack Middleware.
# execution of the Aws DynamoDB Session Store Rack Middleware.
# For more information see the Handling Errors Section.
# @option options [Integer] :max_age (nil) Maximum number of seconds earlier
# from the current time that a session was created.
Expand All @@ -181,6 +189,7 @@ class Configuration
# to wait to acquire lock before giving up.
# @option options [String] :secret_key (SecureRandom.hex(64))
# Secret key for HMAC encription.
# @option options [Boolean] :verbose (true)
def initialize(options = {})
@options = default_options.merge(
env_options.merge(
Expand All @@ -202,15 +211,18 @@ def to_hash

# @return [Hash] DDB client.
def gen_dynamo_db_client
client_opts = client_subset(@options)
client = AWS::DynamoDB::Client
dynamo_db_client = @options[:dynamo_db_client] || client.new(client_opts)
dynamo_db_client = @options[:dynamo_db_client]
if dynamo_db_client.nil?
client_opts = client_subset(@options)
dynamo_db_client = Aws::DynamoDB::Client.new(client_opts)
end

{:dynamo_db_client => dynamo_db_client}
end

# @return [Hash] Default Error Handler
def gen_error_handler
default_handler = AWS::SessionStore::DynamoDB::Errors::DefaultHandler
default_handler = Aws::SessionStore::DynamoDB::Errors::DefaultHandler
error_handler = @options[:error_handler] ||
default_handler.new(@options[:raise_errors])
{:error_handler => error_handler}
Expand Down Expand Up @@ -286,11 +298,17 @@ def symbolize_keys(options)
end
end

CLIENT_KEYS = [
:access_key_id, :secret_access_key, :credentials, :region, :endpoint,
:http_continue_timeout, :http_idle_timeout, :http_open_timeout, :http_read_timeout,
:ssl_ca_bundle, :ssl_ca_directory, :ssl_ca_store, :ssl_verify_peer,
:retry_limit, :log_level, :logger, :profile, :stub_responses
]

# @return [Hash] Client subset options hash.
def client_subset(options = {})
client_keys = [:aws_secret_key, :aws_region, :aws_access_key, :api_version]
options.inject({}) do |opts, (opt_name, opt_value)|
opts[opt_name.to_sym] = opt_value if client_keys.include?(opt_name.to_sym)
opts[opt_name.to_sym] = opt_value if CLIENT_KEYS.include?(opt_name.to_sym)
opts
end
end
Expand Down
10 changes: 5 additions & 5 deletions lib/aws/session_store/dynamo_db/errors/base_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
# language governing permissions and limitations under the License.


module AWS::SessionStore::DynamoDB::Errors
module Aws::SessionStore::DynamoDB::Errors
# BaseErrorHandler provides an interface for error handlers
# that can be passed in to {AWS::SessionStore::DynamoDB::RackMiddleware}.
# that can be passed in to {Aws::SessionStore::DynamoDB::RackMiddleware}.
# Each error handler must implement a handle_error method.
#
# @example Sample ErrorHandler class
Expand All @@ -33,11 +33,11 @@ class BaseHandler
# error up the stack.
# You may reraise the error passed.
#
# @param [AWS::DynamoDB::Errors::Base] error error passed in from
# AWS::SessionStore::DynamoDB::RackMiddleware.
# @param [Aws::DynamoDB::Errors::Base] error error passed in from
# Aws::SessionStore::DynamoDB::RackMiddleware.
# @param [Rack::Request::Environment,nil] env Rack environment
# @return [false] If exception was handled and will not reraise exception.
# @raise [AWS::DynamoDB::Errors] If error has be reraised.
# @raise [Aws::DynamoDB::Errors] If error has be reraised.
def handle_error(error, env = {})
raise NotImplementedError
end
Expand Down
12 changes: 6 additions & 6 deletions lib/aws/session_store/dynamo_db/errors/default_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@
# language governing permissions and limitations under the License.


module AWS::SessionStore::DynamoDB::Errors
module Aws::SessionStore::DynamoDB::Errors
# This class handles errors raised from DynamoDB.
class DefaultHandler < AWS::SessionStore::DynamoDB::Errors::BaseHandler
class DefaultHandler < Aws::SessionStore::DynamoDB::Errors::BaseHandler
# Array of errors that will always be passed up the Rack stack.
HARD_ERRORS = [
AWS::DynamoDB::Errors::ResourceNotFoundException,
AWS::DynamoDB::Errors::ConditionalCheckFailedException,
AWS::SessionStore::DynamoDB::MissingSecretKeyError,
AWS::SessionStore::DynamoDB::LockWaitTimeoutError
Aws::DynamoDB::Errors::ResourceNotFoundException,
Aws::DynamoDB::Errors::ConditionalCheckFailedException,
Aws::SessionStore::DynamoDB::MissingSecretKeyError,
Aws::SessionStore::DynamoDB::LockWaitTimeoutError
]

# Determines behavior of DefaultErrorHandler
Expand Down
13 changes: 7 additions & 6 deletions lib/aws/session_store/dynamo_db/garbage_collection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

require 'aws-sdk'

module AWS::SessionStore::DynamoDB
module Aws::SessionStore::DynamoDB
# Collects and deletes unwanted sessions based on
# their creation and update dates.
module GarbageCollection
Expand All @@ -34,7 +34,7 @@ def collect_garbage(options = {})
# @option (see Configuration#initialize)
# @api private
def load_config(options = {})
AWS::SessionStore::DynamoDB::Configuration.new(options)
Aws::SessionStore::DynamoDB::Configuration.new(options)
end

# Sets scan filter attributes based on attributes specified.
Expand All @@ -50,16 +50,16 @@ def scan_filter(config)
# @api private
def eliminate_unwanted_sessions(config, last_key = nil)
scan_result = scan(config, last_key)
batch_delete(config, scan_result[:member])
scan_result[:last_evaluated_key] || {}
batch_delete(config, scan_result.items)
scan_result.last_evaluated_key || {}
end

# Scans the table for sessions matching the max age and
# max stale time specified.
# @api private
def scan(config, last_item = nil)
options = scan_opts(config)
options.merge(start_key(last_item)) if last_item
options = options.merge(start_key(last_item)) if last_item
config.dynamo_db_client.scan(options)
end

Expand All @@ -68,6 +68,7 @@ def scan(config, last_item = nil)
def batch_delete(config, items)
begin
subset = items.shift(25)
puts "DELETE SESSIONS #{subset.map{|i| i['id']}}" if config.verbose && !subset.empty?
sub_batch = write(subset)
process!(config, sub_batch)
end until subset.empty?
Expand Down Expand Up @@ -114,7 +115,7 @@ def table_opts(config)
# @api private
def oldest_date(sec)
hash = {}
hash[:attribute_value_list] = [:n => "#{((Time.now - sec).to_f)}"]
hash[:attribute_value_list] = ["#{((Time.now - sec).to_f)}"]
hash[:comparison_operator] = 'LT'
hash
end
Expand Down
2 changes: 1 addition & 1 deletion lib/aws/session_store/dynamo_db/invalid_id_error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# language governing permissions and limitations under the License.


module AWS::SessionStore::DynamoDB
module Aws::SessionStore::DynamoDB
class InvalidIDError < RuntimeError
def initialize(msg = "Corrupt Session ID!")
super
Expand Down
2 changes: 1 addition & 1 deletion lib/aws/session_store/dynamo_db/lock_wait_timeout_error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# language governing permissions and limitations under the License.


module AWS::SessionStore::DynamoDB
module Aws::SessionStore::DynamoDB
class LockWaitTimeoutError < RuntimeError
def initialize(msg = 'Maximum time spent to acquire lock has been exceeded!')
super
Expand Down
Loading