riser 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,631 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'drb/drb'
4
+ require 'drb/ssl'
5
+ require 'drb/unix'
6
+ require 'forwardable'
7
+
8
+ module Riser
9
+ DRbService = Struct.new(:front, :at_fork, :preprocess, :postprocess, :ref) # :nodoc:
10
+ DRbService::NO_CALL = proc{|front| } # :nodoc:
11
+
12
+ class DRbServiceFront
13
+ def initialize
14
+ @mutex = Thread::Mutex.new
15
+ @services = {}
16
+ end
17
+
18
+ def ping
19
+ yield
20
+ end
21
+
22
+ def add_service(name, front)
23
+ @services[name] = DRbService.new(front, DRbService::NO_CALL, DRbService::NO_CALL, DRbService::NO_CALL)
24
+ nil
25
+ end
26
+
27
+ def at_fork(name, &block) # :yields: service_front
28
+ @services[name].at_fork = block
29
+ nil
30
+ end
31
+
32
+ def preprocess(name, &block) # :yields: service_front
33
+ @services[name].preprocess = block
34
+ end
35
+
36
+ def postprocess(name, &block) # :yields: service_front
37
+ @services[name].postprocess = block
38
+ nil
39
+ end
40
+
41
+ def apply_at_fork
42
+ @services.each_value do |service|
43
+ service.at_fork.call(service.front)
44
+ end
45
+
46
+ nil
47
+ end
48
+
49
+ def apply_service_hooks_by_name(pos, name_list)
50
+ if (pos < name_list.length) then
51
+ name = name_list[pos]
52
+ service = @services[name]
53
+ service.preprocess.call(service.front)
54
+ begin
55
+ apply_service_hooks_by_name(pos + 1, name_list) {
56
+ yield
57
+ }
58
+ ensure
59
+ service.postprocess.call(service.front)
60
+ end
61
+ else
62
+ yield
63
+ end
64
+ end
65
+ private :apply_service_hooks_by_name
66
+
67
+ def apply_service_hooks
68
+ apply_service_hooks_by_name(0, @services.keys) {
69
+ yield
70
+ }
71
+ end
72
+
73
+ def get_service(name)
74
+ @mutex.synchronize{
75
+ @services[name].ref ||= DRbObject.new(@services[name].front)
76
+ }
77
+ end
78
+ end
79
+
80
+ DRbProcess = Struct.new(:uri, :config, :latch_write_io, :pid) # :nodoc:
81
+
82
+ class DRbServiceServer
83
+ extend Forwardable
84
+
85
+ def initialize
86
+ @front = DRbServiceFront.new
87
+ @druby_process_list = []
88
+ end
89
+
90
+ def_delegators :@front, :add_service, :at_fork, :preprocess, :postprocess
91
+
92
+ def add_druby_process(uri, config={})
93
+ @druby_process_list << DRbProcess.new(uri, config)
94
+ nil
95
+ end
96
+
97
+ def start
98
+ @druby_process_list.each_with_index do |drb_process, pos|
99
+ latch_read_io, latch_write_io = IO.pipe
100
+ pid = Process.fork{
101
+ latch_write_io.close
102
+ pos.times do |i|
103
+ @druby_process_list[i].latch_write_io.close
104
+ end
105
+ @front.apply_at_fork
106
+ @front.apply_service_hooks{
107
+ DRb.start_service(drb_process.uri, @front, drb_process.config)
108
+ while (latch_read_io.gets) # wait
109
+ end
110
+ }
111
+ }
112
+ latch_read_io.close
113
+ drb_process.latch_write_io = latch_write_io
114
+ drb_process.pid = pid
115
+ end
116
+
117
+ nil
118
+ end
119
+
120
+ # for forked child process
121
+ def detach
122
+ for drb_process in @druby_process_list
123
+ drb_process.latch_write_io.close
124
+ end
125
+
126
+ nil
127
+ end
128
+
129
+ def wait
130
+ for drb_process in @druby_process_list
131
+ Process.waitpid(drb_process.pid)
132
+ end
133
+
134
+ nil
135
+ end
136
+
137
+ def stop
138
+ detach
139
+ wait
140
+ end
141
+ end
142
+
143
+ DRbCall = Struct.new(:there, :service_ref) # :nodoc:
144
+ DRbAnyProcessService = Struct.new(:process_type, :callable) # :nodoc:
145
+ DRbSingleProcessService = Struct.new(:process_type, :callable, :index) # :nodoc:
146
+ DRbStickyProcessService = Struct.new(:process_type, :callable) # :nodoc:
147
+
148
+ class DRbServiceCall
149
+ def initialize
150
+ @mutex = Thread::Mutex.new
151
+ @druby_call_list = []
152
+ @single_process_service_count = 0
153
+ @services = {}
154
+ @random = nil
155
+ end
156
+
157
+ def add_druby_call(uri)
158
+ @druby_call_list << DRbCall.new(DRbObject.new_with_uri(uri), {})
159
+ nil
160
+ end
161
+
162
+ def add_any_process_service(name, callable=false)
163
+ @services[name] = DRbAnyProcessService.new(:any, callable)
164
+ nil
165
+ end
166
+
167
+ def add_single_process_service(name, callable=false)
168
+ @services[name] = DRbSingleProcessService.new(:single, callable, @single_process_service_count)
169
+ @single_process_service_count += 1
170
+ nil
171
+ end
172
+
173
+ def add_sticky_process_service(name, callable=false)
174
+ @services[name] = DRbStickyProcessService.new(:sticky, callable)
175
+ nil
176
+ end
177
+
178
+ def self.is_callable(type_or_object)
179
+ case (type_or_object)
180
+ when Class
181
+ object_type = type_or_object
182
+ object_type.method_defined? :call
183
+ else
184
+ object = type_or_object
185
+ object.respond_to? :call
186
+ end
187
+ end
188
+
189
+ def add_any_process_service_with_type(name, type_or_object)
190
+ add_any_process_service(name, DRbServiceCall.is_callable(type_or_object))
191
+ nil
192
+ end
193
+
194
+ def add_single_process_service_with_type(name, type_or_object)
195
+ add_single_process_service(name, DRbServiceCall.is_callable(type_or_object))
196
+ nil
197
+ end
198
+
199
+ def add_sticky_process_service_with_type(name, type_or_object)
200
+ add_sticky_process_service(name, DRbServiceCall.is_callable(type_or_object))
201
+ nil
202
+ end
203
+
204
+ def druby_ping(timeout_seconds)
205
+ t0 = Time.now
206
+ if (timeout_seconds > 0.1) then
207
+ dt = 0.1
208
+ else
209
+ dt = timeout_seconds * 0.1
210
+ end
211
+
212
+ for druby_call in @druby_call_list
213
+ begin
214
+ druby_call.there.ping{ 'pong' }
215
+ rescue DRb::DRbConnError
216
+ if (Time.now - t0 >= timeout_seconds) then
217
+ raise
218
+ else
219
+ sleep(dt)
220
+ end
221
+ retry
222
+ end
223
+ end
224
+
225
+ nil
226
+ end
227
+
228
+ def start(timeout_seconds=30, local_druby_uri=Riser::TemporaryPath.make_drbunix_uri, config={ UNIXFileMode: 0600 })
229
+ @random = Random.new
230
+ unless (DRb.primary_server) then
231
+ DRb.start_service(local_druby_uri, nil, config)
232
+ end
233
+ druby_ping(timeout_seconds)
234
+
235
+ nil
236
+ end
237
+
238
+ def get_druby_service(name, index)
239
+ druby_call = @druby_call_list[index]
240
+ @mutex.synchronize{
241
+ druby_call.service_ref[name] ||= druby_call.there.get_service(name)
242
+ }
243
+ end
244
+ private :get_druby_service
245
+
246
+ def get_any_process_service(name)
247
+ get_druby_service(name, @mutex.synchronize{ @random.rand(@druby_call_list.length) })
248
+ end
249
+ private :get_any_process_service
250
+
251
+ def get_single_process_service(name)
252
+ get_druby_service(name, @services[name].index % @druby_call_list.length)
253
+ end
254
+ private :get_single_process_service
255
+
256
+ def get_sticky_process_service(name, stickiness_key)
257
+ get_druby_service(name, stickiness_key.hash % @druby_call_list.length)
258
+ end
259
+ private :get_sticky_process_service
260
+
261
+ def get_service(name, *optional)
262
+ if (@services.key? name) then
263
+ case (@services[name].process_type)
264
+ when :any
265
+ get_any_process_service(name, *optional)
266
+ when :single
267
+ get_single_process_service(name, *optional)
268
+ when :sticky
269
+ get_sticky_process_service(name, *optional)
270
+ else
271
+ raise "internal error: (service_name,process_type)=(#{name},#{@services[name].process_type})"
272
+ end
273
+ else
274
+ raise KeyError, "not found a service: #{name}"
275
+ end
276
+ end
277
+
278
+ def call_any_process_service(name, *optional, &block)
279
+ get_any_process_service(name).call(*optional, &block)
280
+ end
281
+ private :call_any_process_service
282
+
283
+ def call_single_process_service(name, *optional, &block)
284
+ get_single_process_service(name).call(*optional, &block)
285
+ end
286
+ private :call_single_process_service
287
+
288
+ def call_sticky_process_service(name, stickiness_key, *optional, &block)
289
+ get_sticky_process_service(name, stickiness_key).call(stickiness_key, *optional, &block)
290
+ end
291
+ private :call_sticky_process_service
292
+
293
+ def call_service(name, *optional, &block)
294
+ if (@services.key? name) then
295
+ case (@services[name].process_type)
296
+ when :any
297
+ call_any_process_service(name, *optional, &block)
298
+ when :single
299
+ call_single_process_service(name, *optional, &block)
300
+ when :sticky
301
+ call_sticky_process_service(name, *optional, &block)
302
+ else
303
+ raise "internal error: (service_name,process_type)=(#{name},#{@services[name].process_type})"
304
+ end
305
+ else
306
+ raise KeyError, "not found a service: #{name}"
307
+ end
308
+ end
309
+
310
+ def [](name, *optional, &block)
311
+ if (@services.key? name) then
312
+ if (@services[name].callable) then
313
+ call_service(name, *optional, &block)
314
+ else
315
+ get_service(name, *optional)
316
+ end
317
+ else
318
+ raise KeyError, "not found a service: #{name}"
319
+ end
320
+ end
321
+ end
322
+
323
+ LocalService = Struct.new(:front, :preprocess, :postprocess, :process_type, :callable) # :nodoc:
324
+
325
+ class LocalServiceServerClient
326
+ def initialize
327
+ @services = {}
328
+ @hook_thread = nil
329
+ @mutex = Thread::Mutex.new
330
+ @state = nil
331
+ @state_cond = Thread::ConditionVariable.new
332
+ @stop = false
333
+ @stop_cond = Thread::ConditionVariable.new
334
+ end
335
+
336
+ def add_service(name, front)
337
+ @services[name] = LocalService.new(front, DRbService::NO_CALL, DRbService::NO_CALL)
338
+ nil
339
+ end
340
+
341
+ def preprocess(name, &block) # :yields: service_front
342
+ @services[name].preprocess = block
343
+ nil
344
+ end
345
+
346
+ def postprocess(name, &block) # :yields: service_front
347
+ @services[name].postprocess = block
348
+ nil
349
+ end
350
+
351
+ def apply_service_hooks_by_name(pos, name_list)
352
+ if (pos < name_list.length) then
353
+ name = name_list[pos]
354
+ service = @services[name]
355
+ service.preprocess.call(service.front)
356
+ begin
357
+ apply_service_hooks_by_name(pos + 1, name_list) {
358
+ yield
359
+ }
360
+ ensure
361
+ service.postprocess.call(service.front)
362
+ end
363
+ else
364
+ yield
365
+ end
366
+ end
367
+ private :apply_service_hooks_by_name
368
+
369
+ def apply_service_hooks
370
+ apply_service_hooks_by_name(0, @services.keys) {
371
+ yield
372
+ }
373
+ end
374
+ private :apply_service_hooks
375
+
376
+ def add_any_process_service(name, callable=false)
377
+ @services[name].process_type = :any
378
+ @services[name].callable = callable
379
+ nil
380
+ end
381
+
382
+ def add_single_process_service(name, callable=false)
383
+ @services[name].process_type = :single
384
+ @services[name].callable = callable
385
+ nil
386
+ end
387
+
388
+ def add_sticky_process_service(name, callable=false)
389
+ @services[name].process_type = :sticky
390
+ @services[name].callable = callable
391
+ nil
392
+ end
393
+
394
+ def add_any_process_service_with_type(name, type_or_object)
395
+ add_any_process_service(name, DRbServiceCall.is_callable(type_or_object))
396
+ nil
397
+ end
398
+
399
+ def add_single_process_service_with_type(name, type_or_object)
400
+ add_single_process_service(name, DRbServiceCall.is_callable(type_or_object))
401
+ nil
402
+ end
403
+
404
+ def add_sticky_process_service_with_type(name, type_or_object)
405
+ add_sticky_process_service(name, DRbServiceCall.is_callable(type_or_object))
406
+ nil
407
+ end
408
+
409
+ def get_any_process_service(name)
410
+ @services[name].front
411
+ end
412
+ private :get_any_process_service
413
+
414
+ def get_single_process_service(name)
415
+ @services[name].front
416
+ end
417
+ private :get_single_process_service
418
+
419
+ def get_sticky_process_service(name, stickiness_key)
420
+ @services[name].front
421
+ end
422
+ private :get_sticky_process_service
423
+
424
+ def get_service(name, *optional)
425
+ if (@services.key? name) then
426
+ case (@services[name].process_type)
427
+ when :any
428
+ get_any_process_service(name, *optional)
429
+ when :single
430
+ get_single_process_service(name, *optional)
431
+ when :sticky
432
+ get_sticky_process_service(name, *optional)
433
+ else
434
+ raise "internal error: (service_name,process_type)=(#{name},#{@services[name].process_type})"
435
+ end
436
+ else
437
+ raise KeyError, "not found a service: #{name}"
438
+ end
439
+ end
440
+
441
+ def call_any_process_service(name, *optional, &block)
442
+ get_any_process_service(name).call(*optional, &block)
443
+ end
444
+ private :call_any_process_service
445
+
446
+ def call_single_process_service(name, *optional, &block)
447
+ get_single_process_service(name).call(*optional, &block)
448
+ end
449
+ private :call_single_process_service
450
+
451
+ def call_sticky_process_service(name, stickiness_key, *optional, &block)
452
+ get_sticky_process_service(name, stickiness_key).call(stickiness_key, *optional, &block)
453
+ end
454
+ private :call_sticky_process_service
455
+
456
+ def call_service(name, *optional, &block)
457
+ if (@services.key? name) then
458
+ case (@services[name].process_type)
459
+ when :any
460
+ call_any_process_service(name, *optional, &block)
461
+ when :single
462
+ call_single_process_service(name, *optional, &block)
463
+ when :sticky
464
+ call_sticky_process_service(name, *optional, &block)
465
+ else
466
+ raise "internal error: (service_name,process_type)=(#{name},#{@services[name].process_type})"
467
+ end
468
+ else
469
+ raise KeyError, "not found a service: #{name}"
470
+ end
471
+ end
472
+
473
+ def [](name, *optional, &block)
474
+ if (@services.key? name) then
475
+ if (@services[name].callable) then
476
+ call_service(name, *optional, &block)
477
+ else
478
+ get_service(name, *optional)
479
+ end
480
+ else
481
+ raise KeyError, "not found a service: #{name}"
482
+ end
483
+ end
484
+
485
+ def start_server
486
+ @hook_thread = Thread.new{
487
+ begin
488
+ apply_service_hooks{
489
+ @mutex.synchronize{
490
+ @state = :do
491
+ @state_cond.signal
492
+ }
493
+ @mutex.synchronize{
494
+ until (@stop)
495
+ @stop_cond.wait(@mutex)
496
+ end
497
+ }
498
+ }
499
+ ensure
500
+ @mutex.synchronize{
501
+ @state = :done
502
+ @state_cond.signal
503
+ }
504
+ end
505
+ }
506
+
507
+ nil
508
+ end
509
+
510
+ def start_client
511
+ @mutex.synchronize{
512
+ while (@state.nil?)
513
+ @state_cond.wait(@mutex)
514
+ end
515
+ }
516
+
517
+ # If the `do' state is skipped, propagate the error because an
518
+ # error occurred.
519
+ if (@mutex.synchronize{ @state } == :done) then
520
+ @hook_thread.join
521
+ end
522
+
523
+ nil
524
+ end
525
+
526
+ def stop_server
527
+ if (@mutex.synchronize{ @state } == :do) then
528
+ @mutex.synchronize{
529
+ @stop = true
530
+ @stop_cond.signal
531
+ }
532
+ @hook_thread.join
533
+ end
534
+
535
+ nil
536
+ end
537
+
538
+ def get_server
539
+ LocalServiceServer.new(self)
540
+ end
541
+
542
+ def get_client
543
+ LocalServiceCall.new(self)
544
+ end
545
+
546
+ def self.make_pair
547
+ server_client = new
548
+ return server_client.get_server, server_client.get_client
549
+ end
550
+ end
551
+
552
+ class LocalServiceServer
553
+ extend Forwardable
554
+
555
+ def initialize(services)
556
+ @services = services
557
+ end
558
+
559
+ def at_fork(name) # dummy
560
+ end
561
+
562
+ def detach # dummy
563
+ end
564
+
565
+ def_delegators :@services, :add_service, :preprocess, :postprocess
566
+ def_delegator :@services, :start_server, :start
567
+ def_delegator :@services, :stop_server, :stop
568
+ end
569
+
570
+ class LocalServiceCall
571
+ extend Forwardable
572
+
573
+ def initialize(services)
574
+ @services = services
575
+ end
576
+
577
+ def_delegators :@services, :add_any_process_service, :add_single_process_service, :add_sticky_process_service
578
+ def_delegators :@services, :add_any_process_service_with_type, :add_single_process_service_with_type, :add_sticky_process_service_with_type
579
+ def_delegator :@services, :start_client, :start
580
+ def_delegators :@services, :get_service, :call_service, :[]
581
+ end
582
+
583
+ class DRbServices
584
+ extend Forwardable
585
+
586
+ def initialize(druby_process_num=0)
587
+ if (druby_process_num > 0) then
588
+ @server = DRbServiceServer.new
589
+ @call = DRbServiceCall.new
590
+ druby_process_num.times do
591
+ drb_uri = Riser::TemporaryPath.make_drbunix_uri
592
+ @server.add_druby_process(drb_uri, UNIXFileMode: 0600)
593
+ @call.add_druby_call(drb_uri)
594
+ end
595
+ else
596
+ @server, @call = LocalServiceServerClient.make_pair
597
+ end
598
+ end
599
+
600
+ def add_any_process_service(name, front)
601
+ @server.add_service(name, front)
602
+ @call.add_any_process_service_with_type(name, front)
603
+ nil
604
+ end
605
+
606
+ def add_single_process_service(name, front)
607
+ @server.add_service(name, front)
608
+ @call.add_single_process_service_with_type(name, front)
609
+ nil
610
+ end
611
+
612
+ def add_sticky_process_service(name, front)
613
+ @server.add_service(name, front)
614
+ @call.add_sticky_process_service_with_type(name, front)
615
+ nil
616
+ end
617
+
618
+ def_delegators :@server, :at_fork, :preprocess, :postprocess
619
+ def_delegator :@server, :start, :start_server
620
+ def_delegator :@server, :detach, :detach_server # for forked child process
621
+ def_delegator :@server, :stop, :stop_server
622
+
623
+ def_delegator :@call, :start, :start_client
624
+ def_delegators :@call, :get_service, :call_service, :[]
625
+ end
626
+ end
627
+
628
+ # Local Variables:
629
+ # mode: Ruby
630
+ # indent-tabs-mode: nil
631
+ # End: