riser 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: