Module: Locksmith::Dynamodb

Extended by:
Dynamodb
Included in:
Dynamodb
Defined in:
lib/locksmith/dynamodb.rb

Constant Summary collapse

TTL =
60
MAX_LOCK_ATTEMPTS =
3
LOCK_TIMEOUT =
30
LOCK_TABLE =
"Locks"

Instance Method Summary collapse

Instance Method Details

#dynamoObject



82
83
84
85
86
87
# File 'lib/locksmith/dynamodb.rb', line 82

def dynamo
  @dynamo_lock.synchronize do
    @db ||= AWS::DynamoDB.new(:access_key_id => Config.aws_id,
                              :secret_access_key => Config.aws_secret)
  end
end

#fetch_lock(name) ⇒ Object



60
61
62
63
64
65
66
# File 'lib/locksmith/dynamodb.rb', line 60

def fetch_lock(name)
  if locks.at(name).exists?(consistent_read: true)
    locks[name].attributes.to_h(consistent_read: true)
  else
    locks.put(:Name => name, :Locked => 0).attributes.to_h(consistent_read: true)
  end
end

#lock(name) ⇒ Object



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/locksmith/dynamodb.rb', line 16

def lock(name)
  lock = fetch_lock(name)
  last_rev = (lock["Locked"] || 0).to_i
  new_rev = Time.now.to_i
  attempts = 0
  while attempts < MAX_LOCK_ATTEMPTS
    begin
      Timeout::timeout(LOCK_TIMEOUT) do
        if last_rev != 0 && last_rev < (Time.now.to_i - TTL)
          log(:at => "lock-expired", :lock => name, :last_rev => last_rev)
          release_lock!(name)
        end
        write_lock(name, 0, new_rev)
        log(:at => "lock-acquired", :lock => name, :rev => new_rev)
        result = yield
        release_lock(name, new_rev)
        log(:at => "lock-released", :lock => name, :rev => new_rev)
        return result
      end
    rescue AWS::DynamoDB::Errors::ConditionalCheckFailedException
      log(:at => "lock-not-acquired", :lock => name, :last_rev => last_rev, :new_rev => new_rev)
      attempts += 1
    rescue Timeout::Error
      attempts += 1
      release_lock(name, new_rev)
      log(:at => "timeout-lock-released", :lock => name, :rev => new_rev)
    end
  end
end

#locksObject



68
69
70
# File 'lib/locksmith/dynamodb.rb', line 68

def locks
  table(LOCK_TABLE)
end

#log(data, &blk) ⇒ Object



89
90
91
# File 'lib/locksmith/dynamodb.rb', line 89

def log(data, &blk)
  Log.log({ns: "dynamo-lock"}.merge(data), &blk)
end

#release_lock(name, rev) ⇒ Object



51
52
53
54
# File 'lib/locksmith/dynamodb.rb', line 51

def release_lock(name, rev)
  locks.put({:Name => name, :Locked => 0},
    :if => {:Locked => rev})
end

#release_lock!(name) ⇒ Object



56
57
58
# File 'lib/locksmith/dynamodb.rb', line 56

def release_lock!(name)
  locks.put({:Name => name, :Locked => 0})
end

#table(name) ⇒ Object



72
73
74
# File 'lib/locksmith/dynamodb.rb', line 72

def table(name)
  @table_lock.synchronize {tables[name].items}
end

#tablesObject



76
77
78
79
80
# File 'lib/locksmith/dynamodb.rb', line 76

def tables
  @tables ||= dynamo.tables.
    map {|t| t.load_schema}.
    reduce({}) {|h, t| h[t.name] = t; h}
end

#write_lock(name, rev, new_rev) ⇒ Object



46
47
48
49
# File 'lib/locksmith/dynamodb.rb', line 46

def write_lock(name, rev, new_rev)
  locks.put({:Name => name, :Locked => new_rev},
    :if => {:Locked => rev})
end