elrpc 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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