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 +4 -4
- data/README.md +190 -17
- data/elrpc.gemspec +2 -2
- data/lib/elrpc/version.rb +1 -1
- data/lib/elrpc.rb +22 -5
- data/sample/echo-client.el +2 -2
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1ff7cff0814489d4e855848dd5f6c59ebbcffcf0
|
4
|
+
data.tar.gz: 246d1edd90a7f3ecf6f039b23a8b9c13f7fc053b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 (
|
10
|
+
### Ruby code (server process)
|
11
11
|
|
12
|
-
This code
|
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 (
|
31
|
+
### Emacs Lisp code (client process)
|
31
32
|
|
32
|
-
This elisp code calls the
|
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
|
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 (
|
54
|
+
### Ruby code (client process)
|
53
55
|
|
54
|
-
You can also write the
|
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
|
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
|
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
|
+

|
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
|
-
###
|
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
|
-
|
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
|
-
###
|
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
|
-
|
202
|
+
server.call_method_async("echo", "hello") do |err, value|
|
203
|
+
puts value
|
204
|
+
end
|
121
205
|
|
122
|
-
|
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
|
-
|
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
|
-
|
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
|
24
|
-
spec.add_development_dependency "test-unit", "~> 3
|
23
|
+
spec.add_development_dependency "rake", "~> 10"
|
24
|
+
spec.add_development_dependency "test-unit", "~> 3"
|
25
25
|
end
|
data/lib/elrpc/version.rb
CHANGED
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
|
-
|
77
|
-
|
78
|
-
|
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
|
-
@
|
85
|
-
@port = @
|
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
|
data/sample/echo-client.el
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
(require 'epc)
|
2
2
|
|
3
3
|
(let (epc)
|
4
|
-
;; start a
|
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.
|
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-
|
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
|
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
|
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
|
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
|
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.
|
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.
|