scout-gear 7.1.0 → 7.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/.vimproject +29 -0
  3. data/VERSION +1 -1
  4. data/bin/scout +5 -1
  5. data/lib/rbbt-scout.rb +5 -0
  6. data/lib/scout/concurrent_stream.rb +6 -2
  7. data/lib/scout/config.rb +168 -0
  8. data/lib/scout/exceptions.rb +4 -3
  9. data/lib/scout/indiferent_hash/options.rb +1 -0
  10. data/lib/scout/indiferent_hash.rb +4 -2
  11. data/lib/scout/log/color.rb +3 -1
  12. data/lib/scout/log/progress/report.rb +1 -0
  13. data/lib/scout/log/progress/util.rb +1 -1
  14. data/lib/scout/log/progress.rb +5 -3
  15. data/lib/scout/log.rb +3 -2
  16. data/lib/scout/misc/monitor.rb +3 -0
  17. data/lib/scout/misc/system.rb +15 -0
  18. data/lib/scout/misc.rb +1 -0
  19. data/lib/scout/named_array.rb +68 -0
  20. data/lib/scout/open/stream.rb +38 -7
  21. data/lib/scout/path/find.rb +27 -3
  22. data/lib/scout/path/util.rb +7 -4
  23. data/lib/scout/persist/serialize.rb +7 -14
  24. data/lib/scout/persist.rb +21 -1
  25. data/lib/scout/resource/produce.rb +7 -94
  26. data/lib/scout/resource/software.rb +176 -0
  27. data/lib/scout/tsv/dumper.rb +107 -0
  28. data/lib/scout/tsv/index.rb +49 -0
  29. data/lib/scout/tsv/parser.rb +203 -30
  30. data/lib/scout/tsv/path.rb +13 -0
  31. data/lib/scout/tsv/persist/adapter.rb +348 -0
  32. data/lib/scout/tsv/persist/tokyocabinet.rb +113 -0
  33. data/lib/scout/tsv/persist.rb +15 -0
  34. data/lib/scout/tsv/traverse.rb +48 -0
  35. data/lib/scout/tsv/util.rb +24 -0
  36. data/lib/scout/tsv.rb +16 -3
  37. data/lib/scout/work_queue/worker.rb +3 -3
  38. data/lib/scout/work_queue.rb +22 -7
  39. data/lib/scout/workflow/definition.rb +93 -4
  40. data/lib/scout/workflow/step/config.rb +18 -0
  41. data/lib/scout/workflow/step/dependencies.rb +40 -0
  42. data/lib/scout/workflow/step/file.rb +15 -0
  43. data/lib/scout/workflow/step/info.rb +31 -4
  44. data/lib/scout/workflow/step/provenance.rb +148 -0
  45. data/lib/scout/workflow/step.rb +68 -19
  46. data/lib/scout/workflow/task.rb +3 -2
  47. data/lib/scout/workflow/usage.rb +1 -1
  48. data/lib/scout/workflow.rb +11 -3
  49. data/lib/scout-gear.rb +1 -0
  50. data/lib/scout.rb +1 -0
  51. data/scout-gear.gemspec +34 -3
  52. data/scout_commands/find +1 -1
  53. data/scout_commands/workflow/task +16 -10
  54. data/share/software/install_helpers +523 -0
  55. data/test/scout/log/test_progress.rb +0 -2
  56. data/test/scout/misc/test_system.rb +21 -0
  57. data/test/scout/open/test_stream.rb +159 -0
  58. data/test/scout/path/test_find.rb +14 -7
  59. data/test/scout/resource/test_software.rb +24 -0
  60. data/test/scout/test_config.rb +66 -0
  61. data/test/scout/test_meta_extension.rb +10 -0
  62. data/test/scout/test_named_array.rb +19 -0
  63. data/test/scout/test_persist.rb +35 -0
  64. data/test/scout/test_tmpfile.rb +2 -2
  65. data/test/scout/test_tsv.rb +41 -1
  66. data/test/scout/test_work_queue.rb +40 -13
  67. data/test/scout/tsv/persist/test_adapter.rb +34 -0
  68. data/test/scout/tsv/persist/test_tokyocabinet.rb +92 -0
  69. data/test/scout/tsv/test_dumper.rb +44 -0
  70. data/test/scout/tsv/test_index.rb +64 -0
  71. data/test/scout/tsv/test_parser.rb +86 -0
  72. data/test/scout/tsv/test_persist.rb +36 -0
  73. data/test/scout/tsv/test_traverse.rb +9 -0
  74. data/test/scout/tsv/test_util.rb +0 -0
  75. data/test/scout/work_queue/test_worker.rb +3 -3
  76. data/test/scout/workflow/step/test_dependencies.rb +25 -0
  77. data/test/scout/workflow/step/test_info.rb +15 -17
  78. data/test/scout/workflow/step/test_load.rb +16 -18
  79. data/test/scout/workflow/step/test_provenance.rb +25 -0
  80. data/test/scout/workflow/test_step.rb +206 -10
  81. data/test/scout/workflow/test_task.rb +0 -3
  82. data/test/test_helper.rb +6 -0
  83. metadata +33 -2
