async-bus 0.1.1 → 0.3.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
- checksums.yaml.gz.sig +0 -0
- data/lib/async/bus/client.rb +74 -36
- data/lib/async/bus/controller.rb +58 -0
- data/lib/async/bus/protocol/connection.rb +254 -81
- data/lib/async/bus/protocol/invoke.rb +78 -0
- data/lib/async/bus/protocol/proxy.rb +44 -41
- data/lib/async/bus/protocol/release.rb +37 -0
- data/lib/async/bus/protocol/response.rb +51 -0
- data/lib/async/bus/protocol/transaction.rb +93 -63
- data/lib/async/bus/protocol/wrapper.rb +145 -35
- data/lib/async/bus/server.rb +27 -31
- data/lib/async/bus/version.rb +5 -20
- data/lib/async/bus.rb +3 -19
- data/license.md +21 -0
- data/readme.md +53 -0
- data/releases.md +12 -0
- data.tar.gz.sig +1 -0
- metadata +49 -16
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f14db24c29a1d9ed24379631bcde98474533dd61397d6cf273bdeea5feabdb42
|
|
4
|
+
data.tar.gz: 2a1cf6b2528af9626010b95bfde0a4decf985518707c4469dd69f102ad000582
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 23697b989abd95dc326f2ad370ca68c42488a5eb4ddc01efd87e6fd3b102d7221ff5dc872a13c8f400efcf39d7477d8d63980f9eb097a0a6e0456de28106ab18
|
|
7
|
+
data.tar.gz: bbf61748ccd9685a6708eacf24e32d48b76d9baef531c95a70975427b40afa0552ee05b4a3600900025342bd4ac98c39d1baec4db47482795134b64f55ab3bfc
|
checksums.yaml.gz.sig
ADDED
|
Binary file
|
data/lib/async/bus/client.rb
CHANGED
|
@@ -1,54 +1,92 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
# of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
# in the Software without restriction, including without limitation the rights
|
|
8
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
# copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
# furnished to do so, subject to the following conditions:
|
|
11
|
-
#
|
|
12
|
-
# The above copyright notice and this permission notice shall be included in
|
|
13
|
-
# all copies or substantial portions of the Software.
|
|
14
|
-
#
|
|
15
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
-
# THE SOFTWARE.
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2021-2025, by Samuel Williams.
|
|
22
5
|
|
|
23
|
-
require_relative
|
|
24
|
-
require
|
|
6
|
+
require_relative "protocol/connection"
|
|
7
|
+
require "async/queue"
|
|
25
8
|
|
|
26
9
|
module Async
|
|
27
10
|
module Bus
|
|
11
|
+
# Represents a client that can connect to a server.
|
|
28
12
|
class Client
|
|
29
|
-
|
|
13
|
+
# Initialize a new client.
|
|
14
|
+
# @parameter endpoint [IO::Endpoint] The endpoint to connect to.
|
|
15
|
+
# @parameter options [Hash] Additional options for the connection.
|
|
16
|
+
def initialize(endpoint = nil, **options)
|
|
30
17
|
@endpoint = endpoint || Protocol.local_endpoint
|
|
31
|
-
@
|
|
18
|
+
@options = options
|
|
32
19
|
end
|
|
33
20
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
21
|
+
# Create a new connection to the server.
|
|
22
|
+
#
|
|
23
|
+
# @returns [Protocol::Connection] The new connection.
|
|
24
|
+
protected def connect!
|
|
25
|
+
peer = @endpoint.connect
|
|
26
|
+
return Protocol::Connection.client(peer, **@options)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Called when a connection is established.
|
|
30
|
+
# Override this method to perform setup when a connection is established.
|
|
31
|
+
#
|
|
32
|
+
# @parameter connection [Protocol::Connection] The established connection.
|
|
33
|
+
protected def connected!(connection)
|
|
34
|
+
# Do nothing by default.
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Connect to the server.
|
|
38
|
+
#
|
|
39
|
+
# @parameter persist [Boolean] Whether to keep the connection open indefiniely.
|
|
40
|
+
# @yields {|connection| ...} If a block is given, it will be called with the connection, and the connection will be closed afterwards.
|
|
41
|
+
# @returns [Protocol::Connection] The connection if no block is given.
|
|
42
|
+
def connect(parent: Task.current)
|
|
43
|
+
connection = connect!
|
|
44
|
+
|
|
45
|
+
connection_task = parent.async do
|
|
46
|
+
connection.run
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
connected!(connection)
|
|
50
|
+
|
|
51
|
+
return connection unless block_given?
|
|
52
|
+
|
|
53
|
+
begin
|
|
54
|
+
yield(connection, connection_task)
|
|
43
55
|
ensure
|
|
44
56
|
connection_task&.stop
|
|
57
|
+
connection&.close
|
|
45
58
|
end
|
|
46
59
|
end
|
|
47
60
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
61
|
+
# Run the client in a loop, reconnecting if necessary.
|
|
62
|
+
#
|
|
63
|
+
# Automatically reconnects when the connection fails, with random backoff.
|
|
64
|
+
# This is useful for long-running clients that need to maintain a persistent connection.
|
|
65
|
+
#
|
|
66
|
+
# @parameter parent [Async::Task] The parent task to run under.
|
|
67
|
+
def run
|
|
68
|
+
Sync do |task|
|
|
69
|
+
loop do
|
|
70
|
+
connection = connect!
|
|
71
|
+
|
|
72
|
+
connected_task = task.async do
|
|
73
|
+
connected!(connection)
|
|
74
|
+
|
|
75
|
+
yield(connection) if block_given?
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
connection.run
|
|
79
|
+
rescue => error
|
|
80
|
+
Console.error(self, "Connection failed:", exception: error)
|
|
81
|
+
sleep(rand)
|
|
82
|
+
ensure
|
|
83
|
+
# Ensure any tasks that were created during connection are stopped:
|
|
84
|
+
connected_task&.stop
|
|
85
|
+
|
|
86
|
+
# Close the connection itself:
|
|
87
|
+
connection&.close
|
|
88
|
+
end
|
|
89
|
+
end
|
|
52
90
|
end
|
|
53
91
|
end
|
|
54
92
|
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
module Async
|
|
7
|
+
module Bus
|
|
8
|
+
# Base class for controller objects designed to be proxied over Async::Bus.
|
|
9
|
+
#
|
|
10
|
+
# Controllers provide an explicit API for remote operations, avoiding the
|
|
11
|
+
# confusion that comes from proxying generic objects like Array or Hash.
|
|
12
|
+
#
|
|
13
|
+
# @example Array Controller
|
|
14
|
+
# class ArrayController < Async::Bus::Controller
|
|
15
|
+
# def initialize(array)
|
|
16
|
+
# @array = array
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# def append(*values)
|
|
20
|
+
# @array.concat(values)
|
|
21
|
+
# self # Return self for chaining
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# def get(index)
|
|
25
|
+
# @array[index] # Returns value
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# def size
|
|
29
|
+
# @array.size
|
|
30
|
+
# end
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
# @example Server Setup
|
|
34
|
+
# server.accept do |connection|
|
|
35
|
+
# array = []
|
|
36
|
+
# controller = ArrayController.new(array)
|
|
37
|
+
# connection.bind(:items, controller)
|
|
38
|
+
# end
|
|
39
|
+
#
|
|
40
|
+
# @example Client Usage
|
|
41
|
+
# client.connect do |connection|
|
|
42
|
+
# items = connection[:items] # Returns proxy to controller
|
|
43
|
+
# items.append(1, 2, 3) # Remote call
|
|
44
|
+
# expect(items.size).to be == 3
|
|
45
|
+
# end
|
|
46
|
+
#
|
|
47
|
+
# Controllers are automatically proxied when serialized if registered
|
|
48
|
+
# as a reference type in the Wrapper:
|
|
49
|
+
#
|
|
50
|
+
# Wrapper.new(connection, reference_types: [Async::Bus::Controller])
|
|
51
|
+
#
|
|
52
|
+
# This allows controller methods to return other controllers and have
|
|
53
|
+
# them automatically proxied.
|
|
54
|
+
class Controller
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
@@ -1,69 +1,112 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
# of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
# in the Software without restriction, including without limitation the rights
|
|
8
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
# copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
# furnished to do so, subject to the following conditions:
|
|
11
|
-
#
|
|
12
|
-
# The above copyright notice and this permission notice shall be included in
|
|
13
|
-
# all copies or substantial portions of the Software.
|
|
14
|
-
#
|
|
15
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
-
# THE SOFTWARE.
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2021-2025, by Samuel Williams.
|
|
22
5
|
|
|
23
|
-
require
|
|
24
|
-
require
|
|
6
|
+
require "async"
|
|
7
|
+
require "io/endpoint/unix_endpoint"
|
|
25
8
|
|
|
26
|
-
require_relative
|
|
27
|
-
require_relative
|
|
28
|
-
require_relative
|
|
9
|
+
require_relative "wrapper"
|
|
10
|
+
require_relative "transaction"
|
|
11
|
+
require_relative "proxy"
|
|
12
|
+
require_relative "response"
|
|
29
13
|
|
|
30
14
|
module Async
|
|
31
15
|
module Bus
|
|
16
|
+
# @namespace
|
|
32
17
|
module Protocol
|
|
18
|
+
# Create a local Unix domain socket endpoint.
|
|
19
|
+
# @parameter path [String] The path to the socket file.
|
|
20
|
+
# @returns [IO::Endpoint::Unix] The Unix endpoint.
|
|
33
21
|
def self.local_endpoint(path = "bus.ipc")
|
|
34
|
-
|
|
22
|
+
::IO::Endpoint.unix(path)
|
|
35
23
|
end
|
|
36
24
|
|
|
25
|
+
# Represents a connection between client and server for message passing.
|
|
37
26
|
class Connection
|
|
38
|
-
|
|
39
|
-
|
|
27
|
+
# Create a client-side connection.
|
|
28
|
+
# @parameter peer [IO] The peer connection.
|
|
29
|
+
# @parameter options [Hash] Additional options for the connection.
|
|
30
|
+
# @returns [Connection] A new client connection.
|
|
31
|
+
def self.client(peer, **options)
|
|
32
|
+
self.new(peer, 1, **options)
|
|
40
33
|
end
|
|
41
34
|
|
|
42
|
-
|
|
43
|
-
|
|
35
|
+
# Create a server-side connection.
|
|
36
|
+
# @parameter peer [IO] The peer connection.
|
|
37
|
+
# @parameter options [Hash] Additional options for the connection.
|
|
38
|
+
# @returns [Connection] A new server connection.
|
|
39
|
+
def self.server(peer, **options)
|
|
40
|
+
self.new(peer, 2, **options)
|
|
44
41
|
end
|
|
45
42
|
|
|
46
|
-
|
|
43
|
+
# Initialize a new connection.
|
|
44
|
+
# @parameter peer [IO] The peer connection.
|
|
45
|
+
# @parameter id [Integer] The initial transaction ID.
|
|
46
|
+
# @parameter wrapper [Class] The wrapper class for serialization.
|
|
47
|
+
# @parameter timeout [Float] The timeout for transactions.
|
|
48
|
+
def initialize(peer, id, wrapper: Wrapper, timeout: nil)
|
|
47
49
|
@peer = peer
|
|
50
|
+
@id = id
|
|
48
51
|
|
|
49
|
-
@wrapper =
|
|
52
|
+
@wrapper = wrapper.new(self)
|
|
50
53
|
@unpacker = @wrapper.unpacker(peer)
|
|
51
54
|
@packer = @wrapper.packer(peer)
|
|
52
55
|
|
|
56
|
+
@timeout = timeout
|
|
57
|
+
|
|
53
58
|
@transactions = {}
|
|
54
|
-
@id = id
|
|
55
59
|
|
|
56
60
|
@objects = {}
|
|
57
61
|
@proxies = ::ObjectSpace::WeakMap.new
|
|
58
|
-
@finalized = Thread::Queue.new
|
|
62
|
+
@finalized = ::Thread::Queue.new
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @attribute [Float] The timeout for transactions.
|
|
66
|
+
attr_accessor :timeout
|
|
67
|
+
|
|
68
|
+
# Flush the packer buffer.
|
|
69
|
+
def flush
|
|
70
|
+
@packer.flush
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Write a message to the connection.
|
|
74
|
+
# @parameter message [Object] The message to write.
|
|
75
|
+
def write(message)
|
|
76
|
+
# $stderr.puts "Writing: #{message.inspect}"
|
|
77
|
+
@packer.write(message)
|
|
78
|
+
@packer.flush
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Close the connection and clean up resources.
|
|
82
|
+
def close
|
|
83
|
+
@transactions.each do |id, transaction|
|
|
84
|
+
transaction.close
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
@peer.close
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Return a string representation of the connection.
|
|
91
|
+
# @returns [String] A string describing the connection.
|
|
92
|
+
def inspect
|
|
93
|
+
"#<#{self.class} #{@objects.size} objects>"
|
|
59
94
|
end
|
|
60
95
|
|
|
96
|
+
# @attribute [Hash] The bound objects.
|
|
61
97
|
attr :objects
|
|
98
|
+
|
|
99
|
+
# @attribute [ObjectSpace::WeakMap] The proxy cache.
|
|
62
100
|
attr :proxies
|
|
63
101
|
|
|
102
|
+
# @attribute [MessagePack::Unpacker] The message unpacker.
|
|
64
103
|
attr :unpacker
|
|
104
|
+
|
|
105
|
+
# @attribute [MessagePack::Packer] The message packer.
|
|
65
106
|
attr :packer
|
|
66
107
|
|
|
108
|
+
# Get the next transaction ID.
|
|
109
|
+
# @returns [Integer] The next transaction ID.
|
|
67
110
|
def next_id
|
|
68
111
|
id = @id
|
|
69
112
|
@id += 2
|
|
@@ -71,80 +114,218 @@ module Async
|
|
|
71
114
|
return id
|
|
72
115
|
end
|
|
73
116
|
|
|
117
|
+
# @attribute [Hash] Active transactions.
|
|
74
118
|
attr :transactions
|
|
75
119
|
|
|
120
|
+
Explicit = Struct.new(:object) do
|
|
121
|
+
def temporary?
|
|
122
|
+
false
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
Implicit = Struct.new(:object) do
|
|
127
|
+
def temporary?
|
|
128
|
+
true
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Explicitly bind an object to a name, such that it could be accessed remotely.
|
|
133
|
+
#
|
|
134
|
+
# This is the same as {bind} but due to the semantics of the `[]=` operator, it does not return a proxy instance.
|
|
135
|
+
#
|
|
136
|
+
# Explicitly bound objects are not garbage collected until the connection is closed.
|
|
137
|
+
#
|
|
138
|
+
# @parameter name [String] The name to bind the object to.
|
|
139
|
+
# @parameter object [Object] The object to bind to the given name.
|
|
140
|
+
def []=(name, object)
|
|
141
|
+
@objects[name] = Explicit.new(object)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Generate a proxy for a remotely bound object.
|
|
145
|
+
#
|
|
146
|
+
# **This always returns a proxy, even if the object is bound locally.**
|
|
147
|
+
# The object bus is not shared between client and server, so `[]` always
|
|
148
|
+
# returns a proxy to the remote instance.
|
|
149
|
+
#
|
|
150
|
+
# @parameter name [String] The name of the bound object.
|
|
151
|
+
# @returns [Proxy] A proxy instance for the bound object.
|
|
152
|
+
def [](name)
|
|
153
|
+
return proxy_for(name)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Explicitly bind an object to a name, such that it could be accessed remotely.
|
|
157
|
+
#
|
|
158
|
+
# This method is identical to {[]=} but also returns a {Proxy} instance for the bound object which can be passed by reference.
|
|
159
|
+
#
|
|
160
|
+
# Explicitly bound objects are not garbage collected until the connection is closed.
|
|
161
|
+
#
|
|
162
|
+
# @example Binding an object to a name and accessing it remotely.
|
|
163
|
+
# array_proxy = connection.bind(:items, [1, 2, 3])
|
|
164
|
+
# connection[:remote].register(array_proxy)
|
|
165
|
+
#
|
|
166
|
+
# @parameter name [String] The name to bind the object to.
|
|
167
|
+
# @parameter object [Object] The object to bind to the given name.
|
|
168
|
+
# @returns [Proxy] A proxy instance for the bound object.
|
|
169
|
+
def bind(name, object)
|
|
170
|
+
# Bind the object into the local object store (explicitly bound, not temporary):
|
|
171
|
+
@objects[name] = Explicit.new(object)
|
|
172
|
+
|
|
173
|
+
# Always return a proxy for passing by reference, even for locally bound objects:
|
|
174
|
+
return proxy_for(name)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Implicitly bind an object with a temporary name, such that it could be accessed remotely.
|
|
178
|
+
#
|
|
179
|
+
# Implicitly bound objects are garbage collected when the remote end no longer references them.
|
|
180
|
+
#
|
|
181
|
+
# This method is simliar to {bind} but is designed to be used to generate temporary proxies for objects that are not explicitly bound.
|
|
182
|
+
#
|
|
183
|
+
# @parameter object [Object] The object to bind to a temporary name.
|
|
184
|
+
# @returns [Proxy] A proxy instance for the bound object.
|
|
76
185
|
def proxy(object)
|
|
77
|
-
name =
|
|
186
|
+
name = object.__id__
|
|
78
187
|
|
|
79
|
-
|
|
188
|
+
# Bind the object into the local object store (temporary):
|
|
189
|
+
@objects[name] ||= Implicit.new(object)
|
|
80
190
|
|
|
81
|
-
return
|
|
191
|
+
# Always return a proxy for passing by reference:
|
|
192
|
+
return proxy_for(name)
|
|
82
193
|
end
|
|
83
194
|
|
|
84
|
-
|
|
85
|
-
|
|
195
|
+
# Implicitly bind an object with a temporary name, such that it could be accessed remotely.
|
|
196
|
+
#
|
|
197
|
+
# Implicitly bound objects are garbage collected when the remote end no longer references them.
|
|
198
|
+
#
|
|
199
|
+
# This method is similar to {proxy} but is designed to be used to generate temporary names for objects that are not explicitly bound during serialization.
|
|
200
|
+
#
|
|
201
|
+
# @parameter object [Object] The object to bind to a temporary name.
|
|
202
|
+
# @returns [String] The name of the bound object.
|
|
203
|
+
def proxy_name(object)
|
|
204
|
+
name = object.__id__
|
|
205
|
+
|
|
206
|
+
# Bind the object into the local object store (temporary):
|
|
207
|
+
@objects[name] ||= Implicit.new(object)
|
|
208
|
+
|
|
209
|
+
# Return the name:
|
|
210
|
+
return name
|
|
86
211
|
end
|
|
87
212
|
|
|
88
|
-
|
|
89
|
-
|
|
213
|
+
# Get an object or proxy for a bound object, handling reverse lookup.
|
|
214
|
+
#
|
|
215
|
+
# If the object is bound locally and the proxy is for this connection, returns the actual object.
|
|
216
|
+
# If the object is bound remotely, or the proxy is from a different connection, returns a proxy.
|
|
217
|
+
# This is used when deserializing proxies to handle round-trip scenarios and avoid name collisions.
|
|
218
|
+
#
|
|
219
|
+
# @parameter name [String] The name of the bound object.
|
|
220
|
+
# @parameter local [Boolean] Whether the proxy is for this connection (from serialization). Defaults to true.
|
|
221
|
+
# @returns [Object | Proxy] The object if bound locally and proxy is for this connection, or a proxy otherwise.
|
|
222
|
+
def proxy_object(name)
|
|
223
|
+
# If the proxy is for this connection and the object is bound locally, return the actual object:
|
|
224
|
+
if entry = @objects[name]
|
|
225
|
+
# This handles round-trip scenarios correctly.
|
|
226
|
+
return entry.object
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Otherwise, create a proxy for the remote object:
|
|
230
|
+
return proxy_for(name)
|
|
90
231
|
end
|
|
91
232
|
|
|
92
|
-
|
|
233
|
+
# Get or create a proxy for a named object.
|
|
234
|
+
#
|
|
235
|
+
# @parameter name [String] The name of the object.
|
|
236
|
+
# @returns [Proxy] A proxy instance for the named object.
|
|
237
|
+
private def proxy_for(name)
|
|
93
238
|
unless proxy = @proxies[name]
|
|
94
239
|
proxy = Proxy.new(self, name)
|
|
95
240
|
@proxies[name] = proxy
|
|
96
241
|
|
|
97
|
-
ObjectSpace.define_finalizer(proxy, finalize(name))
|
|
242
|
+
::ObjectSpace.define_finalizer(proxy, finalize(name))
|
|
98
243
|
end
|
|
99
244
|
|
|
100
245
|
return proxy
|
|
101
246
|
end
|
|
102
247
|
|
|
103
|
-
def
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
248
|
+
private def finalize(name)
|
|
249
|
+
proc do
|
|
250
|
+
@finalized.push(name) rescue nil
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Create a new transaction.
|
|
255
|
+
# @parameter id [Integer] The transaction ID.
|
|
256
|
+
# @returns [Transaction] A new transaction.
|
|
257
|
+
def transaction!(id = self.next_id)
|
|
258
|
+
transaction = Transaction.new(self, id, timeout: @timeout)
|
|
108
259
|
@transactions[id] = transaction
|
|
109
260
|
|
|
261
|
+
return transaction
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# Invoke a remote procedure.
|
|
265
|
+
# @parameter name [Symbol] The name of the remote object.
|
|
266
|
+
# @parameter arguments [Array] The arguments to pass.
|
|
267
|
+
# @parameter options [Hash] The keyword arguments to pass.
|
|
268
|
+
# @yields {|*args| ...} Optional block for yielding operations.
|
|
269
|
+
# @returns [Object] The result of the invocation.
|
|
270
|
+
def invoke(name, arguments, options = {}, &block)
|
|
271
|
+
transaction = self.transaction!
|
|
272
|
+
|
|
110
273
|
transaction.invoke(name, arguments, options, &block)
|
|
274
|
+
ensure
|
|
275
|
+
transaction&.close
|
|
111
276
|
end
|
|
112
277
|
|
|
113
|
-
|
|
114
|
-
|
|
278
|
+
# Send a release message for a named object.
|
|
279
|
+
# @parameter name [Symbol] The name of the object to release.
|
|
280
|
+
def send_release(name)
|
|
281
|
+
self.write(Release.new(name))
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# Run the connection message loop.
|
|
285
|
+
# @parameter parent [Async::Task] The parent task to run under.
|
|
286
|
+
def run(parent: Task.current)
|
|
287
|
+
finalizer_task = parent.async do
|
|
115
288
|
while name = @finalized.pop
|
|
116
|
-
|
|
289
|
+
self.send_release(name)
|
|
117
290
|
end
|
|
118
291
|
end
|
|
119
292
|
|
|
120
293
|
@unpacker.each do |message|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
transaction.
|
|
294
|
+
case message
|
|
295
|
+
when Invoke
|
|
296
|
+
# If the object is not found, send an error response and skip the transaction:
|
|
297
|
+
if object = @objects[message.name]&.object
|
|
298
|
+
transaction = self.transaction!(message.id)
|
|
299
|
+
|
|
300
|
+
parent.async(annotation: "Invoke #{message.name}") do
|
|
301
|
+
# $stderr.puts "-> Accepting: #{message.name} #{message.arguments.inspect} #{message.options.inspect}"
|
|
302
|
+
transaction.accept(object, message.arguments, message.options, message.block_given)
|
|
303
|
+
ensure
|
|
304
|
+
# $stderr.puts "<- Accepted: #{message.name}"
|
|
305
|
+
# This will also delete the transaction from @transactions:
|
|
306
|
+
transaction.close
|
|
307
|
+
end
|
|
308
|
+
else
|
|
309
|
+
self.write(Error.new(message.id, NameError.new("Object not found: #{message.name}")))
|
|
310
|
+
end
|
|
311
|
+
when Response
|
|
312
|
+
if transaction = @transactions[message.id]
|
|
313
|
+
transaction.push(message)
|
|
314
|
+
else
|
|
315
|
+
# Stale message - transaction already closed (e.g. timeout) or never existed (ignore silently).
|
|
316
|
+
end
|
|
317
|
+
when Release
|
|
318
|
+
name = message.name
|
|
319
|
+
if @objects[name]&.temporary?
|
|
320
|
+
# Only delete temporary objects, not explicitly bound ones:
|
|
321
|
+
@objects.delete(name)
|
|
141
322
|
end
|
|
142
323
|
else
|
|
143
|
-
|
|
324
|
+
Console.error(self, "Unexpected message:", message)
|
|
144
325
|
end
|
|
145
326
|
end
|
|
146
327
|
ensure
|
|
147
|
-
finalizer_task
|
|
328
|
+
finalizer_task&.stop
|
|
148
329
|
|
|
149
330
|
@transactions.each do |id, transaction|
|
|
150
331
|
transaction.close
|
|
@@ -153,15 +334,7 @@ module Async
|
|
|
153
334
|
@transactions.clear
|
|
154
335
|
@proxies = ::ObjectSpace::WeakMap.new
|
|
155
336
|
end
|
|
156
|
-
|
|
157
|
-
def close
|
|
158
|
-
@transactions.each do |id, transaction|
|
|
159
|
-
transaction.close
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
@peer.close
|
|
163
|
-
end
|
|
164
337
|
end
|
|
165
338
|
end
|
|
166
339
|
end
|
|
167
|
-
end
|
|
340
|
+
end
|