simplews 1.1.6 → 1.3.1

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.
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