parallelpipes 1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/parallelpipes.rb +2322 -0
- 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
|
+
|