Class: Deb::S3::Lock
- Inherits:
-
Object
- Object
- Deb::S3::Lock
- Defined in:
- lib/deb/s3/lock.rb
Instance Attribute Summary collapse
-
#host ⇒ Object
readonly
Returns the value of attribute host.
-
#user ⇒ Object
readonly
Returns the value of attribute user.
Class Method Summary collapse
- .current(codename, component = nil, architecture = nil, cache_control = nil) ⇒ Object
-
.lock(codename, component = nil, architecture = nil, cache_control = nil, max_attempts = 60, max_wait_interval = 10) ⇒ Object
2-phase mutual lock mechanism based on ‘s3:CopyObject`.
- .unlock(codename, component = nil, architecture = nil, cache_control = nil) ⇒ Object
Instance Method Summary collapse
-
#initialize(user, host) ⇒ Lock
constructor
A new instance of Lock.
Constructor Details
#initialize(user, host) ⇒ Lock
Returns a new instance of Lock.
12 13 14 15 |
# File 'lib/deb/s3/lock.rb', line 12 def initialize(user, host) @user = user @host = host end |
Instance Attribute Details
#host ⇒ Object (readonly)
Returns the value of attribute host.
10 11 12 |
# File 'lib/deb/s3/lock.rb', line 10 def host @host end |
#user ⇒ Object (readonly)
Returns the value of attribute user.
9 10 11 |
# File 'lib/deb/s3/lock.rb', line 9 def user @user end |
Class Method Details
.current(codename, component = nil, architecture = nil, cache_control = nil) ⇒ Object
96 97 98 99 100 101 102 103 104 105 |
# File 'lib/deb/s3/lock.rb', line 96 def current(codename, component = nil, architecture = nil, cache_control = nil) lockbody = Deb::S3::Utils.s3_read(lock_path(codename, component, architecture, cache_control)) if lockbody user, host = lockbody.to_s.split("@", 2) lock = Deb::S3::Lock.new(user, host) else lock = Deb::S3::Lock.new("unknown", "unknown") end lock end |
.lock(codename, component = nil, architecture = nil, cache_control = nil, max_attempts = 60, max_wait_interval = 10) ⇒ Object
2-phase mutual lock mechanism based on ‘s3:CopyObject`.
This logic isn’t relying on S3’s enhanced features like Object Lock because it imposes some limitation on using other features like S3 Cross-Region replication. This should work more than good enough with S3’s strong read-after-write consistency which we can presume in all region nowadays.
This is relying on S3 to set object’s ETag as object’s MD5 if an object isn’t comprized from multiple parts. We’d be able to presume it as the lock file is usually an object of some smaller bytes.
acquire lock:
-
call ‘s3:HeadObject` on final lock object
-
If final lock object exists, restart from the beginning
-
Otherwise, call ‘s3:PutObject` to create initial lock object
-
Perform ‘s3:CopyObject` to copy from initial lock object to final lock object with specifying ETag/MD5 of the initial lock object
-
If copy object fails as ‘PreconditionFailed`, restart from the beginning
-
Otherwise, lock has been acquired
release lock:
-
remove final lock object by ‘s3:DeleteObject`
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/deb/s3/lock.rb', line 45 def lock(codename, component = nil, architecture = nil, cache_control = nil, max_attempts=60, max_wait_interval=10) lockbody = "#{Etc.getlogin}@#{Socket.gethostname}" initial_lockfile = initial_lock_path(codename, component, architecture, cache_control) final_lockfile = lock_path(codename, component, architecture, cache_control) md5_b64 = Base64.encode64(Digest::MD5.digest(lockbody)) md5_hex = Digest::MD5.hexdigest(lockbody) max_attempts.times do |i| wait_interval = [(1<<i)/10, max_wait_interval].min if Deb::S3::Utils.s3_exists?(final_lockfile) lock = current(codename, component, architecture, cache_control) $stderr.puts("Repository is locked by another user: #{lock.user} at host #{lock.host} (phase-1)") $stderr.puts("Attempting to obtain a lock after #{wait_interval} secound(s).") sleep(wait_interval) else # upload the file Deb::S3::Utils.s3.put_object( bucket: Deb::S3::Utils.bucket, key: Deb::S3::Utils.s3_path(initial_lockfile), body: lockbody, content_type: "text/plain", content_md5: md5_b64, metadata: { "md5" => md5_hex, }, ) begin Deb::S3::Utils.s3.copy_object( bucket: Deb::S3::Utils.bucket, key: Deb::S3::Utils.s3_path(final_lockfile), copy_source: "/#{Deb::S3::Utils.bucket}/#{Deb::S3::Utils.s3_path(initial_lockfile)}", copy_source_if_match: md5_hex, ) return rescue Aws::S3::Errors::PreconditionFailed => error lock = current(codename, component, architecture, cache_control) $stderr.puts("Repository is locked by another user: #{lock.user} at host #{lock.host} (phase-2)") $stderr.puts("Attempting to obtain a lock after #{wait_interval} second(s).") sleep(wait_interval) end end end # TODO: throw appropriate error class raise("Unable to obtain a lock after #{max_attempts}, giving up.") end |
.unlock(codename, component = nil, architecture = nil, cache_control = nil) ⇒ Object
91 92 93 94 |
# File 'lib/deb/s3/lock.rb', line 91 def unlock(codename, component = nil, architecture = nil, cache_control = nil) Deb::S3::Utils.s3_remove(initial_lock_path(codename, component, architecture, cache_control)) Deb::S3::Utils.s3_remove(lock_path(codename, component, architecture, cache_control)) end |