simplews 1.1.6 → 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,26 @@
1
+ == 1.3.1 2008-12-20
2
+
3
+ * 1 minor enhancement:
4
+ * Use Marshal instead of YAML, it seems faster...
5
+
6
+ * 1 mayor enhancement:
7
+ * Removed old versions of job based servers
8
+
9
+ == 1.3.0 2008-12-20
10
+
11
+ * 1 minor enhancement:
12
+ * New version for asynchronous job. In this case it uses fork to spawn new
13
+ processes.
14
+
15
+
16
+
17
+ == 1.1.7 2008-11-27
18
+
19
+ * 2 minor enhancement:
20
+ * Inline methods are prepended '_inline_' to avoid polluting namespace
21
+ * Asynchronous jobs have a hash where they can save information, it is
22
+ accessible through the 'info' WS method in YAML format
23
+
1
24
  == 1.1.6 2008-11-24
2
25
 
3
26
  * 1 minor enhancement:
data/Manifest.txt CHANGED
@@ -1,12 +1,12 @@
1
1
  History.txt
2
2
  Manifest.txt
3
3
  PostInstall.txt
4
- README.rdoc
4
+ README.txt
5
5
  Rakefile
6
6
  config/hoe.rb
7
7
  config/requirements.rb
8
8
  lib/simplews.rb
9
- lib/simplews/asynchronous.rb
9
+ lib/simplews/jobs.rb
10
10
  lib/simplews/version.rb
11
11
  script/console
12
12
  script/destroy
@@ -16,7 +16,7 @@ setup.rb
16
16
  tasks/deployment.rake
17
17
  tasks/environment.rake
18
18
  tasks/website.rake
19
- test/simplews/test_asynchronous.rb
19
+ test/simplews/test_jobs.rb
20
20
  test/test_helper.rb
21
21
  test/test_simplews.rb
22
22
  website/index.html
