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 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
+ [![CI](https://github.com/paddor/omq-rfc-channel/actions/workflows/ci.yml/badge.svg)](https://github.com/paddor/omq-rfc-channel/actions/workflows/ci.yml)
4
+ [![Gem Version](https://img.shields.io/gem/v/omq-rfc-channel?color=e9573f)](https://rubygems.org/gems/omq-rfc-channel)
5
+ [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](LICENSE)
6
+ [![Ruby](https://img.shields.io/badge/Ruby-%3E%3D%203.3-CC342D?logo=ruby&logoColor=white)](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)
@@ -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,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OMQ
4
+ module RFC
5
+ # CHANNEL socket type extension (ZeroMQ RFC 52).
6
+ module Channel
7
+ VERSION = "0.1.0"
8
+ end
9
+ end
10
+ 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: []