elrpc 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b19cb4f1857c727202dfee06bce97cad08cf54ad
4
+ data.tar.gz: 1038805533721cde69d0a9e1a9b9a7991cd3c61d
5
+ SHA512:
6
+ metadata.gz: 53444d2a46a9fc54d3eed9004b6e23df4553d6b57aa39c42e46f9c8088ac5a523bce1e2f7b54e40e51bc9f50a35f24ead175c042dec6aa7329da8f2febe5c63a
7
+ data.tar.gz: 01a5342c11b311b802d895d88e694e3b5fb848c0d5b1200d1be4f265ff6fefaba4ab1d7c94f4f26f33be0bc5f869c521ce55df44c92cb4f2e64b51630d0b1d56
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in elrpc.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 SAKURAI Masashi
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,135 @@
1
+ # Elrpc : EPC (RPC Stack for Emacs Lisp) for Ruby
2
+
3
+ EPC is an RPC stack for Emacs Lisp and Elrpc is an implementation of EPC in Ruby.
4
+ Using elrpc, you can develop an emacs extension with Ruby code.
5
+
6
+ - https://github.com/kiwanami/emacs-epc
7
+
8
+ ## Sample Code
9
+
10
+ ### Ruby code (child process)
11
+
12
+ This code will be started by the parent process.
13
+
14
+ ```ruby
15
+ require 'elrpc'
16
+
17
+ # start server
18
+ server = Elrpc.start_server()
19
+
20
+ # define a method
21
+ server.def_method "echo" do |arg|
22
+ # just return the given argument value
23
+ arg
24
+ end
25
+
26
+ # sleep the main thread and wait for closing connection
27
+ server.wait
28
+ ```
29
+
30
+ ### Emacs Lisp code (parent process)
31
+
32
+ This elisp code calls the child process.
33
+ The package `epc` is required.
34
+
35
+ ```el
36
+ (require 'epc)
37
+
38
+ (let (epc)
39
+ ;; start a child process (using bundle exec)
40
+ (setq epc (epc:start-epc "bundle" '("exec" "ruby" "echo.rb")))
41
+
42
+ (deferred:$
43
+ (epc:call-deferred epc 'echo '("hello"))
44
+ (deferred:nextc it
45
+ (lambda (x) (message "Return : %S" x))))
46
+
47
+ (message "%S" (epc:call-sync epc 'echo '(world)))
48
+
49
+ (epc:stop-epc epc)) ; just `eval-last-sexp' here
50
+ ```
51
+
52
+ ### Ruby code (parent process)
53
+
54
+ You can also write the parent process code in Ruby.
55
+
56
+ ```ruby
57
+ require 'elrpc'
58
+
59
+ # start a child process
60
+ cl = Elrpc.start_process(["ruby","echo.rb"])
61
+
62
+ # synchronous calling
63
+ puts cl.call_method("echo", "1 hello")
64
+
65
+ # asynchronous calling
66
+ cl.call_method_async("echo", "3 world") do |err, value|
67
+ puts value
68
+ end
69
+
70
+ puts "2 wait"
71
+ sleep 0.2
72
+
73
+ puts "4 ok"
74
+ # kill the child process
75
+ cl.stop
76
+ ```
77
+
78
+ Here is the result.
79
+
80
+ ```
81
+ $ bundle exec ruby echo-client.rb
82
+ 1 hello
83
+ 2 wait
84
+ 3 world
85
+ 4 ok
86
+ ```
87
+
88
+ ## Installation
89
+
90
+ Add this line to your application's Gemfile:
91
+
92
+ ```ruby
93
+ gem 'elrpc'
94
+ ```
95
+
96
+ And then execute:
97
+
98
+ $ bundle
99
+
100
+ Or install it yourself as:
101
+
102
+ $ gem install elrpc
103
+
104
+ ## API Document
105
+
106
+ Please see the EPC document for the overview of EPC stack and protocol details.
107
+
108
+ - [EPC Readme](https://github.com/kiwanami/emacs-epc)
109
+
110
+ Please see the `elparser` document for object serialization.
111
+
112
+ - [elparser Readme](https://github.com/kiwanami/ruby-elparser)
113
+
114
+ ### Start EPC
115
+
116
+ ### Stop EPC
117
+
118
+ ### Define Remote Method
119
+
120
+ ### Call Remote Method
121
+
122
+ ### Error Handling
123
+
124
+ ### Utilities
125
+
126
+ ### Define Server
127
+
128
+ ### Debug
129
+
130
+ ## License
131
+
132
+ Elrpc is licensed under MIT.
133
+
134
+ ----
135
+ (C) 2015 SAKURAI Masashi. m.sakurai at kiwanami.net
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'elrpc/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "elrpc"
8
+ spec.version = Elrpc::VERSION
9
+ spec.authors = ["SAKURAI Masashi"]
10
+ spec.email = ["m.sakurai@kiwanami.net"]
11
+ spec.summary = %q{EPC (RPC stack for the Emacs Lisp) for Ruby.}
12
+ spec.description = %q{EPC (RPC stack for the Emacs Lisp) for Ruby.}
13
+ spec.homepage = "https://github.com/kiwanami/ruby-elrpc"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency "elparser", "~> 0.0"
22
+ spec.add_development_dependency "bundler", "~> 1.7"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "test-unit", "~> 3.0"
25
+ end
@@ -0,0 +1,697 @@
1
+ # -*- coding: utf-8 -*-
2
+ require "elrpc/version"
3
+
4
+ require "socket"
5
+ require "thread"
6
+ require "monitor"
7
+ require "logger"
8
+
9
+ require "elparser"
10
+
11
+
12
+ module Elrpc
13
+
14
+ @@default_log_level = Logger::WARN
15
+
16
+ # Logger::WARN, Logger::INFO, Logger::DEBUG
17
+ def self.set_default_log_level(level)
18
+ @@default_log_level = level
19
+ end
20
+
21
+ def self.default_log_level
22
+ @@default_log_level
23
+ end
24
+
25
+ @@count = 1
26
+
27
+ def self.gen_uid
28
+ @@count += 1
29
+ @@count
30
+ end
31
+
32
+ def self.get_logger_format(comid)
33
+ return "%Y/%m/%d %H:%M:%S #{comid}"
34
+ end
35
+
36
+
37
+
38
+ def self.start_server(methods = [], port = 0)
39
+ server_socket = TCPServer.open(port)
40
+ port_number = server_socket.local_address.ip_port
41
+ STDOUT.puts "#{port_number}"
42
+ STDOUT.flush
43
+ socket = server_socket.accept
44
+ server = RPCService.new("SV", socket, methods)
45
+ server.add_close_hook do
46
+ server_socket.close
47
+ end
48
+ return server
49
+ end
50
+
51
+ def self.start_client(port, methods=[], host = "127.0.0.1")
52
+ socket = TCPSocket.open(host, port)
53
+ client = RPCService.new("CL", socket, methods)
54
+ return client
55
+ end
56
+
57
+ def self.start_process(cmd)
58
+ svr = Service.new(cmd)
59
+ svr.start
60
+ return svr
61
+ end
62
+
63
+ class Service
64
+
65
+ attr_reader :cmd, :port, :client
66
+ attr :output
67
+
68
+ # cmd = ["ruby", "_call.rb"]
69
+ def initialize(cmd)
70
+ @cmd = cmd
71
+ end
72
+
73
+ def _start_logger
74
+ return Thread.start do
75
+ loop do
76
+ ret = @io.readline
77
+ break if ret.nil?
78
+ @logger.puts(ret) if @logger
79
+ end
80
+ end
81
+ end
82
+
83
+ def start
84
+ @io = IO.popen(@cmd)
85
+ @port = @io.readline.to_i
86
+ @output = nil
87
+ @thread = _start_logger
88
+ @client = Elrpc.start_client(@port)
89
+ return self
90
+ end
91
+
92
+ def register_method(method)
93
+ @client.register_method(method)
94
+ end
95
+
96
+ def def_method(name, argdoc=nil, docstring=nil, &block)
97
+ @client.def_method(name, argdoc, docstring, &block)
98
+ end
99
+
100
+ def call_method_async(name, *args, &block)
101
+ @client.call_method_async(name, *args, &block)
102
+ end
103
+
104
+ def call_method(name, *args)
105
+ @client.call_method(name, *args)
106
+ end
107
+
108
+ def query_methods_async(&block)
109
+ @client.query_methods(&block)
110
+ end
111
+
112
+ def query_methods
113
+ @client.query_methods
114
+ end
115
+
116
+ def stop
117
+ @client.stop
118
+ end
119
+
120
+ end
121
+
122
+
123
+
124
+ class Method
125
+
126
+ attr_reader :name, :proc
127
+ attr :argdoc, :docstring
128
+
129
+ def initialize(name, argdoc=nil, docstring=nil, &proc)
130
+ @name = name.to_sym
131
+ @proc = proc
132
+ @argdoc = argdoc
133
+ @docstring = docstring
134
+ end
135
+
136
+ def call(args)
137
+ @proc.call(*args)
138
+ end
139
+
140
+ end
141
+
142
+ class EPCRuntimeError < StandardError
143
+ def initialize(_classname, _message, _backtrace)
144
+ @_classname = _classname
145
+ @_message = _message
146
+ @_backtrace = _backtrace
147
+ end
148
+
149
+ def message
150
+ "#{@_classname} : #{@_message}"
151
+ end
152
+
153
+ def remote_classname
154
+ @_classname
155
+ end
156
+ def remote_message
157
+ @_message
158
+ end
159
+ def remote_backtrace
160
+ @_backtrace
161
+ end
162
+ end
163
+
164
+ class EPCStackError < StandardError
165
+ def initialize(_classname, _message, _backtrace)
166
+ @_classname = _classname
167
+ @_message = _message
168
+ @_backtrace = _backtrace
169
+ end
170
+
171
+ def message
172
+ "#{@_classname} : #{@_message}"
173
+ end
174
+
175
+ def remote_classname
176
+ @_classname
177
+ end
178
+ def remote_message
179
+ @_message
180
+ end
181
+ def remote_backtrace
182
+ @_backtrace
183
+ end
184
+ end
185
+
186
+
187
+ ## 送信用データクラス
188
+ ## キューに入れるために使う
189
+
190
+ class CallMessage
191
+ attr_reader :uid, :method, :args, :block
192
+
193
+ def initialize(uid, method, args, block)
194
+ @uid = uid
195
+ @method = method
196
+ @args = args
197
+ @block = block
198
+ end
199
+
200
+ def to_ast
201
+ [:call, @uid, @method, @args]
202
+ end
203
+ end
204
+
205
+ class MethodsMessage
206
+ attr_reader :uid, :block
207
+
208
+ def initialize(uid, block)
209
+ @uid = uid
210
+ @block = block
211
+ end
212
+
213
+ def to_ast
214
+ [:methods, @uid]
215
+ end
216
+ end
217
+
218
+ class ReturnMessage
219
+ attr_reader :uid, :value
220
+
221
+ def initialize(uid, value)
222
+ @uid = uid
223
+ @value = value
224
+ end
225
+
226
+ def to_ast
227
+ [:return, @uid, @value]
228
+ end
229
+ end
230
+
231
+ class ErrorMessage
232
+ attr_reader :uid, :error_msg
233
+
234
+ def initialize(uid, error_msg)
235
+ @uid = uid
236
+ @error_msg = error_msg
237
+ end
238
+
239
+ def to_ast
240
+ [:'return-error', @uid, @error_msg]
241
+ end
242
+ end
243
+
244
+ class EPCErrorMessage
245
+ attr_reader :uid, :error_msg
246
+
247
+ def initialize(uid, error_msg)
248
+ @uid = uid
249
+ @error_msg = error_msg
250
+ end
251
+
252
+ def to_ast
253
+ [:'epc-error', @uid, @error_msg]
254
+ end
255
+ end
256
+
257
+
258
+ class RPCService
259
+
260
+ attr_reader :socket_state
261
+ attr_accessor :logger
262
+
263
+ def initialize(name, socket, methods = nil)
264
+ @logger = Logger.new(STDOUT)
265
+ @logger.level = Elrpc::default_log_level
266
+ @logger.datetime_format = Elrpc.get_logger_format(name)
267
+
268
+ @methods = Hash.new # name -> Method
269
+ @session = Hash.new # uid -> proc
270
+ @session_lock = Monitor.new
271
+
272
+ @sending_queue = Queue.new # CallMessage
273
+
274
+ @socket = socket
275
+ @socket_state_lock = Monitor.new
276
+ @socket_state = :socket_opened
277
+
278
+ @wait_lock = nil
279
+ @wait_cv = nil
280
+ @close_hooks = []
281
+
282
+ if methods then
283
+ methods.each do |m|
284
+ register_method(m)
285
+ end
286
+ end
287
+
288
+ @sender_thread = Thread.start { sender_loop }
289
+ @receiver_thread = Thread.start { receiver_loop }
290
+ @worker_pool = WorkerPool.new(1, @logger)
291
+
292
+ @logger.debug ":ready for I/O stream."
293
+ end
294
+
295
+ # 自分にメソッドを登録する
296
+ def register_method(method)
297
+ @methods[method.name] = method
298
+ end
299
+
300
+ # register_method の簡易版
301
+ def def_method(name, argdoc=nil, docstring=nil, &block)
302
+ register_method(Method.new(name, argdoc, docstring, &block))
303
+ end
304
+
305
+ # 相手のメソッドを呼ぶ
306
+ # block(err, value)
307
+ def call_method_async(name, *args, &block)
308
+ uid = Elrpc.gen_uid
309
+ msg = CallMessage.new(uid, name, args, block)
310
+ # ここは競合しないのでロックしない
311
+ @session[uid] = msg
312
+ @sending_queue.push(msg)
313
+ uid
314
+ end
315
+
316
+ # 相手のメソッドを呼ぶ(同期版)
317
+ def call_method(name, *args)
318
+ mutex = Mutex.new
319
+ cv = ConditionVariable.new
320
+ ret = nil
321
+ ex = nil
322
+ call_method_async(name, *args) do |err, value|
323
+ mutex.synchronize do
324
+ ex = err
325
+ ret = value
326
+ cv.signal
327
+ end
328
+ end
329
+ mutex.synchronize do
330
+ cv.wait(mutex)
331
+ end
332
+ if !ex.nil?
333
+ raise ex
334
+ end
335
+ return ret
336
+ end
337
+
338
+ # 接続相手のメソッド一覧を返す
339
+ # [[name, argdoc, docstring], ...]
340
+ def query_methods_async(&block)
341
+ uid = Elrpc.gen_uid
342
+ msg = MethodsMessage.new(uid, block)
343
+ @session[uid] = msg
344
+ @sending_queue.push(msg)
345
+ uid
346
+ end
347
+
348
+ # 接続相手のメソッド一覧を返す(同期版)
349
+ # [[name, argdoc, docstring], ...]
350
+ def query_methods
351
+ mutex = Mutex.new
352
+ cv = ConditionVariable.new
353
+ ret = nil
354
+ ex = nil
355
+ query_methods_async do |err, value|
356
+ mutex.synchronize do
357
+ ex = err
358
+ ret = value
359
+ cv.signal
360
+ end
361
+ end
362
+ mutex.synchronize do
363
+ cv.wait(mutex)
364
+ end
365
+ if !ex.nil?
366
+ raise ex
367
+ end
368
+ return ret
369
+ end
370
+
371
+ def stop
372
+ if @socket_state == :socket_opened then
373
+ @logger.debug "RPCService.stop: received!"
374
+ @worker_pool.kill
375
+ @socket_state = :socket_closing
376
+ @socket.close
377
+ @sending_queue << nil # stop message
378
+ @sender_thread.join(4) unless Thread.current == @sender_thread
379
+ @receiver_thread.join(4) unless Thread.current == @receiver_thread
380
+ _clear_waiting_sessions
381
+ @socket_state = :scoket_not_connected
382
+ end
383
+ _wakeup
384
+ @logger.debug "RPCService.stop: completed"
385
+ end
386
+
387
+ # ソケットが相手から切断されるまでメインスレッドを止める
388
+ def wait
389
+ @wait_lock = Mutex.new
390
+ @wait_cv = ConditionVariable.new
391
+ @wait_lock.synchronize do
392
+ @wait_cv.wait(@wait_lock)
393
+ end
394
+ stop
395
+ end
396
+
397
+ def add_close_hook(&block)
398
+ @close_hooks << block
399
+ end
400
+
401
+
402
+ private
403
+
404
+ # RPC呼び出しで待ってるスレッドを全てエラーにして終了させる
405
+ def _clear_waiting_sessions
406
+ @session_lock.synchronize do
407
+ @session.keys.each do |uid|
408
+ _session_return(uid, "EPC Connection closed", nil)
409
+ end
410
+ end
411
+ end
412
+
413
+ def _socket_state_lock
414
+ @socket_state_lock.synchronize do
415
+ yield
416
+ end
417
+ end
418
+
419
+ # もし、メインスレッドが停止していれば再開させて終了させる
420
+ def _wakeup
421
+ if @wait_lock
422
+ @wait_lock.synchronize do
423
+ @wait_cv.signal
424
+ end
425
+ end
426
+ end
427
+
428
+ # 相手にシリアライズされたデータを送る
429
+ def _send_message(msg)
430
+ msg = msg.encode("UTF-8") + "\n"
431
+ len = msg.bytesize
432
+ body = sprintf("%06x%s",len,msg)
433
+ @socket.write(body)
434
+ @socket.flush
435
+ end
436
+
437
+ # 呼び出し元に値を返して、セッションをクリアする
438
+ def _session_return(uid, error, value)
439
+ m = nil
440
+ @session_lock.synchronize do
441
+ m = @session[uid]
442
+ @session.delete(uid)
443
+ end
444
+ if m then
445
+ m.block.call(error, value)
446
+ end
447
+ end
448
+
449
+ def sender_loop
450
+ loop do
451
+ begin
452
+ entry = @sending_queue.shift
453
+ if entry.nil? then
454
+ @logger.debug "Queue.shift received stop message."
455
+ break
456
+ end
457
+ @logger.debug "Queue.shift [#{@sending_queue.size}] : #{entry.uid}"
458
+ body = Elparser.encode( entry.to_ast )
459
+ @logger.debug "Encode : #{body}"
460
+ _send_message( body )
461
+ @logger.debug " Queue -> sent #{entry.uid}"
462
+ rescue Elparser::EncodingError => evar
463
+ @logger.warn "[sendloop] #{evar.to_s} "
464
+ err = EPCStackError.new(evar.class.name, evar.message, evar.backtrace)
465
+ _session_return(entry.uid, err, nil) if entry
466
+ rescue => evar
467
+ mes = evar.message
468
+ @logger.warn "[sendloop] #{evar.to_s} "
469
+ if mes["abort"] then
470
+ @logger.warn " [sendloop] disconnected by the peer."
471
+ @socket_state = :socket_not_connected
472
+ elsif evar.class == IOError then
473
+ @logger.warn evar.backtrace.join("\n")
474
+ @socket_state = :socket_closing
475
+ end
476
+ _session_return(entry.uid, evar, nil) if entry
477
+ end # begin
478
+ if @socket_state == :socket_closing ||
479
+ @socket_state == :socket_not_connected then
480
+ @logger.debug "[sender-thread] terminating..."
481
+ break
482
+ end
483
+ end # loop
484
+ @logger.debug "[sender-thread] loop exit : #{@socket_state}"
485
+ _wakeup
486
+ @logger.debug "[sender-thread] exit--------------"
487
+ end
488
+
489
+ def receiver_loop
490
+ parser = Elparser::Parser.new
491
+ loop do
492
+ ast = nil # for error message and recovery
493
+ uid = nil
494
+ begin
495
+ lenstr = @socket.read(6)
496
+ if lenstr.nil? then
497
+ @logger.debug "[rcvloop] Socket closed!"
498
+ break
499
+ end
500
+ len = lenstr.to_i(16)
501
+ @logger.debug "Receiving a message : len=#{len}"
502
+ body = @socket.read(len) # 1 means LF
503
+ if body.nil? then
504
+ @logger.debug "[rcvloop] Socket closed!"
505
+ break
506
+ end
507
+ @logger.debug "Parse : #{body}"
508
+ ast = parser.parse(body)
509
+ raise "Unexpected multiple s-expression : #{body}" if ast.size != 1
510
+ ast = ast[0].to_ruby
511
+ uid = ast[1]
512
+ case ast[0]
513
+ when :call
514
+ @logger.debug " received: CALL : #{uid}"
515
+ _call(ast)
516
+ when :return
517
+ @logger.debug " received: RETURN: #{uid}"
518
+ _return(ast)
519
+ when :'return-error'
520
+ @logger.debug " received: ERROR: #{uid}"
521
+ _return_error(ast)
522
+ when :'epc-error'
523
+ @logger.debug " received: EPC_ERROR: #{uid}"
524
+ _epc_error(ast)
525
+ when :'methods'
526
+ @logger.debug " received: METHODS: #{uid}"
527
+ _query_methods(ast)
528
+ else
529
+ @logger.debug " Unknown message code. try to reset the connection. >> #{body}"
530
+ @socket_state = :socket_closing
531
+ @sending_queue.push nil # wakeup sender thread
532
+ return
533
+ end # case
534
+ if @socket_state == :socket_closing then
535
+ @logger.debug "[receiver-thread] terminating..."
536
+ break
537
+ end
538
+ rescue Exception => evar
539
+ @logger.debug "[rcvloop] Exception! #{evar}"
540
+ mes = evar.message
541
+ if uid && @session[uid] then
542
+ _session_return(uid, evar, nil)
543
+ end
544
+ if mes["close"] || mes["reset"] then
545
+ @logger.debug " [rcvloop] disconnected by the peer."
546
+ break
547
+ elsif evar.kind_of?(IOError) then
548
+ @logger.debug " [rcvloop] IOError."
549
+ @socket_state = :socket_closing
550
+ break
551
+ else
552
+ @logger.warn " [rcvloop] going to recover the communication."
553
+ bt = evar.backtrace.join("\n")
554
+ @logger.warn " [rcvloop] #{bt}"
555
+ end
556
+ end # begin rescue
557
+ ast = nil
558
+ uid = nil
559
+ end # loop
560
+ @logger.debug "[receiver-thread] loop exit : #{@socket_state}"
561
+ _wakeup
562
+ @logger.debug "[receiver-thread exit]--------------"
563
+ end
564
+
565
+ # 相手からメソッドを呼ばれた
566
+ def _call(ast)
567
+ _, uid, name, args = ast
568
+ @logger.debug ": called: Enter: #{name} : #{uid}"
569
+ method = @methods[name.to_sym]
570
+ if method then
571
+ task = -> do
572
+ msg = nil
573
+ begin
574
+ ret = method.call(args)
575
+ msg = ReturnMessage.new(uid, ret)
576
+ rescue => e
577
+ @logger.debug ": called: Error!: #{name} : #{uid} : #{e}"
578
+ @logger.debug e
579
+ msg = ErrorMessage.new(uid, [e.class.name, e.message, e.backtrace.join("\n")])
580
+ end
581
+ @sending_queue.push(msg)
582
+ end
583
+ @worker_pool.invoke(task)
584
+ else
585
+ # method not found
586
+ @logger.debug ": called: Method not found: #{name} : #{uid} "
587
+ @sending_queue.push(EPCErrorMessage.new(uid, "Not found the name: #{name}"))
588
+ end # if
589
+ @logger.debug ": called: Leave: #{name} : #{uid}"
590
+ end
591
+
592
+ # 相手から返り値が返ってきた
593
+ def _return(ast)
594
+ _, uid, value = ast
595
+ @logger.debug ": return: Start: #{uid} : value = #{value}"
596
+ if @session[uid] then
597
+ _session_return(uid, nil, value)
598
+ else
599
+ @logger.error "Not found a session for #{uid}"
600
+ end
601
+ @logger.debug ": return: End: #{uid}"
602
+ end
603
+
604
+ # 相手からアプリケーションエラーが返ってきた
605
+ def _return_error(ast)
606
+ _, uid, error = ast
607
+ @logger.debug ": return-error: Start: #{uid} : error = #{error}"
608
+ if @session[uid] then
609
+ # error : [classname, message, backtrace]
610
+ _session_return(uid, EPCRuntimeError.new(error[0], error[1], error[2]), nil)
611
+ else
612
+ @logger.error "Not found a session for #{uid}"
613
+ end
614
+ @logger.debug ": return-error: End: #{uid}"
615
+ end
616
+
617
+ # 相手からEPCエラーが返ってきた
618
+ def _epc_error(ast)
619
+ _, uid, error = ast
620
+ @logger.debug ": epc-error: Start: #{uid} : error = #{error}"
621
+ if @session[uid] then
622
+ # error : [classname, message, backtrace]
623
+ _session_return(uid, EPCStackError.new(error[0], error[1], error[2]), nil)
624
+ else
625
+ @logger.error "Not found a session for #{uid}"
626
+ end
627
+ @logger.debug ": epc-error: End: #{uid}"
628
+ end
629
+
630
+ # 相手から一覧要求があった
631
+ def _query_methods(ast)
632
+ _, uid = ast
633
+ @logger.debug ": query-methods: Start: #{uid}"
634
+ begin
635
+ list = @methods.map do |k,m|
636
+ [m.name, m.argdoc, m.docstring]
637
+ end
638
+ msg = ReturnMessage.new(uid, list)
639
+ @sending_queue.push(msg)
640
+ rescue => e
641
+ @logger.warn ": query-method: Exception #{e.message}"
642
+ @logger.warn e.backtrace.join("\n")
643
+ msg = ErrorMessage.new(uid, [e.class.name, e.message, e.backtrace.join("\n")])
644
+ @sending_queue.push(msg)
645
+ end
646
+ @logger.debug ": query-methods: End: #{uid}"
647
+ end
648
+
649
+ end # class RPCService
650
+
651
+
652
+ ## タスク処理用のスレッドプール
653
+
654
+ class WorkerPool
655
+
656
+ def initialize(num, logger)
657
+ @logger = logger
658
+ @job_queue = Queue.new
659
+ @worker_threads = []
660
+ num.times {
661
+ @worker_threads << Thread.start(@job_queue, @logger) { |queue, logger|
662
+ logger.debug("Worker Start")
663
+ loop {
664
+ begin
665
+ job = queue.shift
666
+ logger.debug "Worker Thread : Job #{job}"
667
+ break if job.nil?
668
+ job.call
669
+ rescue => e
670
+ logger.error "Worker Error >>"
671
+ logger.error e
672
+ end
673
+ }
674
+ logger.debug("Worker Exit")
675
+ }
676
+ }
677
+ end
678
+
679
+ def invoke(job)
680
+ if @worker_threads.size == 0 then
681
+ @logger.debug "Worker : Ignore #{job}"
682
+ return
683
+ end
684
+ @job_queue << job
685
+ end
686
+
687
+ def kill
688
+ @worker_threads.size.times {
689
+ invoke(nil)
690
+ }
691
+ @worker_threads.each {|t| t.join }
692
+ @worker_threads.clear
693
+ end
694
+
695
+ end
696
+
697
+ end