Skip to content

Commit 0c8512b

Browse files
Fix handling of implicitly bound objects + add benchmark.
1 parent 000c2d7 commit 0c8512b

File tree

8 files changed

+127
-23
lines changed

8 files changed

+127
-23
lines changed

benchmark/message_passing.rb

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# frozen_string_literal: true
2+
3+
# Released under the MIT License.
4+
# Copyright, 2025, by Samuel Williams.
5+
6+
require "sus/fixtures/benchmark"
7+
require "async/bus/a_server"
8+
9+
describe "Message Passing Performance" do
10+
include Sus::Fixtures::Benchmark
11+
include Async::Bus::AServer
12+
13+
let(:array) {Array.new}
14+
15+
before do
16+
start_server do |connection|
17+
connection.bind(:array, array)
18+
connection.bind(:counter, proc{|value| value + 1})
19+
end
20+
end
21+
22+
measure "simple method call" do |repeats|
23+
client.connect do |connection|
24+
repeats.times do
25+
connection[:array].size
26+
end
27+
end
28+
end
29+
30+
measure "method call with arguments" do |repeats|
31+
client.connect do |connection|
32+
repeats.times do |i|
33+
connection[:array] << i
34+
end
35+
end
36+
end
37+
38+
measure "method call with return value" do |repeats|
39+
client.connect do |connection|
40+
repeats.times do |i|
41+
result = connection[:array].size
42+
end
43+
end
44+
end
45+
46+
measure "proc invocation" do |repeats|
47+
client.connect do |connection|
48+
repeats.times do
49+
connection[:counter].call(1)
50+
end
51+
end
52+
end
53+
54+
measure "multiple sequential calls" do |repeats|
55+
client.connect do |connection|
56+
repeats.times do |i|
57+
connection[:array] << i
58+
connection[:array].size
59+
connection[:array].last
60+
end
61+
end
62+
end
63+
64+
measure "property access" do |repeats|
65+
client.connect do |connection|
66+
repeats.times do
67+
connection[:array].length
68+
end
69+
end
70+
end
71+
end
72+

fixtures/async/bus/a_server.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
module Async
1414
module Bus
1515
AServer = Sus::Shared("a server") do
16-
include Sus::Fixtures::Async::ReactorContext
16+
include Sus::Fixtures::Async::SchedulerContext
1717

1818
let(:ipc_path) {File.join(@root, "bus.ipc")}
1919
let(:endpoint) {Async::Bus::Protocol.local_endpoint(ipc_path)}
@@ -25,11 +25,11 @@ def around(&block)
2525
end
2626
end
2727

28-
def before
28+
before do
2929
@bound_endpoint = endpoint.bound
3030
end
3131

32-
def after(error = nil)
32+
after do
3333
@bound_endpoint&.close
3434
@server_task&.stop
3535
end

gems.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
gem "bake-gem"
1313
gem "bake-releases"
1414

15+
gem "agent-context"
16+
1517
gem "utopia-project"
1618
end
1719

@@ -25,6 +27,7 @@
2527
gem "rubocop-socketry"
2628

2729
gem "sus-fixtures-async"
30+
gem "sus-fixtures-benchmark"
2831

2932
gem "bake-test"
3033
gem "bake-test-external"

lib/async/bus/protocol/connection.rb

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,26 @@ def next_id
8484

8585
attr :transactions
8686

87+
Explicit = Struct.new(:object) do
88+
def temporary?
89+
false
90+
end
91+
end
92+
93+
Implicit = Struct.new(:object) do
94+
def temporary?
95+
true
96+
end
97+
end
98+
8799
# Bind a local object to a name, such that it could be accessed remotely.
88100
#
89101
# @returns [Proxy] A proxy instance for the bound object.
90102
def bind(name, object)
91-
@objects[name] = object
103+
# Bind the object into the local object store (explicitly bound, not temporary):
104+
@objects[name] = Explicit.new(object)
105+
106+
# Return the proxy instance for the bound object:
92107
return self[name]
93108
end
94109

@@ -98,7 +113,11 @@ def bind(name, object)
98113
def proxy(object)
99114
name = "<#{object.class}@#{next_id.to_s(16)}>".freeze
100115

101-
return bind(name, object)
116+
# Bind the object into the local object store (temporary):
117+
@objects[name] = Implicit.new(object)
118+
119+
# This constructs the Proxy instance:
120+
return self[name]
102121
end
103122

