elrpc 0.0.1 → 0.0.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b19cb4f1857c727202dfee06bce97cad08cf54ad
4
- data.tar.gz: 1038805533721cde69d0a9e1a9b9a7991cd3c61d
3
+ metadata.gz: 1ff7cff0814489d4e855848dd5f6c59ebbcffcf0
4
+ data.tar.gz: 246d1edd90a7f3ecf6f039b23a8b9c13f7fc053b
5
5
  SHA512:
6
- metadata.gz: 53444d2a46a9fc54d3eed9004b6e23df4553d6b57aa39c42e46f9c8088ac5a523bce1e2f7b54e40e51bc9f50a35f24ead175c042dec6aa7329da8f2febe5c63a
7
- data.tar.gz: 01a5342c11b311b802d895d88e694e3b5fb848c0d5b1200d1be4f265ff6fefaba4ab1d7c94f4f26f33be0bc5f869c521ce55df44c92cb4f2e64b51630d0b1d56
6
+ metadata.gz: cf75699c742ea27c6c49073a45e5c01c50f4b6411946738236906459dcaf54263e6c6d765681b34d3ad62d884691f38b2ac5d031da070601276b836078e38d88
7
+ data.tar.gz: 502a9ac563cc7886a70dd4c66b0f0a5b99448478d3faa5656f872bbe104ccad86c269b70ad4022f2f533eedd1f975fa0e0e3613715f84b5393872d5fc7821e65
data/README.md CHANGED
@@ -3,14 +3,15 @@
3
3
  EPC is an RPC stack for Emacs Lisp and Elrpc is an implementation of EPC in Ruby.
4
4
  Using elrpc, you can develop an emacs extension with Ruby code.
5
5
 