@@ -0,0 +1,113 @@
1
+ require 'tokyocabinet'
2
+ require_relative 'adapter'
3
+
4
+ module ScoutCabinet
5
+ attr_accessor :persistence_path, :persistence_class
6
+
7
+ CONNECTIONS = {}
8
+ def self.open(path, write, tokyocabinet_class = TokyoCabinet::HDB)
9
+ if String === tokyocabinet_class && tokyocabinet_class.include?(":big")
10
+ big = true
11
+ tokyocabinet_class = tokyocabinet_class.split(":").first
12
+ else
13
+ big = false
14
+ end
15
+
16
+ dir = File.dirname(File.expand_path(path))
17
+ Open.mkdir(dir) unless File.exist?(dir)
18
+
19
+ tokyocabinet_class = TokyoCabinet::HDB if tokyocabinet_class == "HDB" or tokyocabinet_class.nil?
20
+ tokyocabinet_class = TokyoCabinet::BDB if tokyocabinet_class == "BDB"
21
+
22
+ database = CONNECTIONS[path] ||= tokyocabinet_class.new
23
+
24
+ if big and not Open.exists?(path)
25
+ database.tune(nil,nil,nil,tokyocabinet_class::TLARGE | tokyocabinet_class::TDEFLATE)
26
+ end
27
+
28
+ flags = (write ? tokyocabinet_class::OWRITER | tokyocabinet_class::OCREAT : tokyocabinet_class::OREADER)
29
+ database.close
30
+
31
+ if !database.open(path, flags)
32
+ ecode = database.ecode
33
+ raise "Open error: #{database.errmsg(ecode)}. Trying to open file #{path}"
34
+ end
35
+
36
+ database.extend ScoutCabinet
37
+ database.persistence_path ||= path
38
+ database.persistence_class = tokyocabinet_class
39
+
40
+ database.open(path, tokyocabinet_class::OREADER)
41
+
42
+ CONNECTIONS[path] = database
43
+
44
+ database
45
+ end
46
+
47
+ def close
48
+ @closed = true
49
+ @writable = false
50
+ super
51
+ end
52
+
53
+ def read(force = false)
54
+ return if ! write? && ! closed && ! force
55
+ self.close
56
+ if !self.open(@persistence_path, persistence_class::OREADER)
57
+ ecode = self.ecode
58
+ raise "Open error: #{self.errmsg(ecode)}. Trying to open file #{@persistence_path}"
59
+ end
60
+
61
+ @writable = false
62
+ @closed = false
63
+
64
+ self
65
+ end
66
+
67
+ def write(force = true)
68
+ return if write? && ! closed && ! force
69
+ self.close
70
+
71
+ if !self.open(@persistence_path, persistence_class::OWRITER)
72
+ ecode = self.ecode
73
+ raise "Open error: #{self.errmsg(ecode)}. Trying to open file #{@persistence_path}"
74
+ end
75
+
76
+ @writable = true
77
+ @closed = false
78
+
79
+ self
80
+ end
81
+
82
+ #def self.open_tokyocabinet(path, write, serializer = nil, tokyocabinet_class = TokyoCabinet::HDB)
83
+ # raise
84
+ # write = true unless File.exist? path
85
+
86
+ # FileUtils.mkdir_p File.dirname(path) unless File.exist?(File.dirname(path))
87
+
88
+ # database = Persist::TCAdapter.open(path, write, tokyocabinet_class)
89
+
90
+ # unless serializer == :clean
91
+ # TSV.setup database
92
+ # database.write_and_read do
93
+ # database.serializer = serializer
94
+ # end if serializer && database.serializer != serializer
95
+ # end
96
+
97
+ # database
98
+ #end
99
+ end
100
+
101
+ Persist.save_drivers[:HDB] = proc do |file, content|
102
+ data = ScoutCabinet.open(file, true, "HDB")
103
+ content.annotate(data)
104
+ data.extend TSVAdapter
105
+ data.merge!(content)
106
+ data
107
+ end
108
+
109
+ Persist.load_drivers[:HDB] = proc do |file|
110
+ data = ScoutCabinet.open(file, false, "HDB")
111
+ data.extend TSVAdapter unless TSVAdapter === data
112
+ data
113
+ end
@@ -0,0 +1,15 @@
1
+ require_relative 'persist/adapter'
2
+ require_relative 'persist/tokyocabinet'
3
+
4
+ Persist.save_drivers[:tsv] = proc do |file,content|
5
+ stream = if IO === content
6
+ content
7
+ elsif content.respond_to?(:get_stream)
8
+ content.get_stream
9
+ elsif content.respond_to?(:stream)
10
+ content.stream
11
+ end
12
+ Open.sensible_write(file, stream)
13
+ end
14
+
15
+ Persist.load_drivers[:tsv] = proc do |file| TSV.open file end
@@ -0,0 +1,48 @@
1
+ require_relative 'parser'
2
+ module TSV
3
+ def self.traverse_add(into, res)
4
+ case into
5
+ when TSV::Dumper
6
+ into.add *res
7
+ when TSV, Hash
8
+ key, value = res
9
+ into[key] = value
10
+ end
11
+ end
12
+
13
+ def self.traverse(obj, into: nil, cpus: nil, bar: nil, **options, &block)
14
+ case obj
15
+ when TSV
16
+ self.traverse(obj.stream, into: into, cpus: cpus, bar: bar, **options, &block)
17
+ when String
18
+ f = Open.open(obj)
19
+ self.traverse(f, into: into, cpus: cpus, bar: bar, **options, &block)
20
+ when Step
21
+ self.traverse(obj.get_stream, into: into, cpus: cpus, bar: bar, **options, &block)
22
+ when IO
23
+ if into
24
+ into_thread = Thread.new do
25
+ Thread.current.report_on_exception = false
26
+ Thread.current["name"] = "Traverse into"
27
+ TSV.parse obj, **options do |k,v|
28
+ begin
29
+ res = block.call k, v
30
+ traverse_add into, res
31
+ rescue
32
+ into.abort $!
33
+ end
34
+ nil
35
+ end
36
+ into.close if into.respond_to?(:close)
37
+ end
38
+ Thread.pass until into_thread
39
+ into
40
+ else
41
+ TSV.parse obj, **options do |k,v|
42
+ block.call k, v
43
+ nil
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,24 @@
1
+ #require_relative '../../../modules/rbbt-util/lib/rbbt/tsv/manipulate'
2
+ #Log.warn "USING OLD RBBT CODE: #{__FILE__}"
3
+ module TSV
4
+ #[:each, :collect, :map].each do |method|
5
+ # define_method(method) do |*args,&block|
6
+ # super(*args) do |k,v|
7
+ # NamedArray.setup(v, @fields) unless @unnamed
8
+ # block.call k, v
9
+ # end
10
+ # end
11
+ #end
12
+
13
+ #[:select, :reject].each do |method|
14
+ # define_method(method) do |*args,&block|
15
+ # res = super(*args) do |k,v|
16
+ # NamedArray.setup(v, @fields) unless @unnamed
17
+ # block.call k, v
18
+ # end
19
+ # self.annotate(res)
20
+ # res
21
+ # end
22
+ #end
23
+
24
+ end
data/lib/scout/tsv.rb CHANGED
@@ -1,14 +1,27 @@
1
1
  require_relative 'meta_extension'
