Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Example of the .git_transaction feature in https://github.com/ManageIQ/manageiq/pull/19074
$ bin/rails r tmp/test_git_transaction.rb
** Using session_store: ActionDispatch::Session::MemCacheStore
** ManageIQ master, codename: J-release
starting...
creating record... done! (id: 21)
> Process 53627 has started...
> Process 53628 has started...
> Process 53629 has started...
> Process.53628: starting .git_transaction...
> Process.53629: starting .git_transaction...
> Process.53628: lock aquired!
> Process.53628: > .update_repo...
> Process.53628: lockfile skip
> Process.53628: lockfile skip
> Process.53628: preforming clone...
> Process.53627: starting .git_transaction...
> Process.53628: > .transaction...
> Process.53628: > .refresh_branches...
> Process.53628: lockfile skip
> Process.53628: > .refresh_tags...
> Process.53628: lockfile skip
> Process.53628: > .save!...
> Process.53628: released lock!
> Process.53628: .git_transaction complete!
> Process.53629: lock aquired!
> Process.53629: > .update_repo...
> Process.53629: lockfile skip
> Process.53629: lockfile skip
> Process.53629: > .transaction...
> Process.53629: > .refresh_branches...
> Process.53629: lockfile skip
> Process.53629: > .refresh_tags...
> Process.53629: lockfile skip
> Process.53629: > .save!...
> Process.53629: released lock!
> Process.53629: .git_transaction complete!
> Process.53627: lock aquired!
> Process.53627: > .update_repo...
> Process.53627: lockfile skip
> Process.53627: lockfile skip
> Process.53627: > .transaction...
> Process.53627: > .refresh_branches...
> Process.53627: lockfile skip
> Process.53627: > .refresh_tags...
> Process.53627: lockfile skip
> Process.53627: > .save!...
> Process.53627: released lock!
> Process.53627: .git_transaction complete!
completed!
# test_git_transaction.rb
#
# Usage: bin/rails r test_git_transaction.rb
#
# A simple rails runner script for testing GitRepository#git_transaction
#
# Creates a GitRepository record in the main process, then spins up child
# processes (using fork) to run a quick sleep, and then a `git_transaction`
# where the child process will attempt to do a `GitRepository#refresh`.
#
# The outer `git_transaction` is unnecessary (I think), but it helps illustrate
# the nested usage of the method. The first process to get the filelock will
# be the only one that will perform the `git clone`.
module GitRepositoryPatch
# Re-written to add logging
def refresh
PIDColor.log " > .update_repo..."
update_repo
PIDColor.log " > .transaction..."
transaction do
PIDColor.log " > .refresh_branches..."
refresh_branches
PIDColor.log " > .refresh_tags..."
refresh_tags
self.last_refresh_on = Time.now.utc
PIDColor.log " > .save!..."
save!
end
end
private
# Slight overwrite to inject a puts for this script
def clone_repo
puts " > #{PIDColor.id 'Process.'}: preforming clone..."
super
end
# Log when we do and don't actually acquire a lock in a process
def acquire_git_lock
result = super
msg = result ? "lock aquired!" : "lockfile skip"
PIDColor.log msg
PIDColor.log " `defined? @git_lock`: #{(defined? @git_lock).inspect}" if $DEBUG
PIDColor.log " `@git_lock.nil?`: #{@git_lock.nil?.inspect}" if $DEBUG
result
end
# Log when when the lock is finally released
def release_git_lock
puts "calling .release_git_lock..." if $DEBUG
puts " before: @git_lock => #{@git_lock.inspect}" if $DEBUG
super
puts " after: @git_lock => #{@git_lock.inspect}" if $DEBUG
PIDColor.log "released lock!" if @git_lock.nil?
end
end
GitRepository # force autoload
class GitRepository
prepend GitRepositoryPatch
end
class PIDColor
def self.id prefix = ""
if STDOUT.tty? && $pid_color
id ||= "\e[#{$pid_color}m#{prefix}#{Process.pid}\e[0m"
else
Process.pid.to_s
end
end
def self.log msg
puts " > #{id 'Process.'}: #{msg}"
end
end
# Ugh... thanks Obama...
#
# just kidding, I solely blame Apple...
#
# https://github.com/darkskyapp/forecast-ruby/issues/13
#
# You will probably eactually have to export this in the terminal you are
# running this from:
#
# export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES
#
# cuz reasons...
#
ENV["OBJC_DISABLE_INITIALIZE_FORK_SAFETY"] = "YES"
threads = []
git_url = "https://github.com/NickLaMuro/ansible-tower-samples.git"
$pid_color = 30
puts "starting..."
print "creating record..."
new_repo = GitRepository.create!(:url => git_url)
puts " done! (id: #{new_repo.id})"
3.times do
$pid_color += 1
fork do
puts " > Process #{PIDColor.id} has started..."
sleep 1
git_repo = GitRepository.find(new_repo.id)
PIDColor.log "starting .git_transaction..."
git_repo.git_transaction do
git_repo.refresh
end
PIDColor.log ".git_transaction complete!"
end
end
Process.waitall
# Cleanup
begin
GitRepository.find(new_repo.id).destroy!
rescue ActiveRecord::StatementInvalid
# Most likely the other threads will kill the parent's database connection,
# so this makes sure we reconnect if the above fails, and tries again.
ActiveRecord::Base.connection.reconnect!
retry
end
puts "completed!"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment