omq-rfc-channel 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/LICENSE +15 -0
- data/README.md +39 -0
- data/lib/omq/channel.rb +22 -0
- data/lib/omq/rfc/channel/version.rb +10 -0
- data/lib/omq/rfc/channel.rb +18 -0
- data/lib/omq/routing/channel.rb +106 -0
- data/lib/omq/single_frame.rb +23 -0
- metadata +62 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 2bba37b4550d6b127e512a6eb255418b0fc5282cba216370534ebfc94fb89e84
|
|
4
|
+
data.tar.gz: d9767604d3992503b74cacadad5d92db8aefa5e303fd7e4d03d557830efa071b
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ae4d13e17a6ee142da473fa81ad50b30503eeb28a67983eeb92020699948bca3178070ee0deeecb1aacbac538c3578b283bcf39e2a7055da237ca6d55da4b0c7
|
|
7
|
+
data.tar.gz: c03817cbd7cdc8cefb9c1e15e3e46a1c230c425a73678c67ce41b9c5e2100492860dbb81a1c75431093e05a4c26fc9ea2887b8134c0540794627cfa747b702b6
|
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,39 @@
|
|
|
1
|
+
# OMQ::CHANNEL
|
|
2
|
+
|
|
3
|
+
[](https://github.com/paddor/omq-rfc-channel/actions/workflows/ci.yml)
|
|
4
|
+
[](https://rubygems.org/gems/omq-rfc-channel)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://www.ruby-lang.org)
|
|
7
|
+
|
|
8
|
+
CHANNEL socket type ([RFC 52](https://rfc.zeromq.org/spec/52/)) for
|
|
9
|
+
[OMQ](https://github.com/paddor/omq).
|
|
10
|
+
|
|
11
|
+
Single-frame, bidirectional, exclusive pair. Like PAIR but restricted to
|
|
12
|
+
single-frame messages (draft socket).
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
require "omq"
|
|
18
|
+
require "omq/rfc/channel"
|
|
19
|
+
|
|
20
|
+
ch1 = OMQ::CHANNEL.bind("tcp://127.0.0.1:5555")
|
|
21
|
+
ch2 = OMQ::CHANNEL.connect("tcp://127.0.0.1:5555")
|
|
22
|
+
|
|
23
|
+
ch1 << "ping"
|
|
24
|
+
ch2.receive # => "ping"
|
|
25
|
+
ch2 << "pong"
|
|
26
|
+
ch1.receive # => "pong"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
gem "omq-rfc-channel"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Requires `omq >= 0.12`.
|
|
36
|
+
|
|
37
|
+
## License
|
|
38
|
+
|
|
39
|
+
[ISC](LICENSE)
|
data/lib/omq/channel.rb
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
# Exclusive 1-to-1 bidirectional socket (ZeroMQ RFC 52).
|
|
5
|
+
#
|
|
6
|
+
# Allows exactly one peer connection. Both sides can send and receive.
|
|
7
|
+
class CHANNEL < Socket
|
|
8
|
+
include Readable
|
|
9
|
+
include Writable
|
|
10
|
+
include SingleFrame
|
|
11
|
+
|
|
12
|
+
# Creates a new CHANNEL socket.
|
|
13
|
+
#
|
|
14
|
+
# @param endpoints [String, Array<String>, nil] endpoint(s) to connect to
|
|
15
|
+
# @param linger [Integer] linger period in milliseconds
|
|
16
|
+
# @param backend [Object, nil] optional transport backend
|
|
17
|
+
def initialize(endpoints = nil, linger: 0, backend: nil)
|
|
18
|
+
_init_engine(:CHANNEL, linger: linger, backend: backend)
|
|
19
|
+
_attach(endpoints, default: :connect)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# OMQ CHANNEL socket type (ZeroMQ RFC 52).
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# require "omq/rfc/channel"
|
|
7
|
+
#
|
|
8
|
+
# a = OMQ::CHANNEL.bind("tcp://127.0.0.1:5555")
|
|
9
|
+
# b = OMQ::CHANNEL.connect("tcp://127.0.0.1:5555")
|
|
10
|
+
|
|
11
|
+
require "omq"
|
|
12
|
+
|
|
13
|
+
require_relative "channel/version"
|
|
14
|
+
require_relative "../single_frame"
|
|
15
|
+
require_relative "../routing/channel"
|
|
16
|
+
require_relative "../channel"
|
|
17
|
+
|
|
18
|
+
OMQ::Routing.register(:CHANNEL, OMQ::Routing::Channel)
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module Routing
|
|
5
|
+
# CHANNEL socket routing: exclusive 1-to-1 bidirectional.
|
|
6
|
+
#
|
|
7
|
+
class Channel
|
|
8
|
+
include FairRecv
|
|
9
|
+
|
|
10
|
+
# @param engine [Engine]
|
|
11
|
+
#
|
|
12
|
+
def initialize(engine)
|
|
13
|
+
@engine = engine
|
|
14
|
+
@connection = nil
|
|
15
|
+
@recv_queue = FairQueue.new
|
|
16
|
+
@send_queue = nil
|
|
17
|
+
@staging_queue = Routing.build_queue(@engine.options.send_hwm, :block)
|
|
18
|
+
@send_pump = nil
|
|
19
|
+
@tasks = []
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# @return [FairQueue]
|
|
24
|
+
#
|
|
25
|
+
attr_reader :recv_queue
|
|
26
|
+
|
|
27
|
+
# @param connection [Connection]
|
|
28
|
+
# @raise [RuntimeError] if a connection already exists
|
|
29
|
+
#
|
|
30
|
+
def connection_added(connection)
|
|
31
|
+
raise "CHANNEL allows only one peer" if @connection
|
|
32
|
+
@connection = connection
|
|
33
|
+
|
|
34
|
+
add_fair_recv_connection(connection)
|
|
35
|
+
|
|
36
|
+
unless connection.is_a?(Transport::Inproc::DirectPipe)
|
|
37
|
+
@send_queue = Routing.build_queue(@engine.options.send_hwm, :block)
|
|
38
|
+
while (msg = @staging_queue.dequeue(timeout: 0))
|
|
39
|
+
@send_queue.enqueue(msg)
|
|
40
|
+
end
|
|
41
|
+
start_send_pump(connection)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# @param connection [Connection]
|
|
47
|
+
#
|
|
48
|
+
def connection_removed(connection)
|
|
49
|
+
if @connection == connection
|
|
50
|
+
@connection = nil
|
|
51
|
+
@recv_queue.remove_queue(connection)
|
|
52
|
+
@send_queue = nil
|
|
53
|
+
@send_pump&.stop
|
|
54
|
+
@send_pump = nil
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# @param parts [Array<String>]
|
|
60
|
+
#
|
|
61
|
+
def enqueue(parts)
|
|
62
|
+
conn = @connection
|
|
63
|
+
if conn.is_a?(Transport::Inproc::DirectPipe) && conn.direct_recv_queue
|
|
64
|
+
conn.send_message(parts)
|
|
65
|
+
elsif @send_queue
|
|
66
|
+
@send_queue.enqueue(parts)
|
|
67
|
+
else
|
|
68
|
+
@staging_queue.enqueue(parts)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# Stops all background tasks (send pumps).
|
|
74
|
+
def stop
|
|
75
|
+
@tasks.each(&:stop)
|
|
76
|
+
@tasks.clear
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# True when the staging and send queues are empty.
|
|
81
|
+
#
|
|
82
|
+
def send_queues_drained?
|
|
83
|
+
@staging_queue.empty? && (@send_queue.nil? || @send_queue.empty?)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
def start_send_pump(conn)
|
|
89
|
+
@send_pump = @engine.spawn_pump_task(annotation: "send pump") do
|
|
90
|
+
loop do
|
|
91
|
+
batch = [@send_queue.dequeue]
|
|
92
|
+
Routing.drain_send_queue(@send_queue, batch)
|
|
93
|
+
begin
|
|
94
|
+
batch.each { |parts| conn.write_message(parts) }
|
|
95
|
+
conn.flush
|
|
96
|
+
rescue Protocol::ZMTP::Error, *CONNECTION_LOST
|
|
97
|
+
@engine.connection_lost(conn)
|
|
98
|
+
break
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
@tasks << @send_pump
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
# Mixin that rejects multipart messages.
|
|
5
|
+
#
|
|
6
|
+
# All draft socket types (CLIENT, SERVER, RADIO, DISH, SCATTER,
|
|
7
|
+
# GATHER, PEER, CHANNEL) require single-frame messages for
|
|
8
|
+
# thread-safe atomic operations.
|
|
9
|
+
#
|
|
10
|
+
module SingleFrame
|
|
11
|
+
# Sends a message, rejecting multipart messages.
|
|
12
|
+
#
|
|
13
|
+
# @param message [String, Array<String>] message to send (must be single-frame)
|
|
14
|
+
# @raise [ArgumentError] if a multipart message is provided
|
|
15
|
+
# @return [void]
|
|
16
|
+
def send(message)
|
|
17
|
+
if message.is_a?(Array) && message.size > 1
|
|
18
|
+
raise ArgumentError, "#{self.class} does not support multipart messages"
|
|
19
|
+
end
|
|
20
|
+
super
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: omq-rfc-channel
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Patrik Wenger
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: omq
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.12'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.12'
|
|
26
|
+
description: CHANNEL socket type implementing ZeroMQ RFC 52 for the OMQ pure-Ruby
|
|
27
|
+
ZeroMQ library.
|
|
28
|
+
email:
|
|
29
|
+
- paddor@gmail.com
|
|
30
|
+
executables: []
|
|
31
|
+
extensions: []
|
|
32
|
+
extra_rdoc_files: []
|
|
33
|
+
files:
|
|
34
|
+
- LICENSE
|
|
35
|
+
- README.md
|
|
36
|
+
- lib/omq/channel.rb
|
|
37
|
+
- lib/omq/rfc/channel.rb
|
|
38
|
+
- lib/omq/rfc/channel/version.rb
|
|
39
|
+
- lib/omq/routing/channel.rb
|
|
40
|
+
- lib/omq/single_frame.rb
|
|
41
|
+
homepage: https://github.com/paddor/omq-rfc-channel
|
|
42
|
+
licenses:
|
|
43
|
+
- ISC
|
|
44
|
+
metadata: {}
|
|
45
|
+
rdoc_options: []
|
|
46
|
+
require_paths:
|
|
47
|
+
- lib
|
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
49
|
+
requirements:
|
|
50
|
+
- - ">="
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: '3.3'
|
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
54
|
+
requirements:
|
|
55
|
+
- - ">="
|
|
56
|
+
- !ruby/object:Gem::Version
|
|
57
|
+
version: '0'
|
|
58
|
+
requirements: []
|
|
59
|
+
rubygems_version: 4.0.6
|
|
60
|
+
specification_version: 4
|
|
61
|
+
summary: ZMQ CHANNEL socket type (RFC 52) for OMQ
|
|
62
|
+
test_files: []
|