2
+ require_relative 'tsv/util'
2
3
  require_relative 'tsv/parser'
4
+ require_relative 'tsv/dumper'
5
+ require_relative 'tsv/persist'
6
+ require_relative 'tsv/index'
7
+ require_relative 'tsv/path'
8
+ require_relative 'tsv/traverse'
3
9
 
4
10
  module TSV
5
11
  extend MetaExtension
6
- extension_attr :key_field, :fields
12
+ extension_attr :key_field, :fields, :type, :filename, :namespace, :unnamed
7
13
 
8
14
  def self.open(file, options = {})
9
- Open.open(file) do |f|
10
- TSV.parse(f,**options)
15
+ persist, type = IndiferentHash.process_options options, :persist, :persist_type, :persist => false, :persist_type => "HDB"
16
+ Persist.persist(file, type, options.merge(:persist => persist)) do |filename|
17
+ data = filename ? ScoutCabinet.open(filename, true, type) : nil
18
+ options[:data] = data if data
19
+ options[:filename] = file
20
+ Open.open(file) do |f|
21
+ TSV.parse(f, **options)
22
+ end
11
23
  end
12
24
  end
25
+
13
26
  end
14
27
 
@@ -21,7 +21,7 @@ class WorkQueue
21
21
  raise obj
22
22
  end
