Description:
locks dir based on temp file, does not copy dir when copying scripts, added proper rescue for ln_s
Commit status:
[Not Reviewed]
References:
Comments:
0 Commit comments 0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
Add another comment

r103:933325ce824a - - 4 files changed: 4 inserted, 3 deleted

@@ -1,109 +1,109
1 1 require 'ftools'
2 2
3 3 # DirInit::Manager handles directory initialization and clean-up when
4 4 # there are many concurrent processes that wants to modify the
5 5 # directory in the same way.
6 6 #
7 7 # An example usage is when each process wants to copy some temporary
8 8 # files to the directory and delete these files after finishing its
9 9 # job. Problems may occur when the first process delete the files
10 10 # while the second process is still using the files.
11 11 #
12 12 # This library maintain a reference counter on the processes using the
13 13 # directory. It locks the dir to manage critical section when
14 14 # updating the reference counter.
15 15
16 16 module DirInit
17 17
18 18 class Manager
19 19
20 20 def initialize(dir_name, usage_filename='.usage_counter')
21 21 @dir_name = dir_name
22 22 @usage_filename = usage_filename
23 23 end
24 24
25 25 # Check if someone has initialized the dir. If not, call block.
26 26
27 27 def setup # :yields: block
28 - dir = File.new(@dir_name)
28 + dir = File.new(@dir_name + '/lockfile',"w+")
29 29 dir.flock(File::LOCK_EX)
30 30 begin
31 31 counter_filename = get_counter_filename
32 32 if File.exist? counter_filename
33 33 # someone is here
34 34 f = File.new(counter_filename,"r+")
35 35 counter = f.read.to_i
36 36 f.seek(0)
37 37 f.write("#{counter+1}\n")
38 38 f.close
39 39 else
40 40 # i'm the first, create the counter file
41 41 counter = 0
42 42 f = File.new(counter_filename,"w")
43 43 f.write("1\n")
44 44 f.close
45 45 end
46 46
47 47 # if no one is here
48 48 if counter == 0
49 49 if block_given?
50 50 yield
51 51 end
52 52 end
53 53
54 54 rescue
55 55 raise
56 56
57 57 ensure
58 58 # make sure it unlock the directory
59 59 dir.flock(File::LOCK_UN)
60 60 dir.close
61 61 end
62 62 end
63 63
64 64 # Check if I am the last one using the dir. If true, call block.
65 65
66 66 def teardown
67 67 dir = File.new(@dir_name)
68 68 dir.flock(File::LOCK_EX)
69 69 begin
70 70 counter_filename = get_counter_filename
71 71 if File.exist? counter_filename
72 72 # someone is here
73 73 f = File.new(counter_filename,"r+")
74 74 counter = f.read.to_i
75 75 f.seek(0)
76 76 f.write("#{counter-1}\n")
77 77 f.close
78 78
79 79 if counter == 1
80 80 # i'm the last one
81 81
82 82 File.delete(counter_filename)
83 83 if block_given?
84 84 yield
85 85 end
86 86 end
87 87 else
88 88 # This is BAD
89 89 raise "Error: reference count missing"
90 90 end
91 91
92 92 rescue
93 93 raise
94 94
95 95 ensure
96 96 # make sure it unlock the directory
97 97 dir.flock(File::LOCK_UN)
98 98 dir.close
99 99 end
100 100 end
101 101
102 102 protected
103 103
104 104 def get_counter_filename
105 105 return File.join(@dir_name,@usage_filename)
106 106 end
107 107
108 108 end
109 109 end
@@ -1,186 +1,187
1 1 require 'fileutils'
2 2 require File.join(File.dirname(__FILE__),'dir_init')
3 3
4 4 module Grader
5 5
6 6 #
7 7 # A grader engine grades a submission, against anything: a test
8 8 # data, or a user submitted test data. It uses two helpers objects:
9 9 # room_maker and reporter.
10 10 #
11 11 class Engine
12 12
13 13 attr_writer :room_maker
14 14 attr_writer :reporter
15 15
16 16 def initialize(options={})
17 17 # default options
18 18 if not options.include? :room_maker
19 19 options[:room_maker] = Grader::SubmissionRoomMaker.new
20 20 end
21 21 if not options.include? :reporter
22 22 options[:reporter] = Grader::SubmissionReporter.new
23 23 end
24 24
25 25 @config = Grader::Configuration.get_instance
26 26
27 27 @room_maker = options[:room_maker]
28 28 @reporter = options[:reporter]
29 29 end
30 30
31 31 # takes a submission, asks room_maker to produce grading directories,
32 32 # calls grader scripts, and asks reporter to save the result
33 33 def grade(submission)
34 34 current_dir = FileUtils.pwd
35 35
36 36 user = submission.user
37 37 problem = submission.problem
38 38
39 39 # TODO: will have to create real exception for this
40 40 if user==nil or problem == nil
41 41 @reporter.report_error(submission,"Grading error: problem with submission")
42 42 #raise "engine: user or problem is nil"
43 43 end
44 44
45 45 # TODO: this is another hack so that output only task can be judged
46 46 if submission.language!=nil
47 47 language = submission.language.name
48 48 lang_ext = submission.language.ext
49 49 else
50 50 language = 'c'
51 51 lang_ext = 'c'
52 52 end
53 53
54 54 # FIX THIS
55 55 talk 'some hack on language'
56 56 if language == 'cpp'
57 57 language = 'c++'
58 58 end
59 59
60 60 # COMMENT: should it be only source.ext?
61 61 if problem!=nil
62 62 source_name = "#{problem.name}.#{lang_ext}"
63 63 else
64 64 source_name = "source.#{lang_ext}"
65 65 end
66 66
67 67 begin
68 68 grading_dir = @room_maker.produce_grading_room(submission)
69 69 @room_maker.save_source(submission,source_name)
70 70 problem_home = @room_maker.find_problem_home(submission)
71 71
72 72 # puts "GRADING DIR: #{grading_dir}"
73 73 # puts "PROBLEM DIR: #{problem_home}"
74 74
75 75 if !FileTest.exist?(problem_home)
76 76 raise "No test data."
77 77 end
78 78
79 79 dinit = DirInit::Manager.new(problem_home)
80 80
81 81 dinit.setup do
82 82 copy_log = copy_script(problem_home)
83 83 save_copy_log(problem_home,copy_log)
84 84 end
85 85
86 86 call_judge(problem_home,language,grading_dir,source_name)
87 87
88 88 @reporter.report(submission,"#{grading_dir}/test-result")
89 89
90 90 dinit.teardown do
91 91 copy_log = load_copy_log(problem_home)
92 92 clear_copy_log(problem_home)
93 93 clear_script(copy_log,problem_home)
94 94 end
95 95
96 96 rescue RuntimeError => msg
97 97 @reporter.report_error(submission, msg)
98 98
99 99 ensure
100 100 @room_maker.clean_up(submission)
101 101 Dir.chdir(current_dir) # this is really important
102 102 end
103 103 end
104 104
105 105 protected
106 106
107 107 def talk(str)
108 108 if @config.talkative
109 109 puts str
110 110 end
111 111 end
112 112
113 113 def call_judge(problem_home,language,grading_dir,fname)
114 114 ENV['PROBLEM_HOME'] = problem_home
115 115
116 116 talk grading_dir
117 117 Dir.chdir grading_dir
118 118 cmd = "#{problem_home}/script/judge #{language} #{fname}"
119 119 talk "CMD: #{cmd}"
120 120 system(cmd)
121 121 end
122 122
123 123 def get_std_script_dir
124 124 GRADER_ROOT + '/std-script'
125 125 end
126 126
127 127 def copy_script(problem_home)
128 128 script_dir = "#{problem_home}/script"
129 129 std_script_dir = get_std_script_dir
130 130
131 131 raise "std-script directory not found" if !FileTest.exist?(std_script_dir)
132 132
133 133 scripts = Dir[std_script_dir + '/*']
134 134
135 135 copied = []
136 136
137 137 scripts.each do |s|
138 138 fname = File.basename(s)
139 + next if FileTest.directory?(s)
139 140 if !FileTest.exist?("#{script_dir}/#{fname}")
140 141 copied << fname
141 142 FileUtils.cp(s, "#{script_dir}")
142 143 end
143 144 end
144 145
145 146 return copied
146 147 end
147 148
148 149 def copy_log_filename(problem_home)
149 150 return File.join(problem_home, '.scripts_copied')
150 151 end
151 152
152 153 def save_copy_log(problem_home, log)
153 154 f = File.new(copy_log_filename(problem_home),"w")
154 155 log.each do |fname|
155 156 f.write("#{fname}\n")
156 157 end
157 158 f.close
158 159 end
159 160
160 161 def load_copy_log(problem_home)
161 162 f = File.new(copy_log_filename(problem_home),"r")
162 163 log = []
163 164 f.readlines.each do |line|
164 165 log << line.strip
165 166 end
166 167 f.close
167 168 log
168 169 end
169 170
170 171 def clear_copy_log(problem_home)
171 172 File.delete(copy_log_filename(problem_home))
172 173 end
173 174
174 175 def clear_script(log,problem_home)
175 176 log.each do |s|
176 177 FileUtils.rm("#{problem_home}/script/#{s}")
177 178 end
178 179 end
179 180
180 181 def mkdir_if_does_not_exist(dirname)
181 182 Dir.mkdir(dirname) if !FileTest.exist?(dirname)
182 183 end
183 184
184 185 end
185 186
186 187 end
@@ -1,203 +1,203
1 1 #
2 2 # This part contains various test_request helpers for interfacing
3 3 # with Grader::Engine. There are TestRequestRoomMaker and
4 4 # TestRequestReporter.
5 5
6 6 module Grader
7 7
8 8 def self.link_or_copy(src, des)
9 9 begin
10 10 FileUtils.ln_s(src, des)
11 - rescue
11 + rescue NotImplementedError
12 12 FileUtils.cp(src,des)
13 13 end
14 14 end
15 15
16 16 def self.call_and_log(error_message)
17 17 begin
18 18 yield
19 19 rescue
20 20 msg = "ERROR: #{error_message}"
21 21 raise msg
22 22 end
23 23 end
24 24
25 25 #
26 26 # A TestRequestRoomMaker is a helper object for Engine
27 27 # - finds grading room: in user_result_dir/(user)/test_request/ ...
28 28 # - prepare problem configuration for grading --- basically it copy
29 29 # all config files, and copy user's input into the testcase
30 30 # directory. First, it finds the template from problem template
31 31 # directory; if it can't find a template, it'll use the template
32 32 # from default template.
33 33 class TestRequestRoomMaker
34 34 def initialize
35 35 @config = Grader::Configuration.get_instance
36 36 end
37 37
38 38 def produce_grading_room(test_request)
39 39 grading_room = grading_room_dir(test_request)
40 40 FileUtils.mkdir_p(grading_room)
41 41
42 42 #
43 43 # Also copy additional submitted file to this directory as well.
44 44 # The program would see this file only if it is copied
45 45 # to the sandbox directory later. The run script should do it.
46 46 #
47 47 if FileTest.exists?("#{test_request.input_file_name}.files")
48 48 FileUtils.cp_r("#{test_request.input_file_name}.files/.",
49 49 "#{grading_room}")
50 50 end
51 51
52 52 grading_room
53 53 end
54 54
55 55 def find_problem_home(test_request)
56 56 problem_name = test_request.problem_name
57 57
58 58 template_dir = "#{@config.test_request_problem_templates_dir}/" + problem_name
59 59
60 60 raise "Test Request: error template not found" if !File.exists?(template_dir)
61 61
62 62 problem_home = problem_home_dir(test_request)
63 63 FileUtils.mkdir_p(problem_home)
64 64
65 65 copy_problem_template(template_dir,problem_home)
66 66 link_input_file(test_request,problem_home)
67 67
68 68 problem_home
69 69 end
70 70
71 71 def save_source(test_request,source_name)
72 72 dir = self.produce_grading_room(test_request)
73 73 submission = test_request.submission
74 74 f = File.open("#{dir}/#{source_name}","w")
75 75 f.write(submission.source)
76 76 f.close
77 77 end
78 78
79 79 def clean_up(test_request)
80 80 problem_home = problem_home_dir(test_request)
81 81 remove_data_files(problem_home)
82 82 end
83 83
84 84 protected
85 85 def grading_room_dir(test_request)
86 86 problem_name = test_request.problem_name
87 87 user = test_request.user
88 88 grading_room = "#{@config.user_result_dir}" +
89 89 "/#{user.login}/test_request" +
90 90 "/#{problem_name}/#{test_request.id}"
91 91 grading_room
92 92 end
93 93
94 94 def problem_home_dir(test_request)
95 95 problem_name = test_request.problem_name
96 96 user = test_request.user
97 97 "#{@config.user_result_dir}" +
98 98 "/#{user.login}/test_request/#{problem_name}"
99 99 end
100 100
101 101 def copy_problem_template(template_dir,problem_home)
102 102 Grader::call_and_log("Test Request: cannot copy problem template") {
103 103 FileUtils.cp_r("#{template_dir}/.","#{problem_home}")
104 104 }
105 105 end
106 106
107 107 def link_input_file(test_request, problem_home)
108 108 input_fname = "#{test_request.input_file_name}"
109 109 if !File.exists?(input_fname)
110 110 raise "Test Request: input file not found."
111 111 end
112 112
113 113 input_fname_problem_home = "#{problem_home}/test_cases/1/input-1.txt"
114 114 if File.exists?(input_fname_problem_home)
115 115 FileUtils.rm([input_fname_problem_home], :force => true)
116 116 end
117 117
118 118 Grader::link_or_copy("#{input_fname}", "#{input_fname_problem_home}")
119 119 end
120 120
121 121 def remove_data_files(problem_home)
122 122 if File.exists?("#{problem_home}/test_cases/1/input-1.txt")
123 123 Grader::call_and_log("Test Request: cannot remove data files") {
124 124 FileUtils.rm Dir.glob("#{problem_home}/test_cases/1/*")
125 125 }
126 126 end
127 127 end
128 128
129 129 end
130 130
131 131 class TestRequestReporter
132 132 def initialize
133 133 @config = Grader::Configuration.get_instance
134 134 end
135 135
136 136 def report(test_request,test_result_dir)
137 137 save_result(test_request,read_result(test_result_dir))
138 138 end
139 139
140 140 def report_error(test_request, msg)
141 141 save_result(test_request, {:running_stat => {
142 142 :msg => "#{msg}",
143 143 :running_time => nil,
144 144 :exit_status => "Some error occured. Program did not run",
145 145 :memory_usage => nil
146 146 }})
147 147 end
148 148
149 149 protected
150 150 def read_result(test_result_dir)
151 151 # TODO:
152 152 cmp_msg_fname = "#{test_result_dir}/compiler_message"
153 153 cmp_file = File.open(cmp_msg_fname)
154 154 cmp_msg = cmp_file.read
155 155 cmp_file.close
156 156
157 157 result_file_name = "#{test_result_dir}/1/result"
158 158
159 159 if File.exists?(result_file_name)
160 160 output_file_name = "#{test_result_dir}/1/output.txt"
161 161 results = File.open("#{test_result_dir}/1/result").readlines
162 162 stat = extract_running_stat(results)
163 163
164 164 return {
165 165 :output_file_name => output_file_name,
166 166 :running_stat => stat,
167 167 :comment => "",
168 168 :cmp_msg => cmp_msg}
169 169 else
170 170 return {
171 171 :running_stat => nil,
172 172 :comment => "Compilation error",
173 173 :cmp_msg => cmp_msg}
174 174 end
175 175 end
176 176
177 177 def extract_running_stat(results)
178 178 running_stat_line = results[-1]
179 179
180 180 # extract exit status line
181 181 run_stat = ""
182 182 if !(/[Cc]orrect/.match(results[0]))
183 183 run_stat = results[0].chomp
184 184 else
185 185 run_stat = 'Program exited normally'
186 186 end
187 187
188 188 # extract running time
189 189 if res = /r(.*)u(.*)s/.match(running_stat_line)
190 190 seconds = (res[1].to_f + res[2].to_f)
191 191 time_stat = "Time used: #{seconds} sec."
192 192 else
193 193 seconds = nil
194 194 time_stat = "Time used: n/a sec."
195 195 end
196 196
197 197 # extract memory usage
198 198 if res = /s(.*)m/.match(running_stat_line)
199 199 memory_used = res[1].to_i
200 200 else
201 201 memory_used = -1
202 202 end
203 203
You need to be logged in to leave comments. Login now