marloss 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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