23
23
  res = block.call obj
24
- output.write res unless output.nil? || ignore_ouput || res == :ignore
24
+ output.write res unless ignore_ouput || res == :ignore
25
25
  end
26
26
  rescue DoneProcessing
27
27
  rescue Interrupt
@@ -34,14 +34,14 @@ class WorkQueue
34
34
 
35
35
  def abort
36
36
  begin
37
- Log.log "Aborting worker #{@pid}"
37
+ Log.debug "Aborting worker #{@pid}"
38
38
  Process.kill "INT", @pid
39
39
  rescue Errno::ECHILD
40
40
  end
41
41
  end
42
42
 
43
43
  def join
44
- Log.log "Joining worker #{@pid}"
44
+ Log.debug "Joining worker #{@pid}"
45
45
  Process.waitpid @pid
46
46
  end
47
47
 
@@ -36,8 +36,12 @@ class WorkQueue
36
36
 
37
37
  def remove_worker(pid)
38
38
  @worker_mutex.synchronize do
39
- @workers.delete_if{|w| w.pid == pid }
40
- @removed_workers << pid
39
+ worker = @workers.index{|w| w.pid == pid}
40
+ if worker
41
+ Log.debug "Removed worker #{pid}"
42
+ @workers.delete_at(worker)
43
+ @removed_workers << pid
44
+ end
41
45
  end
42
46
  end
43
47
 
@@ -50,11 +54,13 @@ class WorkQueue
50
54
  while true
51
55
  obj = @output.read
52
56
  if DoneProcessing === obj
57
+
53
58
  done = @worker_mutex.synchronize do
54
59
  Log.low "Worker #{obj.pid} done"
