marloss 0.3.0 → 0.4.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3ae07afe91d9f8991ee6f16ac5f0bc73d90373b3a358f56bc5941435c1b4ac88
4
- data.tar.gz: 92ae1c2485e09183222b257d5dfd48ad7b11aede6a83332f2f6f6b00a7c76259
3
+ metadata.gz: ee90d354ebc9dcd4f9bdba03380d2c44616b31614c7e1b92273e533e800415ce
4
+ data.tar.gz: 6cd27c87bb5b65ea18450719ba6e527a8a40e955e40c705d2c8a968a73c30a64
5
5
  SHA512:
6
- metadata.gz: ac62d5a1dce8b8ef243c2a9b57295a10d6382dfa5ede24a709b65bc6c91073a45007c6e6f2b7076fc613971bc5dd31458761075110c58442b4f01f9121c6c6a3
7
- data.tar.gz: ec898a131cccd2c4e6c1f0ce246f9f745c4ba2d2178ad060779760d424f454900305176fe19df41878967aba14ad7c8abbe0e920d15f124a466590be20545c64
6
+ metadata.gz: ede87eeba38468521d6582e6586a307911adc6938e7e85c785ebdf228efc95c398ec3c69db30c64a28de856d576a618cd563d8ed53fb65a7aee93c500005e419
7
+ data.tar.gz: 5751077d681c9c0611e243d662df3b0d79982b0b759feb1b81481a5bf8fccefa72b5595806ef97dab5dc2f1937207fd3504286e284e67b7508034a6097afe72e
@@ -0,0 +1,19 @@
1
+ Metrics/LineLength:
2
+ Max: 100
3
+
4
+ Style/StringLiterals:
5
+ EnforcedStyle: double_quotes
6
+
7
+ Layout/MultilineMethodCallIndentation:
8
+ EnforcedStyle: indented
9
+
10
+ Style/AccessModifierDeclarations:
11
+ EnforcedStyle: inline
12
+
13
+ Style/Documentation:
14
+ Enabled: false # disable for now
15
+
16
+ Metrics/BlockLength:
17
+ Exclude:
18
+ - "spec/**/*"
19
+ - "marloss.gemspec"
@@ -1,6 +1,9 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.3.3
4
- - 2.3.1
3
+ - 2.5.1
4
+ - 2.4.4
5
+ - 2.3.7
6
+ - 2.2.10
5
7
  notifications:
6
8
  email: false
