Skip to content

Commit 05a37bc

Browse files
committed
Implement basic actor pattern
1 parent 688c5b5 commit 05a37bc

File tree

5 files changed

+120
-5
lines changed

5 files changed

+120
-5
lines changed

.github/workflows/test.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
matrix:
1414
# macos-latest uses arm64, macos-13 uses x86
1515
os: [ubuntu-latest]
16-
ruby: ['3.3', 'head']
16+
ruby: ['3.3', '3.4', 'head']
1717

1818
name: ${{matrix.os}}, ${{matrix.ruby}}
1919

lib/uringmachine.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ def fiber_map
1212
@@fiber_map
1313
end
1414

15-
def spin(value = nil, &block)
16-
f = Fiber.new do |resume_value|
15+
def spin(value = nil, fiber_class = Fiber, &block)
16+
f = fiber_class.new do |resume_value|
1717
block.(resume_value)
1818
rescue Exception => e
1919
STDERR.puts "Unhandled fiber exception: #{e.inspect}"

lib/uringmachine/actor.rb

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# frozen_string_literal: true
2+
3+
class UringMachine
4+
def spin_actor(mod, *a, **k)
5+
target = Object.new.extend(mod)
6+
mailbox = UM::Queue.new
7+
actor = spin(nil, Actor) { actor.run(self, target, mailbox) }
8+
target.setup(*a, **k)
9+
snooze
10+
actor
11+
end
12+
13+
class Actor < Fiber
14+
def run(machine, target, mailbox)
15+
@machine = machine
16+
@target = target
17+
@mailbox = mailbox
18+
while (msg = machine.shift(mailbox))
19+
process_message(msg)
20+
end
21+
ensure
22+
@target.teardown if @target.respond_to?(:teardown)
23+
end
24+
25+
def cast(sym, *a, **k)
26+
self << [:cast, nil, sym, a, k]
27+
self
28+
end
29+
30+
def call(sym, *a, **k)
31+
self << [:call, Fiber.current, sym, a, k]
32+
@machine.yield
33+
end
34+
35+
private
36+
37+
def process_message(msg)
38+
type, fiber, sym, args, kwargs = msg
39+
case type
40+
when :cast
41+
@target.send(sym, *args, **kwargs)
42+
when :call
43+
res = @target.send(sym, *args, **kwargs)
44+
@machine.schedule(fiber, res)
45+
end
46+
end
47+
48+
def <<(msg)
49+
@machine.push(@mailbox, msg)
50+
end
51+
end
52+
end

test/test_actor.rb

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# frozen_string_literal: true
2+
3+
require_relative 'helper'
4+
require 'socket'
5+
require 'uringmachine/actor'
6+
7+
class ActorTest < UMBaseTest
8+
module Counter
9+
def setup
10+
@count = 0
11+
end
12+
13+
def incr
14+
@count += 1
15+
end
16+
17+
def get
18+
@count
19+
end
20+
21+
def reset
22+
@count = 0
23+
end
24+
end
25+
26+
def test_basic_actor_functionality
27+
actor = @machine.spin_actor(Counter)
28+
29+
assert_kind_of Fiber, actor
30+
31+
assert_equal 0, actor.call(:get)
32+
assert_equal 1, actor.call(:incr)
33+
assert_equal actor, actor.cast(:incr)
34+
assert_equal 2, actor.call(:get)
35+
assert_equal actor, actor.cast(:reset)
36+
assert_equal 0, actor.call(:get)
37+
end
38+
39+
module Counter2
40+
def setup(count)
41+
@count = count
42+
end
43+
44+
def incr
45+
@count += 1
46+
end
47+
48+
def get
49+
@count
50+
end
51+
52+
def reset
53+
@count = 0
54+
end
55+
end
56+
57+
58+
def test_actor_with_args
59+
actor = @machine.spin_actor(Counter2, 43)
60+
61+
assert_equal 43, actor.call(:get)
62+
end
63+
end

uringmachine.gemspec

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ Gem::Specification.new do |s|
2020
s.require_paths = ["lib"]
2121
s.required_ruby_version = '>= 3.3'
2222

23-
s.add_development_dependency 'rake-compiler', '1.2.8'
24-
s.add_development_dependency 'minitest', '5.25.1'
23+
s.add_development_dependency 'rake-compiler', '1.2.9'
24+
s.add_development_dependency 'minitest', '5.25.4'
2525
s.add_development_dependency 'http_parser.rb', '0.8.0'
2626
s.add_development_dependency 'benchmark-ips', '2.14.0'
2727
s.add_development_dependency 'localhost', '1.3.1'

0 commit comments

Comments
 (0)