55
60
  @done_workers << obj.pid
56
- @done_workers.length == @removed_workers.length + @workers.length
61
+ @closed && @done_workers.length == @removed_workers.length + @workers.length
57
62
  end
63
+
58
64
  break if done
59
65
  elsif Exception === obj
60
66
  raise obj
@@ -65,9 +71,13 @@ class WorkQueue
65
71
  rescue DoneProcessing
66
72
  rescue Aborted
67
73
  rescue WorkerException
68
- Log.error "Exception in worker #{obj.pid} #{Log.fingerprint obj.exception}"
74
+ Log.error "Exception in worker #{obj.pid} in queue #{Process.pid}: #{obj.message}"
69
75
  self.abort
70
- raise obj.exception
76
+ raise obj.worker_exception
77
+ rescue
78
+ Log.error "Exception processing output in queue #{Process.pid}: #{$!.message}"
79
+ self.abort
80
+ raise $!
71
81
  end
72
82
  end
73
83
 
@@ -84,11 +94,12 @@ class WorkQueue
84
94
  while true
85
95
  pid = Process.wait
86
96
  remove_worker(pid)
87
- break if workers.empty?
97
+ break if @worker_mutex.synchronize{ @workers.empty? }
88
98
  end
89
99
  end
90
100
  end
91
101
 
102
+ Thread.pass until @worker_mutex.synchronize{ @workers.select{|w| w.pid.nil? }.empty? }
92
103
  Thread.pass until @waiter["name"]
93
104
  end
94
105
 
@@ -97,10 +108,14 @@ class WorkQueue
97
108
  end
98
109
 
99
110
  def abort
100
- workers.each{|w| w.abort }
111
+ Log.low "Aborting #{@workers.length} workers in queue #{Process.pid}"
112
+ @worker_mutex.synchronize do
113
+ @workers.each{|w| w.abort }
114
+ end
101
115
  end
102
116
 
103
117
  def close
118
+ @closed = true
104
119
  @worker_mutex.synchronize{ @workers.length }.times do
105
120
  @input.write DoneProcessing.new()
106
121
  end
@@ -2,7 +2,7 @@ require_relative '../meta_extension'
2
2
 
3
3
  module Workflow
4
4
  extend MetaExtension
5
- extension_attr :name, :tasks
5
+ extension_attr :name, :tasks, :helpers
6
6
 
7
7
  class << self
8
8
  attr_accessor :directory
@@ -17,6 +17,32 @@ module Workflow
17
17
  @name ||= self.to_s
18
18
  end
19
19
 
20
+ def helpers
21
+ @helpers ||= {}
22
+ end
23
+
24
+ def helper(name, *args, &block)
25
+ if block_given?
26
+ helpers[name] = block
27
+ else
28
+ raise RbbtException, "helper #{name} unkown in #{self} workflow" unless helpers[name]
29
+ helpers[name].call(*args)
30
+ end
31
+ end
32
+
33
+ def step_module
34
+ @_m ||= begin
35
+ m = Module.new
36
+
37
+ helpers.each do |name,block|
38
+ m.send(:define_method, name, &block)
39
+ end
40
+
41
+ m
42
+ end
43
+ @_m
44
+ end
45
+
20
46
  attr_accessor :directory
21
47
  def directory
22
48
  @directory ||= Workflow.directory[name]
@@ -60,19 +86,82 @@ module Workflow
60
86
  annotate_next_task(:inputs, args)
61
87
  end
62
88
 
89
+ def desc(description)
90
+ annotate_next_task_single(:description, description)
91
+ end
92
+
93
+ def returns(type)
94
+ annotate_next_task_single(:returns, type)
95
+ end
96
+
97
+ def extension(extension)
98
+ annotate_next_task_single(:extension, extension)
99
+ end
100
+
63
101
  def task(name_and_type, &block)
64
102
  name, type = name_and_type.collect.first
