omq 0.1.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 +7 -0
- data/CHANGELOG.md +30 -0
- data/LICENSE +15 -0
- data/README.md +145 -0
- data/lib/omq/pair.rb +13 -0
- data/lib/omq/pub_sub.rb +77 -0
- data/lib/omq/push_pull.rb +21 -0
- data/lib/omq/req_rep.rb +23 -0
- data/lib/omq/router_dealer.rb +36 -0
- data/lib/omq/socket.rb +178 -0
- data/lib/omq/version.rb +5 -0
- data/lib/omq/zmtp/codec/command.rb +207 -0
- data/lib/omq/zmtp/codec/frame.rb +104 -0
- data/lib/omq/zmtp/codec/greeting.rb +96 -0
- data/lib/omq/zmtp/codec.rb +18 -0
- data/lib/omq/zmtp/connection.rb +233 -0
- data/lib/omq/zmtp/engine.rb +339 -0
- data/lib/omq/zmtp/mechanism/null.rb +70 -0
- data/lib/omq/zmtp/options.rb +57 -0
- data/lib/omq/zmtp/reactor.rb +142 -0
- data/lib/omq/zmtp/readable.rb +29 -0
- data/lib/omq/zmtp/routing/dealer.rb +57 -0
- data/lib/omq/zmtp/routing/fan_out.rb +89 -0
- data/lib/omq/zmtp/routing/pair.rb +68 -0
- data/lib/omq/zmtp/routing/pub.rb +62 -0
- data/lib/omq/zmtp/routing/pull.rb +48 -0
- data/lib/omq/zmtp/routing/push.rb +57 -0
- data/lib/omq/zmtp/routing/rep.rb +83 -0
- data/lib/omq/zmtp/routing/req.rb +70 -0
- data/lib/omq/zmtp/routing/round_robin.rb +69 -0
- data/lib/omq/zmtp/routing/router.rb +88 -0
- data/lib/omq/zmtp/routing/sub.rb +80 -0
- data/lib/omq/zmtp/routing/xpub.rb +74 -0
- data/lib/omq/zmtp/routing/xsub.rb +80 -0
- data/lib/omq/zmtp/routing.rb +38 -0
- data/lib/omq/zmtp/transport/inproc.rb +299 -0
- data/lib/omq/zmtp/transport/ipc.rb +114 -0
- data/lib/omq/zmtp/transport/tcp.rb +98 -0
- data/lib/omq/zmtp/valid_peers.rb +21 -0
- data/lib/omq/zmtp/writable.rb +44 -0
- data/lib/omq/zmtp.rb +47 -0
- data/lib/omq.rb +19 -0
- metadata +110 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: f11ba4b6d749acef6ad97b6f328aec61aba341ba5759660e23c896af2d96054d
|
|
4
|
+
data.tar.gz: cf91d6bc140f04d000b5add87322ba9f39ea3b5790f75e4b22144b429284b4c7
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 4ecc0a3992c75679ea9cebd9872bdb7a7c9d3f3e86eaccd82dccc7717a84591c5d8493ab0629b14dfdaea35e30b0bae27c6c142841e2f1c61a04a4ef09805b54
|
|
7
|
+
data.tar.gz: 7789734d032134883b3ae71ba0af36a7f89d67625d4e879e03f1be81d3ae83b3f1afcda0610bb203ff25a62ecdfac90a4f56b08b5a8127cbab94b1e305261278
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 — 2026-03-25
|
|
4
|
+
|
|
5
|
+
Initial release. Pure Ruby implementation of ZMTP 3.1 (ZeroMQ) using Async.
|
|
6
|
+
|
|
7
|
+
### Socket types
|
|
8
|
+
|
|
9
|
+
- REQ, REP, DEALER, ROUTER
|
|
10
|
+
- PUB, SUB, XPUB, XSUB
|
|
11
|
+
- PUSH, PULL
|
|
12
|
+
- PAIR
|
|
13
|
+
|
|
14
|
+
### Transports
|
|
15
|
+
|
|
16
|
+
- TCP (with ephemeral port support and IPv6)
|
|
17
|
+
- IPC (Unix domain sockets, including Linux abstract namespace)
|
|
18
|
+
- inproc (in-process, lock-free direct pipes)
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
- Buffered I/O via io-stream (read-ahead buffering, automatic TCP_NODELAY)
|
|
23
|
+
- Heartbeat (PING/PONG) with configurable interval and timeout
|
|
24
|
+
- Automatic reconnection with exponential backoff
|
|
25
|
+
- Per-socket send/receive HWM (high-water mark)
|
|
26
|
+
- Linger on close (drain send queue before closing)
|
|
27
|
+
- `max_message_size` enforcement
|
|
28
|
+
- `connect_timeout` for TCP
|
|
29
|
+
- Works inside Async reactors or standalone (shared IO thread)
|
|
30
|
+
- Optional CURVE encryption via the `omq-curve` gem
|
data/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026, Patrik Wenger
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# OMQ! Where did the C dependency go?!
|
|
2
|
+
|
|
3
|
+
[](https://github.com/paddor/omq/actions/workflows/ci.yml)
|
|
4
|
+
[](https://rubygems.org/gems/omq)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://www.ruby-lang.org)
|
|
7
|
+
|
|
8
|
+
Pure Ruby implementation of the [ZMTP 3.1](https://rfc.zeromq.org/spec/23/) wire protocol ([ZeroMQ](https://zeromq.org/)) using the [Async](https://github.com/socketry/async) gem. No native libraries required.
|
|
9
|
+
|
|
10
|
+
> **186k msg/s** inproc throughput | **12 µs** req/rep roundtrip latency | pure Ruby + YJIT
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Highlights
|
|
15
|
+
|
|
16
|
+
- **Pure Ruby** — no C extensions, no FFI, no libzmq/libczmq dependency
|
|
17
|
+
- **All socket types** — req/rep, pub/sub, push/pull, dealer/router, xpub/xsub, pair
|
|
18
|
+
- **Async-native** — built on [Async](https://github.com/socketry/async) fibers, also works with plain threads
|
|
19
|
+
- **Ruby-idiomatic API** — messages as `Array<String>`, errors as exceptions, timeouts as `IO::TimeoutError`
|
|
20
|
+
- **All transports** — tcp, ipc, inproc
|
|
21
|
+
|
|
22
|
+
## Why pure Ruby?
|
|
23
|
+
|
|
24
|
+
Modern Ruby has closed the gap:
|
|
25
|
+
|
|
26
|
+
- **YJIT** — JIT-compiled hot paths close the throughput gap with C extensions
|
|
27
|
+
- **Fiber Scheduler** — non-blocking I/O without callbacks or threads (`Async` builds on this)
|
|
28
|
+
- **`IO::Buffer`** — zero-copy binary reads/writes, no manual `String#b` packing
|
|
29
|
+
|
|
30
|
+
When [CZTop](https://github.com/paddor/cztop) was written, none of this existed. Today, a pure Ruby ZMTP implementation is fast enough for production use — and you get `gem install` with no compiler toolchain, no system packages, and no segfaults.
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
No system libraries needed — just Ruby:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
gem install omq
|
|
38
|
+
# or in Gemfile
|
|
39
|
+
gem 'omq'
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Learning ZeroMQ
|
|
43
|
+
|
|
44
|
+
New to ZeroMQ? See [ZGUIDE_SUMMARY.md](ZGUIDE_SUMMARY.md) — a ~30 min read covering all major patterns with working OMQ code examples.
|
|
45
|
+
|
|
46
|
+
## Quick Start
|
|
47
|
+
|
|
48
|
+
### Request / Reply
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
require 'omq'
|
|
52
|
+
require 'async'
|
|
53
|
+
|
|
54
|
+
Async do |task|
|
|
55
|
+
rep = OMQ::REP.bind('inproc://example')
|
|
56
|
+
req = OMQ::REQ.connect('inproc://example')
|
|
57
|
+
|
|
58
|
+
task.async do
|
|
59
|
+
msg = rep.receive
|
|
60
|
+
rep << msg.map(&:upcase)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
req << 'hello'
|
|
64
|
+
puts req.receive.inspect # => ["HELLO"]
|
|
65
|
+
ensure
|
|
66
|
+
req&.close
|
|
67
|
+
rep&.close
|
|
68
|
+
end
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Pub / Sub
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
Async do |task|
|
|
75
|
+
pub = OMQ::PUB.bind('inproc://pubsub')
|
|
76
|
+
sub = OMQ::SUB.connect('inproc://pubsub')
|
|
77
|
+
sub.subscribe('') # subscribe to all
|
|
78
|
+
|
|
79
|
+
task.async { pub << 'news flash' }
|
|
80
|
+
puts sub.receive.inspect # => ["news flash"]
|
|
81
|
+
ensure
|
|
82
|
+
pub&.close
|
|
83
|
+
sub&.close
|
|
84
|
+
end
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Push / Pull (Pipeline)
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
Async do
|
|
91
|
+
push = OMQ::PUSH.connect('inproc://pipeline')
|
|
92
|
+
pull = OMQ::PULL.bind('inproc://pipeline')
|
|
93
|
+
|
|
94
|
+
push << 'work item'
|
|
95
|
+
puts pull.receive.inspect # => ["work item"]
|
|
96
|
+
ensure
|
|
97
|
+
push&.close
|
|
98
|
+
pull&.close
|
|
99
|
+
end
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Socket Types
|
|
103
|
+
|
|
104
|
+
| Pattern | Classes | Direction |
|
|
105
|
+
|---------|---------|-----------|
|
|
106
|
+
| Request/Reply | `REQ`, `REP` | bidirectional |
|
|
107
|
+
| Publish/Subscribe | `PUB`, `SUB`, `XPUB`, `XSUB` | unidirectional |
|
|
108
|
+
| Pipeline | `PUSH`, `PULL` | unidirectional |
|
|
109
|
+
| Routing | `DEALER`, `ROUTER` | bidirectional |
|
|
110
|
+
| Exclusive pair | `PAIR` | bidirectional |
|
|
111
|
+
|
|
112
|
+
All classes live under `OMQ::`.
|
|
113
|
+
|
|
114
|
+
## Performance
|
|
115
|
+
|
|
116
|
+
Benchmarked with benchmark-ips on Linux x86_64 (Ruby 4.0.1 +YJIT):
|
|
117
|
+
|
|
118
|
+
#### Throughput (push/pull, 64 B messages)
|
|
119
|
+
|
|
120
|
+
| inproc | ipc | tcp |
|
|
121
|
+
|--------|-----|-----|
|
|
122
|
+
| 184k/s | 35k/s | 18k/s |
|
|
123
|
+
|
|
124
|
+
#### Latency (req/rep roundtrip)
|
|
125
|
+
|
|
126
|
+
| inproc | ipc | tcp |
|
|
127
|
+
|--------|-----|-----|
|
|
128
|
+
| 13 µs | 70 µs | 97 µs |
|
|
129
|
+
|
|
130
|
+
See [`bench/`](bench/) for full results and scripts.
|
|
131
|
+
|
|
132
|
+
## Interop with native ZMQ
|
|
133
|
+
|
|
134
|
+
OMQ speaks ZMTP 3.1 on the wire and interoperates with libzmq, CZMQ, pyzmq, etc. over **tcp** and **ipc**. The `inproc://` transport is OMQ-internal (in-process Ruby queues) and is not visible to native ZMQ running in the same process — use `ipc://` to talk across library boundaries.
|
|
135
|
+
|
|
136
|
+
## Development
|
|
137
|
+
|
|
138
|
+
```sh
|
|
139
|
+
bundle install
|
|
140
|
+
bundle exec rake
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## License
|
|
144
|
+
|
|
145
|
+
[ISC](LICENSE)
|
data/lib/omq/pair.rb
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
class PAIR < Socket
|
|
5
|
+
include ZMTP::Readable
|
|
6
|
+
include ZMTP::Writable
|
|
7
|
+
|
|
8
|
+
def initialize(endpoints = nil, linger: 0)
|
|
9
|
+
_init_engine(:PAIR, linger: linger)
|
|
10
|
+
_attach(endpoints, default: :connect)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
data/lib/omq/pub_sub.rb
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
class PUB < Socket
|
|
5
|
+
include ZMTP::Writable
|
|
6
|
+
|
|
7
|
+
def initialize(endpoints = nil, linger: 0)
|
|
8
|
+
_init_engine(:PUB, linger: linger)
|
|
9
|
+
_attach(endpoints, default: :bind)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# SUB socket.
|
|
14
|
+
#
|
|
15
|
+
class SUB < Socket
|
|
16
|
+
include ZMTP::Readable
|
|
17
|
+
|
|
18
|
+
# @return [String] subscription prefix to subscribe to everything
|
|
19
|
+
#
|
|
20
|
+
EVERYTHING = ''
|
|
21
|
+
|
|
22
|
+
# @param endpoints [String, nil]
|
|
23
|
+
# @param linger [Integer]
|
|
24
|
+
# @param prefix [String, nil] subscription prefix; +nil+ (default)
|
|
25
|
+
# means no subscription — call {#subscribe} explicitly.
|
|
26
|
+
#
|
|
27
|
+
def initialize(endpoints = nil, linger: 0, prefix: nil)
|
|
28
|
+
_init_engine(:SUB, linger: linger)
|
|
29
|
+
_attach(endpoints, default: :connect)
|
|
30
|
+
subscribe(prefix) unless prefix.nil?
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Subscribes to a topic prefix.
|
|
34
|
+
#
|
|
35
|
+
# @param prefix [String]
|
|
36
|
+
# @return [void]
|
|
37
|
+
#
|
|
38
|
+
def subscribe(prefix = EVERYTHING)
|
|
39
|
+
@engine.routing.subscribe(prefix)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Unsubscribes from a topic prefix.
|
|
43
|
+
#
|
|
44
|
+
# @param prefix [String]
|
|
45
|
+
# @return [void]
|
|
46
|
+
#
|
|
47
|
+
def unsubscribe(prefix)
|
|
48
|
+
@engine.routing.unsubscribe(prefix)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
class XPUB < Socket
|
|
53
|
+
include ZMTP::Readable
|
|
54
|
+
include ZMTP::Writable
|
|
55
|
+
|
|
56
|
+
def initialize(endpoints = nil, linger: 0)
|
|
57
|
+
_init_engine(:XPUB, linger: linger)
|
|
58
|
+
_attach(endpoints, default: :bind)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
class XSUB < Socket
|
|
63
|
+
include ZMTP::Readable
|
|
64
|
+
include ZMTP::Writable
|
|
65
|
+
|
|
66
|
+
# @param endpoints [String, nil]
|
|
67
|
+
# @param linger [Integer]
|
|
68
|
+
# @param prefix [String, nil] subscription prefix; +nil+ (default)
|
|
69
|
+
# means no subscription — send a subscribe frame explicitly.
|
|
70
|
+
#
|
|
71
|
+
def initialize(endpoints = nil, linger: 0, prefix: nil)
|
|
72
|
+
_init_engine(:XSUB, linger: linger)
|
|
73
|
+
_attach(endpoints, default: :connect)
|
|
74
|
+
send("\x01#{prefix}".b) unless prefix.nil?
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
class PUSH < Socket
|
|
5
|
+
include ZMTP::Writable
|
|
6
|
+
|
|
7
|
+
def initialize(endpoints = nil, linger: 0)
|
|
8
|
+
_init_engine(:PUSH, linger: linger)
|
|
9
|
+
_attach(endpoints, default: :connect)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class PULL < Socket
|
|
14
|
+
include ZMTP::Readable
|
|
15
|
+
|
|
16
|
+
def initialize(endpoints = nil, linger: 0)
|
|
17
|
+
_init_engine(:PULL, linger: linger)
|
|
18
|
+
_attach(endpoints, default: :bind)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
data/lib/omq/req_rep.rb
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
class REQ < Socket
|
|
5
|
+
include ZMTP::Readable
|
|
6
|
+
include ZMTP::Writable
|
|
7
|
+
|
|
8
|
+
def initialize(endpoints = nil, linger: 0)
|
|
9
|
+
_init_engine(:REQ, linger: linger)
|
|
10
|
+
_attach(endpoints, default: :connect)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class REP < Socket
|
|
15
|
+
include ZMTP::Readable
|
|
16
|
+
include ZMTP::Writable
|
|
17
|
+
|
|
18
|
+
def initialize(endpoints = nil, linger: 0)
|
|
19
|
+
_init_engine(:REP, linger: linger)
|
|
20
|
+
_attach(endpoints, default: :bind)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
class DEALER < Socket
|
|
5
|
+
include ZMTP::Readable
|
|
6
|
+
include ZMTP::Writable
|
|
7
|
+
|
|
8
|
+
def initialize(endpoints = nil, linger: 0)
|
|
9
|
+
_init_engine(:DEALER, linger: linger)
|
|
10
|
+
_attach(endpoints, default: :connect)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# ROUTER socket.
|
|
15
|
+
#
|
|
16
|
+
class ROUTER < Socket
|
|
17
|
+
include ZMTP::Readable
|
|
18
|
+
include ZMTP::Writable
|
|
19
|
+
|
|
20
|
+
def initialize(endpoints = nil, linger: 0)
|
|
21
|
+
_init_engine(:ROUTER, linger: linger)
|
|
22
|
+
_attach(endpoints, default: :bind)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Sends a message to a specific peer by identity.
|
|
26
|
+
#
|
|
27
|
+
# @param receiver [String] peer identity
|
|
28
|
+
# @param message [String, Array<String>]
|
|
29
|
+
# @return [self]
|
|
30
|
+
#
|
|
31
|
+
def send_to(receiver, message)
|
|
32
|
+
parts = message.is_a?(Array) ? message : [message]
|
|
33
|
+
send([receiver, '', *parts])
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/omq/socket.rb
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
# Socket base class.
|
|
5
|
+
#
|
|
6
|
+
class Socket
|
|
7
|
+
# @return [ZMTP::Options]
|
|
8
|
+
#
|
|
9
|
+
attr_reader :options
|
|
10
|
+
|
|
11
|
+
# @return [Integer, nil] last auto-selected TCP port
|
|
12
|
+
#
|
|
13
|
+
attr_reader :last_tcp_port
|
|
14
|
+
|
|
15
|
+
# Delegate socket option accessors to @options.
|
|
16
|
+
#
|
|
17
|
+
%i[
|
|
18
|
+
send_hwm send_hwm=
|
|
19
|
+
recv_hwm recv_hwm=
|
|
20
|
+
linger linger=
|
|
21
|
+
identity identity=
|
|
22
|
+
recv_timeout recv_timeout=
|
|
23
|
+
send_timeout send_timeout=
|
|
24
|
+
read_timeout read_timeout=
|
|
25
|
+
write_timeout write_timeout=
|
|
26
|
+
router_mandatory router_mandatory=
|
|
27
|
+
router_mandatory?
|
|
28
|
+
reconnect_interval reconnect_interval=
|
|
29
|
+
heartbeat_interval heartbeat_interval=
|
|
30
|
+
heartbeat_ttl heartbeat_ttl=
|
|
31
|
+
heartbeat_timeout heartbeat_timeout=
|
|
32
|
+
max_message_size max_message_size=
|
|
33
|
+
connect_timeout connect_timeout=
|
|
34
|
+
mechanism mechanism=
|
|
35
|
+
curve_server curve_server=
|
|
36
|
+
curve_server_key curve_server_key=
|
|
37
|
+
curve_public_key curve_public_key=
|
|
38
|
+
curve_secret_key curve_secret_key=
|
|
39
|
+
curve_authenticator curve_authenticator=
|
|
40
|
+
].each do |method|
|
|
41
|
+
define_method(method) { |*args| @options.public_send(method, *args) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Creates a new socket and binds it to the given endpoint.
|
|
45
|
+
#
|
|
46
|
+
# @param endpoint [String]
|
|
47
|
+
# @param opts [Hash] keyword arguments forwarded to {#initialize}
|
|
48
|
+
# @return [Socket]
|
|
49
|
+
#
|
|
50
|
+
def self.bind(endpoint, **opts)
|
|
51
|
+
new(nil, **opts).tap { |s| s.bind(endpoint) }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Creates a new socket and connects it to the given endpoint.
|
|
55
|
+
#
|
|
56
|
+
# @param endpoint [String]
|
|
57
|
+
# @param opts [Hash] keyword arguments forwarded to {#initialize}
|
|
58
|
+
# @return [Socket]
|
|
59
|
+
#
|
|
60
|
+
def self.connect(endpoint, **opts)
|
|
61
|
+
new(nil, **opts).tap { |s| s.connect(endpoint) }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def initialize(endpoints = nil, linger: 0); end
|
|
65
|
+
|
|
66
|
+
# Binds to an endpoint.
|
|
67
|
+
#
|
|
68
|
+
# @param endpoint [String]
|
|
69
|
+
# @return [void]
|
|
70
|
+
#
|
|
71
|
+
def bind(endpoint)
|
|
72
|
+
@engine.bind(endpoint)
|
|
73
|
+
@last_tcp_port = @engine.last_tcp_port
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Connects to an endpoint.
|
|
77
|
+
#
|
|
78
|
+
# @param endpoint [String]
|
|
79
|
+
# @return [void]
|
|
80
|
+
#
|
|
81
|
+
def connect(endpoint)
|
|
82
|
+
@engine.connect(endpoint)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Disconnects from an endpoint.
|
|
86
|
+
#
|
|
87
|
+
# @param endpoint [String]
|
|
88
|
+
# @return [void]
|
|
89
|
+
#
|
|
90
|
+
def disconnect(endpoint)
|
|
91
|
+
@engine.disconnect(endpoint)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Unbinds from an endpoint.
|
|
95
|
+
#
|
|
96
|
+
# @param endpoint [String]
|
|
97
|
+
# @return [void]
|
|
98
|
+
#
|
|
99
|
+
def unbind(endpoint)
|
|
100
|
+
@engine.unbind(endpoint)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# @return [String, nil] last bound endpoint
|
|
104
|
+
#
|
|
105
|
+
def last_endpoint
|
|
106
|
+
@engine.last_endpoint
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Closes the socket.
|
|
110
|
+
#
|
|
111
|
+
# @return [void]
|
|
112
|
+
#
|
|
113
|
+
def close
|
|
114
|
+
@engine.close
|
|
115
|
+
nil
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Set socket to use unbounded pipes (HWM=0).
|
|
119
|
+
#
|
|
120
|
+
def set_unbounded
|
|
121
|
+
@options.send_hwm = 0
|
|
122
|
+
@options.recv_hwm = 0
|
|
123
|
+
nil
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# @return [String]
|
|
127
|
+
#
|
|
128
|
+
def inspect
|
|
129
|
+
format("#<%s last_endpoint=%p>", self.class, last_endpoint)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
private
|
|
133
|
+
|
|
134
|
+
# Runs a block with a timeout. Uses Async's with_timeout if inside
|
|
135
|
+
# a reactor, otherwise falls back to Timeout.timeout.
|
|
136
|
+
#
|
|
137
|
+
# @param seconds [Numeric]
|
|
138
|
+
# @raise [IO::TimeoutError]
|
|
139
|
+
#
|
|
140
|
+
def with_timeout(seconds, &block)
|
|
141
|
+
return yield if seconds.nil?
|
|
142
|
+
if Async::Task.current?
|
|
143
|
+
Async::Task.current.with_timeout(seconds, &block)
|
|
144
|
+
else
|
|
145
|
+
Timeout.timeout(seconds, &block)
|
|
146
|
+
end
|
|
147
|
+
rescue Async::TimeoutError, Timeout::Error
|
|
148
|
+
raise IO::TimeoutError, "timed out"
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Connects or binds based on endpoint prefix convention.
|
|
152
|
+
#
|
|
153
|
+
# @param endpoints [String, nil]
|
|
154
|
+
# @param default [Symbol] :connect or :bind
|
|
155
|
+
#
|
|
156
|
+
def _attach(endpoints, default:)
|
|
157
|
+
return unless endpoints
|
|
158
|
+
case endpoints
|
|
159
|
+
when /\A@(.+)\z/
|
|
160
|
+
bind($1)
|
|
161
|
+
when /\A>(.+)\z/
|
|
162
|
+
connect($1)
|
|
163
|
+
else
|
|
164
|
+
__send__(default, endpoints)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Initializes engine and options for a socket type.
|
|
169
|
+
#
|
|
170
|
+
# @param socket_type [Symbol]
|
|
171
|
+
# @param linger [Integer]
|
|
172
|
+
#
|
|
173
|
+
def _init_engine(socket_type, linger:)
|
|
174
|
+
@options = ZMTP::Options.new(linger: linger)
|
|
175
|
+
@engine = ZMTP::Engine.new(socket_type, @options)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|