9
+ before_script: bundle exec rubocop
@@ -1,3 +1,16 @@
1
+ ## 0.4.0 01/09/2018
2
+
3
+ IMPROVEMENTS:
4
+
5
+ * Custom ttl attribute name instead of hardcoding `Expires` [#12](https://github.com/eredi93/marloss/pull/12)
6
+ * Add possiblity to pass retries to `wait_until_lock_obtained` preventing spin lock [#11](https://github.com/eredi93/marloss/pull/11)
7
+
8
+ ## 0.3.1 01/09/2018
9
+
10
+ IMPROVEMENTS:
11
+
12
+ * Make `create_table` wait for table [#8](https://github.com/eredi93/marloss/pull/8)
13
+
1
14
  ## 0.3.0 29/11/2017
2
15
 
3
16
  IMPROVEMENTS:
data/README.md CHANGED
@@ -64,6 +64,11 @@ Firstly, we need to initialize a lock store:
64
64
  store = Marloss::Store.new("lock_table_name", "LockHashKeyName")
65
65
  ```
66
66
 
67
+ Create table if it does not exist:
68
+ ```ruby
69
+ store.create_table
70
+ ```
71
+
67
72
  We can use this store to create a single lock
68
73
 
69
74
  ```ruby
@@ -76,7 +81,7 @@ locker.obtain_lock
76
81
  locker.wait_until_lock_obtained
77
82
 
78
83
  # refresh the lock once
79
- locker.refresh
84
+ locker.refresh_lock
80
85
 
81
86
  # delete the lock
82
87
  locker.release_lock
@@ -100,4 +105,4 @@ Marloss.logger = Logger.new("my_app.log")
100
105
 
101
106
  ### Contributing
102
107
 
103
- This repository is [open to contributions](.github/CONTRIBUTING.md).
108
+ This repository is [open to contributions](CONTRIBUTING.md).
data/Rakefile CHANGED
@@ -3,4 +3,4 @@ require "rspec/core/rake_task"
3
3
 
4
4
  RSpec::Core::RakeTask.new(:spec)
5
5
 
6
- task :default => :spec
6
+ task default: :spec
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- #
2
+
3
3
  require "aws-sdk-dynamodb"
4
4
 
5
5
  require "marloss/version"
@@ -8,7 +8,6 @@ require "marloss/store"
8
8
  require "marloss/locker"
9
9
 
10
10
  module Marloss
11
-
12
11
  def self.logger
13
12
  @logger ||= ::Logger.new(STDOUT)
14
13
  end
@@ -19,10 +18,10 @@ module Marloss
19
18
 
20
19
  def self.included(base)
21
20
  base.define_singleton_method(:marloss_options) do |opts|
22
- if opts[:table].nil?
23
- raise(MissingParameterError, "DynamoDB Hash Key not set")
24
- elsif opts[:hash_key].nil?
25
- raise(MissingParameterError, "DynamoDB Table not set")
21
+ %i[table hash_key].each do |key|
22
+ next unless opts[key].nil?
23
+
24
+ raise(MissingParameterError, "DynamoDB #{key.to_s.capitalize.tr('_', ' ')} not set")
26
25
  end
27
26
 
28
27
  define_method(:marloss_options_hash) { opts }
@@ -34,13 +33,12 @@ module Marloss
34
33
  end
35
34
 
36
35
  module InstanceMethods
37
-
38
36
  def marloss_store
39
- @marloss_store ||=begin
37
+ @marloss_store ||= begin
40
38
  table = marloss_options_hash[:table]
41
39
  hash_key = marloss_options_hash[:hash_key]
42
- options = marloss_options_hash.reject do |k, v|
43
- k == :table || k == :hash_key
40
+ options = marloss_options_hash.reject do |k, _|
41
+ %i[table hash_key].include?(k)
44
42
  end
45
43
 
46
44
  Store.new(table, hash_key, options)
@@ -57,10 +55,8 @@ module Marloss
57
55
  locker.wait_until_lock_obtained(opts)
58
56
 
59
57
  yield(locker)
60
-
58
+ ensure
61
59
  locker.release_lock
62
60
  end
63
-
64
61
  end
65
-
66
62
  end
@@ -1,17 +1,15 @@
1
1
  # frozen_string_literal: true
2
- #
2
+
3
3
  module Marloss
4
+ class Error < StandardError; end
4
5
 
5
- class Error < StandardError
6
- end
6
+ class CreateTableError < Error; end
7
7
 
8
- class LockNotObtainedError < Error
9
- end
8
+ class SetTableTtlError < Error; end
10
9
 
11
- class LockNotRefreshedError < Error
12
- end
10
+ class LockNotObtainedError < Error; end
13
11
 
14
- class MissingParameterError < Error
15
- end
12
+ class LockNotRefreshedError < Error; end
16
13
 
14
+ class MissingParameterError < Error; end
17
15
  end
@@ -1,8 +1,7 @@
1
1
  # frozen_string_literal: true
2
- #
2
+
3
3
  module Marloss
4
4
  class Locker
5
-
6
5
  attr_reader :store, :name
7
6
 
8
7
  def initialize(store, name)
@@ -22,12 +21,17 @@ module Marloss
22
21
  store.delete_lock(name)
23
22
  end
24
23
 
25
- def wait_until_lock_obtained(sleep_seconds: 3)
24
+ def wait_until_lock_obtained(sleep_seconds: 3, retries: nil)
26
25
  store.create_lock(name)
27
26
  rescue LockNotObtainedError
28
27
  sleep(sleep_seconds)
28
+
29
+ unless retries.nil?
30
+ retries -= 1
31
+ raise if retries.zero?
32
+ end
33
+
29
34
  retry
30
35
  end
31
-
32
36
  end
33
37
  end
@@ -1,49 +1,81 @@
1
1
  # frozen_string_literal: true
2
- #
3
- module Marloss
4
- class Store
5
2
 
6
- attr_reader :client, :table, :hash_key, :ttl
3
+ module Marloss
4
+ class Store # rubocop:disable Metrics/ClassLength
5
+ attr_reader :client, :table, :hash_key, :expires_key, :ttl
7
6
 
8
- def initialize(table, hash_key, ttl: 30, client_options: {})
7
+ def initialize(table, hash_key, expires_key: "Expires", ttl: 30, client_options: {})
9
8
  @client = Aws::DynamoDB::Client.new(client_options)
10
9
  @table = table
11
10
  @hash_key = hash_key
11
+ @expires_key = expires_key
12
12
  @ttl = ttl
13
13
  end
14
14
 
15
15
  def create_table
16
+ create_ddb_table
17
+ wait_until_ddb_table_exists
18
+ set_ddb_table_ttl
19
+ end
20
+
21
+ private def create_ddb_table # rubocop:disable Metrics/MethodLength
16
22
  client.create_table(
17
23
  attribute_definitions: [
18
24
  {
19
25
  attribute_name: hash_key,
20
- attribute_type: "S",
26
+ attribute_type: "S"
21
27
  }
22
28
  ],
23
29
  key_schema: [
24
30
  {
25
31
  attribute_name: hash_key,
26
- key_type: "HASH",
32
+ key_type: "HASH"
27
33
  }
28
34
  ],
29
35
  provisioned_throughput: {
30
36
  read_capacity_units: 5,
31
- write_capacity_units: 5,
37
+ write_capacity_units: 5
32
38
  },
33
39
  table_name: table
34
40
  )
35
41
 
36
42
  Marloss.logger.info("DynamoDB table created successfully")
43
+ rescue Aws::DynamoDB::Errors::ResourceInUseException => e
44
+ case e.message
45
+ when "Table already exists: #{table}"
46
+ Marloss.logger.warn("DynamoDB table #{table} already exists")
47
+ else
48
+ raise(CreateTableError, e.message)
49
+ end
50
+ end
37
51
 
52
+ private def wait_until_ddb_table_exists
53
+ client.wait_until(:table_exists, table_name: table) do |w|
54
+ w.max_attempts = 10
55
+ w.delay = 1
56
+ end
57
+ rescue Aws::Waiters::Errors::WaiterFailed => e
58
+ Marloss.logger.error("Failed waiting for initialization of table #{table}")
59
+ raise(CreateTableError, e.message)
60
+ end
61
+
62
+ private def set_ddb_table_ttl # rubocop:disable Metrics/MethodLength
38
63
  client.update_time_to_live(
39
64
  table_name: table,
40
65
  time_to_live_specification: {
41
66
  enabled: true,
42
- attribute_name: "Expires"
67
+ attribute_name: expires_key
43
68
  }
44
69
  )
45
70
 
46
71
  Marloss.logger.info("DynamoDB table TTL configured successfully")
72
+ rescue Aws::DynamoDB::Errors::ValidationException => e
73
+ case e.message
74
+ when "TimeToLive is already enabled"
75
+ Marloss.logger.warn("TTL attribute is already configured for table #{table}")
76
+ else
77
+ raise(SetTableTtlError, e.message)
78
+ end
47
79
  end
48
80
 
49
81
  def delete_table
@@ -52,53 +84,51 @@ module Marloss
52
84
  Marloss.logger.info("DynamoDB table deleted successfully")
53
85
  end
54
86
 
55
- def create_lock(name)
87
+ def create_lock(name) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
56
88
  client.put_item(
57
89
  table_name: table,
58
90
  item: {
59
91
  hash_key => name,
60
92
  "ProcessID" => process_id,
61
- "Expires" => (Time.now + ttl).to_i
93
+ expires_key => (Time.now + ttl).to_i
62
94
  },
63
95
  expression_attribute_names: {
64
- "#E" => "Expires",
96
+ "#E" => expires_key,
65
97
  "#P" => "ProcessID"
66
98
  },
67
99
  expression_attribute_values: {
68
100
  ":now" => Time.now.to_i,
69
- ":process_id" => process_id,
101
+ ":process_id" => process_id
70
102
  },
71
103
  condition_expression: "attribute_not_exists(#{hash_key}) OR #E < :now OR #P = :process_id"
72
104
  )
73
105
 
74
106
  Marloss.logger.info("Lock for #{name} created successfully, will expire in #{ttl} seconds")
75
107
  rescue Aws::DynamoDB::Errors::ConditionalCheckFailedException => e
76
-
77
108
  Marloss.logger.error("Failed to create lock for #{name}")
78
109
 
79
110
  raise(LockNotObtainedError, e.message)
80
111
  end
81
112
 
82
- def refresh_lock(name)
113
+ def refresh_lock(name) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
83
114
  client.update_item(
84
115
  table_name: table,
85
116
  key: { hash_key => name },
86
117
  expression_attribute_names: {
87
- "#E" => "Expires",
118
+ "#E" => expires_key,
88
119
  "#P" => "ProcessID"
89
120
  },
90
121
  expression_attribute_values: {
91
122
  ":expires" => (Time.now + ttl).to_i,
92
123
  ":now" => Time.now.to_i,
93
- ":process_id" => process_id,
124
+ ":process_id" => process_id
94
125
  },
95
- update_expression: "SET #E = :expires",
126
+ update_expression: "SET #E = :expires",
96
127
  condition_expression: "attribute_exists(#{hash_key}) AND (#E < :now OR #P = :process_id)"
97
128
  )
98
129
 
99
130
  Marloss.logger.info("Lock for #{name} refreshed successfully, will expire in #{ttl} seconds")
100
131
  rescue Aws::DynamoDB::Errors::ConditionalCheckFailedException => e
101
-
102
132
  Marloss.logger.error("Failed to refresh lock for #{name}")
103
133
 
104
134
  raise(LockNotRefreshedError, e.message)
@@ -116,6 +146,5 @@ module Marloss
116
146
 
117
147
  "#{hostname}:#{pid}"
118
148
  end
119
-
120
149
  end
121
150
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
- #
3
- module Marloss
4
-
5
- VERSION = "0.3.0"
6
2
 
3
+ module Marloss
4
+ VERSION = "0.4.0".freeze
7
5
  end
@@ -1,25 +1,31 @@
1
- # coding: utf-8
2
- lib = File.expand_path("../lib", __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path("lib", __dir__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require "marloss/version"
5
6
 
7
+ files = `git ls-files -z`.split("\x0")
8
+ .reject { |f| f.match(%r{^(test|spec|features)/}) }
9
+
6
10
  Gem::Specification.new do |spec|
7
11
  spec.name = "marloss"
8
12
  spec.version = Marloss::VERSION
9
13
  spec.authors = ["Jacopo Scrinzi"]
10
14
  spec.email = "scrinzi.jcopo@gmail.com"
11
15
 
12
- spec.summary = %q{AWS DynamoDB based Locking}
13
- spec.description = %q{Distributed locking using AWS DynamoDB}
16
+ spec.summary = "AWS DynamoDB based Locking"
17
+ spec.description = "Distributed locking using AWS DynamoDB"
14
18
  spec.homepage = "https://github.com/eredi93/marloss"
15
19
  spec.license = "MIT"
16
20
 
17
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
- spec.require_paths = %w(lib)
21
+ spec.files = files
22
+ spec.require_paths = %w[lib]
19
23
 
20
- spec.add_dependency "aws-sdk-dynamodb", "~> 1.2"
24
+ spec.add_dependency "aws-sdk-dynamodb", "~> 1.11"
21
25
 
22
- spec.add_development_dependency "bundler"
23
- spec.add_development_dependency "rake"
24
- spec.add_development_dependency "rspec"
26
+ spec.add_development_dependency "bundler", "~> 1"
27
+ spec.add_development_dependency "gem-release", "~> 2.0"
28
+ spec.add_development_dependency "rake", "~> 12.3"
29
+ spec.add_development_dependency "rspec", "~> 3.8"
30
+ spec.add_development_dependency "rubocop", "= 0.58"
25
31
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: marloss
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jacopo Scrinzi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-11-29 00:00:00.000000000 Z
11
+ date: 2018-10-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk-dynamodb
@@ -16,56 +16,84 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.2'
19
+ version: '1.11'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.2'
26
+ version: '1.11'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: '1'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: gem-release
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
39
53
  - !ruby/object:Gem::Version
40
- version: '0'
54
+ version: '2.0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: rake
43
57
  requirement: !ruby/object:Gem::Requirement
44
58
  requirements:
45
- - - ">="
59
+ - - "~>"
46
60
  - !ruby/object:Gem::Version
47
- version: '0'
61
+ version: '12.3'
48
62
  type: :development
49
63
  prerelease: false
50
64
  version_requirements: !ruby/object:Gem::Requirement
51
65
  requirements:
52
- - - ">="
66
+ - - "~>"
53
67
  - !ruby/object:Gem::Version
54
- version: '0'
68
+ version: '12.3'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: rspec
57
71
  requirement: !ruby/object:Gem::Requirement
58
72
  requirements:
59
- - - ">="
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.8'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.8'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '='
60
88
  - !ruby/object:Gem::Version
61
- version: '0'
89
+ version: '0.58'
62
90
  type: :development
63
91
  prerelease: false
64
92
  version_requirements: !ruby/object:Gem::Requirement
65
93
  requirements:
66
- - - ">="
94
+ - - '='
67
95
  - !ruby/object:Gem::Version
68
- version: '0'
96
+ version: '0.58'
69
97
  description: Distributed locking using AWS DynamoDB
70
98
  email: scrinzi.jcopo@gmail.com
71
99
  executables: []
@@ -73,6 +101,7 @@ extensions: []
73
101
  extra_rdoc_files: []
74
102
  files:
75
103
  - ".gitignore"
104
+ - ".rubocop.yml"
76
105
  - ".travis.yml"
77
106
  - CHANGELOG.md
78
107
  - CONTRIBUTING.md
@@ -106,7 +135,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
106
135
  version: '0'
107
136
  requirements: []
108
137
  rubyforge_project:
109
- rubygems_version: 2.7.2
138
+ rubygems_version: 2.7.6
110
139
  signing_key:
111
140
  specification_version: 4
112
141
  summary: AWS DynamoDB based Locking