scout-gear 7.1.0 → 7.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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