em-xs 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|