65
103
  @tasks ||= IndiferentHash.setup({})
66
104
  begin
67
105
  @annotate_next_task ||= {}
68
- task = Task.setup(block, @annotate_next_task.merge(name: name, type: type, directory: directory[name]))
106
+ task = Task.setup(block, @annotate_next_task.merge(name: name, type: type, directory: directory[name], workflow: self))
69
107
  @tasks[name] = task
70
108
  ensure
71
109
  @annotate_next_task = {}
72
110
  end
73
111
  end
74
112
 
75
- def desc(description)
76
- annotate_next_task_single(:description, description)
113
+ def task_alias(name, workflow, oname, *rest, &block)
114
+ dep(workflow, oname, *rest, &block)
115
+ extension :dep_task unless @extension
116
+ returns workflow.tasks[oname].returns if workflow.tasks.include?(oname) unless @returns
117
+ task name => nil do
118
+ raise RbbtException, "dep_task does not have any dependencies" if dependencies.empty?
119
+ Step.wait_for_jobs dependencies.select{|d| d.streaming? }
120
+ dep = dependencies.last
121
+ dep.join
122
+ raise dep.get_exception if dep.error?
123
+ raise Aborted, "Aborted dependency #{dep.path}" if dep.aborted?
124
+ set_info :result_type, dep.info[:result_type]
125
+ forget = config :forget_dep_tasks, "forget_dep_tasks", :default => FORGET_DEP_TASKS
126
+ if forget
127
+ remove = config :remove_dep_tasks, "remove_dep_tasks", :default => REMOVE_DEP_TASKS
128
+
129
+ self.archive_deps
130
+ self.copy_files_dir
131
+ self.dependencies = self.dependencies - [dep]
132
+ Open.rm_rf self.files_dir if Open.exist? self.files_dir
133
+ FileUtils.cp_r dep.files_dir, self.files_dir if Open.exist?(dep.files_dir)
134
+
135
+ if dep.overriden || ! Workflow.job_path?(dep.path)
136
+ Open.link dep.path, self.tmp_path
137
+ else
138
+ Open.ln_h dep.path, self.tmp_path
139
+
140
+ case remove.to_s
141
+ when 'true'
142
+ dep.clean
143
+ when 'recursive'
144
+ (dep.dependencies + dep.rec_dependencies).uniq.each do |d|
145
+ next if d.overriden
146
+ d.clean unless config(:remove_dep, d.task_signature, d.task_name, d.workflow.to_s, :default => true).to_s == 'false'
147
+ end
148
+ dep.clean unless config(:remove_dep, dep.task_signature, dep.task_name, dep.workflow.to_s, :default => true).to_s == 'false'
149
+ end
150
+ end
151
+ else
152
+ if Open.exists?(dep.files_dir)
153
+ Open.rm_rf self.files_dir
154
+ Open.link dep.files_dir, self.files_dir
155
+ end
156
+ if defined?(RemoteStep) && RemoteStep === dep
157
+ Open.write(self.tmp_path, Open.read(dep.path))
158
+ else
159
+ Open.link dep.path, self.path
160
+ end
161
+ end
162
+ nil
163
+ end
77
164
  end
165
+
166
+ alias dep_task task_alias
78
167
  end
