parallelpipes 1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (2) hide show
  1. data/lib/parallelpipes.rb +2322 -0
  2. metadata +53 -0
@@ -0,0 +1,2322 @@
1
+ # = Parallel Pipes
2
+ #
3
+ # <b> Genuine and easy parallelization comes to Ruby! </b>
4
+ #
5
+ # Quick Links: PPipe, PPipe::Methods, PPipe::Controller, PPipe::Message, PPipe::Log, PPipe::Reader
6
+ #
7
+ # To install: <tt> gem install parallelpipes </tt>
8
+ #
9
+ # = Overview
10
+ #
11
+ # Parallel Pipes is a simple, easy to use, MPI-like implentation for Ruby, allowing you to
12
+ # create and send messages between multiple ruby processes and allowing your code to run on
13
+ # multiple processors.
14
+ #
15
+ # Requires Ruby 1.9 +
16
+ #
17
+ # Written by Edmund Highcock (Copyright 2009-10)
18
+ #
19
+ # Current status: Production/Stable
20
+ #
21
+ # Version: 0.1.0
22
+ #
23
+ # This is free software released under the GNU GPL. It comes with no warranty.
24
+ # You are free to modify and release this software, as long as due credit is given to the authors.
25
+ #
26
+ #
27
+ # = Introduction
28
+ # A PPipe (Parallel Pipe) is a communicator, which can create and pass messages between a number of processes.
29
+ #
30
+ # These processes are separate operating system processes; thus, Parallel Pipes enables genuine (and easy) parallelization in Ruby Programs.
31
+ #
32
+ #
33
+ # A Parallel Pipe has two distinct philosophies of use:
34
+ # 1. <b>Simple MPI:</b> Allows creating of new parallel processes and synchronous and asynchronous messaging passing between them.
35
+ # 2. <b>Using a Controller</b>: Allows messaging passing, and also blocking synchronization (mutexes), and controlled access to shared resources, with automatic loading and saving of those resources.
36
+ #
37
+ # === Simple MPI
38
+ #
39
+ # This approach will be familiar to anyone who uses MPI proper. New processes can be created by forking and messages can be passed between them. Any message contents are acceptable where <tt>eval(contents.inspect) == contents</tt>. Messages can be sent and received both synchronously and asynchronously.
40
+ #
41
+ # <tt>ppipe = PPipe.new(3, false)</tt>
42
+ #
43
+ # <tt>pipe_no = ppipe.fork do
44
+ #
45
+ # ppipe.i_send(:aeroplanes, ["boeing", "airbus"], tp: 0) # asynchronous; note new ruby 1.9 hash syntax at end; tp means 'to pipe'
46
+ #
47
+ # ppipe.w_recv(:finish, fp: 0) \# synchronous
48
+ #
49
+ # end
50
+ #
51
+ # message = ppipe.i_recv(:aeroplanes, fp: pipe_no) \# asynchronous
52
+ #
53
+ # ppipe.w_send(:finish, true, tp: pipe_no) \# synchronous
54
+ #
55
+ # puts 'waiting for asynchronous message'
56
+ #
57
+ # puts message.join.inspect \# ["boeing", "airbus"]
58
+ #
59
+ # ppipe.finish</tt>
60
+ #
61
+ #
62
+ # MPI old-timers may prefer the non block form of fork. Here we create 8 heavy-weight processes as if we were about to run a large parallel calculation.
63
+ #
64
+ # <tt>
65
+ # ppipe = PPipe.new(8)
66
+ #
67
+ # ppipe.fork(7)
68
+ #
69
+ # my_rank = ppipe.mpn
70
+ #
71
+ # \# ...big calculation
72
+ #
73
+ # if ppipe.is_root
74
+ # ppipe.finish
75
+ # else
76
+ # ppipe.die
77
+ # end
78
+ # </tt>
79
+ #
80
+ # === Using a Controller
81
+ #
82
+ # In this mode of operation previous messages still work, but several new functions are possible:
83
+ #
84
+ # ==== Synchronization:
85
+ #
86
+ # Synchronization ensures that the protected sections of code in different process are never executed at the same time.
87
+ #
88
+ # <tt> ppipe= PPipe.new(10, true) \# creates a controller and ten pipes
89
+ #
90
+ # ppipe.fork do
91
+ # ppipe.synchronize(:sleep_sync) do # :sleep_sync identifies the mutex; it could have been any symbol
92
+ # 10.times do
93
+ # sleep rand
94
+ # $stderr.print 'z'
95
+ # end
96
+ # end
97
+ # end
98
+ #
99
+ # ppipe.synchronize(:sleep_sync){$stderr.puts "This will never appear in between the zs, only before or after"}
100
+ #
101
+ # ppipe.waitall </tt>
102
+ #
103
+ # Why $stderr I hear you ask? This is because the $stdout and $stdin of all sub-processes are routed to the main process by default (this can be changed).
104
+ #
105
+ # ==== Shared Resources
106
+ #
107
+ # The controller provides synchronized access to shared resources. Only one process can access any shared resource at any given time. Shared resources can be any object where <tt>eval(object.inspect) = object</tt>.
108
+ # <tt> \# ... continuing from previous
109
+ #
110
+ # new_pipe = ppipe.fork do
111
+ # people = ppipe.get_shared_resource(:list_of_people)
112
+ # people = {}
113
+ # ppipe.return_shared_resource(:list_of_people, people)
114
+ # ppipe.i_send(:people_initialized, true, tp: 0)
115
+ #
116
+ # ppipe.shared_resource(:list_of_people) do |people|
117
+ # people[:Bill] = "Walker"
118
+ # people # The last line in the block form must be the shared resource
119
+ # end
120
+ # end
121
+ #
122
+ # ppipe.w_recv(:people_initialized)
123
+ #
124
+ # ppipe.shared_resource(:list_of_people) do |people|
125
+ # people[:Tom] = "Climber"
126
+ # people # The last line in the block form must be the shared resource
127
+ # end </tt>
128
+ #
129
+ # An important point to note is that synchronization and shared resource methods do not
130
+ # guarantee the order in which processes execute them, only that they will never be executed at the same time. That is why the <tt>:people_initialized</tt> message is necessary, to ensure the order in which the first two accesses to the shared resource happen.
131
+ #
132
+ # ==== Auto Load Save
133
+ #
134
+ # Suppose before the first access to the shared resource <tt>:list_of_people</tt> we add a call:
135
+ #
136
+ # <tt>ppipe.auto_load_save(:list_of_people, 'some_file.rb') </tt>
137
+ #
138
+ # If the file 'some_file.rb' does not exist, it will be created the first time :list_of_people is accessed, and it will be updated at every subsequent time. If the file 'some_file.rb' exists (e.g. from a previous run of the program), it will be reloaded. Thus, :list_of_people becomes a persistant object: it is remembered from one execution to the next.
139
+ #
140
+ # = Threading
141
+ #
142
+ # === The Good News
143
+ #
144
+ # PPipe fully supports being combined with Ruby Threads. All controller mutexes are automatically thread mutexes as well:
145
+ #
146
+ # <tt> t1 = Thread.new{ppipe.synchronize(:thread_test){print 'abraca'; sleep rand; puts "dabra ";}}
147
+ #
148
+ # t2 = Thread.new{ppipe.synchronize(:thread_test){print 'hippo'; sleep rand; puts "potamus ";}}
149
+ #
150
+ # t1.join; t2.join </tt>
151
+ #
152
+ # You can also pass messages between threads, just as you can between processes:
153
+ #
154
+ # <tt> first_thread = Thread.new do
155
+ # second_thread_id = ppipe.w_recv(:stid)
156
+ # ppipe.i_send(:message_to_second_thread, "I love Ruby!!", tt: second_thread_id) # tt means 'to thread'
157
+ # end
158
+ #
159
+ # second_thread = Thread.new do
160
+ # ppipe.i_send(:stid, Thread.current.object_id, tt: first_thread.object_id)
161
+ # puts ppipe.w_recv(:message_to_second_thread, ft: first_thread.object_id) # outputs 'I love Ruby!!'; ft means 'from thread' (obviously!)
162
+ #
163
+ # end
164
+ #
165
+ # first_thread.join; second_thread.join</tt>
166
+ #
167
+ # But these threads don't have to be on the same process...
168
+ #
169
+ # <tt>
170
+ # new_pipe = ppipe.fork do
171
+ # first_thread = Thread.new do
172
+ # ppipe.i_send(:ftid, Thread.current.object_id, tp: 0)
173
+ # second_thread_id = ppipe.w_recv(:stid, fp: 0)
174
+ # ppipe.i_send(:message_to_second_thread, "I love Ruby!!", tp: 0, tt: second_thread_id) # Note that the order of tt and tp doesn't matter (because they form a hash)
175
+ # end
176
+ # first_thread.join
177
+ # end
178
+ #
179
+ # second_thread = Thread.new do
180
+ # ppipe.i_send(:stid, Thread.current.object_id, tp: new_pipe)
181
+ # first_thread_id = ppipe.w_recv(:ftid, fp: new_pipe)
182
+ # puts ppipe.w_recv(:message_to_second_thread, ft: first_thread_id, fp: new_pipe) # outputs 'I love Ruby!!'
183
+ #
184
+ # end
185
+ #
186
+ # second_thread.join</tt>
187
+ #
188
+ # === The Bad News
189
+ #
190
+ # It should always be borne in mind that mixing threads and processes is powerful but potentially a big problem causer. The biggest problems are when you fork with more than one live thread in operation. While you think you've only got one thread in the new process, in actual fact you have all of them.
191
+ #
192
+ # By default, PPipe checks if you're trying to do this and raises an error. You can turn this off by setting the option <tt> :thread_check </tt> to <tt>false</tt> (see PPipe.new).
193
+ #
194
+ # You can also set another option: <tt>:thread_safe</tt> to <tt>true</tt>. Then, whenever you fork, PPipe will automatically kill all threads in the new process except the current one.
195
+ #
196
+ # If you have both the options set to false, you're on your own! Here be dragons!
197
+ #
198
+ # = Modules
199
+ #
200
+ # The primary purpose of the modules PPipe::Methods and PPipe::Controller are to provide the methods for the class PPipe. Therefore all the method documentation specifies having a ppipe as the receiver, e.g.
201
+ #
202
+ # <tt> ppipe = PPipe.new(10, false)
203
+ #
204
+ # new_pipe_number = ppipe.fork do
205
+ # ppipe.i_send(:greetings, "Hello World", tp: 0)
206
+ # end
207
+ #
208
+ # puts ppipe.w_recv(:greetings)
209
+ #
210
+ # ppipe.finish</tt>
211
+ #
212
+ # <b>However</b>, you can also include these modules at the top level. In this case you call set_up_pipes instead of PPipe.new, and you don't need to put a PPipe as the receiver:
213
+ #
214
+ # <tt>
215
+ #
216
+ # include PPipe::Methods
217
+ #
218
+ # include PPipe::Controller \# if you want to use the controller methods.
219
+ #
220
+ # set_up_pipes(10, false)
221
+ #
222
+ # new_pipe_number = fork do \# NB!!! fork has been redefined. You have to use Kernel.fork for the old fork
223
+ # i_send(:greetings, "Hello World", tp: 0)
224
+ # end
225
+ #
226
+ # puts w_recv(:greetings)
227
+ #
228
+ # finish
229
+ # </tt>
230
+ #
231
+ #
232
+ # You can also include these modules in any class you like, and now your class has become a parallel pipe!
233
+ #
234
+ # <tt> class MyClass
235
+ #
236
+ # include PPipe::Methods
237
+ #
238
+ # include PPipe::Controller \# if you want to use the controller methods.
239
+ #
240
+ # end
241
+ #
242
+ # my_object = MyClass.new
243
+ #
244
+ # my_object.set_up_pipes(10, false)
245
+ #
246
+ # new_pipe_number = my_object.fork do
247
+ # my_object.i_send(:greetings, "Hello World", tp: 0)
248
+ # end
249
+ #
250
+ # puts my_object.w_recv(:greetings)
251
+ #
252
+ # my_object.finish</tt>
253
+ #
254
+
255
+
256
+
257
+ # require 'box_of_tricks'
258
+ require 'thread'
259
+ require 'timeout'
260
+
261
+ class Symbol
262
+ def +(other)
263
+ (self.to_s + other.to_s).to_sym
264
+ end
265
+
266
+ end
267
+
268
+
269
+
270
+
271
+
272
+ class Thread
273
+ @@ppipe_threads=[]
274
+ def self.ppipe_new(*args, &block)
275
+ th = new(*args, &block)
276
+ @@ppipe_threads.push th
277
+ return th
278
+ end
279
+ class PPipeIRecv < StandardError
280
+ end
281
+ def self.ppipe_join
282
+ @@ppipe_threads.each do |thread|
283
+ raise PPipeIRecv.new("i_recv call with label '#{thread[:label]}' has not returned. Please kill it or make sure it returns, before calling fork") if thread[:ppipe_message] and thread.alive?
284
+ thread.join if thread.alive? and not thread == Thread.current
285
+ end
286
+ end
287
+ # def Thread.idpp
288
+ # return Thread.current.idpp
289
+ # end
290
+ # def idpp
291
+ # return object_id
292
+ # end
293
+ def Thread.ppipe_list_live_ids
294
+ return Thread.list.inject([]){|arr, thread| arr.push thread.object_id if thread.alive?; arr}
295
+ end
296
+
297
+ end
298
+
299
+ class Object
300
+
301
+ # returns false unless the object is a PPipe::Message. See PPipe::Message#method_missing for an explanation.
302
+
303
+ def is_a_ppipe_message?
304
+ false
305
+ end
306
+ end
307
+
308
+ # class IO
309
+ #
310
+ # def non_blocking_gets(timeout=0.01)
311
+ # t = Thread.ppipe_new{th = Thread.current; th[:line] = nil; th[:line] = gets}
312
+ # sleep timeout; t.kill;
313
+ # return t[:line]
314
+ # end
315
+ # end
316
+
317
+ class ArgumentError
318
+ def self.check(*values, &block)
319
+ values.each do |name, value, test_data|
320
+ if block
321
+ raise new("#{name} failed argument correctness test (value given was '#{value.inspect}')") unless yield(value, test_data)
322
+ else
323
+ is_array = test_data.class == Array
324
+ raise new("#{name} was of class #{value.class} instead of #{test_data.inspect} (value given was #{value.inspect})") unless (is_array ? test_data.include?(value.class) : value.class == test_data)
325
+ end
326
+ end
327
+ end
328
+ end
329
+ #
330
+ #
331
+ # Quick Links: PPipe, parallelpipes.rb, PPipe::Methods, PPipe::Controller, PPipe::Message
332
+ #
333
+ # Parallel Pipes is a simple, easy to use, MPI-like implentation for Ruby, allowing you to
334
+ # create and send messages between multiple ruby processes and allowing your code to run on
335
+ # multiple processors.
336
+ #
337
+ # Written by Edmund Highcock
338
+ #
339
+ # Copyright 2009
340
+ #
341
+ # Current status: alpha
342
+ #
343
+ # This is free software released under the GNU GPL. It comes with no warranty.
344
+ # You are free to modify and release this software, as long as due credit is given to the authors.
345
+ #
346
+ # Requires Ruby 1.9 +
347
+ #
348
+ # For more information, see the detailed introduction in parallelpipes.rb, or the documentation in PPipe::Methods or PPipe::Controller for individual methods.
349
+
350
+
351
+ class PPipe
352
+
353
+ class DeadMutex < StandardError
354
+ end
355
+
356
+ class PPipeFatal < StandardError
357
+ end
358
+
359
+ class DeadPipe < StandardError
360
+ end
361
+
362
+ class UnassignedPipe < StandardError
363
+ end
364
+
365
+ class ThreadingError < StandardError
366
+ end
367
+
368
+ class NoController < StandardError
369
+ def initialize(mess="")
370
+ super(mess + "\nYou need a controller to use this function. Make a ParallelPipe with the second argument true")
371
+ end
372
+ end
373
+
374
+ class ControllerError < StandardError
375
+ end
376
+
377
+ class DeadParallelPipe < StandardError
378
+ def initialize(mess="")
379
+ super(mess + "\nError: This parallel pipe is as dead as a doornail!")
380
+ end
381
+ end
382
+
383
+ class SelfReference < StandardError
384
+ end
385
+
386
+ class Ambiguity < StandardError
387
+ end
388
+
389
+ class RecvTimeout < StandardError
390
+ end
391
+
392
+ # class LabelError < StandardError
393
+ # end
394
+
395
+ # A rough and ready logging module. Can be used independently of PPipe
396
+
397
+ module Log
398
+
399
+ @@log_file = nil
400
+ @@log_io = nil
401
+
402
+ # The verbosity of the object. Only log calls with a verbosity level lower than or equal to the verbosity will execute
403
+
404
+ attr_accessor :verbosity
405
+
406
+
407
+ def self.log_file
408
+ @@log_file
409
+ end
410
+
411
+ # Give a log file name for logging.
412
+
413
+ def self.log_file=(file)
414
+ @@log_file=file
415
+ # @@io = nil
416
+ end
417
+
418
+ # Give an io object for logging (overrides any file given for logging).
419
+
420
+ def self.io=(_io)
421
+ @@log_io = _io
422
+ end
423
+
424
+ # Deletes the log file if there is one
425
+
426
+ def self.clean_up
427
+ File.delete @@log_file if FileTest.exist? @@log_file
428
+ end
429
+
430
+ @@log_group_options = [:t, :v]
431
+ @@log_individual_options = [:i, :f, :c, :p]
432
+ @@log_possible_options = @@log_group_options + @@log_individual_options
433
+ class BadLogOption < StandardError
434
+ end
435
+ class BadVerbosity < StandardError
436
+ end
437
+
438
+ # messages can be anything that defines <tt>to_s</tt>
439
+ #
440
+ # options is either a string of letters, or an array of symbols.
441
+ #
442
+ # options are:
443
+ #
444
+ # * i - call inspect on every message
445
+ # * f - add the word 'Function' before the message, and the class of the logger afterwards (which will be PPipe if a PPipe is the receiver of the call)
446
+ # * c - add the word 'complete' after the message
447
+ # * v - specify the verbosity level of the call. This is a number 0-9 which immediately follows v. The messages will not be logged unless the instance variable @verbosity is greater than or equal to the verbosity level.
448
+ # * p - print mpn (my pipe number) of the ppipe. (Lets you know which process logged the message)
449
+ #
450
+ # ppipe.log('pv4', 'main calculation') # main calculation: my_pipe_number: 3
451
+ #
452
+ # ppipe.log 'fv2p', :analyze # Function: analyze: PPipe : my_pipe_number: 3
453
+ #
454
+ # ppipe.log([:c, :v, 3], 'main calculation', 'analysis') # main calculation : complete
455
+ # # analysis : complete
456
+ #
457
+ # It is possible (within a class that includes PPipe::Log) to define default verbosity levels for different combinations of options. This verbosity level will be used if none is specified in the log call.
458
+ #
459
+ # class MyClass
460
+ # include PPipe::Log
461
+ #
462
+ # def set_up_log(file_name)
463
+ # PPipe::Log.file_name = file_name
464
+ # @log_defaults ||= {}
465
+ #
466
+ # @log_defaults[[:f, :c].sort] = 7 # the sort call is very important. Don't (obviously) include :v in the array of options
467
+ # end
468
+ #
469
+ # end
470
+ #
471
+ # my_object = MyClass.new
472
+ # my_object.set_up_log('my_log_file.txt')
473
+ # my_object.verbosity = some_verbosity
474
+ # my_object.log 'fc', :window_open # Function: window_open: MyClass: complete
475
+ #
476
+ # # will only be logged if some_verbosity >= 7
477
+
478
+
479
+ def log(options, *messages)
480
+ return unless @@log_io or @@log_file
481
+ raise BadVerbosity.new("#{@verbosity.class}: #{@verbosity.inspect}") unless @verbosity.class == Fixnum and @verbosity > -1
482
+ options_in = options
483
+ options = options.to_s.split(//).map{|op| op.to_sym} if options.class == String or options.class == Symbol
484
+ options ||= []
485
+
486
+ if options.include? :v
487
+ @log_defaults ||= {}
488
+ vi = options.index(:v)
489
+ used_v = nil
490
+ unless options[vi+1] and options[vi+1].to_s =~ /^\d$/
491
+ return unless @verbosity >= (@log_defaults[(options - [:v]).sort] or @log_defaults[(options - [:v]).sort] or 9)
492
+ options.delete_at(vi)
493
+ else
494
+ # $stderr.puts "verbosity required is", options[vi+1].to_s.to_i, "verbosity is: ", @verbosity
495
+ return unless @verbosity >= options[vi+1].to_s.to_i
496
+ options.delete_at(vi)
497
+ options.delete_at(vi)
498
+ end
499
+
500
+ end
501
+ raise BadLogOption.new("#{options_in.inspect} --> " + (options - @@log_possible_options).inspect) if (options - @@log_possible_options).size > 0
502
+ messages.each do |message|
503
+ message = (options - @@log_group_options).inject(message){|message, option| message = send(:log_convert_ + option, message)}
504
+ if @@log_io
505
+ @@log_io.print message.to_s + "\n"
506
+ else
507
+ File.open(@@log_file, 'a'){|file| file.print message.to_s + "\n"}
508
+ end
509
+ end
510
+ if options.include? :t
511
+ log("Traceback \n" + caller.join("\n"))
512
+ end
513
+ end
514
+
515
+ def log_convert_i(message)
516
+ message.inspect
517
+ end
518
+ def log_convert_f(message)
519
+ "Function: " + message + ": " + self.class.to_s
520
+ end
521
+ def log_convert_c(message)
522
+ message + ": complete"
523
+ end
524
+ def log_convert_p(message)
525
+ message + " : my_pipe_number: #@my_pipe_number"
526
+ end
527
+
528
+ private :log_convert_c, :log_convert_f, :log_convert_i, :log_convert_p
529
+
530
+
531
+ end
532
+
533
+
534
+
535
+ #
536
+ # Quick Links: PPipe, parallelpipes.rb, PPipe::Methods, PPipe::Controller, PPipe::Message
537
+ #
538
+ #
539
+ # A Message object is designed to make the syntax of receiving messages as beautiful and Ruby-esque as possible. It stores the label, contents and options of the message. At all times it tries to 'be' its contents, except if you want specific information out of it, like the label, options, pipe it was sent from (fp) or thread it was sent from (ft).
540
+ #
541
+ # NB: as well as defining the following methods (see below for their documentation):
542
+ #
543
+ # tp, tps, tp=, pop_tps # tp means 'to pipe(s)'
544
+ #
545
+ # Message also dynamically defines 12 other methods, with very similar meanings.
546
+ #
547
+ # ep, eps, ep=, pop_eps # ep means 'exclude pipe(s)'
548
+ # tt, tts, tt=, pop_tts # tt means 'to thread(s)'
549
+ # et, ets, et=, pop_ets # et means 'exclude thread(s)'
550
+ #
551
+ # Footnote:
552
+ #
553
+ # tp, ep, tt and et make up the Message's address. A message can be given an address before it can be sent. (Although if it has no address this is not an error, as it will be sent to every pipe and every process - see Methods#i_send). Messages that have been returned by t_recv, w_recv, i_recv, i.e. messages that have arrived at their destination, do not have an address.
554
+
555
+ class Message
556
+
557
+ instance_methods.each{|method| undef_method method unless [:object_id, :__send__, :dup].include? method}
558
+
559
+ # see Methods#i_send
560
+ attr_reader :label, :contents, :options
561
+
562
+ # Create a new message. The arguments are identical to those for Methods#i_send
563
+
564
+ def initialize(label, contents, options={})
565
+ ArgumentError.check([:label, label, [Symbol, String, Fixnum, NilClass]], [:options, options, [Hash, NilClass]])
566
+ ArgumentError.check(['options[:tp]', options[:tp], [Integer, Fixnum, Array, NilClass]], ['options[:ep]', options[:ep], [Integer, Fixnum, Array, NilClass]], ['options[:tt]', options[:tt], [Integer, Fixnum, Array, NilClass]], ['options[:et]', options[:et], [Integer, Fixnum, Array, NilClass]]) if options
567
+ @label = label
568
+ @contents = contents
569
+ @options = options
570
+ check_ambiguities
571
+ end
572
+ def check_ambiguities
573
+ return unless @options
574
+ raise Ambiguity.new("this message (with label: #@label) has both ep and tp specified") if @options[:tp] and @options[:ep]
575
+ raise Ambiguity.new("this message (#@label) has both tt and et specified") if @options[:tt] and @options[:et]
576
+ end
577
+ private :check_ambiguities
578
+
579
+ # The number of the pipe the message came from
580
+
581
+ def fp
582
+ return @options[:fp]
583
+ end
584
+
585
+ # The id of the thread the message came from
586
+
587
+ def ft
588
+ return @options[:ft]
589
+ end
590
+
591
+
592
+ #NB all the following four methods are dynamically defined. This is just documentation.
593
+
594
+ # The pipe the message is addressed to (@options[:tp])
595
+
596
+ def tp
597
+ end
598
+
599
+ # A list of pipes the message is addressed to (returns @options[:tp] if @options[:tp] is an array, and [@options[:tp]] if it is an integer)
600
+
601
+ def tps
602
+ end
603
+
604
+ # Set the pipe the message is address to (value can be an Integer or and array of integers)
605
+
606
+ def tp=(value)
607
+ end
608
+
609
+ # Return a list of pipes the message is addressed to (see tps) and delete @options[:tp]
610
+
611
+ def pop_tps
612
+ end
613
+
614
+
615
+ [:tp, :ep, :tt, :et].each do |address|
616
+ define_method(address){ return @options[address]}
617
+ define_method(address + '='.to_sym) do |value|
618
+ ArgumentError.check( ["options[#{address}]", options[address], [Integer, Fixnum, Array, NilClass]])
619
+ @options[address] = value
620
+ check_ambiguities
621
+ end
622
+ define_method(address + :s) do
623
+ check_ambiguities
624
+ return @options[address] ? (@options[address].class == Array ? @options[address] : [@options[address]]) : nil
625
+ end
626
+ define_method(:pop_ + address + :s) do
627
+ # $stderr.puts methods
628
+ ans = __send__(address + :s)
629
+ @options.delete(address) if @options[address]
630
+ return ans
631
+ end
632
+ end
633
+
634
+ # Was the message sent by Methods#w_send ?
635
+
636
+ def blocking
637
+ return @options[:blocking]
638
+ end
639
+
640
+ # parallelpipes.rb adds a method to Object: Object#is_a_ppipe_message? This method will return false for every object except a Message:
641
+ #
642
+ # puts message.is_a_ppipe_message? # true
643
+ #
644
+ # puts message.contents.is_a_ppipe_message? # false
645
+ #
646
+ # See Message#method_missing for an explanation.
647
+
648
+ def is_a_ppipe_message?
649
+ true
650
+ end
651
+
652
+ def self.with_listening_thread(thread) # :nodoc:
653
+ new(nil, nil, nil).with_listening_thread(thread)
654
+ end
655
+
656
+ # Internal use only
657
+
658
+ def with_listening_thread(thread)
659
+ @thread = Thread.ppipe_new do
660
+ thread.join
661
+ message = thread[:message]
662
+ @contents = message.contents
663
+ @label = message.label
664
+ @options = message.options
665
+ end
666
+ return self
667
+ end
668
+
669
+ # Has the message arrived? (see Methods#i_recv)
670
+
671
+ def arrived?
672
+ # raise ("This message is not listening for anything: it was not created by i_recv") unless @thread
673
+ return true unless @thread
674
+ return !@thread.alive?
675
+ end
676
+
677
+ # Stop checking to see if the message has arrived. (see Methods#i_recv)
678
+
679
+ def kill
680
+ raise ("This message is not listening for anything: it was not created by i_recv") unless @thread
681
+ @thread.kill
682
+ end
683
+
684
+ # Wait until the message has arrived. (see Methods#i_recv)
685
+
686
+
687
+ def join
688
+ raise ("This message is not listening for anything: it was not created by i_recv") unless @thread
689
+ # return self unless @thread
690
+ @thread.join
691
+ self
692
+ end
693
+
694
+ # Any other methods (except for object_id) are passed to the message's contents...
695
+ #
696
+ # message = PPipe::Message.new(:Winnie_the_Pooh, 'I like honey', {})
697
+ #
698
+ # puts message # I like honey
699
+ #
700
+ # puts message.inspect # "I like honey"
701
+ #
702
+ # puts message + ' very much' # I like honey very much
703
+ #
704
+ # puts message.length # 12
705
+ #
706
+ # puts message.sub(/honey/, 'bees') # I like bees
707
+ #
708
+ # puts message.class # String
709
+ #
710
+ # So how do you tell if something is a PPipe::Message? parallelpipes.rb adds a method to Object: Object#is_a_ppipe_message? This method will return false for every object except a Message:
711
+ #
712
+ # puts message.is_a_ppipe_message? # true
713
+ #
714
+ # puts message.contents.is_a_ppipe_message? # false
715
+ #
716
+
717
+ def method_missing(*args)
718
+ @contents.send(*args)
719
+ end
720
+
721
+ # Internal use only
722
+ SPLITTER = "PPipe:::" # :nodoc:
723
+ # private :SPLITTER
724
+
725
+ def to_transmission
726
+ return SPLITTER + [@label, @contents, @options].inspect + "\n"
727
+ end
728
+ def self.from_transmission(line) # :nodoc:
729
+ packets = line.split(SPLITTER, -1) # in case two lines got stuck together.
730
+ extra = packets.shift # something put into the pipe not by ppipe.
731
+ # raise PPipeFatal.new("extra is #{extra.inspect}, line was #{line.inspect}") unless extra
732
+ if extra == nil # line was "", a new line submitted by the user
733
+ extra = ""
734
+ elsif extra == "" # lines was "PPipe:::[etc]", a ppipe message
735
+ extra = nil
736
+ else # Line was "stuffPPipe:::[etc]", a ppipe message with some stuff in front of it, or "stuff", a line submitted by the user
737
+ end
738
+
739
+ if packets.size == 1
740
+ # if line =~ /^PPipe:::(?<message>.+$)/
741
+ # message = new(*eval($~[:message]))
742
+ message = new(*eval(packets[0]))
743
+ elsif packets.size == 0
744
+ message = nil
745
+ else
746
+ raise PPipeFatal.new("Corrupted message: stuck together: #{line.inspect}")
747
+ end
748
+ return extra, message
749
+ end
750
+ end
751
+
752
+ Log.io = $stderr
753
+ # Log.log_file = "/dev/null"
754
+ # Log.log_file = "ppipe_log.txt"
755
+
756
+ # class_accessor :print_messages, :thread_safe
757
+ # @@print_messages = false
758
+
759
+ @@thread_safe = true
760
+
761
+ # Set an io object to write log messages to. Useful for debugging. Can be e.g. $stderr
762
+
763
+ def self.log_io=(io_oject)
764
+ Log.io = io_object
765
+ end
766
+
767
+ # Set a file to write log messages to. Useful for debugging.
768
+
769
+ def self.log_file_name=(string)
770
+ Log.file_name = string
771
+ end
772
+
773
+ # Make a new PPipe. All this does is call Methods#set_up_pipes(*args) - see this function for documentation.
774
+
775
+ def initialize(*args)
776
+ set_up_pipes(*args)
777
+ end
778
+
779
+
780
+ module MethodMissing
781
+
782
+ # If redirect is on, the $stdout of any child processes is redirected to the root pipe. method_missing passes the methods to a pipe where this output is stored. Thus, if you call ppipe.gets, you will get the next thing out put by any child process.
783
+
784
+ def method_missing(*args)
785
+ raise DeadParallelPipe unless @alive
786
+ raise PPipeFatal.new("calling this doesn't make any sense unless redirect is on") unless @redirect
787
+ check_messages
788
+ return @user_end.send(*args)
789
+ end
790
+
791
+ end
792
+
793
+ # PPipe requires a method of reading pipes that will not block even if the pipe is empty, is at eof, or has not been written to yet. configure_reader dynamically defines this method, which is called read_pipe
794
+ #
795
+ # Reader is included in PPipe. It can also be included independently in any class
796
+
797
+ module Reader
798
+ include Log
799
+ include Timeout
800
+ # class PPipeReaderError < StandardError
801
+ # end
802
+
803
+ # read_pipe is defined dynamically...
804
+
805
+ # Read all the contents of the pipe. Return nil if the pipe is empty. Never, ever, ever, ever block!
806
+
807
+ def read_pipe(pipe)
808
+ end
809
+
810
+ # Get the next line from the pipe. Return nil if the pipe is empty or at eof. Blocks if the pipe is not empty but does not contain a complete line.
811
+
812
+ def get_line(pipe, sep = $/)
813
+ end
814
+
815
+
816
+
817
+ # These work on OS X
818
+
819
+ def read_stat_size(pipe)
820
+ size = pipe.stat.size
821
+ return nil if size == 0
822
+ log 'v8', 'running read_stat_size'
823
+ return pipe.read(size)
824
+ end
825
+ private :read_stat_size
826
+
827
+ def read_stat_size_get_line(pipe, sep = $/)
828
+ return nil if pipe.stat.size == 0
829
+ return pipe.gets
830
+ end
831
+ private :read_stat_size_get_line
832
+
833
+
834
+
835
+ # These work on Linux (openSUSE tested so far)
836
+
837
+ def set_up_joke_pipe
838
+ unless @joke_read
839
+ @joke_read, @joke_write = IO.pipe
840
+ @joke_write.puts 'jokes!'
841
+ @joke_write.close
842
+ end
843
+ end
844
+ private :set_up_joke_pipe
845
+
846
+ def read_joke_pipe_select(pipe)
847
+ set_up_joke_pipe
848
+ message = ""
849
+ while select([@joke_read, pipe])[0].include? pipe
850
+ begin
851
+ message += pipe.readpartial(4096)
852
+ rescue EOFError
853
+ break
854
+ end
855
+ end
856
+ return message.length > 0 ? message : nil
857
+ end
858
+
859
+ private :read_joke_pipe_select
860
+
861
+ def read_joke_pipe_select_get_line(pipe, sep = $/)
862
+ set_up_joke_pipe
863
+ if select([@joke_read, pipe])[0].include? pipe
864
+ return pipe.gets(sep)
865
+ else
866
+ return nil
867
+ end
868
+ end
869
+ private :read_joke_pipe_select_get_line
870
+
871
+
872
+
873
+ # This method does not work! Threading issues mean that the whole process sometimes hangs on the gets even though the gets call is in a separate thread. It may work in a future version of Ruby (when they get rid of the GIL)
874
+
875
+ def read_gets_timeout(pipe)
876
+ message = ""
877
+ loop do
878
+ fetched = nil
879
+ begin
880
+ timeout(0.001){fetched = pipe.gets}
881
+ rescue
882
+ break
883
+ end
884
+ break unless fetched
885
+ message += fetched
886
+ end
887
+ log 'v8', 'read this message: ' + message if message.size > 0
888
+ return message.size > 0 ? message : nil
889
+ end
890
+
891
+ private :read_gets_timeout
892
+
893
+ # Works sometimes... but fails if a process writes to the pipe at exactly the same time as it is being read.
894
+
895
+ def read_atime_readpartial(pipe)
896
+ # p pipe.stat.mtime.to_f - pipe.stat.atime.to_f
897
+ return nil unless pipe.stat.mtime.to_f > pipe.stat.atime.to_f
898
+ p pipe.stat.ctime.to_f, pipe.stat.mtime.to_f, pipe.stat.atime.to_f
899
+ ans = pipe.readpartial(4096)
900
+ p pipe.stat.ctime.to_f, pipe.stat.mtime.to_f, pipe.stat.atime.to_f
901
+
902
+ return ans.size > 0 ? ans : nil
903
+ end
904
+
905
+ private :read_atime_readpartial
906
+
907
+ # Doesn't work!
908
+
909
+ def read_ungetc_readpartial(pipe)
910
+ # log 'fv8', 'read_ungetc_readpartial'
911
+ ans = ""
912
+ loop do
913
+ pipe.ungetc(84)
914
+ fetched = pipe.readpartial(4096)
915
+ # log 'v8', 'fetched was...', fetched
916
+ if fetched.length > 1
917
+ log 'v8', fetched
918
+ ans += fetched[1, (fetched.length - 1)]
919
+ else
920
+ break
921
+ end
922
+ end
923
+ return ans.length > 0 ? ans : nil
924
+ end
925
+ private :read_ungetc_readpartial
926
+
927
+ def determine_read(pipe, message)
928
+ # fetched = ""
929
+ # puts 'fetching...'
930
+ # mutex = Mutex.new
931
+ # threads = []
932
+ fetched = {}
933
+
934
+ start_time = Time.now.to_f
935
+ while Time.now.to_f - start_time < 3.0
936
+ [:read_stat_size, :read_joke_pipe_select].each do |method|
937
+ fetched[method] ||= ""
938
+ fetched[method] += (send(method, pipe) or "")
939
+ log 'v8i', 'fetched...' + fetched[method] if fetched[method].size > 0
940
+ return method if fetched[method] == message
941
+ # raise PPipeReaderError.new("Should have matched fetched: #{fetched.inspect} to message: #{message.inspect}") if fetched.size
942
+ sleep 0.0001
943
+ end
944
+ end
945
+ raise PPipeFatal.new("Failed to determine the correct pipe reading method")
946
+ end
947
+ private :determine_read
948
+
949
+ #The best way of defining Reader#read_pipe is operating system dependent. Work out which is the best way and then define the method read_pipe to be this method.
950
+
951
+ def configure_reader
952
+ @verbosity ||= 0 # for logging purposes
953
+ pipes = [nil, IO.pipe]
954
+
955
+ pipe = IO.pipe
956
+
957
+ pipes[1][1].sync = true
958
+ pipe[1].sync = true
959
+
960
+ my_pipes = []
961
+
962
+ pid = Kernel.fork
963
+
964
+ if pid
965
+ pipe[1].close
966
+ my_pipes.push pipe[0]
967
+ pipes[1][0].close
968
+ pipes[1] = pipes[1][1]
969
+ # pipes[1].puts
970
+ # sleep 0.002
971
+ log 'v8', 'sending pears'
972
+ pipes[1].sync = true
973
+ pipes[1].print('pears' + "\n")
974
+ # cmd = [2].pack("S")
975
+ # pipes[1].ioctl(5, cmd)
976
+ # sleep 1
977
+ log 'v8', 'waiting for apples...'
978
+ method = determine_read(my_pipes[0], "apples\nare\nnice\n")
979
+ log 'v8', method
980
+ self.class.send(:alias_method, :read_pipe, method)
981
+ self.class.send(:alias_method, :get_line, method + :_get_line)
982
+ my_pipes[0].close
983
+ pipes[1].close
984
+ return method
985
+ else
986
+ pipes[1][1].close
987
+ my_pipes.push pipes[1][0]
988
+ pipes[1] = nil
989
+ pipe[0].close
990
+ pipes[0] = pipe[1]
991
+ pipes[0].sync = true
992
+ log 'v8', pipes.inspect
993
+ # sleep 0.5
994
+ log 'v8', 'waiting for pears'
995
+ log 'v8', determine_read(my_pipes[0], "pears\n")
996
+
997
+
998
+ log 'v8', 'sending apples'
999
+ pipes[0].print("apples\nare\nnice" + "\n")
1000
+ pipes[0].flush
1001
+ exit
1002
+ end
1003
+ end
1004
+ end
1005
+
1006
+ # if $0 == __FILE__
1007
+ # Log.io = $stderr
1008
+ # include PPipeReader
1009
+ # @verbosity = 9
1010
+ # 100.times{$stderr.puts "\n\n", configure_reader}
1011
+ # end
1012
+
1013
+ #
1014
+ # Quick Links: PPipe, parallelpipes.rb, PPipe::Methods, PPipe::Controller, PPipe::Message
1015
+ #
1016
+ # The primary purpose of this module is to provide the methods for the class PPipe.
1017
+ # Therefore all the method documentation specifies having a PPipe as the receiver. However, it is perfectly possible to use this module as a module in its own right; see the section Modules in parallelpipes.rb
1018
+
1019
+ module Methods
1020
+
1021
+ include Reader
1022
+
1023
+ include Log
1024
+
1025
+ # my pipe number - the pipe number of the PPipe
1026
+ attr_reader :mpn
1027
+
1028
+
1029
+ # is the ppipe the root ppipe (the first one to be created)?
1030
+ attr_reader :is_root
1031
+
1032
+ # see set_up_pipes
1033
+ attr_reader :thread_safe, :tt_required, :tp_required, :redirect
1034
+
1035
+
1036
+ # Set a number of pipes, and (optionally) start the controller. The parallel pipe will be able to fork one fewer times than the number of pipes you give it (the first one is for itself). There is a maximum number of pipes you can have open in any one process (this set by Ruby, or the operating system). This number includes all pipes opened by every PPipe, and any pipes opened elsewhere. The number is about 100
1037
+ #
1038
+ # If use_controller is set to true, a separate controller process will be created, allowing synchronization and access to shared resources (see Controller in the head notes).
1039
+ #
1040
+ # Options are:
1041
+ #
1042
+ # * controller_refresh - set the refresh rate of the controller. Higher rates mean faster performance but more CPU usage. See PPipe#controller_refresh= for some stats.
1043
+ # * thread_check - true or false; default true (see parallelpipes.rb)
1044
+ # * thread_safe - true or false; default false (see parallelpipes.rb)
1045
+ # * redirect - true or false; default true. Should the $stdin and $stdout of the child process be hooked up to the parent PPipe?
1046
+ # * verbosity - 0 to 9; how much debug info do you want PPipe to print into log_io or log_file_name ? Useful settings are 1 (just prints received messages), 2 (prints sent messages as well) and 7 (prints every function call). See PPipe::Log.
1047
+ #
1048
+ # e.g.
1049
+ #
1050
+ # if using the class PPipe
1051
+ #
1052
+ # ppipe = PPipe.new(20, true)
1053
+ # ppipe = PPipe.new(10, false, thread_check: false, log_io: $stderr)
1054
+ #
1055
+ # if using the module PPipe::Methods:
1056
+ #
1057
+ # set_up_pipes(20, true)
1058
+ # set_up_pipes(10, false, thread_check: false, log_io: $stderr)
1059
+
1060
+
1061
+ def set_up_pipes(num_pipes, use_controller, options={})
1062
+ ArgumentError.check([:num_pipes, num_pipes, Fixnum], [:options, options, Hash], [:use_controller, use_controller, [TrueClass, FalseClass]])
1063
+ raise PPipeFatal.new("Only the root PPipe can set up pipes (otherwise chaos would ensue!)") unless @is_root == true or @is_root == nil
1064
+ raise PPipeFatal.new("Attempting to set up pipes when this ppipe is already set up (call die or finish first if you want to set up a bunch of new pipes)") if @alive
1065
+
1066
+ @alive = true
1067
+ @pipes = [nil] + (1...num_pipes).to_a.map{|a| IO.pipe}
1068
+ @pipe_counter = 1 # 0 is the root process
1069
+ @my_pipes = []
1070
+ @mutexes = {}
1071
+ @messages = {}
1072
+ @user_end, @my_end = IO.pipe
1073
+ @is_root = true
1074
+ @mpn = 0
1075
+ @max_mutex_id = 0
1076
+ @envelope = 0
1077
+ @pids = {}
1078
+ @redirect = true unless options[:redirect] == false
1079
+ @controller_asleep = false
1080
+ @thread_mutexes = {}
1081
+ @shared_resource_mutexes = {}
1082
+ @weak_synchronization = options[:weak_synchronization]
1083
+ @check_messages_mutex = Mutex.new
1084
+ @thread_safe = options[:thread_safe]
1085
+ @thread_check = true unless options[:thread_check] == false
1086
+ # @print_messages = options[:print_messages]
1087
+ @tt_required = options[:tt_required]
1088
+ @tp_required = options[:tp_required]
1089
+ # @fvb = 6
1090
+ @last_segment = ""
1091
+ @verbosity = (options[:verbosity] or $log_verbosity or 0)
1092
+ @log_defaults = {}
1093
+ @log_defaults[[:f, :p].sort] = 7
1094
+ @log_defaults[[:f, :p, :c].sort] = 7
1095
+ @message_counter = 0
1096
+ @finished_pipes = {}
1097
+
1098
+ #These parts must happen after initialization of variables
1099
+ configure_reader
1100
+ begin
1101
+ controller_refresh = (options[:controller_refresh] or 0.001)
1102
+ start_controller(controller_refresh) if use_controller
1103
+ rescue NoMethodError => err
1104
+ log 'v3', err
1105
+ log 'v3', err.class, err.backtrace
1106
+ raise PPipeFatal.new("Tried to use a controller without including the Controller module")
1107
+ end
1108
+ end
1109
+
1110
+ # Create a new process. If num_forks is unspecified, creates one new process, and returns the pipe number of that process. If num_forks is specified, creates num_forks new processes and returns an array of pipe numbers.
1111
+ #
1112
+ # If a block is given, the child process(es) will execute the block and then exit
1113
+ #
1114
+ # If there is no controller, only the root PPipe can fork. (Otherwise there is no way to prevent two processes forking at the same time).
1115
+
1116
+ def fork(num_forks=nil, &block)
1117
+ ArgumentError.check([:num_forks, num_forks, [NilClass, Fixnum]])
1118
+ raise DeadParallelPipe unless @alive
1119
+ raise PPipeFatal.new("Only the root parallel pipe can fork (you can test this with .is_root), unless there is a controller. Otherwise it is impossible to synchronize forking, and to provide unique ids to all new processes.") unless @is_root or @controller
1120
+ forks = []
1121
+ lock(:ppipe_fork_mutex) if @controller
1122
+ (num_forks or 1).times do
1123
+ log 'fpv', :fork
1124
+ log 'v9', 'about to check messages before forking'
1125
+ check_messages
1126
+ raise PPipeFatal.new("insufficient pipes") if @pipe_counter >= @pipes.size
1127
+ log 'v9', 'checked messages before forking'
1128
+ pipe_counter = @pipe_counter
1129
+ new_pipe = IO.pipe
1130
+ @pipes[@mpn] = new_pipe[1]
1131
+ cur_pid = Process.pid
1132
+ begin
1133
+ Thread.ppipe_join
1134
+ rescue => err
1135
+ (unlock(:ppipe_fork_mutex) if @controller; raise err)
1136
+ end
1137
+ Thread.list.each{|th| (unlock(:ppipe_fork_mutex) if @controller; raise ThreadingError.new("Must have only one live thread when calling fork, if PPipe.thread_safe == true (suggest you join all other threads before calling). PPipe.thread_safe can be set to false, but this can lead to many errors if forks are made with multiple live threads.")) if th.alive? and not th == Thread.current} if @thread_check
1138
+ pid = Process.fork
1139
+ if not pid
1140
+ Thread.list.each{|th| th.kill if th.alive? and not th == Thread.current} if @thread_safe
1141
+ @is_root = false
1142
+ @mpn = pipe_counter
1143
+ log 'pv3', :forked
1144
+ log 'v9', "My New Pipe No is: #{pipe_counter}"
1145
+ @pipes[pipe_counter][1].close
1146
+ @my_pipes = [[cur_pid, @pipes[pipe_counter][0]]]
1147
+ @pipes[pipe_counter] = nil #@pipes[pipe_counter][0]
1148
+ @user_end, @my_end = IO.pipe
1149
+ if @redirect
1150
+ $stdout = @pipes[0]
1151
+ $stdin = self #@user_end
1152
+ end
1153
+ log 'v9', 'about to yield', @mpn
1154
+ @thread_mutexes = {}
1155
+ @shared_resource_mutexes = {}
1156
+ @messages = {}
1157
+ @old_controllers = {}
1158
+ @message_counter = 0
1159
+ (block.call; exit) if block
1160
+ return nil
1161
+ end
1162
+ # @my_pipes.each{|(pid, pipe)| pipe.close}
1163
+ # @pipes.slice(0...@mpn).each{|pipe| next unless pipe; pipe.close}
1164
+ @my_pipes.push [pid, new_pipe[0]]
1165
+ new_pipe[1].close
1166
+ i_send(:add_pid, [pipe_counter, pid], {evaluate: true, tc: true})
1167
+ i_send(:increase_pipe_counter, true, {evaluate: true, tc: true})
1168
+ forks.push pipe_counter
1169
+ end
1170
+ unlock(:ppipe_fork_mutex) if @controller
1171
+ return num_forks ? forks : forks[0]
1172
+ end
1173
+
1174
+ def add_pid(array)
1175
+ ArgumentError.check([:pid_array, array, Array])
1176
+ @pids[array[0]] = array[1]
1177
+ end
1178
+ private :add_pid
1179
+ def increase_pipe_counter(contents=nil)
1180
+ # ArgumentError.check([:pid_array, array, Array])
1181
+ raise DeadParallelPipe unless @alive
1182
+ log 'fpv', :increase_pipe_counter
1183
+ log 'v7', 'pipe_counter currently', @pipe_counter
1184
+ if @pipe_counter == @mpn
1185
+ @pipe_counter+=1
1186
+ else
1187
+ unless @finished_pipes[@pipe_counter] # i.e. the process finished before this process heard about its existence
1188
+ @pipes[@pipe_counter][0].close
1189
+ @pipes[@pipe_counter] = @pipes[@pipe_counter][1]
1190
+ end
1191
+ # @pipes[@pipe_counter].puts "testing"
1192
+ @pipe_counter += 1
1193
+ end
1194
+
1195
+ end
1196
+ private :increase_pipe_counter
1197
+ # def gets
1198
+ # raise DeadParallelPipe unless @alive
1199
+ # check_messages
1200
+ # return @user_end.non_blocking_gets
1201
+ # end
1202
+
1203
+
1204
+ def check_messages(check_options={})
1205
+ log 'v9', :check_messages
1206
+ raise DeadParallelPipe unless @alive
1207
+ @check_messages_mutex.lock
1208
+ @last_segments ||= {}
1209
+ @my_pipes.each_with_index do |(pid, pipe), index|
1210
+ input = read_pipe(pipe)
1211
+ next unless input
1212
+ @last_segments[pid] ||= ""
1213
+
1214
+ @old_last_segment = @last_segments[pid] #for debugging only
1215
+
1216
+ lines = (@last_segments[pid] + input).split("\n", -1)
1217
+ @last_segments[pid] = lines.pop
1218
+
1219
+ log 'v8', "input: #{input.inspect}\nlines: #{lines.inspect}\n@last_segment: #{@last_segments[pid].inspect}\n@old_last_segment: #{@old_last_segment.inspect}\n"
1220
+ # raise PPipeFatal.new("problem with lines: #{lines.inspect} (input was #{input.inspect}, @last_segment was #{@old_last_segment.inspect}); should have size greater than 0") unless lines.size > 0
1221
+ next unless lines.size > 0
1222
+ lines.each do |line|
1223
+ # packets =
1224
+ extra, message = Message.from_transmission(line)
1225
+ @my_end.puts extra if extra # extra will be something put into the pipe by the user, and not by PPipe
1226
+
1227
+ # messages.each do |message| #note intentional assignment, not equality test!!
1228
+ next unless message
1229
+ log 'v1', @mpn.to_s + ": " + line #if @print_messages
1230
+ if message.options[:evaluate]
1231
+ send(message.label, message.contents) if !message.tt or message.tt == tid
1232
+ else
1233
+ to_threads = (message.pop_tts or (Thread.ppipe_list_live_ids - (message.pop_ets or [])))
1234
+ to_threads.each do |id|
1235
+ @messages[id] ||= {}
1236
+ @messages[id][message.label] ||= {}
1237
+ @messages[id][message.label][message.fp] ||= {} #fp = from pipe number
1238
+ @messages[id][message.label][message.fp][message.ft] ||= []
1239
+
1240
+ @messages[id][message.label][message.fp][message.ft].push message
1241
+ end
1242
+ end
1243
+ # end
1244
+ end
1245
+ end
1246
+ @check_messages_mutex.unlock
1247
+ log 'v9c', :check_messages
1248
+ return @messages[tid]
1249
+ end
1250
+ private :check_messages
1251
+
1252
+ # returns Thread.current.object_id
1253
+
1254
+ def tid
1255
+ return Thread.current.object_id
1256
+ end
1257
+
1258
+ # When a new process is created, there is a delay before that new process has a pipe assigned to it in every other process. This method checks whether the new process (with pipe number equal to pipe_no) has had its pipe assigned in the calling process.
1259
+ #
1260
+ # pipe_no can be the pipe number of the calling process
1261
+
1262
+ def assigned?(pipe_no)
1263
+ raise PPipeFatal.new("tried to call assigned? for pipe #{pipe_no} when the highest pipe number is #{@pipes.size - 1} (remember pipe numbers are 0 based)") if pipe_no >= @pipes.size
1264
+ check_messages
1265
+ return @pipe_counter > pipe_no
1266
+ end
1267
+
1268
+ # args is a list of pipe numbers. Wait till Methods#assigned?(pipe_no) is true for each pipe_no.
1269
+ #
1270
+ # e.g.
1271
+ #
1272
+ # wait_till_assigned(2,5,9)
1273
+ #
1274
+ # or
1275
+ #
1276
+ # ppipe = PPipe.new(7, false)
1277
+ # pipe_numbers = ppipe.fork(6) # pipe_numbers is [1,2,3,4,5,6]
1278
+ # wait_till_assigned(*pipe_numbers)
1279
+ #
1280
+
1281
+ def wait_till_assigned(*args)
1282
+ args.each do |pipe_no|
1283
+ until assigned?(pipe_no)
1284
+ sleep 0.01
1285
+ end
1286
+ end
1287
+ end
1288
+
1289
+
1290
+ # <tt>i_send(label, contents, options={})
1291
+ #
1292
+ # i_send(message, options={})</tt>
1293
+ #
1294
+ # Immediate Send
1295
+ #
1296
+ # Send a message and don't wait for the destination pipe(s) to confirm they have received it (i.e. this call will return immediately)
1297
+ #
1298
+ # * <i>label</i> must be a Symbol, String or Integer
1299
+ # * <i>contents</i> can be any object where <tt>eval(contents.inspect) == contents</tt>, except for <tt>nil</tt> or <tt>false</tt>
1300
+ # * <i>message</i> must be a PPipe::Message
1301
+ # Possible options are:
1302
+ #
1303
+ # One of:
1304
+ #
1305
+ # * <i>tp</i> (Integer or Array): the pipe to send the message to or a list of pipes to send the message to
1306
+ # * <i>ep</i> (Integer or Array): a pipe <i>not</i> to send the message to or a list of pipes <i>not</i> to send the message to
1307
+ #
1308
+ # Note: if you don't specify tp, the message will be sent to every currently assigned pipe (excluding pipes in ep if it is specified) (think broadcast in standard MPI) . However, be warned: a new process that you have just created may not yet be known about yet in every other process. Therefore, if you send a message to every pipe just after you have called fork in a different process, the newly created process might not get the message. See Methods#wait_till_assigned.
1309
+ #
1310
+ # One of:
1311
+ #
1312
+ # * <i>tt</i> (Integer or Array): the thread id to send the message to or a list of thread ids to send the message to
1313
+ # * <i>et</i> (Integer or Array): a thread id <i>not</i> to send the message to or a list of thread ids <i>not</i> to send the message to #
1314
+ #
1315
+ # Note: if you specify neither, the message will be sent to every thread
1316
+ #
1317
+ # Any of:
1318
+ #
1319
+ # * <i>reject_unassigned</i> (true or false, default false): when a new process is created, its existence is not known immediately to every other process (a message is send out automatically and is processed at some point). If you send a message to a pipe that is currently unassigned to a process, PPipe will assume that you know such a process is about to be created, and wait till it has been informed that the new process has been created. If that process is never created, i_send will hang. You can change this behaviour by setting reject_unassigned to true. If you do, PPipe will return an error if the pipe is unassigned.
1320
+ # * <i>blocking</i> (true or false, default false): wait until the other process(es) or thread(s) have received the message. To make your code more clear, it is recommended that you do not use this option, and instead use Methods#w_send.
1321
+ # * <i>tc</i> (true or false, default false): If no tp or tps is specified, the message will be sent to every pipe, except the controller - unless tc == true. Not needed for normal use (used mostly for internal messages).
1322
+ #
1323
+ # e.g.
1324
+ # i_send(:trees, ['birch', 'pine'], tp: 5)
1325
+ #
1326
+ # i_send(:heroes, ['David', 'Perseus'], tp: [2,5,21], tt: 6346023, reject_unassigned: true)
1327
+ #
1328
+ # i_send(:greeting, 'Hello every other pipe')
1329
+ #
1330
+
1331
+ def i_send(*args)
1332
+ log 'fpv', :i_send
1333
+ @message_counter += 1
1334
+ message = nil
1335
+ case args.size
1336
+ when 3
1337
+
1338
+ message = Message.new(*args)
1339
+ when 2
1340
+ case args[0].class
1341
+ when PPipe::Message
1342
+ message = args[0]
1343
+ message.options[:fp] = nil; message.options[:ft] = nil
1344
+ args[1].each{|key, value| message.options[key] = value}
1345
+ else
1346
+ message = Message.new(*args)
1347
+ end
1348
+ when 1
1349
+ raise ArgumentError.new("Argument to i_send must be a PPipe::Message if only one argument provided") unless args[0].is_a_ppipe_message?
1350
+ message = args[0]
1351
+ message.options[:fp] = nil; message.options[:ft] = nil
1352
+ when 0
1353
+ raise ArgumentError.new("No argument supplied to i_send")
1354
+ end
1355
+ raise ArgumentError.new("Specifying fp or ft when sending messages is redundant; did you mean to specify tp or tt?") if message.fp or message.ft
1356
+ raise DeadParallelPipe unless @alive
1357
+ raise ArgumentError.new("Contents cannot be nil or false; label #{label}; pipe_no #{pipe_no}") unless message.contents
1358
+ log 'v9', "To pipe_no: " + message.tp.to_s
1359
+ broadcast = message.tps ? false : true
1360
+
1361
+ # to_pipes = message.tp ? (message.tp.class == Array ? message.tp : [message.tp]) : (0...@pipes.size).to_a
1362
+ message.options[:fp] = @mpn #fp = from pipe number
1363
+ message.options[:ft] = tid #ft = from thread
1364
+
1365
+ # $stderr.puts 'calling broadcast' if broadcast
1366
+ to_pipes = (message.pop_tps or ((0...@pipes.size).to_a - (message.pop_eps or [])))
1367
+ to_pipes.each do |pipe_no|
1368
+ next if @controller and pipe_no == @controller and broadcast and not message.options[:tc]
1369
+ if pipe_no == @mpn #tp = to pipe number
1370
+ if message.options[:evaluate]
1371
+ log 'v9', 'evaluating locally...'
1372
+ send(message.label, message.contents) if !message.tt or message.tt == tid
1373
+ else
1374
+
1375
+ to_threads = (message.tts or (Thread.ppipe_list_live_ids - (message.ets or [])))
1376
+ to_threads.each do |id|
1377
+ @messages[id] ||= {}
1378
+ @messages[id][message.label] ||= {}
1379
+ @messages[id][message.label][@mpn] ||= {}
1380
+
1381
+ @messages[id][message.label][@mpn][message.ft] ||= []
1382
+ @messages[id][message.label][@mpn][message.ft].push message
1383
+ end
1384
+ end
1385
+ elsif @pipes[pipe_no].class == IO
1386
+ begin
1387
+ # $stderr.puts message.class
1388
+ (message.options[:re] = @envelope; @envelope+=1) if message.blocking #re = return envelope
1389
+ log 'v2', "#@mpn->#{pipe_no}: #{message.to_transmission}"
1390
+ @pipes[pipe_no].print message.to_transmission
1391
+ if message.blocking
1392
+ if message.tts
1393
+ message.tts.each do |thread_id|
1394
+ w_recv(message.label + message.options[:re].to_s.to_sym, fp: pipe_no, ft: thread_id, timeout: message.options[:timeout])
1395
+ end
1396
+ else
1397
+ w_recv(message.label + message.options[:re].to_s.to_sym, fp: pipe_no, timeout: message.options[:timeout])
1398
+ end
1399
+
1400
+ # @messages[tid].delete(@messages[tid].key(label + no.to_s))
1401
+ end
1402
+
1403
+ rescue Errno::EPIPE
1404
+ pipe_finished(pipe_no)
1405
+ # i_send(:pipe_finished, pipe_no, evaluate: true, tp: @mpn)
1406
+ i_send(:pipe_finished, pipe_no, evaluate: true, tc: true)
1407
+ # @pipes[pipe_no] = nil; @pids.delete(pipe_no)
1408
+ raise DeadPipe.new("This pipe: #{pipe_no} is dead; probably the process it corresponds to has terminated or called die") unless broadcast
1409
+
1410
+ end
1411
+ elsif pipe_no >= @pipes.size
1412
+ raise PPipeFatal.new("Pipe #@mpn tried to send a message to pipe #{pipe_no} out of only #{@pipes.size} pipes")
1413
+ elsif @finished_pipes[pipe_no]
1414
+ raise DeadPipe.new("This pipe: #{pipe_no} is finished; probably the process it corresponds to has terminated or called die") unless broadcast
1415
+ elsif !@pipes[pipe_no]
1416
+ pipe_finished(pipe_no)
1417
+ i_send(:pipe_finished, pipe_no, evaluate: true, tc: true)
1418
+ raise DeadPipe.new("This pipe: #{pipe_no} is dead; probably the process it corresponds to has terminated") unless broadcast
1419
+ elsif @pipes[pipe_no].class == Array
1420
+ if message.options[:reject_unassigned]
1421
+ raise UnassignedPipe.new("Pipe #@mpn attempted to write to a pipe that has not yet been assigned to a process") unless broadcast
1422
+ elsif !broadcast
1423
+ sleep 0.001
1424
+ check_messages
1425
+ # $stderr.puts 'Houston, we would have had a problem'; exit
1426
+ redo
1427
+ end
1428
+ else
1429
+ raise PPipeFatal.new("Unknown error in i_send from pipe #@mpn: @pipes evaluted at the requested pipe_no is neither a pipe, nil or an array")
1430
+ end
1431
+ end
1432
+ log 'fpcv6', :i_send
1433
+ end
1434
+
1435
+ # Wait Send
1436
+ #
1437
+ # Calls i_send with <i>blocking</i> set to <tt>true</tt>.
1438
+ #
1439
+ # The call will not return until the other process(es) or thread(s) has received the message. Thus, doing a w_send, w_recv is a way of ensuring that two processes are at a given place at the same time.
1440
+ #
1441
+ # If you specify more than one pipe for a blocking message to be sent to, it will wait till <i>all</i> the destination processes have confirmed they have received it. (NB Specifying ep or neither ep or tp (see i_send) means that you are potentially sending the message to more than one process).
1442
+ #
1443
+ # With threads, it is slightly different. If you specify tt, it will wait till all the threads you specified have confirmed receiving the message. However, if you specify et or neither et or tt, it will return when the <i>first</i> thread confirms it has received it. This is so one doesn't have to specify tt every time a blocking message is sent (which would be tedious!).
1444
+
1445
+ def w_send(*args)
1446
+ message = nil
1447
+ case args.size
1448
+ when 3
1449
+ message = Message.new(*args)
1450
+ when 2
1451
+ case args[0].class
1452
+ when PPipe::Message
1453
+ message = args[0]
1454
+ args[1].each{|key, value| message.options[key] = value}
1455
+ else
1456
+ message = Message.new(*args)
1457
+ end
1458
+ when 1
1459
+ raise ArgumentError.new("Argument to w_send must be a PPipe::Message if only one argument provided") unless args[0].is_a_ppipe_message?
1460
+ message = args[0]
1461
+ when 0
1462
+ raise ArgumentError.new("No argument supplied to w_send")
1463
+ else
1464
+ raise ArgumentError.new("Number of arguments supplied to w_send should be 1, 2 or 3")
1465
+ end
1466
+ message.options[:blocking] = true
1467
+ # $stderr.puts message.inspect, message.class
1468
+ i_send(message)
1469
+ end
1470
+ # private :w_send
1471
+ # alias :w_send :w_send
1472
+
1473
+ # Try Receive
1474
+ #
1475
+ # Try to receive a message with the given <i>label</i>. If no message has arrived return <tt>nil</tt> immediately.
1476
+ #
1477
+ # Possible options are:
1478
+ #
1479
+ # One of:
1480
+ #
1481
+ # * <i>fp</i> (Integer): the pipe number the message is coming from
1482
+ # * <i>fps</i> (Array): a list of pipe numbers the message might come from
1483
+ #
1484
+ # Note: if you specify neither, the method will return the first message with the correct label from any pipe.
1485
+ #
1486
+ # One of:
1487
+ #
1488
+ # * <i>ft</i> (Integer): the thread id the message is coming from
1489
+ # * <i>fts</i> (Array): a list of thread ids the message might come from
1490
+ #
1491
+ # Note: if you specify neither, the method will return the first message with the correct label from any thread.
1492
+ #
1493
+
1494
+
1495
+
1496
+ def t_recv(label, options={}) # => nil or PPipe::Message
1497
+ ArgumentError.check([:label, label, [Symbol, String, Fixnum]], [:options, options, Hash])
1498
+
1499
+ raise DeadParallelPipe unless @alive
1500
+ # $stderr.puts options.class
1501
+ raise ArgumentError.new("Specifying tp or tt when receiving messages is redundant; did you mean to specify fp or ft?") if options[:tt] or options[:tp]
1502
+ if options[:fp]
1503
+ raise Ambiguity.new("specified fp and fps simultaneously") if options[:fps]
1504
+ fps = [options[:fp]]
1505
+ elsif options[:fps]
1506
+ fps = options[:fps]
1507
+ else
1508
+ fps = nil
1509
+ end
1510
+ if options[:ft]
1511
+ raise Ambiguity.new("specified fp and fps simultaneously") if options[:fts]
1512
+ fts = [options[:ft]]
1513
+ elsif options[:fts]
1514
+ fts = options[:fts]
1515
+ else
1516
+ fts = nil
1517
+ end
1518
+
1519
+ message = nil
1520
+ index = nil
1521
+
1522
+ check_messages
1523
+ # $stderr.puts @messages.inspect
1524
+ return nil unless @messages[tid] and @messages[tid][label]
1525
+ search_by_pipe = fps ? @messages[tid][label].values_at(*fps).compact : @messages[tid][label].values.compact
1526
+ search_by_pipe.each do |list|
1527
+ next unless list
1528
+ search_by_thread = fts ? list.values_at(*fts).compact : list.values.compact
1529
+ search_by_thread.each{|th_list| message = th_list.shift if th_list.size > 0}
1530
+ break if message
1531
+ end
1532
+
1533
+
1534
+ if message and message.blocking
1535
+ # $stderr.puts 'found blocking message', message.label
1536
+ loop do
1537
+ begin
1538
+ i_send((message.label+message.options[:re].to_s).to_sym, true, {tt: message.ft, tp: message.fp})
1539
+ break
1540
+ rescue UnassignedPipe
1541
+ log 'v3', 'rescued unassigned pipe in w_recv, retrying...'
1542
+ check_messages
1543
+ end
1544
+ end
1545
+ end
1546
+ return message
1547
+ end
1548
+
1549
+ #
1550
+ # Wait Receive
1551
+ #
1552
+ # Receive a message with the given <i>label</i>. Does not return until the message has been received.
1553
+ #
1554
+ # Possible options
1555
+ #
1556
+ # * timeout (seconds): raise a PPipe::RecvTimeout error if the message has not arrived after the give number of seconds
1557
+ # * refresh_time (seconds, default is 0.001): The time between rechecking to see if the message has arrived.
1558
+ #
1559
+ # For other possible <i>options</i>, see t_recv .
1560
+ #
1561
+ # NB: all this does is keep calling t_recv in a loop!
1562
+
1563
+ def w_recv(label, options={})
1564
+ ArgumentError.check([:label, label, [Symbol, String, Fixnum]], [:options, options, Hash])
1565
+ log 'fpv', :w_recv
1566
+ time = Time.now.to_f
1567
+ message = nil
1568
+ loop do
1569
+ # $stderr.puts "trying to get #{label.inspect} from #{options[:fp]}"
1570
+ message = t_recv(label, options)
1571
+ break if message
1572
+ raise RecvTimeout.new("breaking w_recv on timeout") if options[:timeout] and (Time.now.to_f > time + options[:timeout])
1573
+ sleep (0.001 or options[:refresh_time])
1574
+ end
1575
+ # $stderr.puts "got message #{message}"
1576
+ return message
1577
+ end
1578
+ # alias :w_recv :w_recv
1579
+
1580
+ # Immediate Receive
1581
+ #
1582
+ # Receive a message with the given <i>label</i>. Returns a PPipe::Message immediately. The message has an internal thread which keeps checking if the message has arrived.
1583
+ #
1584
+ # You can find out if the message has arrived by calling Message#arrived?. You can wait for the message to arrive by calling Message#join. You can stop the message checking by calling Message#kill.
1585
+ #
1586
+ # NB You cannot fork (in this process) until the message has arrived (or been killed) - otherwise two processes will be checking for the same message.
1587
+ #
1588
+ # See t_recv for possible options.
1589
+
1590
+ def i_recv(label, options={})
1591
+ th = Thread.ppipe_new do
1592
+ thread = Thread.current
1593
+ thread[:ppipe_message] = true
1594
+ thread[:label] = label
1595
+ thread[:message]= w_recv(label, options)
1596
+ end
1597
+ return Message.with_listening_thread(th)
1598
+ end
1599
+
1600
+ # Send something to the $stdin of the child process with pipe number pipe_no (if redirect is on). If pipe_no = nil call Kernel.puts. Can be called by any other process.
1601
+
1602
+ def puts(pipe_no, *args)
1603
+ raise PPipeFatal.new("calling this doesn't make any sense unless redirect is on") unless @redirect
1604
+ return Kernel.puts(*args) unless pipe_no
1605
+ @pipes[pipe_no].puts(*args)
1606
+ end
1607
+
1608
+ # Read all output from pipe. Return nil if there is no output. Never, ever blocks. (This calls a special function read_pipe which PPipe#Methods use to read pipes.)
1609
+ #
1610
+ # If no argument is given:
1611
+ # * if it is a child process or root process, read everything that has been put into its pipe.
1612
+ # * if it is the root process, in addition, reads everything that child pipes have written to their standard out (if redirect is on)
1613
+
1614
+
1615
+ def read_all(pipe=@user_end)
1616
+ check_messages
1617
+ return read_pipe(pipe)
1618
+ end
1619
+
1620
+ # Gets next line written to this process.
1621
+ # * if it is a child process or root process, read the next line that has been put into its pipe.
1622
+ # * if it is the root process, may read the next line that child pipes have written to their standard out (if redirect is on)
1623
+
1624
+
1625
+ def gets(sep = $/)
1626
+ check_messages
1627
+ until ans = get_line(@user_end, $/)
1628
+ check_messages
1629
+ sleep 0.001
1630
+ end
1631
+ return ans
1632
+ end
1633
+
1634
+ # If redirect is on, the $stdout of all other processes is connected to the root process. Any output from these processes ends up in a pipe called @user_end. So if this is the root ppipe then this method provides access to that output. This method also causes PPipe to process input, flushing any input that is not a PPipe message into @user_end.
1635
+ #
1636
+ # Notes:
1637
+ # * Only the root ppipe can call this function.
1638
+ # * This function will raise an error if redirect == false
1639
+ # * The pipe @user_end contains output from every other process, in no order
1640
+ #
1641
+ # If the PPipe has not yet processed the output from the child pipe, it will be stored internally in PPipe, and not in @user_end. Therefore a call such as
1642
+ #
1643
+ # ppipe.user_end.gets
1644
+ #
1645
+ # may block forever.
1646
+ #
1647
+ # It is recommended that instead something is written like:
1648
+ #
1649
+ # while input = ppipe.read_all(ppipe.user_end) # read_all never blocks
1650
+ # #... process input
1651
+ # end
1652
+ #
1653
+ def user_end
1654
+ raise PPipeFatal.new("redirect is off - calling user_end makes no sense") unless @redirect
1655
+ raise PPipeFatal.new("Only the root pipe can call user_end") unless @is_root
1656
+ check_messages
1657
+ @user_end
1658
+ end
1659
+
1660
+ # Return a hash of {pipe_number => pid} - allows you to know the pid that each pipe number corresponds to.
1661
+
1662
+
1663
+ def pids
1664
+ return @pids.dup
1665
+ end
1666
+
1667
+ # Wait for all other processes to either finish or call ppipe.die - see wait
1668
+ #
1669
+ # NB If more than one process calls waitall, both will wait for each other and hang forever!
1670
+
1671
+ def waitall #(wait_remote_child=false, *args)
1672
+ log 'fpv', 'waitall'
1673
+ raise DeadParallelPipe unless @alive
1674
+ # raise PPipeFatal.new("Only the root process (ppipe.is_root == true) can call waitall if there is no controller") unless @controller or @is_root
1675
+ # stop_controller if @controller
1676
+ loop do
1677
+ @pids.keys.each do |pipe_no|
1678
+ log 'v5', 'waiting for pipe_no', pipe_no
1679
+ must_wait = !(pipe_no == @mpn or (@controller and pipe_no == @controller))
1680
+ wait(pipe_no) if must_wait
1681
+ # must_wait
1682
+ # arr.push status if status
1683
+ end
1684
+ log 'iv8', '@pids = ', @pids
1685
+ check_messages
1686
+ break unless @pids.keys.find{|pipe_no| !(pipe_no == @mpn) and (!@controller or !(pipe_no == @controller))}
1687
+ end
1688
+ end
1689
+
1690
+ # Wait for another process (whose pipe number is pipe_no) to either call Methods#die or to exit (whichever is sooner).
1691
+ #
1692
+ # If the process did not call die before it finished, then it sees if the process is dead by checking if its pipe is broken. However this may not work on all operating systems (so it's much better to make every process call die before it exits).
1693
+
1694
+ def wait(pipe_no) #, wait_remote_child=false, *args)
1695
+ raise DeadParallelPipe unless @alive
1696
+ raise ControllerError.new("Attempted to wait for the controller (would never return). Use stop_controller if you want the controller to finish") if @controller and pipe_no == @controller
1697
+ raise SelfReference.new("Attempted to wait for self") if pipe_no == @mpn
1698
+ check_messages
1699
+ return if @finished_pipes[pipe_no]
1700
+ unless @pipes[pipe_no]
1701
+ # @finished_pipes.push[pipe_no]
1702
+ # @pids.delete(pipe_no)
1703
+ i_send(:pipe_finished, pipe_no, {evaluate: true, tc: true})
1704
+ return
1705
+ end
1706
+ begin
1707
+ loop do
1708
+ check_messages
1709
+ return if @finished_pipes[pipe_no]
1710
+ sleep 0.4
1711
+ @pipes[pipe_no].puts
1712
+ end
1713
+ rescue Errno::EPIPE
1714
+ # If the pipe is broken we deduce that the corresponding process has exited or called die
1715
+ i_send(:pipe_finished, pipe_no, evaluate: true, tc: true)
1716
+ # @finished_pipes.push[pipe_no]
1717
+ return
1718
+ end
1719
+ end
1720
+
1721
+ def pipe_finished(pipe_no)
1722
+ return if @finished_pipes[pipe_no]
1723
+ begin
1724
+ case @pipes[pipe_no]
1725
+ when Array
1726
+ @pipes[pipe_no][0].close
1727
+ @pipes[pipe_no][1].close
1728
+ when IO
1729
+ @pipes[pipe_no].close
1730
+ else
1731
+ end
1732
+ rescue
1733
+ end
1734
+ @pids.delete(pipe_no)
1735
+ @pipes[pipe_no] = nil
1736
+ @finished_pipes[pipe_no] = true
1737
+ end
1738
+ private :pipe_finished
1739
+
1740
+ # Close all pipes and terminate all communication with every other process. Send a message to every other process letting them know this process has terminated communication.
1741
+ #
1742
+ # It's good practice to call this in every process before the process exits.
1743
+ #
1744
+ # NB If you pass a block to Methods#fork, it will call die automatically when the block is finished, so die only needs to be called if the process exits midway through the block (consider using Methods#exit instead of Kernel.exit in this case)
1745
+
1746
+ def die
1747
+ raise DeadParallelPipe unless @alive
1748
+ i_send(:pipe_finished, @mpn, {evaluate: true, tc: true})
1749
+ @alive = false
1750
+ @pipes.each do |pipe|
1751
+ next unless pipe
1752
+ begin
1753
+ # $stderr.puts 'closing', i
1754
+ if pipe.class == IO
1755
+ pipe.close
1756
+ else
1757
+ pipe[0].close; pipe[1].close
1758
+ end
1759
+ rescue IOError, NoMethodError
1760
+ next
1761
+
1762
+ end
1763
+ end
1764
+ @pipes = nil
1765
+ @mutexes = nil
1766
+ @pid=nil
1767
+ end
1768
+
1769
+ # Calls Methods#die (if the ppipe is still alive) then calls Kernel.exit
1770
+
1771
+ def exit
1772
+ die if @alive
1773
+ Kernel.exit
1774
+ end
1775
+
1776
+ # Calls Methods#waitall, stops the controller if there is a controller and then calls Methods#die
1777
+
1778
+ def finish
1779
+ waitall
1780
+ stop_controller if @controller
1781
+ die
1782
+ # return statuses
1783
+ end
1784
+
1785
+
1786
+
1787
+ # Suddenly and violently terminate every process except the current one. Messy and unpredictable. An emergency measure only.
1788
+ #
1789
+ # Doesn't kill the controller if kill_controller is set to false.
1790
+
1791
+ def kill_all(kill_controller=@controller) #(force=false)
1792
+ # stop_controller if @controller and not force
1793
+ raise NoController.new("Tried to kill controller but no controller") if kill_controller and not @controller
1794
+ @pids.each do |pipe_no, pid|
1795
+ next if @controller and pipe_no == @controller
1796
+ begin
1797
+ kill_pipe(pipe_no) unless pid == Process.pid
1798
+ rescue DeadPipe
1799
+ next
1800
+ end
1801
+ end
1802
+ Process.kill('TERM', @pids[@controller]) if kill_controller
1803
+ # die
1804
+ end
1805
+
1806
+ # Suddenly terminate the process corresponding to pipe_no.
1807
+
1808
+ def kill_pipe(pipe_no, signal='TERM')
1809
+ raise ControllerError.new("Can't kill controller: use stop_controller instead") if @controller and pipe_no == @controller
1810
+ log '', 'killing pipe: ', pipe_no
1811
+ begin
1812
+ @pipes[pipe_no].puts
1813
+ rescue PPipe::DeadPipe
1814
+ raise DeadPipe.new("This pipe (#{pipe_no}) is already dead")
1815
+ end
1816
+ begin
1817
+ Process.kill(signal, @pids[pipe_no])
1818
+ rescue Errno::ESRCH
1819
+ raise DeadPipe.new("The process corresponding to this pipe (#{pipe_no}) is already dead")
1820
+ end
1821
+ pipe = @pipes[pipe_no]
1822
+ begin
1823
+ # $stderr.puts 'closing', i
1824
+ if pipe.class == IO
1825
+ pipe.close
1826
+ else
1827
+ pipe[0].close; pipe[1].close
1828
+ end
1829
+ rescue IOError, NoMethodError
1830
+ end
1831
+ @pipes[pipe_no] = nil
1832
+ @pids.delete(pipe_no)
1833
+ end
1834
+
1835
+ end #module Methods
1836
+
1837
+
1838
+ #
1839
+ # Quick Links: PPipe, parallelpipes.rb, PPipe::Methods, PPipe::Controller, PPipe::Message
1840
+ #
1841
+ #
1842
+ # The primary purpose of this module is to provide the methods for the class PPipe.
1843
+ # Therefore all the method documentation specifies having a PPipe as the receiver. However, it is perfectly possible to use this module as a module in its own right; see the section Modules in parallelpipes.rb
1844
+ #
1845
+ # For a general introduction to using the controller, see parallelpipes.rb
1846
+
1847
+
1848
+ module Controller
1849
+
1850
+ include Methods
1851
+
1852
+ # the pipe number of the controller (nil if there is no controller)
1853
+ attr_reader :controller
1854
+
1855
+ # Start a controller process. Raises an error if there already is one. controller_refresh is the delay between the controller dealing with requests. A shorter refresh time means the controller runs faster, but consumes more CPU (typically a controller consumes about 1-6% of CPU).
1856
+
1857
+ def start_controller(controller_refresh=0.001)
1858
+ raise DeadParallelPipe unless @alive
1859
+ raise ControllerError.new("This parallel pipe already has a controller") if @controller
1860
+ parent = [mpn, tid]
1861
+ @controller = fork do
1862
+ log 'v4', 'Started Controller...'
1863
+ @controller_refresh = controller_refresh
1864
+ @messages[tid] ||= {}
1865
+ @messages[tid][CON] = {}
1866
+ @controller_status = {}
1867
+ @resource_status = {}
1868
+ @resources = {}
1869
+ @resource_file_names = {}
1870
+ # @resource_accessed = {}
1871
+ log 'v5', 'commencing controller...'
1872
+ i_send(:controller_started, true, tp: parent[0], tt: parent[1])
1873
+ run_controller
1874
+ log 'v5', 'controller exiting...'
1875
+ end
1876
+ w_recv(:controller_started, fp: @controller)
1877
+ # broadcast(:set_controller, @controller, {evaluate: true})
1878
+ end
1879
+
1880
+ # Stop the controller. The controller sends out an exit warning to all processes. No new lock, synchronize, get_shared_resource or shared_resource resource commands can be issued once that warning has been received. However, the controller will not exit until all existing locks on mutexes have been unlocked and all shared_resources have been returned.
1881
+
1882
+ def stop_controller
1883
+ raise NoController unless @controller
1884
+ raise DeadParallelPipe unless @alive
1885
+ controller = @controller
1886
+ w_send(:exit, true, tp: @controller)
1887
+ w_recv(:exited, fp: controller)
1888
+ Process.wait(@pids[controller])
1889
+ @pids.delete(controller)
1890
+ # $stderr.puts '@controller is', @controller.inspect
1891
+ @controller = nil
1892
+ end
1893
+
1894
+ # Change the controller refresh time. controller_refresh is the delay between the controller dealing with requests. A shorter refresh time means the controller runs faster, but consumes more CPU (typically a controller consumes about 1-6% of CPU). Default is 0.001. There is no point having it smaller than 0.0001 - there is no improvement in performance.
1895
+ #
1896
+ # Benchmarks <i>(2GHz AMD 64 (32bit OS))</i>
1897
+ #
1898
+ # Time taken to lock and unlock a mutex twice
1899
+ #
1900
+ # controller_refresh : time taken
1901
+ #
1902
+ # * 0.1: 0.313996553421021
1903
+ # * 0.01: 0.132316589355469
1904
+ # * 0.001: 0.0158429145812988
1905
+ # * 0.0001: 0.00638794898986816
1906
+ # * 1.0e-05: 0.0312449932098389
1907
+ #
1908
+ # Footnote:
1909
+ #
1910
+ # Consuming 1-6% of CPU seems like a lot. However, PPipe is aiming to bring the multi-core, multi-process world to Ruby. Most new computers have at least two CPU cores nowadays, many have more and the number will rise in the future. 1-6% of 1 processor is not much when the computer has 4, 8 or 16 processors.
1911
+ #
1912
+ # Having said which, if someone finds a better way of doing what the controller does, please share it!
1913
+ #
1914
+ # And finally, if this overhead is unacceptable, PPipe run without a Controller (see Methods#set_up_pipes) is still a powerful parallelization tool.
1915
+
1916
+ def controller_refresh=(value)
1917
+ raise NoController unless @controller
1918
+ raise DeadParallelPipe unless @alive
1919
+ i_send(:set_controller_refresh, value, tp: @controller, evaluate: true)
1920
+ end
1921
+
1922
+ def set_controller_refresh(value)
1923
+ @controller_refresh = value
1924
+ end
1925
+ private :set_controller_refresh
1926
+
1927
+ def set_controller(value)
1928
+ @controller = (value == :off ? nil : value)
1929
+ end
1930
+ private :set_controller
1931
+
1932
+ CON = :controller_switch #_GHAkxsadf0a0s98
1933
+
1934
+ # Lock a mutex named mutex_name. If the mutex did not exist before, create it. No other process or thread can lock this mutex until it has been unlocked.
1935
+ #
1936
+ # mutex_name must be a symbol or an integer.
1937
+ #
1938
+ # Note, every different mutex_name corresponds to a different mutex. Thus, it is easy to create vast numbers of mutexes. However, there is a memory and processor overhead associated with each mutex. Although this is small, creating very large numbers of mutexes is not a good idea.
1939
+ #
1940
+ # Mutexes cannot be destroyed.
1941
+
1942
+ def lock(mutex_name)
1943
+ ArgumentError.check([:mutex_name, mutex_name, [Symbol, Integer, Fixnum]])
1944
+ log 'fpv', :lock
1945
+ # $stderr.puts "#@mpn thinks controller is #@controller"
1946
+ @thread_mutexes[mutex_name] ||= Mutex.new
1947
+ @thread_mutexes[mutex_name].lock
1948
+ check_messages
1949
+ raise NoController unless @controller
1950
+ raise DeadParallelPipe unless @alive
1951
+ # check_for_exit_signal
1952
+ @old_controllers ||= {}
1953
+ @old_controllers[mutex_name] = @controller
1954
+ i_send(CON, [mutex_name, :lock], tp: @controller)
1955
+ w_recv(mutex_name, fp: @controller)
1956
+ log 'fpvc', :lock
1957
+ end
1958
+
1959
+ # Unlock a mutex. See Controller#lock
1960
+ #
1961
+ # If the process and thread calling unlock did not previously lock the mutex, this call will hang forever.
1962
+
1963
+ def unlock(mutex_name)
1964
+ ArgumentError.check([:mutex_name, mutex_name, [Symbol, Integer, Fixnum]])
1965
+ log 'fpv', :unlock
1966
+ # check_messages
1967
+ raise NoController.new("Controller dead, or a lock call has not been issued with the name #{mutex_name} from this process") unless @old_controllers[mutex_name]
1968
+ raise DeadParallelPipe unless @alive
1969
+ i_send(CON, [mutex_name, :unlock], tp: @old_controllers[mutex_name])
1970
+ w_recv(mutex_name, fp: @old_controllers[mutex_name])
1971
+ @old_controllers.delete(mutex_name)
1972
+ @thread_mutexes[mutex_name].unlock
1973
+ log 'fpvc', :unlock
1974
+ end
1975
+
1976
+ # Lock the mutex, call the block and then unlock the mutex. See Controller#lock.
1977
+ #
1978
+ # ppipe.synchronize(:sentence){print 'a fragmented sen'; sleep rand; print "tence\n"}
1979
+ #
1980
+ # Is identical to:
1981
+ #
1982
+ # ppipe.lock(:sentence)
1983
+ # print 'a fragmented sen'; sleep rand; print "tence\n"
1984
+ # ppipe.unlock(:sentence)
1985
+
1986
+ def synchronize(mutex_name, &block)
1987
+ ArgumentError.check([:mutex_name, mutex_name, [Symbol, Integer, Fixnum]])
1988
+ log 'fpv', :synchronize
1989
+ raise DeadParallelPipe unless @alive
1990
+ lock(mutex_name)
1991
+ yield
1992
+ unlock(mutex_name)
1993
+ log 'fpvc', :synchronize
1994
+ end
1995
+
1996
+ # Is the mutex called <i>mutex_name</i> locked? Returns [pipe_no, thread_id] of the locking thread and process if locked, nil if it is not locked.
1997
+
1998
+ def mutex_locked?(mutex_name)
1999
+ ArgumentError.check([:mutex_name, mutex_name, [Symbol, Integer, Fixnum]])
2000
+ log 'fpv', :locked?
2001
+ check_messages
2002
+ raise NoController unless @controller
2003
+ raise DeadParallelPipe unless @alive
2004
+ i_send(CON, [mutex_name, :mutex_locked?], tp: @controller)
2005
+ ans = w_recv(mutex_name, fp: @controller)[0]
2006
+ log 'fpvc', :mutex_locked?
2007
+ return ans
2008
+ end
2009
+
2010
+ # Get a shared resource from the controller. No other process or thread can access the resource until this process has returned it.
2011
+ #
2012
+ # resource_name must be a Symbol or Integer
2013
+ #
2014
+ # A shared resource can be any object where
2015
+ #
2016
+ # eval(object.inspect) == object
2017
+ #
2018
+ # If the resource has not been accessed before it is initialized to nil.
2019
+ #
2020
+ # ppipe = PPipe.new(5, true)
2021
+ # savings = ppipe.get_shared_resource(:savings)
2022
+ #
2023
+ #
2024
+
2025
+ def get_shared_resource(resource_name)
2026
+ ArgumentError.check([:resource_name, resource_name, [Symbol, Integer, Fixnum]])
2027
+
2028
+ log 'fpv', :get_shared_resource
2029
+ @shared_resource_mutexes[resource_name] ||= Mutex.new
2030
+ @shared_resource_mutexes[resource_name].lock
2031
+ check_messages
2032
+ raise NoController unless @controller
2033
+ raise DeadParallelPipe unless @alive
2034
+ @old_resource_controllers ||= {}
2035
+ @old_resource_controllers[resource_name] = @controller
2036
+
2037
+ # check_for_exit_signal
2038
+ i_send(CON, [resource_name, :fetch], tp: @controller)
2039
+ # $stderr.puts 'request sent'
2040
+ ans = w_recv(resource_name, fp: @controller)
2041
+ # $stderr.puts 'got answer'
2042
+ log 'fpvc', :get_shared_resource
2043
+ return ans[0]
2044
+ end
2045
+
2046
+ # Return a shared resource.
2047
+ #
2048
+ # If the process and thread calling return_shared_resource did not previously call get_shared_resource, this call will hang forever.
2049
+ #
2050
+ # savings += 20.03
2051
+ # ppipe.return_shared_resource(:savings, savings)
2052
+
2053
+ def return_shared_resource(resource_name, resource)
2054
+ ArgumentError.check([:resource_name, resource_name, [Symbol, Integer, Fixnum]])
2055
+ log 'fpv', :return_shared_resource
2056
+
2057
+ # check_messages
2058
+ raise NoController unless @old_resource_controllers[resource_name]
2059
+ raise DeadParallelPipe unless @alive
2060
+ # check_for_exit_signal
2061
+ i_send(CON, [resource_name, resource], tp: @old_resource_controllers[resource_name])
2062
+ w_recv(resource_name, fp: @old_resource_controllers[resource_name])
2063
+ @old_resource_controllers.delete(resource_name)
2064
+ @shared_resource_mutexes[resource_name].unlock
2065
+ log 'fpvc', :return_shared_resource
2066
+ end
2067
+
2068
+ # Fetch a shared resource, edit it and then return it. The resource is passed as the parameter to the block.
2069
+ #
2070
+ # ppipe.shared_resource(:savings) do |savings|
2071
+ # savings += 102.96
2072
+ # # ... some other calculations
2073
+ # savings
2074
+ # end
2075
+ #
2076
+ # NB The shared resource to be returned <b> must be the last line</b> of the block
2077
+
2078
+ def shared_resource(resource_name, &block)
2079
+ ArgumentError.check([:resource_name, resource_name, [Symbol, Integer, Fixnum]])
2080
+ log 'fpv', :shared_resource
2081
+ resource = get_shared_resource(resource_name)
2082
+ resource = yield(resource)
2083
+ return_shared_resource(resource_name, resource)
2084
+ log 'fpvc', :shared_resource
2085
+ end
2086
+
2087
+ # Is the shared resource called <i>resource_name</i> locked? Returns [pipe_no, thread_id] of the locking thread and process if locked, nil if it is not locked.
2088
+
2089
+ def resource_locked?(resource_name)
2090
+ ArgumentError.check([:resource_name, resource_name, [Symbol, Integer, Fixnum]])
2091
+ log 'fpv', :locked?
2092
+ check_messages
2093
+ raise NoController unless @controller
2094
+ raise DeadParallelPipe unless @alive
2095
+ i_send(CON, [resource_name, :resource_locked?], tp: @controller)
2096
+ ans = w_recv(resource_name, fp: @controller)[0]
2097
+ log 'fpvc', :resource_locked?
2098
+ return ans
2099
+ end
2100
+
2101
+
2102
+ # Maintain the shared resource automatically in the file <i>file_name</i>.
2103
+ #
2104
+ # If the file <i>file_name</i> does not exist, it will be created the first time the shared resource is accessed, and it will be updated at every subsequent access. If the file <i>file_name</i> exists (e.g. from a previous run of the program), it will be reloaded. Thus, the shared resource becomes a persistant object: it is remembered from one execution to the next.
2105
+ #
2106
+ # auto_load_save must be called before any access to the shared resource.
2107
+ #
2108
+ # ppipe.auto_load_save(:savings, 'my_savings_file.txt')
2109
+ #
2110
+ def auto_load_save(resource_name, file_name)
2111
+ ArgumentError.check([:resource_name, resource_name, [Symbol, Integer, Fixnum]])
2112
+ log 'fpv', :auto_load_save
2113
+ raise NoController unless @controller
2114
+ raise DeadParallelPipe unless @alive
2115
+ i_send(:auto_load_save, [resource_name, file_name], tp: @controller)
2116
+ reply = w_recv(:auto_load_save_ + resource_name, fp: @controller)
2117
+ raise ControllerError.new("if auto_load_save is called it must be before any access to a shared resource (#{resource_name})") if reply == :already_accessed
2118
+ raise ControllerError.new("another thread or process has already called auto_load_save for #{resource_name}") if reply == :already_opened
2119
+ log 'fpvc', :auto_load_save
2120
+ end
2121
+
2122
+ # If the controller will not be need for a while, put it to sleep for <i>sleep_time</i> seconds. If <i>wait_for_waking</i> is false, wake up automatically. If it is a number, check for a wake up call every <i>wait_for_waiting</i> seconds.
2123
+ #
2124
+ # Experimental.
2125
+
2126
+ def put_controller_to_sleep(sleep_time, wait_for_waking)
2127
+ raise NoController unless @controller
2128
+ raise DeadParallelPipe unless @alive
2129
+ w_send(:sleep, [sleep_time, wait_for_waking], tp: @controller)
2130
+ end
2131
+
2132
+ # Wake up the controller
2133
+ #
2134
+ # Experimental
2135
+
2136
+ def wake_controller
2137
+ raise NoController unless @controller
2138
+ raise DeadParallelPipe unless @alive
2139
+ w_send(:wake, true, tp: @controller)
2140
+ end
2141
+
2142
+ # Returns true if the controller is awake, false if it is asleep. Currently hangs if the controller has not been told to sleep.
2143
+ #
2144
+ # Experimental
2145
+
2146
+ def controller_woken?(wait_till_woken=false)
2147
+ #blocks unless controller has been put to sleep. Returns true immediately if controller is asleep. Returns false immediately if controller has been put to sleep and woken
2148
+ raise NoController unless @controller
2149
+ raise DeadParallelPipe unless @alive
2150
+ check_messages
2151
+ while @messages[tid][:controller_sleep_switch][@controller].values[0].size > 2
2152
+ 2.times{@messages[tid][:controller_sleep_switch][@controller].values[0].shift}
2153
+ end
2154
+ unless @controller_asleep
2155
+ w_recv(:controller_sleep_switch, fp: @controller)
2156
+ @controller_asleep = true
2157
+ else
2158
+ if wait_till_woken
2159
+ @controller_asleep = w_recv(:controller_sleep_switch, fp: @controller)
2160
+ else
2161
+ @controller_asleep = !t_recv(:controller_sleep_switch, fp: @controller)
2162
+ end
2163
+ end
2164
+ end
2165
+
2166
+ # Is there a controller?
2167
+
2168
+ def controller_alive?
2169
+ check_messages
2170
+ return @controller ? true : false
2171
+ end
2172
+
2173
+ def run_controller
2174
+ log 'fpv', 'run_controller'
2175
+ exit_caller = false; sent_exit = false
2176
+ loop do
2177
+ log 'v9', 'controller up and running'
2178
+
2179
+ reply = t_recv(:sleep)
2180
+ if reply
2181
+ i_send(:controller_sleep_switch, true)
2182
+ initial_sleep_time = reply[0]
2183
+ # $stderr.puts :initial_sleep_time, initial_sleep_time
2184
+ sleep initial_sleep_time
2185
+ if reply[1]
2186
+ loop do
2187
+ break if t_recv(:wake)
2188
+ sleep reply[1]
2189
+ end
2190
+ end
2191
+ i_send(:controller_sleep_switch, true)
2192
+ end
2193
+ reply = t_recv(:exit)
2194
+ exit_caller = reply.fp if reply
2195
+ if exit_caller
2196
+ if sent_exit and (@controller_status.values + @resource_status.values).inject(true){|bool, value| bool and !value} #ie no resources are currently locked
2197
+ w_send(:exited, true, tp: exit_caller)
2198
+ Kernel.exit
2199
+ elsif not sent_exit
2200
+ i_send(:set_controller, :off, evaluate: true); sleep 0.01
2201
+ sent_exit = true
2202
+ elsif sent_exit
2203
+ log 'v9', (@controller_status.values + @resource_status.values).inspect
2204
+ end
2205
+ end
2206
+ check_messages
2207
+ @messages[tid][:auto_load_save] ||= {}
2208
+ @messages[tid][:auto_load_save].each do |pipe_no, thread_list|
2209
+ thread_list.each do |thread_no, requests|
2210
+ requests.each do |message|
2211
+ options = message.options
2212
+ name, file_name = message.contents
2213
+ if @resource_file_names[name]
2214
+ i_send(:auto_load_save_ + name, :already_opened, tp: pipe_no, tt: thread_no)
2215
+ elsif @resources.keys.include? name
2216
+ i_send(:auto_load_save_ + name, :already_accessed, tp: pipe_no, tt: thread_no)
2217
+ else
2218
+ @resource_file_names[name] = file_name
2219
+ @resources[name] = eval(File.read(file_name)) if FileTest.exist?(file_name)
2220
+ i_send(:auto_load_save_ + name, :loaded, {tp: pipe_no, tt: thread_no}) #warning - this will cause the same problems as i_send later in controller - fix!
2221
+ end
2222
+ end
2223
+ end
2224
+ end
2225
+ @messages[tid][:auto_load_save] = {}
2226
+ @messages[tid][CON].each do |pipe_no, thread_list|
2227
+ thread_list.each do |thread_no, requests|
2228
+ @messages[tid][CON][pipe_no][thread_no] = []
2229
+ # ungranted_requests = []
2230
+ requests.each do |message|
2231
+ options = message.options
2232
+ name, request = message.contents
2233
+ request_granted = false
2234
+ # @rescued_messages ||=[]
2235
+ # log 'pv3', "considering #{name}, #{request} from #{pipe_no}, #{thread_no}" if @rescued_messages.include? message
2236
+ log 'pv9', "considering #{name}, #{request} from #{pipe_no}, #{thread_no}"
2237
+ # @controller_status[name] = :unlocked unless @controller_status[name]
2238
+ case request
2239
+ when :lock
2240
+ unless @controller_status[name]
2241
+ # $stderr.puts "locking #{name} from #{pipe_no}"
2242
+ @controller_status[name] = [pipe_no, thread_no]
2243
+ request_granted = true
2244
+ end
2245
+ when :unlock
2246
+ if @controller_status[name] == [pipe_no, thread_no] or (@controller_status[name] and @weak_synchonization)
2247
+ @controller_status[name] = nil
2248
+ request_granted = true
2249
+ end
2250
+ when :mutex_locked?
2251
+ request_granted = [@controller_status[name]]
2252
+ when :fetch
2253
+ unless @resource_status[name]
2254
+ # $stderr.puts "#@mpn thinks controller is #@controller"
2255
+
2256
+ # $stderr.puts "considering fetch: #{name}"
2257
+ @resource_status[name] = [pipe_no, thread_no]
2258
+ @resources[name] ||= nil
2259
+ request_granted = [(@resources[name])]
2260
+ # $stderr.puts request_granted.inspect
2261
+ end
2262
+ when :resource_locked?
2263
+ request_granted = [@resource_status[name]]
2264
+ else #a shared resource is being returned
2265
+ if @resource_status[name] == [pipe_no, thread_no] or (@resource_status[name] and @weak_synchonization)
2266
+ @resource_status[name] = nil
2267
+ @resources[name] = request
2268
+ File.open(@resource_file_names[name], 'w'){|f| f.puts @resources[name].inspect} if @resource_file_names[name]
2269
+ request_granted = true
2270
+ end
2271
+ # else
2272
+ # raise PPipeFatal.new("Bad request to controller: #{request.inspect}")
2273
+ end
2274
+ if request_granted
2275
+ # $stderr.puts 'granted to', pipe_no, request_granted
2276
+ # loop do
2277
+ # begin
2278
+ i_send(name, request_granted, tp: pipe_no, tt: thread_no)
2279
+ # break
2280
+ # rescue UnassignedPipe #the request has come from a new pipe before the controller received the news about the new pipe's existence (the message :increase_pipe_counter)
2281
+ # @messages[tid][CON][pipe_no][thread_no].push message
2282
+ # check_messages
2283
+ # log 'v3', "rescued unassigned pipe...", "@messages is now... " + @messages.inspect
2284
+ # @rescued_messages.push message
2285
+ # ungranted_requests.push message
2286
+
2287
+ # end
2288
+ # end
2289
+ # w_send(0, :random, true)
2290
+ # $stderr.puts 'reply sent'
2291
+ else
2292
+ @messages[tid][CON][pipe_no][thread_no].push message
2293
+ # ungranted_requests.push message
2294
+ end
2295
+ end
2296
+ # @messages[tid][CON][pipe_no][thread_no] = ungranted_requests
2297
+ end
2298
+ end
2299
+ sleep @controller_refresh
2300
+ end
2301
+ end
2302
+ private :run_controller
2303
+
2304
+ end #module Controller
2305
+
2306
+
2307
+
2308
+
2309
+
2310
+ #
2311
+ include Log
2312
+ include Reader
2313
+ include Methods
2314
+ include Controller
2315
+ include MethodMissing
2316
+
2317
+ end
2318
+
2319
+
2320
+
2321
+
2322
+