em-xs 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.
- data/.gitignore +8 -0
- data/Gemfile +15 -0
- data/Guardfile +8 -0
- data/LICENSE +22 -0
- data/README.md +101 -0
- data/Rakefile +18 -0
- data/em-xs.gemspec +19 -0
- data/examples/req_rep.rb +60 -0
- data/lib/em-xs.rb +14 -0
- data/lib/em-xs/context.rb +53 -0
- data/lib/em-xs/helpers.rb +24 -0
- data/lib/em-xs/patterns/pub_sub_pattern.rb +25 -0
- data/lib/em-xs/patterns/push_pull_pattern.rb +13 -0
- data/lib/em-xs/patterns/req_rep_pattern.rb +13 -0
- data/lib/em-xs/patterns/router_dealer_pattern.rb +13 -0
- data/lib/em-xs/patterns/surveyor_pattern.rb +15 -0
- data/lib/em-xs/socket.rb +192 -0
- data/lib/em-xs/version.rb +5 -0
- data/specs/spec_helper.rb +31 -0
- data/specs/unit/context_spec.rb +32 -0
- data/specs/unit/helpers_spec.rb +24 -0
- data/specs/unit/patterns/pub_sub_pattern_spec.rb +47 -0
- data/specs/unit/patterns/push_pull_pattern_spec.rb +35 -0
- data/specs/unit/patterns/req_rep_pattern_spec.rb +41 -0
- data/specs/unit/patterns/router_dealer_pattern_spec.rb +45 -0
- data/specs/unit/patterns/surveyor_pattern_spec.rb +47 -0
- data/specs/unit/socket_spec.rb +9 -0
- metadata +110 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
|
2
|
+
# parameters:
|
3
|
+
# output => the formatted to use
|
4
|
+
# backtrace => number of lines, nil = everything
|
5
|
+
guard 'bacon', :output => "BetterOutput", :backtrace => nil do
|
6
|
+
watch(%r{^lib/em-xs/(.+)\.rb$}) { |m| "specs/unit/#{m[1]}_spec.rb" }
|
7
|
+
watch(%r{specs/.+\.rb$})
|
8
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Julien Ammous
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
# em-xs
|
2
|
+
|
3
|
+
Crossroads EventMachine library, I used em-zeromq as base but turned it into something slightly
|
4
|
+
different.
|
5
|
+
|
6
|
+
## Prerequesites
|
7
|
+
|
8
|
+
You need to have libxs installed, on mac os x you can use homebrew:
|
9
|
+
```bash
|
10
|
+
brew install crossroads
|
11
|
+
```
|
12
|
+
|
13
|
+
|
14
|
+
## Example
|
15
|
+
|
16
|
+
I decided to go for a long example to be closer than a "real" application, here it is:
|
17
|
+
you can view the source [here](https://github.com/schmurfy/em-xs/blob/master/examples/req_rep.rb) too
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
require 'rubygems'
|
21
|
+
require 'bundler/setup'
|
22
|
+
|
23
|
+
require 'em-xs'
|
24
|
+
|
25
|
+
ENDPOINT = 'inproc://socket'
|
26
|
+
|
27
|
+
|
28
|
+
class Client
|
29
|
+
def initialize(context, endpoint, delay)
|
30
|
+
@context = context
|
31
|
+
@endpoint = endpoint
|
32
|
+
@delay = delay
|
33
|
+
|
34
|
+
@socket = @context.socket(XS::REQ, self)
|
35
|
+
if @socket.connect(endpoint) == -1
|
36
|
+
raise XS::Util.error_string
|
37
|
+
end
|
38
|
+
|
39
|
+
send_query
|
40
|
+
end
|
41
|
+
|
42
|
+
def on_readable(s, parts)
|
43
|
+
puts "Client received: #{parts}"
|
44
|
+
send_query
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
def send_query
|
49
|
+
EM::add_timer(@delay) do
|
50
|
+
@socket.send_msg("ping")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
class EchoServer
|
58
|
+
def initialize(context, endpoint)
|
59
|
+
@context = context
|
60
|
+
@socket = @context.socket(XS::REP, self)
|
61
|
+
if @socket.bind(endpoint) == -1
|
62
|
+
raise XS::Util.error_string
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def on_readable(s, parts)
|
67
|
+
puts "Server received: #{parts}"
|
68
|
+
s.send_msg("re: #{parts[0]}")
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
EM::run do
|
75
|
+
context = EM::XS::Context.new
|
76
|
+
|
77
|
+
EchoServer.new(context, ENDPOINT)
|
78
|
+
Client.new(context, ENDPOINT, 1)
|
79
|
+
end
|
80
|
+
```
|
81
|
+
|
82
|
+
|
83
|
+
## Contributing
|
84
|
+
|
85
|
+
first install dependencies (thanks bundler !)
|
86
|
+
```bash
|
87
|
+
bundle
|
88
|
+
```
|
89
|
+
|
90
|
+
then you can run the specs:
|
91
|
+
|
92
|
+
```bash
|
93
|
+
bundle exec rake
|
94
|
+
```
|
95
|
+
|
96
|
+
and if you can to do some changes you can start guard to
|
97
|
+
watch the files and restart associated specs in the background:
|
98
|
+
```bash
|
99
|
+
bundle exec guard
|
100
|
+
```
|
101
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
|
5
|
+
task :default => :test
|
6
|
+
|
7
|
+
task :test do
|
8
|
+
require 'bacon'
|
9
|
+
|
10
|
+
ENV['COVERAGE'] = "1"
|
11
|
+
|
12
|
+
Dir[File.expand_path('../specs/**/*_spec.rb', __FILE__)].each do |file|
|
13
|
+
puts "File: #{file}:"
|
14
|
+
load(file)
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
data/em-xs.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/em-xs/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Julien Ammous"]
|
6
|
+
gem.email = ["schmurfy@gmail.com"]
|
7
|
+
gem.description = %q{EventMachine wrapper for crossroads}
|
8
|
+
gem.summary = %q{EventMachine wrapper for crossroads}
|
9
|
+
gem.homepage = "https://github.com/schmurfy/em-xs"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.name = "em-xs"
|
14
|
+
gem.require_paths = ["lib"]
|
15
|
+
gem.version = EM::XS::VERSION
|
16
|
+
|
17
|
+
gem.add_dependency 'ffi-rxs'
|
18
|
+
gem.add_dependency 'eventmachine', '~> 1.0.0.beta4'
|
19
|
+
end
|
data/examples/req_rep.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
require 'em-xs'
|
5
|
+
|
6
|
+
ENDPOINT = 'inproc://socket'
|
7
|
+
|
8
|
+
|
9
|
+
class Client
|
10
|
+
def initialize(context, endpoint, delay)
|
11
|
+
@context = context
|
12
|
+
@endpoint = endpoint
|
13
|
+
@delay = delay
|
14
|
+
|
15
|
+
@socket = @context.socket(XS::REQ, self)
|
16
|
+
if @socket.connect(endpoint) == -1
|
17
|
+
raise XS::Util.error_string
|
18
|
+
end
|
19
|
+
|
20
|
+
send_query
|
21
|
+
end
|
22
|
+
|
23
|
+
def on_readable(s, parts)
|
24
|
+
puts "Client received: #{parts}"
|
25
|
+
send_query
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def send_query
|
30
|
+
EM::add_timer(@delay) do
|
31
|
+
@socket.send_msg("ping")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
class EchoServer
|
39
|
+
def initialize(context, endpoint)
|
40
|
+
@context = context
|
41
|
+
@socket = @context.socket(XS::REP, self)
|
42
|
+
if @socket.bind(endpoint) == -1
|
43
|
+
raise XS::Util.error_string
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def on_readable(s, parts)
|
48
|
+
puts "Server received: #{parts}"
|
49
|
+
s.send_msg("re: #{parts[0]}")
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
EM::run do
|
56
|
+
context = EM::XS::Context.new
|
57
|
+
|
58
|
+
EchoServer.new(context, ENDPOINT)
|
59
|
+
Client.new(context, ENDPOINT, 1)
|
60
|
+
end
|
data/lib/em-xs.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'ffi-rxs'
|
3
|
+
|
4
|
+
require_relative 'em-xs/version'
|
5
|
+
require_relative 'em-xs/context'
|
6
|
+
|
7
|
+
require_relative 'em-xs/socket'
|
8
|
+
require_relative 'em-xs/patterns/req_rep_pattern'
|
9
|
+
require_relative 'em-xs/patterns/router_dealer_pattern'
|
10
|
+
require_relative 'em-xs/patterns/surveyor_pattern'
|
11
|
+
require_relative 'em-xs/patterns/push_pull_pattern'
|
12
|
+
require_relative 'em-xs/patterns/pub_sub_pattern'
|
13
|
+
|
14
|
+
require_relative 'em-xs/helpers'
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module XS
|
3
|
+
|
4
|
+
class Context
|
5
|
+
READABLES = [ ::XS::SUB, ::XS::PULL, ::XS::ROUTER, ::XS::DEALER, ::XS::REP, ::XS::REQ, ::XS::RESPONDENT, ::XS::SURVEYOR ]
|
6
|
+
WRITABLES = [ ::XS::PUB, ::XS::PUSH, ::XS::ROUTER, ::XS::DEALER, ::XS::REP, ::XS::REQ, ::XS::RESPONDENT, ::XS::SURVEYOR ]
|
7
|
+
|
8
|
+
def initialize(io_threads = nil)
|
9
|
+
@context = ::XS::Context.new
|
10
|
+
if io_threads
|
11
|
+
rc = @context.setctxopt(::XS::IO_THREADS, io_threads)
|
12
|
+
unless ::XS::Util.resultcode_ok?(rc)
|
13
|
+
::XS::raise_error('xs_setctxopt', rc)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
##
|
20
|
+
# Create a socket in this context.
|
21
|
+
#
|
22
|
+
# @param [Integer] socket_type One of ZMQ::REQ, ZMQ::REP, ZMQ::PULL, ZMQ::PUSH,
|
23
|
+
# ZMQ::ROUTER, ZMQ::DEALER
|
24
|
+
#
|
25
|
+
# @param [Object] handler an object which respond to on_readable(socket, parts)
|
26
|
+
# and can respond to on_writeable(socket)
|
27
|
+
#
|
28
|
+
def socket(socket_type, handler = nil)
|
29
|
+
klass = Socket.get_class_for_type(socket_type)
|
30
|
+
unless klass
|
31
|
+
raise "Unsupported socket type: #{socket_type}"
|
32
|
+
end
|
33
|
+
|
34
|
+
socket = @context.socket(socket_type)
|
35
|
+
|
36
|
+
fd = []
|
37
|
+
rc = socket.getsockopt(::XS::FD, fd)
|
38
|
+
unless ::XS::Util.resultcode_ok?(rc)
|
39
|
+
raise "Unable to get socket FD: #{::XS::Util.error_string}"
|
40
|
+
end
|
41
|
+
|
42
|
+
EM.watch(fd[0], klass, socket, socket_type, handler).tap do |s|
|
43
|
+
s.register_readable if READABLES.include?(socket_type)
|
44
|
+
s.register_writable if WRITABLES.include?(socket_type)
|
45
|
+
|
46
|
+
yield(s) if block_given?
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module XS
|
3
|
+
|
4
|
+
class Socket
|
5
|
+
TYPES_MAPPING = {
|
6
|
+
::XS::REQ => Sockets::Request,
|
7
|
+
::XS::REP => Sockets::Reply,
|
8
|
+
::XS::ROUTER => Sockets::Router,
|
9
|
+
::XS::DEALER => Sockets::Dealer,
|
10
|
+
::XS::SUB => Sockets::Subscriber,
|
11
|
+
::XS::PUB => Sockets::Publisher,
|
12
|
+
::XS::SURVEYOR => Sockets::Surveyor,
|
13
|
+
::XS::RESPONDENT => Sockets::Respondent,
|
14
|
+
::XS::PUSH => Sockets::Push,
|
15
|
+
::XS::PULL => Sockets::Pull
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
def self.get_class_for_type(socket_type)
|
19
|
+
TYPES_MAPPING[socket_type]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module XS
|
3
|
+
module Sockets
|
4
|
+
|
5
|
+
class Publisher < Socket
|
6
|
+
|
7
|
+
end
|
8
|
+
|
9
|
+
class Subscriber < Socket
|
10
|
+
|
11
|
+
def subscribe(what = '')
|
12
|
+
raise "only valid on sub socket type (was #{@socket.name})" unless @socket.name == 'SUB'
|
13
|
+
@socket.setsockopt(::XS::SUBSCRIBE, what)
|
14
|
+
end
|
15
|
+
|
16
|
+
def unsubscribe(what)
|
17
|
+
raise "only valid on sub socket type (was #{@socket.name})" unless @socket.name == 'SUB'
|
18
|
+
@socket.setsockopt(::XS::UNSUBSCRIBE, what)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/em-xs/socket.rb
ADDED
@@ -0,0 +1,192 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module XS
|
3
|
+
class Socket < EventMachine::Connection
|
4
|
+
attr_accessor :on_readable, :on_writable, :handler
|
5
|
+
attr_reader :socket, :socket_type
|
6
|
+
|
7
|
+
def initialize(socket, socket_type, handler)
|
8
|
+
@socket = socket
|
9
|
+
@socket_type = socket_type
|
10
|
+
@handler = handler
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.map_sockopt(opt, name, writer = true)
|
14
|
+
define_method(name){ getsockopt(opt) }
|
15
|
+
if writer
|
16
|
+
define_method("#{name}="){|val| @socket.setsockopt(opt, val) }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
map_sockopt(::XS::AFFINITY, :affinity)
|
21
|
+
map_sockopt(::XS::IDENTITY, :identity)
|
22
|
+
map_sockopt(::XS::SNDBUF, :sndbuf)
|
23
|
+
map_sockopt(::XS::RCVBUF, :rcvbuf)
|
24
|
+
map_sockopt(::XS::TYPE, :type, false)
|
25
|
+
map_sockopt(::XS::LINGER, :linger)
|
26
|
+
map_sockopt(::XS::RECONNECT_IVL, :reconnect_interval)
|
27
|
+
map_sockopt(::XS::RECONNECT_IVL_MAX, :max_reconnect_interval)
|
28
|
+
map_sockopt(::XS::BACKLOG, :backlog)
|
29
|
+
map_sockopt(::XS::MAXMSGSIZE, :max_msgsize)
|
30
|
+
map_sockopt(::XS::SNDHWM, :snd_hwm)
|
31
|
+
map_sockopt(::XS::RCVHWM, :rcv_hwm)
|
32
|
+
map_sockopt(::XS::RCVTIMEO, :rcv_timeout)
|
33
|
+
map_sockopt(::XS::SNDTIMEO, :snd_timeout)
|
34
|
+
map_sockopt(::XS::IPV4ONLY, :ipv4only)
|
35
|
+
map_sockopt(::XS::KEEPALIVE, :keepalive)
|
36
|
+
|
37
|
+
|
38
|
+
# pgm
|
39
|
+
map_sockopt(::XS::RATE, :rate)
|
40
|
+
map_sockopt(::XS::MULTICAST_HOPS, :max_multicast_hops)
|
41
|
+
map_sockopt(::XS::RECOVERY_IVL, :recovery_interval)
|
42
|
+
|
43
|
+
# User method
|
44
|
+
def bind(address)
|
45
|
+
@socket.bind(address)
|
46
|
+
end
|
47
|
+
|
48
|
+
def connect(address)
|
49
|
+
@socket.connect(address)
|
50
|
+
end
|
51
|
+
|
52
|
+
# send a non blocking message
|
53
|
+
# parts: if only one argument is given a signle part message is sent
|
54
|
+
# if more than one arguments is given a multipart message is sent
|
55
|
+
#
|
56
|
+
# return: true is message was queued, false otherwise
|
57
|
+
#
|
58
|
+
def send_msg(*parts)
|
59
|
+
parts = Array(parts[0]) if parts.size == 0
|
60
|
+
sent = true
|
61
|
+
|
62
|
+
# multipart
|
63
|
+
parts[0...-1].each do |msg|
|
64
|
+
sent = @socket.send_string(msg, ::XS::DONTWAIT | ::XS::SNDMORE)
|
65
|
+
if sent == false
|
66
|
+
break
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
if sent
|
71
|
+
# all the previous parts were queued, send
|
72
|
+
# the last one
|
73
|
+
ret = @socket.send_string(parts[-1], ::XS::DONTWAIT)
|
74
|
+
if ret < 0
|
75
|
+
raise "Unable to send message: #{::XS::Util.error_string}"
|
76
|
+
end
|
77
|
+
else
|
78
|
+
# error while sending the previous parts
|
79
|
+
# register the socket for writability
|
80
|
+
self.notify_writable = true
|
81
|
+
sent = false
|
82
|
+
end
|
83
|
+
|
84
|
+
notify_readable()
|
85
|
+
|
86
|
+
sent
|
87
|
+
end
|
88
|
+
|
89
|
+
def getsockopt(opt)
|
90
|
+
ret = []
|
91
|
+
rc = @socket.getsockopt(opt, ret)
|
92
|
+
unless ::XS::Util.resultcode_ok?(rc)
|
93
|
+
::XS::Util.raise_error('getsockopt', rc)
|
94
|
+
end
|
95
|
+
|
96
|
+
(ret.size == 1) ? ret[0] : ret
|
97
|
+
end
|
98
|
+
|
99
|
+
def setsockopt(opt, value)
|
100
|
+
@socket.setsockopt(opt, value)
|
101
|
+
end
|
102
|
+
|
103
|
+
# cleanup when ending loop
|
104
|
+
def unbind
|
105
|
+
detach_and_close
|
106
|
+
end
|
107
|
+
|
108
|
+
# Make this socket available for reads
|
109
|
+
def register_readable
|
110
|
+
if readable?
|
111
|
+
notify_readable
|
112
|
+
end
|
113
|
+
|
114
|
+
# Subscribe to EM read notifications
|
115
|
+
self.notify_readable = true
|
116
|
+
end
|
117
|
+
|
118
|
+
# Trigger on_readable when socket is readable
|
119
|
+
def register_writable
|
120
|
+
# Subscribe to EM write notifications
|
121
|
+
self.notify_writable = true
|
122
|
+
end
|
123
|
+
|
124
|
+
def notify_readable
|
125
|
+
# Not sure if this is actually necessary. I suppose it prevents us
|
126
|
+
# from having to to instantiate a XS::Message unnecessarily.
|
127
|
+
# I'm leaving this is because its in the docs, but it could probably
|
128
|
+
# be taken out.
|
129
|
+
return unless readable?
|
130
|
+
|
131
|
+
loop do
|
132
|
+
msg_parts = []
|
133
|
+
msg = get_message
|
134
|
+
if msg
|
135
|
+
msg_parts << msg
|
136
|
+
while @socket.more_parts?
|
137
|
+
msg = get_message
|
138
|
+
if msg
|
139
|
+
msg_parts << msg
|
140
|
+
else
|
141
|
+
raise "Multi-part message missing a message!"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
@handler.on_readable(self, msg_parts)
|
146
|
+
else
|
147
|
+
break
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def notify_writable
|
153
|
+
return unless writable?
|
154
|
+
|
155
|
+
# once a writable event is successfully received the socket
|
156
|
+
# should be accepting messages again so stop triggering
|
157
|
+
# write events
|
158
|
+
self.notify_writable = false
|
159
|
+
|
160
|
+
if @handler.respond_to?(:on_writable)
|
161
|
+
@handler.on_writable(self)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
def readable?
|
165
|
+
(getsockopt(::XS::EVENTS) & ::XS::POLLIN) == ::XS::POLLIN
|
166
|
+
end
|
167
|
+
|
168
|
+
def writable?
|
169
|
+
true
|
170
|
+
# return false
|
171
|
+
# (getsockopt(::XS::EVENTS) & ::XS::POLLOUT) == ::XS::POLLOUT
|
172
|
+
end
|
173
|
+
|
174
|
+
|
175
|
+
private
|
176
|
+
|
177
|
+
# internal methods
|
178
|
+
def get_message
|
179
|
+
msg = ""
|
180
|
+
msg_recvd = @socket.recv_string(msg, ::XS::DONTWAIT)
|
181
|
+
msg_recvd != -1 ? msg : nil
|
182
|
+
end
|
183
|
+
|
184
|
+
# Detaches the socket from the EM loop,
|
185
|
+
# then closes the socket
|
186
|
+
def detach_and_close
|
187
|
+
detach
|
188
|
+
@socket.close
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
require 'bacon'
|
5
|
+
|
6
|
+
if ENV['COVERAGE']
|
7
|
+
Bacon.allow_focused_run = false
|
8
|
+
|
9
|
+
require 'simplecov'
|
10
|
+
SimpleCov.start do
|
11
|
+
add_filter ".*_spec"
|
12
|
+
add_filter "/helpers/"
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
$LOAD_PATH.unshift( File.expand_path('../../lib' , __FILE__) )
|
18
|
+
require 'em-xs'
|
19
|
+
|
20
|
+
require 'bacon/ext/mocha'
|
21
|
+
require 'bacon/ext/em'
|
22
|
+
|
23
|
+
Thread.abort_on_exception = true
|
24
|
+
|
25
|
+
Bacon.summary_on_exit()
|
26
|
+
|
27
|
+
ENDPOINTS = {
|
28
|
+
'inproc' => 'inproc://socket',
|
29
|
+
'ipc' => 'ipc:///tmp/socket',
|
30
|
+
'tcp' => 'tcp://127.0.0.1:1234'
|
31
|
+
}.freeze
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
require 'em-xs/context'
|
4
|
+
|
5
|
+
describe 'Context' do
|
6
|
+
before do
|
7
|
+
@ctx = EM::XS::Context.new
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'can create socket' do
|
11
|
+
s = @ctx.socket(XS::ROUTER)
|
12
|
+
s.class.should == EM::XS::Sockets::Router
|
13
|
+
s.socket_type.should == XS::ROUTER
|
14
|
+
end
|
15
|
+
|
16
|
+
should 'set io threads count' do
|
17
|
+
proc{
|
18
|
+
EM::XS::Context.new(2)
|
19
|
+
}.should.not.raise
|
20
|
+
end
|
21
|
+
|
22
|
+
should 'raise an error if socket type is unsupported' do
|
23
|
+
err = proc {
|
24
|
+
@ctx.socket(99)
|
25
|
+
}.should.raise(RuntimeError)
|
26
|
+
|
27
|
+
err.message.should == "Unsupported socket type: 99"
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
require 'em-xs'
|
4
|
+
|
5
|
+
describe 'Socket' do
|
6
|
+
|
7
|
+
should 'return correct types' do
|
8
|
+
EM::XS::Socket.get_class_for_type(XS::REQ).should == EM::XS::Sockets::Request
|
9
|
+
EM::XS::Socket.get_class_for_type(XS::REP).should == EM::XS::Sockets::Reply
|
10
|
+
|
11
|
+
EM::XS::Socket.get_class_for_type(XS::ROUTER).should == EM::XS::Sockets::Router
|
12
|
+
EM::XS::Socket.get_class_for_type(XS::DEALER).should == EM::XS::Sockets::Dealer
|
13
|
+
|
14
|
+
EM::XS::Socket.get_class_for_type(XS::PUB).should == EM::XS::Sockets::Publisher
|
15
|
+
EM::XS::Socket.get_class_for_type(XS::SUB).should == EM::XS::Sockets::Subscriber
|
16
|
+
|
17
|
+
EM::XS::Socket.get_class_for_type(XS::SURVEYOR).should == EM::XS::Sockets::Surveyor
|
18
|
+
EM::XS::Socket.get_class_for_type(XS::RESPONDENT).should == EM::XS::Sockets::Respondent
|
19
|
+
|
20
|
+
EM::XS::Socket.get_class_for_type(XS::PUSH).should == EM::XS::Sockets::Push
|
21
|
+
EM::XS::Socket.get_class_for_type(XS::PULL).should == EM::XS::Sockets::Pull
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require_relative '../../spec_helper'
|
2
|
+
|
3
|
+
describe 'Pattern: Publisher/Subscriber' do
|
4
|
+
before do
|
5
|
+
@ctx = EM::XS::Context.new
|
6
|
+
end
|
7
|
+
|
8
|
+
describe 'PUB/SUB' do
|
9
|
+
before do
|
10
|
+
@client_handler = stub()
|
11
|
+
@server1_handler = stub()
|
12
|
+
@server2_handler = stub()
|
13
|
+
@client = @ctx.socket(XS::PUB, @client_handler)
|
14
|
+
@server1 = @ctx.socket(XS::SUB, @server1_handler)
|
15
|
+
@server2 = @ctx.socket(XS::SUB, @server2_handler)
|
16
|
+
end
|
17
|
+
|
18
|
+
after do
|
19
|
+
@client.send(:detach_and_close)
|
20
|
+
@server1.send(:detach_and_close)
|
21
|
+
@server2.send(:detach_and_close)
|
22
|
+
end
|
23
|
+
|
24
|
+
ENDPOINTS.each do |label, endpoint|
|
25
|
+
should "works with #{label} endpoints" do
|
26
|
+
@client.bind(endpoint).should >= 0
|
27
|
+
@server1.connect(endpoint).should >= 0
|
28
|
+
@server2.connect(endpoint).should >= 0
|
29
|
+
|
30
|
+
@server1.subscribe('')
|
31
|
+
@server2.subscribe('')
|
32
|
+
|
33
|
+
@server1_handler.expects(:on_readable).with(@server1, ['1: some message'])
|
34
|
+
@server2_handler.expects(:on_readable).with(@server2, ['3: some message'])
|
35
|
+
|
36
|
+
EM::add_timer(0.1) do
|
37
|
+
@client.send_msg("1: some message")
|
38
|
+
# @client.send_msg("2: some message")
|
39
|
+
end
|
40
|
+
|
41
|
+
wait(0.2)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require_relative '../../spec_helper'
|
2
|
+
|
3
|
+
describe 'Pattern: Push/Pull' do
|
4
|
+
before do
|
5
|
+
@ctx = EM::XS::Context.new
|
6
|
+
end
|
7
|
+
|
8
|
+
describe 'PUSH/PULL' do
|
9
|
+
before do
|
10
|
+
@client_handler = stub()
|
11
|
+
@server_handler = stub()
|
12
|
+
@client = @ctx.socket(XS::PUSH, @client_handler)
|
13
|
+
@server = @ctx.socket(XS::PULL, @server_handler)
|
14
|
+
end
|
15
|
+
|
16
|
+
after do
|
17
|
+
@client.send(:detach_and_close)
|
18
|
+
@server.send(:detach_and_close)
|
19
|
+
end
|
20
|
+
|
21
|
+
ENDPOINTS.each do |label, endpoint|
|
22
|
+
should "works with #{label} endpoints" do
|
23
|
+
@server.bind(endpoint).should >= 0
|
24
|
+
@client.connect(endpoint).should >= 0
|
25
|
+
|
26
|
+
@server_handler.expects(:on_readable).with(@server, ['some message'])
|
27
|
+
@client.send_msg("some message")
|
28
|
+
|
29
|
+
wait(0.1)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require_relative '../../spec_helper'
|
2
|
+
|
3
|
+
describe 'Pattern: Request/Reply' do
|
4
|
+
before do
|
5
|
+
@ctx = EM::XS::Context.new
|
6
|
+
end
|
7
|
+
|
8
|
+
describe 'REQ/REP' do
|
9
|
+
before do
|
10
|
+
@client_handler = stub()
|
11
|
+
@server_handler = stub()
|
12
|
+
@client = @ctx.socket(XS::REQ, @client_handler)
|
13
|
+
@server = @ctx.socket(XS::REP, @server_handler)
|
14
|
+
end
|
15
|
+
|
16
|
+
after do
|
17
|
+
@client.send(:detach_and_close)
|
18
|
+
@server.send(:detach_and_close)
|
19
|
+
end
|
20
|
+
|
21
|
+
ENDPOINTS.each do |label, endpoint|
|
22
|
+
should "works with #{label} endpoints" do
|
23
|
+
@server.bind(endpoint).should >= 0
|
24
|
+
@client.connect(endpoint).should >= 0
|
25
|
+
|
26
|
+
@server_handler.expects(:on_readable).with(@server, ['hello server'])
|
27
|
+
@client.send_msg("hello server")
|
28
|
+
|
29
|
+
EM::add_timer(0.1) do
|
30
|
+
@client_handler.expects(:on_readable).with(@client, ['yo !'])
|
31
|
+
@server.send_msg("yo !")
|
32
|
+
end
|
33
|
+
|
34
|
+
wait(0.3)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require_relative '../../spec_helper'
|
2
|
+
|
3
|
+
describe 'Pattern: Router/Dealer' do
|
4
|
+
before do
|
5
|
+
@ctx = EM::XS::Context.new
|
6
|
+
end
|
7
|
+
|
8
|
+
describe 'XREQ/XREP' do
|
9
|
+
before do
|
10
|
+
@client_handler = stub()
|
11
|
+
|
12
|
+
@server_handler = Class.new do
|
13
|
+
def on_readable(sock, parts)
|
14
|
+
id, *parts = parts
|
15
|
+
sock.send_msg(id, "re: #{parts[0]}")
|
16
|
+
end
|
17
|
+
end.new
|
18
|
+
|
19
|
+
@client = @ctx.socket(XS::XREQ, @client_handler)
|
20
|
+
@server = @ctx.socket(XS::XREP, @server_handler)
|
21
|
+
end
|
22
|
+
|
23
|
+
after do
|
24
|
+
@client.send(:detach_and_close)
|
25
|
+
@server.send(:detach_and_close)
|
26
|
+
end
|
27
|
+
|
28
|
+
ENDPOINTS.each do |label, endpoint|
|
29
|
+
should "works with #{label} endpoints" do
|
30
|
+
@server.bind(endpoint).should >= 0
|
31
|
+
@client.connect(endpoint).should >= 0
|
32
|
+
|
33
|
+
@client_handler.expects(:on_readable).with(@client, ['re: first question'])
|
34
|
+
@client.send_msg("first question")
|
35
|
+
|
36
|
+
@client_handler.expects(:on_readable).with(@client, ['re: second question'])
|
37
|
+
@client.send_msg("second question")
|
38
|
+
|
39
|
+
wait(0.2)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require_relative '../../spec_helper'
|
2
|
+
|
3
|
+
describe 'Pattern: Surveyor/Respondent' do
|
4
|
+
before do
|
5
|
+
@ctx = EM::XS::Context.new
|
6
|
+
end
|
7
|
+
|
8
|
+
describe 'SURVEYOR/RESPONDENT' do
|
9
|
+
before do
|
10
|
+
@client_handler = stub()
|
11
|
+
|
12
|
+
@server_handler_class = Class.new do
|
13
|
+
def initialize(id)
|
14
|
+
@id = id
|
15
|
+
end
|
16
|
+
|
17
|
+
def on_readable(sock, parts)
|
18
|
+
p parts
|
19
|
+
sock.send_msg("#{parts[0]} : #{@id}")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
@server1 = @ctx.socket(XS::RESPONDENT, @server_handler)
|
24
|
+
@server2 = @ctx.socket(XS::RESPONDENT, @server_handler)
|
25
|
+
@client = @ctx.socket(XS::SURVEYOR, @client_handler)
|
26
|
+
end
|
27
|
+
|
28
|
+
# => CRASH
|
29
|
+
# ENDPOINTS.each do |label, endpoint|
|
30
|
+
# should "works with #{label} endpoints" do
|
31
|
+
# @server1.connect(endpoint).should == 0
|
32
|
+
# @server2.connect(endpoint).should == 0
|
33
|
+
# @client.bind(endpoint).should == 0
|
34
|
+
#
|
35
|
+
# @client_handler.expects(:on_readable).with(@server, ['msg : 1'])
|
36
|
+
# @client_handler.expects(:on_readable).with(@server, ['msg : 2'])
|
37
|
+
# @client.send_msg("msg")
|
38
|
+
#
|
39
|
+
#
|
40
|
+
# wait(0.2)
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
metadata
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: em-xs
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Julien Ammous
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-05-26 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: ffi-rxs
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: eventmachine
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 1.0.0.beta4
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.0.0.beta4
|
46
|
+
description: EventMachine wrapper for crossroads
|
47
|
+
email:
|
48
|
+
- schmurfy@gmail.com
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- .gitignore
|
54
|
+
- Gemfile
|
55
|
+
- Guardfile
|
56
|
+
- LICENSE
|
57
|
+
- README.md
|
58
|
+
- Rakefile
|
59
|
+
- em-xs.gemspec
|
60
|
+
- examples/req_rep.rb
|
61
|
+
- lib/em-xs.rb
|
62
|
+
- lib/em-xs/context.rb
|
63
|
+
- lib/em-xs/helpers.rb
|
64
|
+
- lib/em-xs/patterns/pub_sub_pattern.rb
|
65
|
+
- lib/em-xs/patterns/push_pull_pattern.rb
|
66
|
+
- lib/em-xs/patterns/req_rep_pattern.rb
|
67
|
+
- lib/em-xs/patterns/router_dealer_pattern.rb
|
68
|
+
- lib/em-xs/patterns/surveyor_pattern.rb
|
69
|
+
- lib/em-xs/socket.rb
|
70
|
+
- lib/em-xs/version.rb
|
71
|
+
- specs/spec_helper.rb
|
72
|
+
- specs/unit/context_spec.rb
|
73
|
+
- specs/unit/helpers_spec.rb
|
74
|
+
- specs/unit/patterns/pub_sub_pattern_spec.rb
|
75
|
+
- specs/unit/patterns/push_pull_pattern_spec.rb
|
76
|
+
- specs/unit/patterns/req_rep_pattern_spec.rb
|
77
|
+
- specs/unit/patterns/router_dealer_pattern_spec.rb
|
78
|
+
- specs/unit/patterns/surveyor_pattern_spec.rb
|
79
|
+
- specs/unit/socket_spec.rb
|
80
|
+
homepage: https://github.com/schmurfy/em-xs
|
81
|
+
licenses: []
|
82
|
+
post_install_message:
|
83
|
+
rdoc_options: []
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ! '>='
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
segments:
|
93
|
+
- 0
|
94
|
+
hash: 2028971236709690266
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ! '>='
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
segments:
|
102
|
+
- 0
|
103
|
+
hash: 2028971236709690266
|
104
|
+
requirements: []
|
105
|
+
rubyforge_project:
|
106
|
+
rubygems_version: 1.8.22
|
107
|
+
signing_key:
|
108
|
+
specification_version: 3
|
109
|
+
summary: EventMachine wrapper for crossroads
|
110
|
+
test_files: []
|