104123
# Generate a proxy name for an object and bind it, returning just the name.
@@ -107,12 +126,16 @@ def proxy(object)
107126
# @returns [String] The name of the bound object.
108127
def proxy_name(object)
109128
name = "<#{object.class}@#{next_id.to_s(16)}>".freeze
110-
bind(name, object)
129+
130+
# Bind the object into the local object store (temporary):
131+
@objects[name] = Implicit.new(object)
132+
133+
# Return the name:
111134
return name
112135
end
113136

114137
def object(name)
115-
@objects[name]
138+
@objects[name]&.object
116139
end
117140

118141
private def finalize(name)
@@ -122,7 +145,7 @@ def object(name)
122145
end
123146

124147
def []=(name, object)
125-
@objects[name] = object
148+
@objects[name] = Explicit.new(object)
126149
end
127150

128151
def [](name)
@@ -164,11 +187,9 @@ def run(parent: Task.current)
164187

165188
@unpacker.each do |message|
166189
case message
167-
when Release
168-
@objects.delete(message.name)
169190
when Invoke
170191
# If the object is not found, send an error response and skip the transaction:
171-
if object = @objects[message.name]
192+
if object = @objects[message.name]&.object
172193
transaction = self.transaction!(message.id)
173194

174195
parent.async(annotation: "Invoke #{message.name}") do
@@ -188,6 +209,12 @@ def run(parent: Task.current)
188209
else
189210
# Stale message - transaction already closed (e.g. timeout) or never existed (ignore silently).
190211
end
212+
when Release
213+
name = message.name
214+
if @objects[name]&.temporary?
215+
# Only delete temporary objects, not explicitly bound ones:
216+
@objects.delete(name)
217+
end
191218
else
192219
Console.error(self, "Unexpected message:", message)
193220
end

lib/async/bus/protocol/wrapper.rb

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,27 +25,27 @@ def initialize(bus, reference_types: [Controller])
2525
# The order here matters.
2626

2727
self.register_type(0x00, Invoke, recursive: true,
28-
packer: ->(invoke, packer) {invoke.pack(packer)},
29-
unpacker: ->(unpacker) {Invoke.unpack(unpacker)},
28+
packer: ->(invoke, packer){invoke.pack(packer)},
29+
unpacker: ->(unpacker){Invoke.unpack(unpacker)},
3030
)
3131

3232
[Return, Yield, Error, Next, Throw, Close].each_with_index do |klass, index|
3333
self.register_type(0x01 + index, klass, recursive: true,
34-
packer: ->(value, packer) {value.pack(packer)},
35-
unpacker: ->(unpacker) {klass.unpack(unpacker)},
34+
packer: ->(value, packer){value.pack(packer)},
35+
unpacker: ->(unpacker){klass.unpack(unpacker)},
3636
)
3737
end
3838

3939
# Reverse serialize proxies back into proxies:
4040
# When a Proxy is received, create a proxy pointing back
4141
self.register_type(0x10, Proxy,
42-
packer: ->(proxy) {proxy.__name__},
42+
packer: ->(proxy){proxy.__name__},
4343
unpacker: @bus.method(:[]),
4444
)
4545

4646
self.register_type(0x11, Release, recursive: true,
47-
packer: ->(release, packer) {release.pack(packer)},
48-
unpacker: ->(unpacker) {Release.unpack(unpacker)},
47+
packer: ->(release, packer){release.pack(packer)},
48+
unpacker: ->(unpacker){Release.unpack(unpacker)},
4949
)
5050

5151
self.register_type(0x20, Symbol)
@@ -56,8 +56,8 @@ def initialize(bus, reference_types: [Controller])
5656
)
5757

5858
self.register_type(0x22, Class,
59-
packer: ->(klass) {klass.name},
60-
unpacker: ->(name) {Object.const_get(name)},
59+
packer: ->(klass){klass.name},
60+
unpacker: ->(name){Object.const_get(name)},
6161
)
6262

6363
# Serialize objects into proxies:

releases.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
# Releases
22

33
## Unreleased
4+
5+
- Fix handling of temporary objects.

test/async/bus/protocol/transaction.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ def service.yielding_method
113113
def service.yielding_method
114114
yield 1
115115
end
116-
116+
117117
connection.bind(:service, service)
118118
end
119119

@@ -136,7 +136,7 @@ def service.yielding_method
136136
def service.error_method
137137
raise ArgumentError, "Invalid argument"
138138
end
139-
139+
140140
connection.bind(:service, service)
141141
end
142142

test/async/bus/server.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ def before
125125
@server_task = Async do
126126
server.accept do |connection|
127127
connection.bind(:hash, hash)
128-
connection.bind(:sum_key, proc {|key| hash[key].sum})
128+
connection.bind(:sum_key, proc{|key| hash[key].sum})
129129
end
130130
end
131131
end

0 commit comments

Comments
 (0)