自動提出機での TLE の処理

更新日時:

前から公言している通り、私は AtCoder のサンプルチェックから提出を自動化している。 手元でサンプルチェックをするわけであるが、当然、ときには TLE のコードを書いてしまう。 TLE のコードを書くと、どこかで中断する必要がある。 その時の処理をメモしておく。

本編

今までは

Timeout.timeoutrescue Timeout::Error することで、問題文の TLE の秒数だけ待ち、オーバーしたら TLE 判定をしていた。

      begin
        Timeout.timeout(problem.tle) {
          system(language.exe.call(file) +
                 " < #{sample_file_path(problem, i, 'in')}" +
                 " > #{sample_file_path(problem, i, time)}")
        }
      rescue Timeout::Error
        puts "Case #{i+1}: TLE"
        # 中略
      end

これで十分なように思うが…

何が良くなかったか

これだと、 TLE したプログラムが止まっていない 。何日も経った後、 mac が遅いと。そしてアクティビティモニタを見て見ると、 CPU を一番使っているところに a.out と書いてある。そこで気づいたのである。言われてみれば当たり前だが、設計時には気づかず。

こうすることにした

要するに Timeout::Error をしたらそのプロセスを Kill すれば良い。このためには Kernel#system は不向きである(プロセスに対する情報を全く返せないから)。 IO#popen で子プロセスとして a.out なりなんなりを走らせて、 IO.putsIO.write を用いる。気をつけるべき点は、 IO#popen したものは 'w+' で開くことである。子プロセスから見れば、標準入力からの受け渡しは「書き込み」に相当する。

      begin
        io = nil
        Timeout.timeout(problem.tle) {
          io = IO.popen(language.exe.call(file), 'w+')
          open(sample_file_path(problem, i, 'in')) {|input|
            open(sample_file_path(problem, i, time), 'w') {|output|
              io.puts(input.read)
              output.write(io.read)
            }
          }
        }
      rescue Timeout::Error
        Process.kill('KILL', io.pid)
        puts "Case #{i+1}: TLE"
        # 中略
      end

stderr は普通に出てくるからデバグしやすい。

参考文献

コメントする