6
- - https://github.com/kiwanami/emacs-epc
6
+ - [EPC at github](https://github.com/kiwanami/emacs-epc)
7
7
 
8
8
  ## Sample Code
9
9
 
10
- ### Ruby code (child process)
10
+ ### Ruby code (server process)
11
11
 
12
- This code will be started by the parent process.
12
+ This code is started by the client process, such as Emacs Lisp.
13
13
 
14
+ `echo.rb`
14
15
  ```ruby
15
16
  require 'elrpc'
16
17
 
@@ -27,16 +28,17 @@ end
27
28
  server.wait
28
29
  ```
29
30
 
30
- ### Emacs Lisp code (parent process)
31
+ ### Emacs Lisp code (client process)
31
32
 
32
- This elisp code calls the child process.
33
+ This elisp code calls the server process.
33
34
  The package `epc` is required.
34
35
 
36
+ `echo-client.el`
35
37
  ```el
36
38
  (require 'epc)
37
39
 
38
40
  (let (epc)
39
- ;; start a child process (using bundle exec)
41
+ ;; start a server process (using bundle exec)
40
42
  (setq epc (epc:start-epc "bundle" '("exec" "ruby" "echo.rb")))
41
43
 
42
44
  (deferred:$
@@ -49,14 +51,15 @@ The package `epc` is required.
49
51
  (epc:stop-epc epc)) ; just `eval-last-sexp' here
50
52
  ```
51
53
 
52
- ### Ruby code (parent process)
54
+ ### Ruby code (client process)
53
55
 
54
- You can also write the parent process code in Ruby.
56
+ You can also write the client process code in Ruby.
55
57
 
58
+ `echo-client.rb`
56
59
  ```ruby
57
60
  require 'elrpc'
58
61
 
59
- # start a child process
62
+ # start a server process
60
63
  cl = Elrpc.start_process(["ruby","echo.rb"])
61
64
 
62
65
  # synchronous calling
@@ -71,7 +74,7 @@ puts "2 wait"
71
74
  sleep 0.2
72
75
 
73
76
  puts "4 ok"
74
- # kill the child process
77
+ # kill the server process
75
78
  cl.stop
76
79
  ```
77
80
 
@@ -103,6 +106,16 @@ Or install it yourself as:
103
106
 
104
107
  ## API Document
105
108
 
109
+ ### EPC Overview
110
+
111
+ The EPC uses a peer-to-peer-architecture. After the connection is established, both peers can define remote methods and call the methods at the other side.
112
+
113
+ Let we define the words *server* and *client*. *Server* is a process which opens a TCP port and waiting for the connection. *Client* is a process which connects to the *server*. In most cases, a *client* process starts a *server* process. Then, the *server* process provides some services to the *client* process.
114
+
115
+ This diagram shows the API usage and the relation of processes.
116
+
117
+ ![EPC Overview](https://cacoo.com/diagrams/cFQgb5d5W9MphN8z-B5059.png)
118
+
106
119
  Please see the EPC document for the overview of EPC stack and protocol details.
107
120
 
108
121
  - [EPC Readme](https://github.com/kiwanami/emacs-epc)
@@ -111,21 +124,181 @@ Please see the `elparser` document for object serialization.
111
124
 
112
125
  - [elparser Readme](https://github.com/kiwanami/ruby-elparser)
113
126
 
114
- ### Start EPC
127
+ ### Building Server-Process
128
+
129
+ - Module method : `Elrpc::start_server(methods = [], port = 0)`
130
+ - Arguments
131
+ - `methods` : Array of `Elrpc::Method` instances.
132
+ - `port` : TCP Port number. 0 means that the number is decided by OS.
133
+ - Return
134
+ - A `Elrpc::RPCService` instance
135
+
136
+
137
+ *Sample Code*
138
+ ```ruby
139
+ server = Elrpc::start_server
140
+ ```
141
+
142
+
143
+ ### Defining Remote Method
144
+
145
+ - `Elrpc::RPCService#def_method(name, argdoc=nil, docstring=nil, &block)`
146
+ - Arguments
147
+ - `name` : String. Method name which is referred by the peer process.
148
+ - `argdoc` : String[optional]. Argument information for human.
149
+ - `docstring` : String[optional]. Method information for human.
150
+ - `&block` : Block. Code block which is called by the peer process. The return value is serialized and sent to the peer.
151
+
152
+ The return value of the code block is serialized and sent to the peer process. So, if the return value includes wrong values which can't be serialized by `elparser`, the runtime exception `EPCStackError` is thrown to the method calling of the peer process.
153
+
154
+ *Sample Code*
155
+ ```ruby
156
+ server.def_method("echo") do |arg|
157
+ arg
158
+ end
159
+
160
+ server.def_method("add") do |a, b|
161
+ a + b
162
+ end
115
163
 
116
- ### Stop EPC
164
+ server.def_method("inject",
165
+ "init(initial value), op(operator symbol), list",
166
+ "Apply Enumerable#inject method.") do |init, op, list|
167
+ list.inject(init, op)
168
+ end
169
+ ```
117
170
 
118
- ### Define Remote Method
171
+ ### Calling Remote Method
172
+
173
+ If the peer process defines some methods, the instance of `Elrpc::RPCService` can call the peer's method, regardless of the server process or the client one. (See the EPC document.)
174
+
175
+ - `Elrpc::RPCService#call_method(name, *args)`
176
+ - Synchronous method calling. The current thread is blocked until the calling result is returned.
177
+ - Arguments
178
+ - `name` : String. Method name to call.
179
+ - `args` : Array(Variable length arguments) Argument values.
180
+ - Return
181
+ - The return value which is returned by the peer process.
182
+ - Exception
183
+ - `EPCRuntimeError` : An exception which is thrown by the peer's method.
184
+ - `EPCStackError` : An exception which is thrown by the EPC protocol stack.
185
+
186
+ - `Elrpc::RPCService#call_method_async(name, *args, &block)`
187
+ - Asynchronous method calling. The current thread is not blocked. The calling result is passed to the code block.
188
+ - Arguments
189
+ - `name` : String. Method name to call.
190
+ - `args` : Array(Variable length arguments) Argument values.
191
+ - `block` : Block.
192
+ - Block Arguments
193
+ - `err` : If `nil`, the method calling succeed and the return value is bounded by `value`. If not `nil`, an exception is thrown within the method calling.
194
+ - `EPCRuntimeError` : An exception which is thrown by the peer's method.
195
+ - `EPCStackError` : An exception which is thrown by the EPC protocol stack.
196
+ - `value` : The return value which is returned by the peer process.
197
+
198
+ *Sample Code*
199
+ ```ruby
200
+ puts server.call_method("echo", "hello")
119
201
 
120
- ### Call Remote Method
202
+ server.call_method_async("echo", "hello") do |err, value|
203
+ puts value
204
+ end
121
205
 
122
- ### Error Handling
206
+ puts server.call_method("add", 1, 2)
207
+
208
+ server.call_method("add", 1, 2) do |err, value|
209
+ puts value
210
+ end
211
+
212
+ puts server.call_method("inject", 0, :+, [1,2,3,4])
213
+ puts server.call_method("inject", 1, :*, [1,2,3,4])
214
+ ```
123
215
 
124
216
  ### Utilities
125
217
 
126
- ### Define Server
218
+ - `Elrpc::RPCService#query_methods`
219
+ - `Elrpc::RPCService#query_methods_async`
220
+ - Return
221
+ - Array of method specs of the peer process.
222
+
223
+ *Sample Code*
224
+ ```ruby
225
+ server.query_methods
226
+ # => [[:echo, nil, nil], [:add, nil, nil], [:inject, "init, op, list", "Enumerable#inject"]]
227
+ ```
228
+
229
+ ### EPC Process
230
+
231
+ Elrpc can implement the client process which starts a server process. The server process can be implemented in Ruby and the other language, such as Perl, Python and Emacs Lisp.
232
+
233
+ - Module method `Elrpc::start_process(cmd)`
234
+ - Argument
235
+ - `cmd` : Array. Command line elements for the server process.
236
+ - Return
237
+ - An instance of `Elrpc::Service`.
238
+
239
+ *Sample Code*
240
+ ```ruby
241
+ cl = Elrpc.start_process(["ruby","echo.rb"])
242
+
243
+ puts cl.call_method("echo", "1 hello")
244
+
245
+ cl.stop
246
+ ```
247
+
248
+ ## Development
249
+
250
+ In most cases, the client process is Emacs and the server one is implemented by Elrpc to extend Emacs functions in Ruby.
251
+ However, it may be difficult to develop the programs which belong to the different environment.
252
+ So, at first, it is better to implement both sides in Ruby and write tests.
253
+
254
+ If you want to watch the STDOUT and STDERR of the server process, start the process from command line and connect to the process with `irb` or `pry`, like following:
255
+
256
+ *Starting the server process*
257
+ ```
258
+ $ bundle exec ruby echo.rb
259
+ 12345
260
+ ```
261
+
262
+ `12345` is port number to connect from the client process. The number changes each time.
263
+ Then, start `irb` in the another terminal.
264
+
265
+ *Connecting to the process from irb*
266
+ ```
267
+ $ bundle exec irb
268
+ > require 'elrpc'
269
+ > cl = Elrpc.start_client(12345)
270
+ > cl.call_method("echo", "hello")
271
+ ```
272
+
273
+ When you invoke `call_method`, the first terminal in which the server process runs, may show some output.
274
+
275
+ ## Performance
276
+
277
+ EPC is designed for fast communication between Emacs and other processes.
278
+ Employing S-exp serialization and keeping TCP connection, EPC is faster than HTTP-based RPC, such as JSON-RPC.
279
+
280
+ Executing the benchmark program `test/echo-bench.rb`, You can check the performance in your environment. The program measures following aspects:
281
+
282
+ - round trip time of method invocation
283
+ - synchronous/asynchronous calling
284
+ - string transportation
285
+ - array/list serialization and transportation
286
+ - hash/alist serialization and transportation
287
+
288
+ Here is the result on Lenovo X240 with Intel Core i7-4600U CPU 2.10GHz, 8GB RAM, ruby 2.1.4p265 x86_64-linux.
289
+
290
+ ```
291
+ $ bundle exec ruby test/echo-bench.rb
292
+ user system total real
293
+ int 0.180000 0.040000 0.220000 ( 0.402582)
294
+ int_a 0.180000 0.010000 0.190000 ( 0.205672)
295
+ string 0.390000 0.040000 0.430000 ( 0.753418)
296
+ array 1.410000 0.070000 1.480000 ( 2.687667)
297
+ hash 3.930000 0.070000 4.000000 ( 7.632865)
298
+ (call/sec) int: 9090.9 int_async: 10526.3 string: 4651.2 array: 1351.4 hash: 500.0
299
+ ```
127
300
 
128
- ### Debug
301
+ In the condition Ruby to Ruby, `elrpc` can perform around 10000 call/sec.
129
302
 
130
303
  ## License
131
304
 
data/elrpc.gemspec CHANGED
@@ -20,6 +20,6 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_runtime_dependency "elparser", "~> 0.0"
22
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"
23
+ spec.add_development_dependency "rake", "~> 10"
24
+ spec.add_development_dependency "test-unit", "~> 3"
25
25
  end
data/lib/elrpc/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Elrpc
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
data/lib/elrpc.rb CHANGED
@@ -5,6 +5,7 @@ require "socket"
5
5
  require "thread"
6
6
  require "monitor"
7
7
  require "logger"
8
+ require "open3"
8
9
 
9
10
  require "elparser"
10
11
 
@@ -72,17 +73,24 @@ module Elrpc
72
73
 
73
74
  def _start_logger
74
75
  return Thread.start do
76
+ stdout = @io3[1]
77
+ stderr = @io3[2]
75
78
  loop do
76
- ret = @io.readline
77
- break if ret.nil?
78
- @logger.puts(ret) if @logger
79
+ IO.select([stdout, stderr]).flatten.compact.each do |io|
80
+ io.each do |line|
81
+ next if line.nil? || line.empty?
82
+ @logger.puts(line) if @logger
83
+ end
84
+ end
85
+ break if stdout.eof? && stderr.eof?
79
86
  end
80
87
  end
81
88
  end
82
89
 
83
90
  def start
84
- @io = IO.popen(@cmd)
85
- @port = @io.readline.to_i
91
+ @io3 = Open3.popen3(@cmd) # stdin, stdout, stderr, wait_thr
92
+ @port = @io3[2].readline.to_i
93
+ @io3[0].close
86
94
  @output = nil
87
95
  @thread = _start_logger
88
96
  @client = Elrpc.start_client(@port)
@@ -115,6 +123,8 @@ module Elrpc
115
123
 
116
124
  def stop
117
125
  @client.stop
126
+ @io3[1].close
127
+ @io3[2].close
118
128
  end
119
129
 
120
130
  end
@@ -149,6 +159,9 @@ module Elrpc
149
159
  def message
150
160
  "#{@_classname} : #{@_message}"
151
161
  end
162
+ def to_s
163
+ message
164
+ end
152
165
 
153
166
  def remote_classname
154
167
  @_classname
@@ -159,6 +172,7 @@ module Elrpc
159
172
  def remote_backtrace
160
173
  @_backtrace
161
174
  end
175
+
162
176
  end
163
177
 
164
178
  class EPCStackError < StandardError
@@ -171,6 +185,9 @@ module Elrpc
171
185
  def message
172
186
  "#{@_classname} : #{@_message}"
173
187
  end
188
+ def to_s
189
+ message
190
+ end
174
191
 
175
192
  def remote_classname
176
193
  @_classname
@@ -1,7 +1,7 @@
1
1
  (require 'epc)
2
2
 
3
3
  (let (epc)
4
- ;; start a child process
4
+ ;; start a server process
5
5
  ;; bundle exec
6
6
  (setq epc (epc:start-epc "bundle" '("exec" "ruby" "echo.rb")))
7
7
 
@@ -21,7 +21,7 @@
21
21
  ;; synchronous calling (debug purpose)
22
22
  (message "%S" (epc:call-sync epc 'echo '(world)))
23
23
 
24
- ;;
24
+ ;; kill the server process
25
25
  (epc:stop-epc epc)
26
26
  )
27
27
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elrpc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - SAKURAI Masashi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-17 00:00:00.000000000 Z
11
+ date: 2015-12-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: elparser
@@ -44,28 +44,28 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '10.0'
47
+ version: '10'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '10.0'
54
+ version: '10'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: test-unit
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '3.0'
61
+ version: '3'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '3.0'
68
+ version: '3'
69
69
  description: EPC (RPC stack for the Emacs Lisp) for Ruby.
70
70
  email:
71
71
  - m.sakurai@kiwanami.net
@@ -112,7 +112,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
112
112
  version: '0'
113
113
  requirements: []
114
114
  rubyforge_project:
115
- rubygems_version: 2.4.3
115
+ rubygems_version: 2.4.8
116
116
  signing_key:
117
117
  specification_version: 4
118
118
  summary: EPC (RPC stack for the Emacs Lisp) for Ruby.