@@ -0,0 +1,18 @@
1
+ require_relative '../../config'
2
+
3
+ class Step
4
+ def config(key, *tokens)
5
+ options = tokens.pop if Hash === tokens.last
6
+ options ||= {}
7
+
8
+ new_tokens = []
9
+ if workflow
10
+ workflow_name = workflow.to_s
11
+ new_tokens << ("workflow:" << workflow_name)
12
+ new_tokens << ("task:" << workflow_name << "#" << task_name.to_s)
13
+ end
14
+ new_tokens << ("task:" << task_name.to_s)
15
+
16
+ Scout::Config.get(key, tokens + new_tokens, options)
17
+ end
18
+ end
@@ -0,0 +1,40 @@
1
+ class Step
2
+ def recursive_inputs
3
+ dependencies.inject(@inputs.annotate(@inputs.dup)) do |acc,dep|
4
+ acc.concat(dep.inputs) if dep.inputs
5
+ acc
6
+ end
7
+ end
8
+
9
+ def input_dependencies
10
+ return [] unless inputs
11
+ inputs.select do |d|
12
+ Step === d
13
+ end
14
+ end
15
+
16
+ def prepare_dependencies
17
+ inverse_dep = {}
18
+ dependencies.each{|dep|
19
+ next if dep.done?
20
+ if dep.dependencies
21
+ dep.dependencies.each do |d|
22
+ inverse_dep[d] ||= []
23
+ inverse_dep[d] << dep
24
+ end
25
+ end
26
+ input_dependencies.each do |d|
27
+ inverse_dep[d] ||= []
28
+ inverse_dep[d] << dep
29
+ end
30
+ }
31
+ inverse_dep.each do |dep,list|
32
+ dep.tee_copies = list.length
33
+ end
34
+ end
35
+
36
+ def run_dependencies
37
+ dependencies.each{|dep| dep.run unless dep.running? || dep.done? }
38
+ end
39
+
40
+ end
@@ -0,0 +1,15 @@
1
+ class Step
2
+ def files_dir
3
+ @files_dir ||= begin
4
+ dir = @path + ".files"
5
+ @path.annotate(dir) if Path === @path
6
+ dir
7
+ end
8
+ end
9
+
10
+ def file(file)
11
+ dir = files_dir
12
+ Path.setup(dir) unless Path === dir
13
+ dir[file]
14
+ end
15
+ end
@@ -1,5 +1,5 @@
1
1
  class Step
2
- SERIALIZER = :json
2
+ SERIALIZER = :marshal
3
3
  def info_file
4
4
  @info_file ||= begin
5
5
  info_file = @path + ".info"
@@ -20,7 +20,11 @@ class Step
20
20
  end
21
21
 
22
22
  def info
23
- outdated = @info && Open.exists?(info_file) && @info_load_time && Open.mtime(info_file) > @info_load_time
23
+ outdated = begin
24
+ @info_load_time && (mtime = Open.mtime(info_file)) && mtime > @info_load_time
25
+ rescue
26
+ true
27
+ end
24
28
 
25
29
  if @info.nil? || outdated
26
30
  load_info
@@ -33,6 +37,19 @@ class Step
33
37
  info = self.info
34
38
  new_info.each do |key,value|
35
39
  report_status new_info[:status], new_info[:message] if key == :status
40
+ if Exception === value
41
+ begin
42
+ Marshal.dump(value)
43
+ rescue TypeError
44
+ if ScoutException === value
45
+ new = ScoutException.new value.message
46
+ else
47
+ new = Exception.new value.message
48
+ end
49
+ new.set_backtrace(value.backtrace)
50
+ value = new
51
+ end
52
+ end
36
53
  if info.include?(key)
37
54
  case info[key]
38
55
  when Array
@@ -62,7 +79,6 @@ class Step
62
79
  end
63
80
 
64
81
  def log(status, message = nil)
65
- report_status status, message
66
82
  if message
67
83
  merge_info :status => status, :messages => [message]
68
84
  else
@@ -71,7 +87,18 @@ class Step
71
87
  end
72
88
 
73
89
  def status
74
- info[:status]
90
+ info[:status].tap{|s| s.nil? ? s : s.to_sym }
91
+ end
92
+
93
+ def error?
94
+ status == :error
75
95
  end
76
96
 
97
+ def aborted?
98
+ status == :aborted
99
+ end
100
+
101
+ def running?
102
+ ! done? && (info[:pid] && Misc.pid_alive?(info[:pid]))
103
+ end
77
104
  end