cztop 1.1.1 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +16 -0
- data/README.md +61 -56
- data/cztop.gemspec +2 -4
- data/lib/cztop/actor.rb +26 -22
- data/lib/cztop/certificate.rb +19 -11
- data/lib/cztop/message.rb +5 -1
- data/lib/cztop/send_receive_methods.rb +106 -2
- data/lib/cztop/socket/types.rb +18 -0
- data/lib/cztop/version.rb +1 -1
- data/lib/cztop/zsock_options.rb +16 -2
- metadata +3 -66
- data/.github/workflows/coverage.yml +0 -20
- data/.github/workflows/draft_api.yml +0 -27
- data/.github/workflows/stable_api.yml +0 -26
- data/.gitignore +0 -10
- data/.projections.json +0 -4
- data/.rspec +0 -2
- data/.rubocop.yml +0 -175
- data/.yardopts +0 -1
- data/Rakefile +0 -6
- data/ci/install-libczmq +0 -22
- data/ci/install-libzmq +0 -22
- data/examples/async/.gitignore +0 -1
- data/examples/async/Gemfile +0 -5
- data/examples/async/README.md +0 -1
- data/examples/async/async.rb +0 -35
- data/examples/ruby_actor/actor.rb +0 -100
- data/examples/simple_req_rep/rep.rb +0 -12
- data/examples/simple_req_rep/req.rb +0 -35
- data/examples/taxi_system/.gitignore +0 -2
- data/examples/taxi_system/Makefile +0 -2
- data/examples/taxi_system/README.gsl +0 -115
- data/examples/taxi_system/README.md +0 -276
- data/examples/taxi_system/broker.rb +0 -97
- data/examples/taxi_system/client.rb +0 -34
- data/examples/taxi_system/generate_keys.rb +0 -24
- data/examples/taxi_system/start_broker.sh +0 -2
- data/examples/taxi_system/start_clients.sh +0 -11
- data/examples/weather_pub_sub/pub.rb +0 -24
- data/examples/weather_pub_sub/sub.rb +0 -33
- data/lib/cztop/async.rb +0 -124
- data/perf/README.md +0 -80
- data/perf/inproc_lat.rb +0 -49
- data/perf/inproc_thru.rb +0 -42
- data/perf/local_lat.rb +0 -35
- data/perf/remote_lat.rb +0 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a5218cd3606b597506aa29ea2bc0ccfe2ccd8030f1a0ce1c6ea90791fe7c88e
|
4
|
+
data.tar.gz: 3ad76dccf7e6bad9aff9a19f679bb3420dcf08ec742b109ff6c0a688f3fce4e4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 36ac2f1e2b1f0ddfb78f3cd77fc7fbfdb78591c1c379b3fb96b92d80566fc8e07e39f666c78b1b2aa6771fcd2fb22d6e6b7a173040b9c489053af27030689220
|
7
|
+
data.tar.gz: ef8061d7082e73bf757335d5c6cd550aeb39dfbdb54314b893e44350e9065ea96874d09b89720fbbe4b98873a0e1b575f76f91266025dcb2d5d45b88b5c11c90
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,19 @@
|
|
1
|
+
1.2.0 (1/7/2024)
|
2
|
+
-----
|
3
|
+
* refactor waiting for socket readability/writability
|
4
|
+
* fix ROUTER socket hanging in busy loop if ZMQ_ROUTER_MANDATORY flag is set and sending a message while no peers are connected
|
5
|
+
* same for unconnected CLIENT sockets
|
6
|
+
* slim down packaged gem
|
7
|
+
* modernize examples
|
8
|
+
* require Ruby 3.2
|
9
|
+
* some exceptions changed from EAGAIN to IO::TimeoutError
|
10
|
+
* Certificate.load and .new_from raise NotImplementedError if CURVE is not available
|
11
|
+
|
12
|
+
1.1.2 (1/5/2024)
|
13
|
+
-----
|
14
|
+
* refactor to make code Fiber Scheduler agnostic
|
15
|
+
* remove Async::IO::CZTopSocket
|
16
|
+
|
1
17
|
1.1.1 (1/4/2024)
|
2
18
|
-----
|
3
19
|
* speed up Async::IO#wait_readable and #wait_writable
|
data/README.md
CHANGED
@@ -13,22 +13,22 @@ mechanisms (like CURVE).
|
|
13
13
|
|
14
14
|
## Example with Async
|
15
15
|
|
16
|
+
See [this example](https://github.com/paddor/cztop/blob/master/examples/async.rb):
|
17
|
+
|
16
18
|
```ruby
|
17
19
|
#! /usr/bin/env ruby
|
18
20
|
|
19
|
-
require 'cztop
|
21
|
+
require 'cztop'
|
20
22
|
|
21
23
|
Async do |task|
|
22
24
|
task.async do |t|
|
23
25
|
socket = CZTop::Socket::REP.new("inproc://req_rep_example")
|
24
|
-
io = Async::IO.try_convert socket
|
25
|
-
|
26
26
|
socket.options.rcvtimeo = 50 # ms
|
27
27
|
|
28
28
|
loop do
|
29
|
-
msg =
|
29
|
+
msg = socket.receive
|
30
30
|
puts "<<< #{msg.to_a.inspect}"
|
31
|
-
|
31
|
+
socket << msg.to_a.map(&:upcase)
|
32
32
|
rescue IO::TimeoutError
|
33
33
|
break
|
34
34
|
end
|
@@ -38,11 +38,10 @@ Async do |task|
|
|
38
38
|
|
39
39
|
task.async do
|
40
40
|
socket = CZTop::Socket::REQ.new("inproc://req_rep_example")
|
41
|
-
io = Async::IO.try_convert socket
|
42
41
|
|
43
42
|
10.times do |i|
|
44
|
-
|
45
|
-
msg =
|
43
|
+
socket << "foobar ##{i}"
|
44
|
+
msg = socket.receive
|
46
45
|
puts ">>> #{msg.to_a.inspect}"
|
47
46
|
end
|
48
47
|
|
@@ -54,6 +53,8 @@ end
|
|
54
53
|
|
55
54
|
Output:
|
56
55
|
```
|
56
|
+
$ cd examples
|
57
|
+
$ time ./async.rb
|
57
58
|
<<< ["foobar #0"]
|
58
59
|
>>> ["FOOBAR #0"]
|
59
60
|
<<< ["foobar #1"]
|
@@ -76,61 +77,20 @@ Output:
|
|
76
77
|
>>> ["FOOBAR #9"]
|
77
78
|
REQ done.
|
78
79
|
REP done.
|
79
|
-
0.46user 0.09system 0:00.60elapsed 90%CPU (0avgtext+0avgdata 47296maxresident)k
|
80
|
-
0inputs+0outputs (0major+13669minor)pagefaults 0swaps
|
81
|
-
```
|
82
|
-
|
83
|
-
## Overview
|
84
|
-
|
85
|
-
### Class Hierarchy
|
86
80
|
|
87
|
-
|
81
|
+
________________________________________________________
|
82
|
+
Executed in 401.51 millis fish external
|
83
|
+
usr time 308.44 millis 605.00 micros 307.83 millis
|
84
|
+
sys time 40.08 millis 278.00 micros 39.81 millis
|
88
85
|
|
89
|
-
|
90
|
-
* [Actor](http://www.rubydoc.info/gems/cztop/CZTop)
|
91
|
-
* [Authenticator](http://www.rubydoc.info/gems/cztop/CZTop/Authenticator)
|
92
|
-
* [Beacon](http://www.rubydoc.info/gems/cztop/CZTop/Beacon)
|
93
|
-
* [Certificate](http://www.rubydoc.info/gems/cztop/CZTop/Certificate)
|
94
|
-
* [CertStore](http://www.rubydoc.info/gems/cztop/CZTop/CertStore)
|
95
|
-
* [Config](http://www.rubydoc.info/gems/cztop/CZTop/Config)
|
96
|
-
* [Frame](http://www.rubydoc.info/gems/cztop/CZTop/Frame)
|
97
|
-
* [Message](http://www.rubydoc.info/gems/cztop/CZTop/Message)
|
98
|
-
* [Monitor](http://www.rubydoc.info/gems/cztop/CZTop/Monitor)
|
99
|
-
* [Metadata](http://www.rubydoc.info/gems/cztop/CZTop/Metadata)
|
100
|
-
* [Proxy](http://www.rubydoc.info/gems/cztop/CZTop/Proxy)
|
101
|
-
* [Poller](http://www.rubydoc.info/gems/cztop/CZTop/Poller) (based on `zmq_poller_*()` functions)
|
102
|
-
* [Aggregated](http://www.rubydoc.info/gems/cztop/CZTop/Poller/Aggregated)
|
103
|
-
* [ZPoller](http://www.rubydoc.info/gems/cztop/CZTop/Poller/ZPoller)
|
104
|
-
* [Socket](http://www.rubydoc.info/gems/cztop/CZTop/Socket)
|
105
|
-
* [REQ](http://www.rubydoc.info/gems/cztop/CZTop/Socket/REQ) < Socket
|
106
|
-
* [REP](http://www.rubydoc.info/gems/cztop/CZTop/Socket/REP) < Socket
|
107
|
-
* [ROUTER](http://www.rubydoc.info/gems/cztop/CZTop/Socket/ROUTER) < Socket
|
108
|
-
* [DEALER](http://www.rubydoc.info/gems/cztop/CZTop/Socket/DEALER) < Socket
|
109
|
-
* [PUSH](http://www.rubydoc.info/gems/cztop/CZTop/Socket/PUSH) < Socket
|
110
|
-
* [PULL](http://www.rubydoc.info/gems/cztop/CZTop/Socket/PULL) < Socket
|
111
|
-
* [PUB](http://www.rubydoc.info/gems/cztop/CZTop/Socket/PUB) < Socket
|
112
|
-
* [SUB](http://www.rubydoc.info/gems/cztop/CZTop/Socket/SUB) < Socket
|
113
|
-
* [XPUB](http://www.rubydoc.info/gems/cztop/CZTop/Socket/XPUB) < Socket
|
114
|
-
* [XSUB](http://www.rubydoc.info/gems/cztop/CZTop/Socket/XSUB) < Socket
|
115
|
-
* [PAIR](http://www.rubydoc.info/gems/cztop/CZTop/Socket/PAIR) < Socket
|
116
|
-
* [STREAM](http://www.rubydoc.info/gems/cztop/CZTop/Socket/STREAM) < Socket
|
117
|
-
* [CLIENT](http://www.rubydoc.info/gems/cztop/CZTop/Socket/CLIENT) < Socket
|
118
|
-
* [SERVER](http://www.rubydoc.info/gems/cztop/CZTop/Socket/SERVER) < Socket
|
119
|
-
* [RADIO](http://www.rubydoc.info/gems/cztop/CZTop/Socket/RADIO) < Socket
|
120
|
-
* [DISH](http://www.rubydoc.info/gems/cztop/CZTop/Socket/DISH) < Socket
|
121
|
-
* [SCATTER](http://www.rubydoc.info/gems/cztop/CZTop/Socket/SCATTER) < Socket
|
122
|
-
* [GATHER](http://www.rubydoc.info/gems/cztop/CZTop/Socket/GATHER) < Socket
|
123
|
-
* [Z85](http://www.rubydoc.info/gems/cztop/CZTop/Z85)
|
124
|
-
* [Padded](http://www.rubydoc.info/gems/cztop/CZTop/Z85/Padded) < Z85
|
125
|
-
* [Pipe](http://www.rubydoc.info/gems/cztop/CZTop/Z85/Pipe)
|
126
|
-
* [ZAP](http://www.rubydoc.info/gems/cztop/CZTop/ZAP)
|
86
|
+
```
|
127
87
|
|
128
|
-
|
88
|
+
## Overview
|
129
89
|
|
130
90
|
### Features
|
131
91
|
|
132
92
|
* Ruby idiomatic API
|
133
|
-
*
|
93
|
+
* Fiber Scheduler aware
|
134
94
|
* errors as exceptions
|
135
95
|
* CURVE security
|
136
96
|
* supports CZMQ DRAFT API
|
@@ -175,6 +135,51 @@ Or install it yourself as:
|
|
175
135
|
|
176
136
|
$ gem install cztop
|
177
137
|
|
138
|
+
### Class Hierarchy
|
139
|
+
|
140
|
+
Here's an overview of the core classes:
|
141
|
+
|
142
|
+
* [CZTop](http://www.rubydoc.info/gems/cztop/CZTop)
|
143
|
+
* [Actor](http://www.rubydoc.info/gems/cztop/CZTop)
|
144
|
+
* [Authenticator](http://www.rubydoc.info/gems/cztop/CZTop/Authenticator)
|
145
|
+
* [Beacon](http://www.rubydoc.info/gems/cztop/CZTop/Beacon)
|
146
|
+
* [Certificate](http://www.rubydoc.info/gems/cztop/CZTop/Certificate)
|
147
|
+
* [CertStore](http://www.rubydoc.info/gems/cztop/CZTop/CertStore)
|
148
|
+
* [Config](http://www.rubydoc.info/gems/cztop/CZTop/Config)
|
149
|
+
* [Frame](http://www.rubydoc.info/gems/cztop/CZTop/Frame)
|
150
|
+
* [Message](http://www.rubydoc.info/gems/cztop/CZTop/Message)
|
151
|
+
* [Monitor](http://www.rubydoc.info/gems/cztop/CZTop/Monitor)
|
152
|
+
* [Metadata](http://www.rubydoc.info/gems/cztop/CZTop/Metadata)
|
153
|
+
* [Proxy](http://www.rubydoc.info/gems/cztop/CZTop/Proxy)
|
154
|
+
* [Poller](http://www.rubydoc.info/gems/cztop/CZTop/Poller) (based on `zmq_poller_*()` functions)
|
155
|
+
* [Aggregated](http://www.rubydoc.info/gems/cztop/CZTop/Poller/Aggregated)
|
156
|
+
* [ZPoller](http://www.rubydoc.info/gems/cztop/CZTop/Poller/ZPoller)
|
157
|
+
* [Socket](http://www.rubydoc.info/gems/cztop/CZTop/Socket)
|
158
|
+
* [REQ](http://www.rubydoc.info/gems/cztop/CZTop/Socket/REQ) < Socket
|
159
|
+
* [REP](http://www.rubydoc.info/gems/cztop/CZTop/Socket/REP) < Socket
|
160
|
+
* [ROUTER](http://www.rubydoc.info/gems/cztop/CZTop/Socket/ROUTER) < Socket
|
161
|
+
* [DEALER](http://www.rubydoc.info/gems/cztop/CZTop/Socket/DEALER) < Socket
|
162
|
+
* [PUSH](http://www.rubydoc.info/gems/cztop/CZTop/Socket/PUSH) < Socket
|
163
|
+
* [PULL](http://www.rubydoc.info/gems/cztop/CZTop/Socket/PULL) < Socket
|
164
|
+
* [PUB](http://www.rubydoc.info/gems/cztop/CZTop/Socket/PUB) < Socket
|
165
|
+
* [SUB](http://www.rubydoc.info/gems/cztop/CZTop/Socket/SUB) < Socket
|
166
|
+
* [XPUB](http://www.rubydoc.info/gems/cztop/CZTop/Socket/XPUB) < Socket
|
167
|
+
* [XSUB](http://www.rubydoc.info/gems/cztop/CZTop/Socket/XSUB) < Socket
|
168
|
+
* [PAIR](http://www.rubydoc.info/gems/cztop/CZTop/Socket/PAIR) < Socket
|
169
|
+
* [STREAM](http://www.rubydoc.info/gems/cztop/CZTop/Socket/STREAM) < Socket
|
170
|
+
* [CLIENT](http://www.rubydoc.info/gems/cztop/CZTop/Socket/CLIENT) < Socket
|
171
|
+
* [SERVER](http://www.rubydoc.info/gems/cztop/CZTop/Socket/SERVER) < Socket
|
172
|
+
* [RADIO](http://www.rubydoc.info/gems/cztop/CZTop/Socket/RADIO) < Socket
|
173
|
+
* [DISH](http://www.rubydoc.info/gems/cztop/CZTop/Socket/DISH) < Socket
|
174
|
+
* [SCATTER](http://www.rubydoc.info/gems/cztop/CZTop/Socket/SCATTER) < Socket
|
175
|
+
* [GATHER](http://www.rubydoc.info/gems/cztop/CZTop/Socket/GATHER) < Socket
|
176
|
+
* [Z85](http://www.rubydoc.info/gems/cztop/CZTop/Z85)
|
177
|
+
* [Padded](http://www.rubydoc.info/gems/cztop/CZTop/Z85/Padded) < Z85
|
178
|
+
* [Pipe](http://www.rubydoc.info/gems/cztop/CZTop/Z85/Pipe)
|
179
|
+
* [ZAP](http://www.rubydoc.info/gems/cztop/CZTop/ZAP)
|
180
|
+
|
181
|
+
More information in the [API documentation](http://www.rubydoc.info/github/paddor/cztop).
|
182
|
+
|
178
183
|
## Documentation
|
179
184
|
|
180
185
|
The API should be fairly straight-forward to anyone who is familiar with CZMQ
|
data/cztop.gemspec
CHANGED
@@ -12,13 +12,13 @@ Gem::Specification.new do |spec|
|
|
12
12
|
spec.description = 'CZMQ binding based on the generated low-level FFI bindings of CZMQ'
|
13
13
|
spec.homepage = "https://rubygems.org/gems/cztop"
|
14
14
|
spec.license = "ISC"
|
15
|
-
spec.required_ruby_version = Gem::Requirement.new(">= 3.
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 3.2.0")
|
16
16
|
|
17
17
|
spec.metadata["homepage_uri"] = spec.homepage
|
18
18
|
spec.metadata["source_code_uri"] = "https://github.com/paddor/cztop"
|
19
19
|
spec.metadata["changelog_uri"] = "https://github.com/paddor/cztop/blob/master/CHANGELOG.md"
|
20
20
|
|
21
|
-
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(
|
21
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match?(%r{^(\.|Rakefile|spec/|examples/|ci/|perf/)}) }
|
22
22
|
spec.bindir = "exe"
|
23
23
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
24
24
|
spec.require_paths = ["lib"]
|
@@ -32,10 +32,8 @@ Gem::Specification.new do |spec|
|
|
32
32
|
spec.add_development_dependency "rspec-given", "~> 3.8.0"
|
33
33
|
spec.add_development_dependency "pry"
|
34
34
|
spec.add_development_dependency "yard"
|
35
|
-
spec.add_development_dependency "rubocop", "~> 1.36.0"
|
36
35
|
|
37
36
|
if RUBY_VERSION >= '3.1'
|
38
37
|
spec.add_development_dependency "async", ">= 2.0.1"
|
39
|
-
spec.add_development_dependency "async-io"
|
40
38
|
end
|
41
39
|
end
|
data/lib/cztop/actor.rb
CHANGED
@@ -12,7 +12,7 @@ module CZTop
|
|
12
12
|
#
|
13
13
|
# = About termination
|
14
14
|
# Actors should be terminated explicitly, either by calling {#terminate}
|
15
|
-
# from the current process or sending them the
|
15
|
+
# from the current process or sending them the {TERMINATE} command (from
|
16
16
|
# outside). Not terminating them explicitly might make the process block at
|
17
17
|
# exit.
|
18
18
|
#
|
@@ -48,12 +48,20 @@ module CZTop
|
|
48
48
|
class DeadActorError < RuntimeError; end
|
49
49
|
|
50
50
|
|
51
|
+
# the command which causes an actor handler to terminate
|
52
|
+
TERMINATE = '$TERM'
|
53
|
+
|
54
|
+
|
55
|
+
# timeout to use when sending the actor a message
|
56
|
+
SEND_TIMEOUT = 20 # ms
|
57
|
+
|
58
|
+
|
51
59
|
# @return [Exception] the exception that crashed this actor, if any
|
52
60
|
attr_reader :exception
|
53
61
|
|
54
62
|
|
55
63
|
# Creates a new actor. Either pass a callback directly or a block. The
|
56
|
-
# block will be called for every received message.
|
64
|
+
# callback/block will be called for every received message.
|
57
65
|
#
|
58
66
|
# In case the given callback is an FFI::Pointer (to a C function), it's
|
59
67
|
# used as-is. It is expected to do the handshake (signal) itself.
|
@@ -71,8 +79,9 @@ module CZTop
|
|
71
79
|
@callback = callback || handler
|
72
80
|
@callback = shim(@callback) unless @callback.is_a? ::FFI::Pointer
|
73
81
|
ffi_delegate = Zactor.new(@callback, c_args)
|
82
|
+
|
74
83
|
attach_ffi_delegate(ffi_delegate)
|
75
|
-
options.sndtimeo =
|
84
|
+
options.sndtimeo = SEND_TIMEOUT # see #<<
|
76
85
|
end
|
77
86
|
|
78
87
|
|
@@ -83,11 +92,11 @@ module CZTop
|
|
83
92
|
# @raise [IO::EAGAINWaitWritable, RuntimeError] anything that could be
|
84
93
|
# raised by {Message#send_to}
|
85
94
|
# @note Normally this method is asynchronous, but if the message is
|
86
|
-
#
|
95
|
+
# {TERMINATE}, it blocks until the actor is terminated.
|
87
96
|
def <<(message)
|
88
97
|
message = Message.coerce(message)
|
89
98
|
|
90
|
-
if
|
99
|
+
if TERMINATE == message[0]
|
91
100
|
# NOTE: can't just send this to the actor. The sender might call
|
92
101
|
# #terminate immediately, which most likely causes a hang due to race
|
93
102
|
# conditions.
|
@@ -99,7 +108,7 @@ module CZTop
|
|
99
108
|
|
100
109
|
message.send_to(self)
|
101
110
|
end
|
102
|
-
rescue IO::EAGAINWaitWritable
|
111
|
+
rescue IO::EAGAINWaitWritable, IO::TimeoutError
|
103
112
|
# The sndtimeo has been reached.
|
104
113
|
#
|
105
114
|
# This should fix the race condition (mainly on JRuby) between
|
@@ -111,9 +120,8 @@ module CZTop
|
|
111
120
|
# at least when using a Ruby handler.
|
112
121
|
#
|
113
122
|
# In case of a C function handler, it MUST NOT crash and only
|
114
|
-
# terminate when being sent the
|
115
|
-
# #await_handler_death can set
|
116
|
-
# @running to false).
|
123
|
+
# terminate when being sent the {TERMINATE} message using #terminate (so
|
124
|
+
# #await_handler_death can set @running to false).
|
117
125
|
retry
|
118
126
|
end
|
119
127
|
end
|
@@ -138,13 +146,13 @@ module CZTop
|
|
138
146
|
# it.
|
139
147
|
# @param message [Message] the request to the actor
|
140
148
|
# @return [Message] the actor's response
|
141
|
-
# @raise [ArgumentError] if the message is
|
149
|
+
# @raise [ArgumentError] if the message is {TERMINATE} (use {#terminate})
|
142
150
|
def request(message)
|
143
151
|
@mtx.synchronize do
|
144
152
|
raise DeadActorError unless @running
|
145
153
|
|
146
154
|
message = Message.coerce(message)
|
147
|
-
raise ArgumentError, 'use #terminate' if
|
155
|
+
raise ArgumentError, 'use #terminate' if TERMINATE == message[0]
|
148
156
|
|
149
157
|
message.send_to(self)
|
150
158
|
Message.receive_from(self)
|
@@ -189,11 +197,11 @@ module CZTop
|
|
189
197
|
@mtx.synchronize do
|
190
198
|
return false unless @running
|
191
199
|
|
192
|
-
Message.new(
|
200
|
+
Message.new(TERMINATE).send_to(self)
|
193
201
|
await_handler_death
|
194
202
|
true
|
195
203
|
end
|
196
|
-
rescue IO::EAGAINWaitWritable
|
204
|
+
rescue IO::EAGAINWaitWritable, IO::TimeoutError
|
197
205
|
# same as in #<<
|
198
206
|
retry
|
199
207
|
end
|
@@ -236,8 +244,8 @@ module CZTop
|
|
236
244
|
end
|
237
245
|
|
238
246
|
process_messages(handler)
|
239
|
-
rescue Exception
|
240
|
-
@exception =
|
247
|
+
rescue Exception => e
|
248
|
+
@exception = e
|
241
249
|
ensure
|
242
250
|
signal_shimmed_handler_death
|
243
251
|
end
|
@@ -251,16 +259,12 @@ module CZTop
|
|
251
259
|
end
|
252
260
|
|
253
261
|
|
254
|
-
# the command which causes an actor handler to terminate
|
255
|
-
TERM = '$TERM'
|
256
|
-
|
257
|
-
|
258
262
|
# Successively receive messages that were sent to the actor and
|
259
263
|
# yield them to the given handler to process them. The a pipe (a
|
260
264
|
# {Socket::PAIR} socket) is also passed to the handler so it can send back
|
261
265
|
# the result of a command, if needed.
|
262
266
|
#
|
263
|
-
# When a message is
|
267
|
+
# When a message is {TERMINATE}, or when the waiting for a message is
|
264
268
|
# interrupted, execution is aborted and the actor will terminate.
|
265
269
|
#
|
266
270
|
# @param handler [Proc, #call] the handler used to process messages
|
@@ -274,7 +278,7 @@ module CZTop
|
|
274
278
|
rescue Interrupt
|
275
279
|
break
|
276
280
|
else
|
277
|
-
break if
|
281
|
+
break if TERMINATE == message[0]
|
278
282
|
end
|
279
283
|
|
280
284
|
handler.call(message, @pipe)
|
@@ -294,7 +298,7 @@ module CZTop
|
|
294
298
|
#
|
295
299
|
# This is needed to avoid the race condition between zactor_destroy()
|
296
300
|
# which will wait for a signal from the handler in case it was able to
|
297
|
-
# send the
|
301
|
+
# send the {TERMINATE} command, and the @callback which might still haven't
|
298
302
|
# returned, but doesn't receive any messages anymore.
|
299
303
|
#
|
300
304
|
# @return [void]
|
data/lib/cztop/certificate.rb
CHANGED
@@ -8,19 +8,15 @@ module CZTop
|
|
8
8
|
extend CZTop::HasFFIDelegate::ClassMethods
|
9
9
|
include ::CZMQ::FFI
|
10
10
|
|
11
|
-
unless ::CZMQ::FFI::Zsys.has_curve
|
12
|
-
def self.new(...)
|
13
|
-
fail NotImplementedError
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
|
18
11
|
# Warns if CURVE security isn't available.
|
19
|
-
# @return [
|
12
|
+
# @return [Boolean] whether it's available
|
20
13
|
def self.check_curve_availability
|
21
|
-
|
22
|
-
|
23
|
-
|
14
|
+
if Zsys.has_curve
|
15
|
+
true
|
16
|
+
else
|
17
|
+
warn "CZTop: CURVE isn't available. Consider installing libsodium."
|
18
|
+
false
|
19
|
+
end
|
24
20
|
end
|
25
21
|
|
26
22
|
|
@@ -57,6 +53,18 @@ module CZTop
|
|
57
53
|
from_ffi_delegate(ptr)
|
58
54
|
end
|
59
55
|
|
56
|
+
unless ::CZMQ::FFI::Zsys.has_curve
|
57
|
+
def self.new(...)
|
58
|
+
fail NotImplementedError
|
59
|
+
end
|
60
|
+
def self.load(...)
|
61
|
+
fail NotImplementedError
|
62
|
+
end
|
63
|
+
def self.new_from(...)
|
64
|
+
fail NotImplementedError
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
60
68
|
|
61
69
|
# Initialize a new in-memory certificate with random keys.
|
62
70
|
def initialize
|
data/lib/cztop/message.rb
CHANGED
@@ -49,7 +49,7 @@ module CZTop
|
|
49
49
|
# @raise [IO::EAGAINWaitWritable] if the send timeout has been reached
|
50
50
|
# (see {ZsockOptions::OptionsAccessor#sndtimeo=})
|
51
51
|
# @raise [SocketError] if the message can't be routed to the destination
|
52
|
-
# (either if
|
52
|
+
# (either if ZMQ_ROUTER_MANDATORY flag is set on a {Socket::ROUTER} socket
|
53
53
|
# and the peer isn't connected or its SNDHWM is reached (see
|
54
54
|
# {ZsockOptions::OptionsAccessor#router_mandatory=}, or if it's
|
55
55
|
# a {Socket::SERVER} socket and there's no connected CLIENT
|
@@ -61,6 +61,8 @@ module CZTop
|
|
61
61
|
# returns with failure. Please report as bug.
|
62
62
|
#
|
63
63
|
def send_to(destination)
|
64
|
+
destination.wait_writable
|
65
|
+
|
64
66
|
rc = Zmsg.send(ffi_delegate, destination)
|
65
67
|
return if rc.zero?
|
66
68
|
|
@@ -79,6 +81,8 @@ module CZTop
|
|
79
81
|
# @raise [SystemCallError] for any other error code set after +zmsg_recv+
|
80
82
|
# returns with failure. Please report as bug.
|
81
83
|
def self.receive_from(source)
|
84
|
+
source.wait_readable
|
85
|
+
|
82
86
|
delegate = Zmsg.recv(source)
|
83
87
|
return from_ffi_delegate(delegate) unless delegate.null?
|
84
88
|
|
@@ -10,7 +10,7 @@ module CZTop
|
|
10
10
|
# Sends a message.
|
11
11
|
#
|
12
12
|
# @param message [Message, String, Array<parts>] the message to send
|
13
|
-
# @raise [IO::EAGAINWaitWritable] if send timeout has been reached (see
|
13
|
+
# @raise [IO::EAGAINWaitWritable, IO::TimeoutError] if send timeout has been reached (see
|
14
14
|
# {ZsockOptions::OptionsAccessor#sndtimeo=})
|
15
15
|
# @raise [Interrupt, ArgumentError, SystemCallError] anything raised by
|
16
16
|
# {Message#send_to}
|
@@ -26,7 +26,7 @@ module CZTop
|
|
26
26
|
# Receives a message.
|
27
27
|
#
|
28
28
|
# @return [Message]
|
29
|
-
# @raise [IO::EAGAINWaitReadable] if receive timeout has been reached (see
|
29
|
+
# @raise [IO::EAGAINWaitReadable, IO::TimeoutError] if receive timeout has been reached (see
|
30
30
|
# {ZsockOptions::OptionsAccessor#rcvtimeo=})
|
31
31
|
# @raise [Interrupt, ArgumentError, SystemCallError] anything raised by
|
32
32
|
# {Message.receive_from}
|
@@ -35,5 +35,109 @@ module CZTop
|
|
35
35
|
Message.receive_from(self)
|
36
36
|
end
|
37
37
|
|
38
|
+
|
39
|
+
JIFFY = 0.015 # 15 ms
|
40
|
+
|
41
|
+
|
42
|
+
# Waits for socket to become readable.
|
43
|
+
# @param timeout [Numeric, nil] timeout in seconds
|
44
|
+
# @return [true] if readable within timeout
|
45
|
+
# @raise [IO::EAGAINWaitReadable, IO::TimeoutError] if timeout has been reached
|
46
|
+
def wait_readable(timeout = read_timeout)
|
47
|
+
return true if readable?
|
48
|
+
|
49
|
+
@fd_io ||= to_io
|
50
|
+
|
51
|
+
if timeout
|
52
|
+
timeout_at = now + timeout
|
53
|
+
|
54
|
+
while true
|
55
|
+
@fd_io.wait_readable(timeout)
|
56
|
+
break if readable? # NOTE: ZMQ FD can't be trusted
|
57
|
+
raise ::IO::TimeoutError if now >= timeout_at
|
58
|
+
|
59
|
+
# HACK for edge case: avoid hogging CPU if FD for socket type doesn't block and just insists
|
60
|
+
sleep JIFFY
|
61
|
+
end
|
62
|
+
else
|
63
|
+
until readable?
|
64
|
+
@fd_io.wait_readable
|
65
|
+
|
66
|
+
# HACK for edge case: avoid hogging CPU if FD for socket type doesn't block and just insists
|
67
|
+
sleep JIFFY
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
true
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
# Waits for socket to become writable.
|
76
|
+
# @param timeout [Numeric, nil] timeout in seconds
|
77
|
+
# @return [true] if writable within timeout
|
78
|
+
# @raise [IO::EAGAINWaitReadable, IO::TimeoutError] if timeout has been reached
|
79
|
+
def wait_writable(timeout = write_timeout)
|
80
|
+
return true if writable?
|
81
|
+
|
82
|
+
@fd_io ||= to_io
|
83
|
+
|
84
|
+
if timeout
|
85
|
+
timeout_at = now + timeout
|
86
|
+
|
87
|
+
while true
|
88
|
+
@fd_io.wait_writable(timeout)
|
89
|
+
break if writable? # NOTE: ZMQ FD can't be trusted
|
90
|
+
raise ::IO::TimeoutError if now >= timeout_at
|
91
|
+
|
92
|
+
# HACK for edge case: avoid hogging CPU if FD for socket type doesn't block and just insists
|
93
|
+
sleep JIFFY
|
94
|
+
end
|
95
|
+
else
|
96
|
+
until writable?
|
97
|
+
@fd_io.wait_writable
|
98
|
+
|
99
|
+
# HACK for edge case: avoid hogging CPU if FD for socket type doesn't block and just insists
|
100
|
+
sleep JIFFY
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
true
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
# @return [Float, nil] the timeout in seconds used by {IO#wait_readable}
|
109
|
+
def read_timeout
|
110
|
+
timeout = options.rcvtimeo
|
111
|
+
|
112
|
+
if timeout <= 0
|
113
|
+
timeout = nil
|
114
|
+
else
|
115
|
+
timeout = timeout.to_f / 1000
|
116
|
+
end
|
117
|
+
|
118
|
+
timeout
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
# @return [Float, nil] the timeout in seconds used by {IO#wait_writable}
|
123
|
+
def write_timeout
|
124
|
+
timeout = options.sndtimeo
|
125
|
+
|
126
|
+
if timeout <= 0
|
127
|
+
timeout = nil
|
128
|
+
else
|
129
|
+
timeout = timeout.to_f / 1000
|
130
|
+
end
|
131
|
+
|
132
|
+
timeout
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
private
|
137
|
+
|
138
|
+
|
139
|
+
def now
|
140
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
141
|
+
end
|
38
142
|
end
|
39
143
|
end
|
data/lib/cztop/socket/types.rb
CHANGED
@@ -76,6 +76,15 @@ module CZTop
|
|
76
76
|
attach_ffi_delegate(Zsock.new_client(endpoints))
|
77
77
|
end
|
78
78
|
|
79
|
+
|
80
|
+
# @raise [SocketError] if no peer is connected
|
81
|
+
def wait_writable(...)
|
82
|
+
if !writable?
|
83
|
+
fail SocketError, "no peer connected"
|
84
|
+
end
|
85
|
+
|
86
|
+
super
|
87
|
+
end
|
79
88
|
end
|
80
89
|
|
81
90
|
|
@@ -160,6 +169,15 @@ module CZTop
|
|
160
169
|
self << message
|
161
170
|
end
|
162
171
|
|
172
|
+
|
173
|
+
# @raise [SocketError] if ZMQ_ROUTER_MANDATORY option and message is currently not routable
|
174
|
+
def wait_writable(...)
|
175
|
+
if options.router_mandatory? && !writable?
|
176
|
+
fail SocketError, "no peer connected"
|
177
|
+
end
|
178
|
+
|
179
|
+
super
|
180
|
+
end
|
163
181
|
end
|
164
182
|
|
165
183
|
|
data/lib/cztop/version.rb
CHANGED
data/lib/cztop/zsock_options.rb
CHANGED
@@ -43,6 +43,12 @@ module CZTop
|
|
43
43
|
end
|
44
44
|
|
45
45
|
|
46
|
+
# @return [IO] IO for FD
|
47
|
+
def to_io
|
48
|
+
IO.for_fd fd, autoclose: false
|
49
|
+
end
|
50
|
+
|
51
|
+
|
46
52
|
# Used to access the options of a {Socket} or {Actor}.
|
47
53
|
class OptionsAccessor
|
48
54
|
|
@@ -319,11 +325,19 @@ module CZTop
|
|
319
325
|
|
320
326
|
# @!endgroup
|
321
327
|
|
322
|
-
# Accept only routable messages on ROUTER sockets. Default is off.
|
323
|
-
# @param bool [Boolean] whether to
|
328
|
+
# ZMQ_ROUTER_MANDATORY: Accept only routable messages on ROUTER sockets. Default is off.
|
329
|
+
# @param bool [Boolean] whether to raise a SocketError if a message isn't routable
|
324
330
|
# (either if the that peer isn't connected or its SNDHWM is reached)
|
331
|
+
# @see https://libzmq.readthedocs.io/en/latest/zmq_setsockopt.html#_zmq_router_mandatory_accept_only_routable_messages_on_router_sockets
|
325
332
|
def router_mandatory=(bool)
|
326
333
|
Zsock.set_router_mandatory(@zocket, bool ? 1 : 0)
|
334
|
+
@router_mandatory = bool # NOTE: no way to read this option, so we need to remember
|
335
|
+
end
|
336
|
+
|
337
|
+
|
338
|
+
# @return [Boolean] whether ZMQ_ROUTER_MANDATORY has been set
|
339
|
+
def router_mandatory?
|
340
|
+
@router_mandatory
|
327
341
|
end
|
328
342
|
|
329
343
|
|