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,251 +1,251
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
204 204 return {
205 205 :msg => "#{run_stat}\n#{time_stat}",
206 206 :running_time => seconds,
207 207 :exit_status => run_stat,
208 208 :memory_usage => memory_used
209 209 }
210 210 end
211 211
212 212 def save_result(test_request,result)
213 213 if result[:output_file_name]!=nil
214 214 test_request.output_file_name = link_output_file(test_request,
215 215 result[:output_file_name])
216 216 end
217 217 test_request.graded_at = Time.now
218 218 test_request.compiler_message = (result[:cmp_msg] or '')
219 219 test_request.grader_comment = (result[:comment] or '')
220 220 if result[:running_stat]!=nil
221 221 test_request.running_stat = (result[:running_stat][:msg] or '')
222 222 test_request.running_time = (result[:running_stat][:running_time] or nil)
223 223 test_request.exit_status = result[:running_stat][:exit_status]
224 224 test_request.memory_usage = result[:running_stat][:memory_usage]
225 225 else
226 226 test_request.running_stat = ''
227 227 end
228 228 test_request.save
229 229 end
230 230
231 231 protected
232 232 def link_output_file(test_request, fname)
233 233 target_file_name = random_output_file_name(test_request.user,
234 234 test_request.problem)
235 235 FileUtils.mkdir_p(File.dirname(target_file_name))
236 236 Grader::link_or_copy("#{fname}", "#{target_file_name}")
237 237 return target_file_name
238 238 end
239 239
240 240 def random_output_file_name(user,problem)
241 241 problem_name = TestRequest.name_of(problem)
242 242 begin
243 243 tmpname = "#{@config.test_request_output_base_dir}" +
244 244 "/#{user.login}/#{problem_name}/#{rand(10000)}"
245 245 end while File.exists?(tmpname)
246 246 tmpname
247 247 end
248 248
249 249 end
250 250
251 251 end
You need to be logged in to leave comments. Login now