summaryrefslogtreecommitdiff
path: root/lib/task.rb
blob: f7e4c6f1284881a3480997a63f3e7724f165354c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
DEFAULT_TASK_MAX_DURATION = 36000
module OpenTox

  # Class for handling asynchronous tasks
  class Task 

    def self.run(description, creator=nil)

      task = Task.new
      task[:description] = description.to_s
      task[:creator] = creator.to_s
      task[:percentageCompleted] = 0
      task[:code] = 202 
      task.save
      pid = fork do
        begin
          task.completed yield
        rescue => e
          # wrap non-opentox-errors first
          e = OpenTox::Error.new(500,e.message,nil,e.backtrace) unless e.is_a?(OpenTox::Error)
          $logger.error "error in task #{task.id} created by #{creator}" # creator is not logged because error is logged when thrown
          task.update(:errorReport => e.metadata, :code => e.http_code, :finished_at => Time.now)
          task.get
          task.kill
        end
      end
      Process.detach(pid)
      task[:pid] = pid

      # watch if task has been cancelled 
      observer_pid = fork do
        task.wait
        begin
          Process.kill(9,task[:pid]) if task.cancelled?
        rescue
          $logger.warn "Could not kill process of task #{task.id}, pid: #{task[:pid]}"
        end
      end
      Process.detach(observer_pid)
      task[:observer_pid] = observer_pid
      task

    end

    def kill
      Process.kill(9,task[:pid])
      Process.kill(9,task[:observer_pid])
    rescue # no need to raise an exception if processes are not running
    end

    def cancel
      kill
      update(:code => 503, :finished_at => Time.now)
      get
    end

    def completed(result)
      update(:code => 200, :finished_at => Time.now, :percentageCompleted => 100, :result => result)
      get
    end

    # waits for a task, unless time exceeds or state is no longer running
    def wait
      start_time = Time.new
      due_to_time = start_time + DEFAULT_TASK_MAX_DURATION
      dur = 0.2
      while running? 
        sleep dur
        dur = [[(Time.new - start_time)/20.0,0.3].max,300.0].min
        request_timeout_error "max wait time exceeded ("+DEFAULT_TASK_MAX_DURATION.to_s+"sec), task: '"+id.to_s+"'" if (Time.new > due_to_time)
      end
    end

  end

  def running?
    code == 202 
  end

  def cancelled?
    code == 503
  end

  def completed?
    code == 200
  end

  def error?
    code >= 400 and code != 503
  end

  # Check status of a task
  # @return [String] Status
  def status
    case code
    when 202
      "Running"
    when 200
      "Completed"
    when 503
      "Cancelled"
    else
      "Error"
    end
  end

  [:code, :description, :creator, :finished_at, :percentageCompleted, :result, :errorReport].each do |method|
    define_method method do
      $mongo[:task].find(:_id => self.id).distinct(method).first
    end
  end

  def error_report
    self.errorReport
  end

end