cztop 1.1.1 → 1.2.0
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/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
|
|