@@ -0,0 +1,405 @@
1
+ require File.join(File.dirname(File.dirname(__FILE__)) + '/simplews')
2
+
3
+ require 'yaml'
4
+ require 'drb'
5
+ require 'singleton'
6
+ require 'rand'
7
+
8
+
9
+ class SimpleWS::Jobs < SimpleWS
10
+ class JobNotFound < Exception; end
11
+ class ResultNotFound < Exception; end
12
+ class Aborted < Exception; end
13
+
14
+
15
+ #{{{ Scheduler
16
+
17
+ module Scheduler
18
+ include Process
19
+
20
+ @@task_results = {}
21
+
22
+ @@names = []
23
+ @@pids = {}
24
+
25
+ @@queue = []
26
+ @@max_jobs = 3
27
+
28
+ def self.random_name(s="", num=20)
29
+ num.times{
30
+ r = rand
31
+ if r < 0.3
32
+ s << (rand * 10).to_i.to_s
33
+ elsif r < 0.6
34
+ s << (rand * (?z - ?a) + ?a).to_i.chr
35
+ else
36
+ s << (rand * (?Z - ?A) + ?A).to_i.chr
37
+ end
38
+ }
39
+ s.to_s
40
+ end
41
+
42
+ def self.make_name(name = "")
43
+ name = Scheduler::random_name("job-") unless name =~ /\w/
44
+
45
+ taken = @@names.select{|n| n =~ /^#{ name }(-\d+)?$/}
46
+ if taken.any?
47
+ if taken.length == 1
48
+ return name + '-1'
49
+ else
50
+ last = taken.collect{|s|
51
+ if s.match(/-(\d+)$/)
52
+ $1.to_i
53
+ else 0
54
+ end
55
+ }.sort.last
56
+ return name + '-' + (last + 1).to_s
57
+ end
58
+ else
59
+ return name
60
+ end
61
+ end
62
+
63
+ def self.configure(name, value)
64
+ Job::configure(name, value)
65
+ end
66
+
67
+ def self.helper(name, block)
68
+ Job.send :define_method, name, block
69
+ end
70
+
71
+ def self.task(name, results, block)
72
+ @@task_results[name] = results
73
+ Job.send :define_method, name, block
74
+ end
75
+
76
+ def self.dequeue
77
+ if @@pids.length < @@max_jobs && @@queue.any?
78
+ job_info = @@queue.pop
79
+
80
+ pid = Job.new.run(job_info[:task], job_info[:name], @@task_results[job_info[:task]], *job_info[:args])
81
+ @@pids[job_info[:name]] = pid
82
+ pid
83
+ else
84
+ nil
85
+ end
86
+ end
87
+
88
+ def self.run(task, *args)
89
+ suggested_name = *args.pop
90
+ name = make_name(suggested_name)
91
+ @@names << name
92
+
93
+ @@queue.push( {:name => name, :task => task, :args => args})
94
+ state = {
95
+ :name => name,
96
+ :status => :queued,
97
+ :messages => [],
98
+ :info => {},
99
+ }
100
+ Job.save(name,state)
101
+
102
+ name
103
+ end
104
+
105
+ def self.clean_job(pid)
106
+ name = @@pids.select{|name, p| p == pid}.first[0]
107
+ puts "Process #{ name } with pid #{ pid } finished with exitstatus #{$?.exitstatus}"
108
+ state = Job.job_info(name)
109
+ if ![:error, :done, :aborted].include?(state[:status])
110
+ state[:status] = :error
111
+ state[:messages] << "Job finished for unknown reasons"
112
+ Job.save(name, state)
113
+ end
114
+ @@pids.delete(name)
115
+ end
116
+
117
+ def self.job_monitor
118
+ Thread.new{
119
+ while true
120
+ pid = dequeue
121
+ if pid
122
+ Thread.new(pid){|pid|
123
+ begin
124
+ pid = Process.wait(-1,Process::WUNTRACED) if @@pids.any?
125
+ clean_job(pid) if @@pids.values.include? pid
126
+ rescue Exception
127
+ puts $!.message
128
+ end
129
+ }
130
+ end
131
+ sleep 2
132
+ end
133
+ }
134
+ end
135
+
136
+ def self.abort(name)
137
+ Process.kill "INT", @@pids[name]
138
+ end
139
+
140
+ def self.abort_jobs
141
+ @@pids.values{|pid|
142
+ Process.kill "INT", pid
143
+ }
144
+ end
145
+
146
+ def self.job_info(name)
147
+ Job.job_info(name)
148
+ end
149
+
150
+ def self.workdir=(workdir)
151
+ Job.workdir = workdir
152
+ end
153
+
154
+ def self.job_results(name)
155
+ Job.results(name)
156
+ end
157
+
158
+ #{{{ Job
159
+
160
+ class Job
161
+ def self.workdir=(workdir)
162
+ @@workdir = workdir
163
+ @@savedir = File.join(@@workdir, '.save')
164
+ FileUtils.mkdir_p @@workdir unless File.exist? @@workdir
165
+ FileUtils.mkdir_p @@savedir unless File.exist? @@savedir
166
+ end
167
+
168
+ def self.path(file, name)
169
+ if file =~ /^\//
170
+ file.gsub(/\{JOB\}/)
171
+ else
172
+ File.join(@@workdir, file.gsub(/\{JOB\}/,name))
173
+ end
174
+ end
175
+
176
+ def self.save(name, state)
177
+ fout = File.open(File.join(@@savedir,name + '.marshal'),'w')
178
+ fout.write Marshal::dump(state)
179
+ fout.close
180
+ end
181
+
182
+ def self.job_info(name)
183
+ info = nil
184
+ begin
185
+ sleep 1
186
+ info = Marshal::load(File.open(File.join(@@savedir,name + '.marshal')))
187
+ raise Exception unless info.is_a? Hash
188
+ rescue Exception
189
+ raise JobNotFound, "Job with name #{ name } was not found"
190
+ end
191
+
192
+ info
193
+ end
194
+
195
+ def self.results(name)
196
+ job_info(name)[:results].collect{|file|
197
+ code = Scheduler.random_name("res-")
198
+ [code, file]
199
+ }
200
+ end
201
+
202
+ @@config = {}
203
+ def self.configure(name, value)
204
+ @@config[name] = value
205
+ end
206
+
207
+ def workdir
208
+ @@workdir
209
+ end
210
+
211
+ def config
212
+ @@config
213
+ end
214
+
215
+ def path(file)
216
+ Job.path(file, @name)
217
+ end
218
+
219
+ def save
220
+ Job.save(@name, @state)
221
+ end
222
+
223
+ def write(file, contents)
224
+ path = Job.path(file, @name)
225
+ fout = File.open(path,'w')
226
+ fout.write contents
227
+ fout.close
228
+ end
229
+
230
+ def step(status, message = nil)
231
+ @state[:status] = status
232
+ @state[:messages] << message if message && message != ""
233
+ save
234
+ end
235
+
236
+ def error(message = nil)
237
+ step(:error, message)
238
+ save
239
+ end
240
+
241
+ def info(info)
242
+ @state[:info].merge!(info)
243
+ save
244
+ end
245
+
246
+ def results(results)
247
+ @state[:results] = results.collect{|file| path(file)}
248
+ save
249
+ end
250
+
251
+ def abort
252
+ raise SimpleWS::Jobs::Aborted
253
+ save
254
+ end
255
+
256
+ def job_name
257
+ @name
258
+ end
259
+
260
+ def run(task, name, results, *args)
261
+ @name = name
262
+ @state = {
263
+ :name => @name,
264
+ :status => :prepared,
265
+ :messages => [],
266
+ :info => {},
267
+ :results => results.collect{|file| path(file)},
268
+ }
269
+ save
270
+ @pid = Process.fork do
271
+ begin
272
+ trap(:INT) { raise SimpleWS::Jobs::Aborted }
273
+ self.send task, *args
274
+ step :done
275
+ exit(0)
276
+ rescue SimpleWS::Jobs::Aborted
277
+ step(:aborted)
278
+ exit(-1)
279
+ rescue Exception
280
+ if !$!.kind_of? SystemExit
281
+ error($!.message)
282
+ puts "Error in job #{ @name }"
283
+ puts $!.message
284
+ puts $!.backtrace
285
+ exit(-1)
286
+ end
287
+ end
288
+ end
289
+
290
+ @pid
291
+ end
292
+ end
293
+ end
294
+
295
+
296
+ def self.helper(name, &block)
297
+ Scheduler.helper name, block
298
+ end
299
+
300
+ def helper(name,&block)
301
+ Scheduler.helper name, block
302
+ end
303
+
304
+ def self.configure(name, value)
305
+ Scheduler.configure(name, value)
306
+ end
307
+
308
+ def configure(name, value)
309
+ self.class.configure(name, value)
310
+ end
311
+
312
+ def task(name, params=[], types={}, results = [], &block)
313
+ Scheduler.task name, results, block
314
+ serve name.to_s, params + ['suggested_name'], types.merge(:suggested_name => 'string', :return => :string) do |*args|
315
+ Scheduler.run name, *args
316
+ end
317
+ end
318
+
319
+ @@tasks = {}
320
+ def self.task(name, params=[], types={}, results =[], &block)
321
+ Scheduler.task name, results, block
322
+ @@tasks[name] = {:params => params, :types => types};
323
+ end
324
+
325
+ def abort_jobs
326
+ Scheduler.abort_jobs
327
+ end
328
+
329
+
330
+ def workdir
331
+ @workdir
332
+ end
333
+
334
+ def initialize(name, description, host, port, workdir = nil, *args)
335
+ super(name, description, host, port, *args)
336
+
337
+ @workdir = workdir || "/tmp/#{ name }"
338
+ Scheduler.workdir = @workdir
339
+ @results = {}
340
+ @@tasks.each{|task, values|
341
+ serve task.to_s, values[:params] + ['suggested_name'], values[:types].merge(:suggested_name => 'string', :return => :string) do |*args|
342
+ Scheduler.run task, *args
343
+ end
344
+ }
345
+
346
+ serve :status, ['job'], :job => :string, :return => :string do |job|
347
+ Scheduler.job_info(job)[:status].to_s
348
+ end
349
+
350
+ serve :messages, ['job'], :job => :string, :return => :array do |job|
351
+ Scheduler.job_info(job)[:messages]
352
+ end
353
+
354
+ serve :info, ['job'], :job => :string, :return => :string do |job|
355
+ Scheduler.job_info(job)[:info].to_yaml
356
+ end
357
+
358
+ serve :abort, %w(job), :job => :string do |job|
359
+ Scheduler.abort(job)
360
+ end
361
+
362
+ serve :done, %w(job), :job => :string, :return => :boolean do |job|
363
+ [:done, :error, :aborted].include? Scheduler.job_info(job)[:status]
364
+ end
365
+
366
+ serve :error, %w(job), :job => :string, :return => :boolean do |job|
367
+ Scheduler.job_info(job)[:status] == :error
368
+ end
369
+
370
+ serve :aborted, %w(job), :job => :string, :return => :boolean do |job|
371
+ Scheduler.job_info(job)[:status] == :aborted
372
+ end
373
+
374
+ serve :results, %w(job), :return => :array do |job|
375
+ results = Scheduler.job_results(job)
376
+ @results.merge! Hash[*results.flatten]
377
+ results.collect{|p| p[0]}
378
+ end
379
+
380
+ serve :result, %w(result), :return => :binary do |result|
381
+ path = @results[result]
382
+ raise ResultNotFound unless File.exist? path
383
+ File.open(path).read
384
+ end
385
+
386
+ end
387
+
388
+ alias_method :old_start, :start
389
+ def start(*args)
390
+ Scheduler.job_monitor
391
+ old_start(*args)
392
+ end
393
+
394
+ alias_method :old_shutdown, :shutdown
395
+ def shutdown(*args)
396
+ Scheduler.abort_jobs
397
+ old_shutdown(*args)
398
+ end
399
+
400
+
401
+
402
+ end
403
+
404
+
405
+
@@ -1,8 +1,8 @@
1
1
  module Simplews
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 1
4
- MINOR = 1
5
- TINY = 6
4
+ MINOR = 3
5
+ TINY = 1
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  self
data/lib/simplews.rb CHANGED
@@ -109,11 +109,14 @@ class SimpleWS < SOAP::RPC::StandaloneServer
109
109
  def serve(name, args=[], types={}, &block)
110
110
 
111
111
  if block
112
- add_to_ruby(name, &block)
112
+ inline_name = "_inline_" + name.to_s
113
+ add_to_ruby(inline_name, &block)
114
+ add_method_as(self,inline_name, name.to_s,*args)
115
+ else
116
+ add_method(self,name.to_s,*args)
113
117
  end
114
118
 
115
119
  add_to_wsdl(name, args, types)
116
- add_method(self,name.to_s,*args)
117
120
 
118
121
  nil
119
122
  end
@@ -0,0 +1,93 @@
1
+ require File.dirname(File.dirname(__FILE__)) + '/test_helper.rb'
2
+
3
+
4
+ class TestJobs < Test::Unit::TestCase
5
+ class TestJWS < SimpleWS::Jobs
6
+
7
+ task :process,[],{},['test.txt'] do
8
+ begin
9
+ info(:steps => 3)
10
+ write('test.txt', job_name)
11
+ step(:init)
12
+ sleep 2
13
+ step(:step1, "Step1")
14
+ sleep 2
15
+ step(:step2, "Step2")
16
+ sleep 2
17
+ step(:step3, "Step3")
18
+ rescue
19
+ error($!.message)
20
+ end
21
+ end
22
+ end
23
+
24
+
25
+ def setup
26
+ @server = TestJWS.new("TestJWS", "Asynchronous Job Server", 'localhost', '1984', "tmp-TestJWS")
27
+
28
+ Thread.new do
29
+ @server.start
30
+ end
31
+
32
+ end
33
+
34
+ def teardown
35
+ @server.shutdown
36
+ end
37
+
38
+ def test_client
39
+
40
+ require 'soap/wsdlDriver'
41
+
42
+ driver = SimpleWS.get_driver('http://localhost:1984', 'TestJWS')
43
+
44
+
45
+ name = driver.process("test")
46
+ puts "Job name #{ name }"
47
+
48
+ while !driver.done(name)
49
+ puts "status: " + driver.status(name)
50
+ sleep 1
51
+ end
52
+
53
+ assert_equal(3, YAML.load(driver.info(name))[:steps])
54
+ assert(!driver.error(name))
55
+ assert(driver.messages(name).include? "Step3")
56
+ assert(driver.results(name).length == 1)
57
+ result = driver.results(name).first
58
+ assert_match(name, driver.result(result))
59
+
60
+ name = driver.process("test-abort")
61
+ sleep 2
62
+ puts "status: " + driver.status(name)
63
+ driver.abort(name)
64
+ sleep 2
65
+ puts driver.status(name)
66
+ puts driver.messages(name)
67
+ assert(driver.aborted(name))
68
+
69
+ FileUtils.cp "tmp-TestJWS/.save/test-abort.yaml", "tmp-TestJWS/.save/copy.yaml"
70
+ assert(driver.aborted("copy"))
71
+
72
+ # Test queue
73
+ threads = []
74
+ 10.times {
75
+ threads << Thread.new{
76
+ test_name = driver.process("test-queue")
77
+ puts "Starting #{ test_name }"
78
+ while !driver.done(test_name)
79
+ puts "#{ test_name }: " + driver.status(test_name)
80
+ sleep 2
81
+ end
82
+ }
83
+ sleep 2
84
+ }
85
+
86
+ puts "Threads " + threads.length.to_s
87
+ threads.each{|t| t.join}
88
+ sleep 10
89
+
90
+
91
+ end
92
+
93
+ end
data/test/test_helper.rb CHANGED
@@ -1,3 +1,5 @@
1
1
  require 'test/unit'
2
2
  require File.dirname(__FILE__) + '/../lib/simplews'
3
3
  require File.dirname(__FILE__) + '/../lib/simplews/asynchronous'
4
+ require File.dirname(__FILE__) + '/../lib/simplews/forked'
5
+ require File.dirname(__FILE__) + '/../lib/simplews/jobs'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simplews
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.6
4
+ version: 1.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Miguel Vazquez
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-11-24 00:00:00 +01:00
12
+ date: 2009-01-14 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -20,7 +20,7 @@ dependencies:
20
20
  requirements:
21
21
  - - ">="
22
22
  - !ruby/object:Gem::Version
23
- version: 1.7.0
23
+ version: 1.8.0
24
24
  version:
25
25
  description: Simplifies the development of Web Services, producing WSDL automatically for example. Wraps soap4r.
26
26
  email:
@@ -33,17 +33,18 @@ extra_rdoc_files:
33
33
  - History.txt
34
34
  - Manifest.txt
35
35
  - PostInstall.txt
36
+ - README.txt
36
37
  - website/index.txt
37
38
  files:
38
39
  - History.txt
39
40
  - Manifest.txt
40
41
  - PostInstall.txt
41
- - README.rdoc
42
+ - README.txt
42
43
  - Rakefile
43
44
  - config/hoe.rb
44
45
  - config/requirements.rb
45
46
  - lib/simplews.rb
46
- - lib/simplews/asynchronous.rb
47
+ - lib/simplews/jobs.rb
47
48
  - lib/simplews/version.rb
48
49
  - script/console
49
50
  - script/destroy
@@ -53,7 +54,7 @@ files:
53
54
  - tasks/deployment.rake
54
55
  - tasks/environment.rake
55
56
  - tasks/website.rake
56
- - test/simplews/test_asynchronous.rb
57
+ - test/simplews/test_jobs.rb
57
58
  - test/test_helper.rb
58
59
  - test/test_simplews.rb
59
60
  - website/index.html
@@ -91,11 +92,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
91
92
  requirements: []
92
93
 
93
94
  rubyforge_project: simplews
94
- rubygems_version: 1.2.0
95
+ rubygems_version: 1.3.1
95
96
  signing_key:
96
97
  specification_version: 2
97
98
  summary: Simplifies the development of Web Services, producing WSDL automatically for example. Wraps soap4r.
98
99
  test_files:
99
100
  - test/test_simplews.rb
100
101
  - test/test_helper.rb
101
- - test/simplews/test_asynchronous.rb
102
+ - test/simplews/test_jobs.rb
@@ -1,502 +0,0 @@
1
- require File.join(File.dirname(File.dirname(__FILE__)) + '/simplews')
2
-
3
- require 'yaml'
4
-
5
- # This class extends SimpleWS and allows provides with a framework to
6
- # run asynchronous jobs. Job are initiated and can be latter be accessed
7
- # using a unique job id. The status can be queried, and when finished,
8
- # the results can be gathered. Jobs are also able to save their states
9
- # between executions of the server.
10
- #
11
- # The class provides a instance function start_job, that receives a
12
- # block of code and creates a new thread to run this block of code. It
13
- # also provides some helper functions to set the state of the job, as
14
- # well as messages or to abort of report errors.
15
- #
16
- # Each job can produce a number of results saved into files that can
17
- # latter be accessed by the client. At any time the code inside the
18
- # block can access any of the following helper functions: step, read,
19
- # write, abort_process, job_name and save. As in the following example.
20
- # (+process+ will be a method served by the server):
21
- #
22
- # def process(name)
23
- # start_job(name, %w(test.txt)) do
24
- # begin
25
- # write('test.txt', job_name)
26
- # step(:init)
27
- # sleep 2
28
- # step(:step1, "Step1")
29
- # sleep 2
30
- # step(:step2, "Step2")
31
- # sleep 2
32
- # step(:step2, "Step3")
33
- # rescue
34
- # error($!.message)
35
- # end
36
- # end
37
- # end
38
- #
39
- # Note that any file specified in the write or read method is relative
40
- # to the workdir directory. The optional array passed as second argument
41
- # to start_job lists the results the job will be generating and that
42
- # will be accessible latter to the clients, one could have also done
43
- # this:
44
- #
45
- # def process(name)
46
- # actual_name = start_job(name) do
47
- # begin
48
- # write(job_name + ".txt", "Hi")
49
- # step(:init)
50
- # sleep 2
51
- # step(:step1, "Step1")
52
- # sleep 2
53
- # step(:step2, "Step2")
54
- # sleep 2
55
- # step(:step2, "Step3")
56
- # rescue
57
- # error($!.message)
58
- # end
59
- # end
60
- #
61
- # set_results(actual_name, [ actual_name + ".txt" ])
62
- #
63
- # actual_name
64
- # end
65
- #
66
- # Note in this example that the name provided to start_job is only a
67
- # suggestion, it a job is already available with that name a number will
68
- # be prepended to it. The actual name used is return by the start_job
69
- # method and is the one that must be used in the set_results method.
70
- # Also note that the +process+ method in the example has to return the
71
- # actual name of the job so it will be accessible to the client.
72
- #
73
- #
74
- # The clients have a number of methods available to query and modify the
75
- # state of the job, as well as to gather results: status, messages,
76
- # done, error, aborted, abort, results and result.
77
- #
78
- #
79
- # While reading the documentation please note that there are three types
80
- # of methods here, methods used inside the web server methods, methods
81
- # used inside code blocks executed by Job Threads, and methods that are
82
- # exported by the web server. Its important to call each method in the
83
- # right context.
84
- #
85
- class SimpleWS::Asynchronous < SimpleWS
86
-
87
-
88
- class JobNotFound < Exception; end
89
- class JobAborted < Exception; end
90
-
91
-
92
- class Job < Thread # :nodoc:
93
-
94
- private
95
-
96
- def self.random_name(s="", num=20)
97
- num.times{
98
- r = rand
99
- if r < 0.3
100
- s << (rand * 10).to_i.to_s
101
- elsif r < 0.6
102
- s << (rand * (?z - ?a) + ?a).to_i.chr
103
- else
104
- s << (rand * (?Z - ?A) + ?A).to_i.chr
105
- end
106
- }
107
- s.to_s
108
- end
109
-
110
- def self.make_name(name)
111
-
112
- name = Job::random_name("job-") if name == ""
113
-
114
- taken = @@jobs.keys.select{|n| n =~ /^#{ name }(-\d+)?$/}
115
- if taken.any?
116
- last = taken.sort.last
117
- if last == name
118
- return name + '-1'
119
- else
120
- num = last.match(/-(\d+)$/)[1]
121
- return name + '-' + (num.to_i + 1).to_s
122
- end
123
- else
124
- return name
125
- end
126
- end
127
-
128
- public
129
-
130
- # Returns the job specified in the +name+. If it is not currently
131
- # found but it has a saved state, then it is loaded.
132
- def self.job(name)
133
- if @@jobs[name].nil?
134
- if File.exists?(File.join(@@workdir, ".save/#{name}.yaml"))
135
- job = Job.new(name, [])
136
- job.load
137
- @@jobs[name] = job
138
- else
139
- raise SimpleWS::Asynchronous::JobNotFound, "Job with name '#{ name }' was not found"
140
- end
141
- end
142
- @@jobs[name]
143
- end
144
-
145
- def self.start(name, results, &block)
146
- job = Job.new(name, results, &block)
147
- @@jobs[job.name] = job
148
- job.name
149
- end
150
-
151
- def self.read(name, path)
152
- Job::job(name).read(path)
153
- end
154
-
155
-
156
- @@workdir = nil
157
- @@jobs = {}
158
-
159
- def self.workdir
160
- @@workdir
161
- end
162
-
163
- def self.workdir=(workdir)
164
- @@workdir = workdir
165
- begin
166
- FileUtils.mkdir_p(@@workdir) unless File.exist?( @@workdir)
167
- FileUtils.mkdir(File.join(@@workdir,'.save')) unless File.exist?( File.join(@@workdir,'.save'))
168
- rescue
169
- puts "Error creating work directory:"
170
- raise $!
171
- end
172
- end
173
-
174
- attr_reader :messages, :status, :name
175
- def initialize(name, results, &block)
176
- @status = ''
177
- @messages = []
178
- @name = Job::make_name(name)
179
- @results = results
180
- @results_hash = {}
181
-
182
- if block_given?
183
- super do
184
- begin
185
- block.call
186
- step(:done)
187
-
188
- rescue SimpleWS::Asynchronous::JobAborted
189
- step(:aborted, $!.message)
190
- rescue Exception
191
- error($!.message)
192
- raise $!
193
- ensure
194
- save
195
- Thread.exit
196
- end
197
- end
198
- else
199
- super do end
200
- end
201
- end
202
-
203
- def save
204
- info = {
205
- :status => @status,
206
- :messages => @messages,
207
- :name => @name,
208
- :results => @results,
209
- :results_hash => @results_hash,
210
- }
211
-
212
- fout = File.open(File.join(@@workdir, ".save/#{name}.yaml"),'w')
213
- fout.write info.to_yaml
214
- fout.close
215
- end
216
-
217
- def load
218
- info = YAML::load(File.open(File.join(@@workdir, ".save/#{@name}.yaml")))
219
- @status = info[:status]
220
- @messages = info[:messages]
221
- @name = info[:name]
222
- @results = info[:results]
223
- @results_hash = info[:results_hash]
224
- end
225
-
226
- #{{{ Instance methods
227
- def name
228
- @name
229
- end
230
-
231
- def step(status, message= nil)
232
- @status = status
233
- @messages << message if message && message != ""
234
- end
235
-
236
- def read(path)
237
- File.open(File.join(@@workdir, path)).read
238
- end
239
-
240
- def write(path, content)
241
- f = File.open(File.join(@@workdir, path),'w')
242
- f.write(content)
243
- f.close
244
- end
245
-
246
- #{{{ Results Related
247
- def results=(list)
248
- @results = list
249
- end
250
-
251
- def results
252
- @results.collect{|res|
253
- res_name = Job.random_name("result-")
254
- @results_hash[res_name] = res
255
- res_name
256
- }
257
- end
258
-
259
- def result(res_name)
260
- raise ArgumentError, "Result #{ res_name } not found" if @results_hash[res_name].nil?
261
- File.open(File.join(@@workdir, @results_hash[res_name])).read
262
- end
263
-
264
- #{{{ Status related
265
- def status
266
- @status.to_s
267
- end
268
-
269
- def done?
270
- @status.to_sym == :done || @status.to_sym == :error || @status.to_sym == :aborted
271
- end
272
-
273
- def error?
274
- @status.to_sym == :error
275
- end
276
-
277
- def error(message)
278
- step(:error, message)
279
- end
280
-
281
- def abort
282
- raise SimpleWS::Asynchronous::JobAborted, "Aborted"
283
- nil
284
- end
285
-
286
- def aborted?
287
- @status.to_sym == :aborted
288
- end
289
-
290
-
291
- end
292
-
293
- ############################################################################################3
294
- #{{{ Server
295
-
296
- # SERVER METHOD:
297
- #
298
- # Sets the workdir for the jobs
299
- def set_workdir(workdir)
300
- Job::workdir = workdir
301
- end
302
-
303
- # SERVER METHOD:
304
- #
305
- # Returns the workdir that any jobs created will be using
306
- def workdir
307
- Job::workdir
308
- end
309
-
310
- # SERVER METHOD:
311
- #
312
- # Starts a new job. The parameter +name+ is just a suggestion, if it
313
- # is not available a number will be prepended to it. Results are a
314
- # list of relative paths to files where results are expected to be
315
- # stored. All paths are relative to the jobs workdir. Finally, block
316
- # is the block of code to be executed by the Job Thread.
317
- #
318
- # Returns the actual name used as unique identifier for the job
319
- def start_job(name, results = [], &block)
320
- Job.start(name, results, &block)
321
- end
322
-
323
- # SERVER METHOD:
324
- #
325
- # Sets the list of paths to results for a particular job. This
326
- # overwrites the ones specified by start_job.
327
- def set_results(name, results)
328
- Job.job(name).results = results
329
- end
330
-
331
-
332
-
333
- #{{{ Thread helper methods
334
-
335
-
336
- # CODE HELPER FUNCTION:
337
- #
338
- # Saves the job to it can survive server restarts. This is
339
- # automatically called when the code is finished.
340
- def save
341
- Thread.current.save
342
- end
343
-
344
- # CODE HELPER FUNCTION:
345
- #
346
- # Writes +content+ into the file specified by +path+. The +path+`is
347
- # taken relative to the job's workdir.
348
- def write(path, content)
349
- Thread.current.write(path, content)
350
- end
351
-
352
- # CODE HELPER FUNCTION:
353
- #
354
- # Reads the content of the file specified by +path+ relative to the
355
- # job's workdir.
356
- def read(path)
357
- Thread.current.read(path)
358
- end
359
-
360
-
361
- # CODE HELPER FUNCTION:
362
- #
363
- # Sets the status of the job and adds a message to the message queue
364
- # if the message is specified, and is different from an empty string.
365
- # This method is called automatically in the following situations.
366
- #
367
- # * The block has finished executing:
368
- # step(:done)
369
- # * The job recieves an untreated SimpleWS::Asynchronous::JobAborted exception
370
- # step(:aborted, $!.message)
371
- # * The job recieves any other exception
372
- # step(:error, $!.message)
373
- #
374
- def step(status, message=nil)
375
- Thread.current.step(status, message)
376
- end
377
-
378
-
379
- # CODE HELPER FUNCTION:
380
- #
381
- # Returns the actual name of the job running the code.
382
- def job_name
383
- Thread.current.name
384
- end
385
-
386
- # CODE HELPER FUNCTION:
387
- #
388
- # Aborts the job raising a SimpleWS::Asynchronous::JobAborted
389
- # exception.
390
- def abort_process
391
- Thread.current.abort
392
- end
393
-
394
-
395
- #{{{ WS Methods
396
-
397
- # CLIENT METHOD SERVED BY DEFAULT:
398
- #
399
- # +name+ is the unique job identifier.
400
- #
401
- # Returns job status of the job.
402
- def status(name)
403
- Job.job(name).status
404
- end
405
-
406
- # CLIENT METHOD SERVED BY DEFAULT:
407
- #
408
- # +name+ is the unique job identifier.
409
- #
410
- # Raises a SimpleWS::Asynchronous::JobAborted exception in the job
411
- def abort(name)
412
- Job.job(name).abort
413
- end
414
-
415
- # CLIENT METHOD SERVED BY DEFAULT:
416
- #
417
- # +name+ is the unique job identifier.
418
- #
419
- # Checks if the job is still running or has finished, either normally
420
- # or after been aborted or suffering an error.
421
- def done(name)
422
- Job.job(name).done?
423
- end
424
-
425
- # CLIENT METHOD SERVED BY DEFAULT:
426
- #
427
- # +name+ is the unique job identifier.
428
- #
429
- # Checks if the job has been aborted.
430
- def aborted(name)
431
- Job.job(name).aborted?
432
- end
433
-
434
- # CLIENT METHOD SERVED BY DEFAULT:
435
- #
436
- # +name+ is the unique job identifier.
437
- #
438
- # Checks if the job has suffered an error
439
- def error(name)
440
- Job.job(name).error?
441
- end
442
-
443
- # CLIENT METHOD SERVED BY DEFAULT:
444
- #
445
- # +name+ is the unique job identifier.
446
- #
447
- # Returns the list of messages of the job.
448
- def messages(name)
449
- Job.job(name).messages
450
- end
451
-
452
- # CLIENT METHOD SERVED BY DEFAULT:
453
- #
454
- # +name+ is the unique job identifier.
455
- #
456
- # Returns a list of unique results identifiers, one for each of the
457
- # results of the job.
458
- def results(name)
459
- Job.job(name).results
460
- end
461
-
462
- # CLIENT METHOD SERVED BY DEFAULT:
463
- #
464
- # +name+ is the unique job identifier.
465
- # +result+ is the unique result identifier as returned by the results
466
- # method.
467
- #
468
- # Retrieves a result of a given job.
469
- def result(name, result)
470
- Job.job(name).result(result)
471
- end
472
-
473
-
474
- # Creates an instance. All the parameters are as in SimpleWS, except
475
- # for the additional workdir parameter. It the workdir is not provided
476
- # a directory in the /tmp directory with the name of the server will
477
- # be used.
478
- #
479
- # It serves by default the methods: abort, messages, status, done,
480
- # error, abort, aborted, results and result, as well as the +wsdl+
481
- # method from SimpleWS.
482
- def initialize(name="WS", description="", host="localhost", port="1984", workdir=nil, *args)
483
- super(name, description, host, port, *args)
484
- @results = {}
485
-
486
- workdir ||= "/tmp/#{ name }"
487
- set_workdir(workdir)
488
-
489
- serve :abort, %w(name), :name => :string
490
-
491
- serve :messages, %w(name), :name => :string, :return => :string
492
-
493
- serve :status, %w(name), :name => :string, :return => :string
494
- serve :done, %w(name), :name => :string, :return => :boolean
495
- serve :error, %w(name), :name => :string, :return => :boolean
496
- serve :aborted, %w(name), :name => :string, :return => :boolean
497
-
498
- serve :results, %w(name), :return => :array
499
- serve :result, %w(name result), :return => :binary
500
- end
501
-
502
- end
@@ -1,88 +0,0 @@
1
- require File.dirname(File.dirname(__FILE__)) + '/test_helper.rb'
2
-
3
- class TestSimpleWS < Test::Unit::TestCase
4
-
5
- class TestAWS < SimpleWS::Asynchronous
6
-
7
- def text
8
- "Hi"
9
- end
10
-
11
- def process(name)
12
- actual_name = start_job(name, %w(test.txt)) do
13
- begin
14
- write('test.txt', job_name)
15
- step(:init)
16
- sleep 2
17
- step(:step1, "Step1")
18
- sleep 2
19
- step(:step2, "Step2")
20
- sleep 2
21
- step(:step2, "Step3")
22
- rescue
23
- error($!.message)
24
- end
25
- end
26
-
27
- actual_name
28
- end
29
-
30
- def initialize(*args)
31
- super(*args)
32
- serve :process, %w(name), :name => :string, :return => :string
33
- end
34
- end
35
-
36
-
37
- def setup
38
- server = TestAWS.new("TestAWS", "Asynchronous Job Server", 'localhost', '1984')
39
- server.set_workdir("tmp-TestAWS")
40
-
41
- Thread.new do
42
- server.start
43
- end
44
-
45
- end
46
-
47
- def test_client
48
-
49
- require 'soap/wsdlDriver'
50
-
51
- driver = SimpleWS.get_driver('http://localhost:1984', 'TestAWS')
52
-
53
- name = driver.process("")
54
-
55
-
56
-
57
- puts "Job name #{ name }"
58
-
59
- while !driver.done(name)
60
- puts "status: " + driver.status(name)
61
- sleep 1
62
- end
63
-
64
- assert(!driver.error(name))
65
- assert(driver.messages(name).include? "Step3")
66
- assert(driver.results(name).length == 1)
67
-
68
- result = driver.results(name).first
69
- assert_match(name, driver.result(name, result))
70
-
71
-
72
- name = driver.process("test-AWS")
73
- sleep 2
74
- puts "status: " + driver.status(name)
75
- driver.abort(name)
76
- sleep 2
77
- puts driver.status(name)
78
- assert(driver.aborted(name))
79
-
80
- FileUtils.cp "tmp-TestAWS/.save/test-AWS.yaml", "tmp-TestAWS/.save/copy-AWS.yaml"
81
- assert(driver.aborted("copy-AWS"))
82
-
83
-
84
- end
85
-
86
-
87
-
88
- end
File without changes