diff --git a/lib/dir_init.rb b/lib/dir_init.rb new file mode 100644 --- /dev/null +++ b/lib/dir_init.rb @@ -0,0 +1,107 @@ +require 'ftools' + +# DirInit::Manager handles directory initialization and clean-up when +# there are many concurrent processes that wants to modify the +# directory in the same way. +# +# An example usage is when each process wants to copy some temporary +# files to the directory and delete these files after finishing its +# job. Problems may occur when the first process delete the files +# while the second process is still using the files. +# +# This library maintain a reference counter on the processes using the +# directory. It locks the dir to manage critical section when +# updating the reference counter. + +module DirInit + + class Manager + + def initialize(dir_name, usage_filename='.usage_counter') + @dir_name = dir_name + @usage_filename = usage_filename + end + + # Check if someone has initialized the dir. If not, call block. + + def setup # :yields: block + dir = File.new(@dir_name) + dir.flock(File::LOCK_EX) + begin + counter_filename = get_counter_filename + if File.exist? counter_filename + # someone is here + f = File.new(counter_filename,"r+") + counter = f.read.to_i + f.seek(0) + f.write("#{counter+1}\n") + f.close + else + # i'm the first, create the counter file + counter = 0 + f = File.new(counter_filename,"w") + f.write("1\n") + f.close + end + + # if no one is here + if counter == 0 + if block_given? + yield + end + end + + rescue + raise + + ensure + # make sure it unlock the directory + dir.flock(File::LOCK_UN) + end + end + + # Check if I am the last one using the dir. If true, call block. + + def teardown + dir = File.new(@dir_name) + dir.flock(File::LOCK_EX) + begin + counter_filename = get_counter_filename + if File.exist? counter_filename + # someone is here + f = File.new(counter_filename,"r+") + counter = f.read.to_i + f.seek(0) + f.write("#{counter-1}\n") + f.close + + if counter == 1 + # i'm the last one + + File.delete(counter_filename) + if block_given? + yield + end + end + else + # This is BAD + raise "Error: reference count missing" + end + + rescue + raise + + ensure + # make sure it unlock the directory + dir.flock(File::LOCK_UN) + end + end + + protected + + def get_counter_filename + return File.join(@dir_name,@usage_filename) + end + + end +end diff --git a/lib/engine.rb b/lib/engine.rb --- a/lib/engine.rb +++ b/lib/engine.rb @@ -1,4 +1,6 @@ require 'fileutils' +require 'ftools' +require 'lib/dir_init' module Grader @@ -63,13 +65,22 @@ # puts "GRADING DIR: #{grading_dir}" # puts "PROBLEM DIR: #{problem_home}" - copy_log = copy_script(problem_home) + dinit = DirInit::Manager.new(problem_home) + + dinit.setup do + copy_log = copy_script(problem_home) + save_copy_log(problem_home,copy_log) + end call_judge(problem_home,language,grading_dir,source_name) @reporter.report(submission,"#{grading_dir}/test-result") - clear_script(copy_log,problem_home) + dinit.teardown do + copy_log = load_copy_log(problem_home) + clear_copy_log(problem_home) + clear_script(copy_log,problem_home) + end rescue RuntimeError => msg @reporter.report_error(submission,"Grading error: #{msg}") @@ -122,7 +133,33 @@ return copied end + + def copy_log_filename(problem_home) + return File.join(problem_home, '.scripts_copied') + end + + def save_copy_log(problem_home, log) + f = File.new(copy_log_filename(problem_home),"w") + log.each do |fname| + f.write("#{fname}\n") + end + f.close + end + def load_copy_log(problem_home) + f = File.new(copy_log_filename(problem_home),"r") + log = [] + f.readlines.each do |line| + log << line.strip + end + f.close + log + end + + def clear_copy_log(problem_home) + File.delete(copy_log_filename(problem_home)) + end + def clear_script(log,problem_home) log.each do |s| system("rm #{problem_home}/script/#{s}") diff --git a/std-script/.gitignore b/std-script/.gitignore new file mode 100644 --- /dev/null +++ b/std-script/.gitignore @@ -0,0 +1,2 @@ +box +