Last active
September 21, 2023 22:24
-
-
Save ysbaddaden/fb41d89663e40dbd1e4d19cc2438931e to your computer and use it in GitHub Desktop.
A GenServer in Crystal
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Bus(A, M) | |
include GenServer | |
def initialize | |
@listeners = [] of A | |
end | |
# public interface | |
def subscribe(listener : A) : Nil | |
async(:subscribe, listener) | |
end | |
def unsubscribe(listener : A) : Nil | |
async(:unsubscribe, listener) | |
end | |
def broadcast(message : M) : Nil | |
async(:broadcast, message) | |
end | |
def size | |
sync(:size) | |
end | |
# private implementation | |
handle_async(:subscribe, listener : A) do | |
@listeners << listener | |
end | |
handle_async(:unsubscribe, listener : A) do | |
@listeners.delete(listener) | |
end | |
handle_async(:broadcast, message : M) do | |
@listeners.each(&.send(message)) | |
end | |
handle_sync(:size) do | |
@listeners.size | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require "./bus" | |
class Foo | |
def initialize(@id : Int32) | |
end | |
def send(message : String) | |
puts "foo#{@id}: #{message}" | |
end | |
end | |
bus = Bus(Foo, String).new | |
spawn { bus.call } | |
foo1 = Foo.new(1) | |
foo2 = Foo.new(2) | |
bus.subscribe(foo1) | |
bus.subscribe(foo2) | |
bus.broadcast("lorem ipsum") | |
p bus.size | |
bus.unsubscribe(foo1) | |
bus.broadcast("dolor sit amet") | |
p bus.size | |
bus.terminate |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require "syn/core/future" | |
require "syn/core/wait_group" | |
module GenServer | |
# :nodoc: | |
annotation Async; end | |
# :nodoc: | |
annotation Sync; end | |
# :nodoc: | |
annotation Info; end | |
macro included | |
macro finished | |
\{% types = [] of String %} | |
\{% methods = [] of String %} | |
\{% for type in [@type] + @type.ancestors %} | |
\{% type.methods.each do |d| | |
if ann = d.annotation(Async) || d.annotation(Sync) || d.annotation(Info) | |
types << {Symbol, d.args.map(&.restriction).splat} | |
methods << {d, ann} | |
end | |
end %} | |
\{% end %} | |
@mailbox : Channel(\{{ types.uniq.join(" | ").id }}) | |
@mailbox = Channel(\{{ types.uniq.join(" | ").id }}).new(128) | |
protected def call : Nil | |
while msg = @mailbox.receive? | |
case msg[0] | |
\{% for tuple in methods %} | |
\{% | |
d, ann = tuple | |
meth = | |
if ann = d.annotation(Async) | |
:async | |
elsif ann = d.annotation(Sync) | |
:sync | |
elsif ann = d.annotation(Info) | |
:info | |
end | |
%} | |
\{% if ann %} | |
when \{{ann.named_args[:name]}} | |
# TODO :check for missing handle_async_name method to report an explanatory error message | |
handle_\{{meth.id}}_\{{ann.named_args[:name].id}}( | |
\{% for arg, idx in d.args %} | |
msg[\{{idx + 1}}]?.unsafe_as(\{{arg.restriction}}), | |
\{% end %} | |
) | |
\{% end %} | |
\{% end %} | |
else | |
raise "unknown message #{msg.inspect}" | |
end | |
end | |
end | |
end | |
end | |
macro async(name, *args) | |
@mailbox.send({ {{name}}, {{args.splat}} }) | |
nil | |
end | |
macro sync(name, *args) | |
%future = Syn::Core::Future(typeof(handle_sync_{{name.id}}({% if args.size > 0 %}{{args.splat}}, {% end %}Pointer(Void).null))).new | |
@mailbox.send({ {{name}}, {% if args.size > 0 %}{{args.splat}}, {% end %}pointerof(%future).unsafe_as(Pointer(Void)) }) | |
%future.get | |
end | |
macro info(name, *args) | |
%wg = Syn::Core::WaitGroup.new(1) | |
@mailbox.send({ {{name}}, {% if args.size > 0 %}{{args.splat}}, {% end %}pointerof(%wg) }) | |
%wg.wait | |
nil | |
end | |
macro handle_async(name, *args) | |
@[GenServer::Async(name: {{name}})] | |
protected def handle_async_{{name.id}}({{args.splat}}) | |
{{yield}} | |
end | |
end | |
macro handle_sync(name, *args) | |
@[GenServer::Sync(name: {{name}})] | |
protected def handle_sync_{{name.id}}({% if args.size > 0 %}{{args.splat}}, {% end %}future : Pointer(Void)) | |
value = {{yield}} | |
future.unsafe_as(Pointer(Syn::Core::Future(typeof(value)))).value.set(value) | |
value | |
end | |
end | |
macro handle_info(name, *args) | |
@[GenServer::Info(name: {{name}})] | |
protected def handle_info_{{name.id}}({% if args.size > 0 %}{{args.splat}}, {% end %}wg : Pointer(Syn::Core::WaitGroup)) | |
{{yield}}.tap { wg.value.done } | |
end | |
end | |
def trap(agent : Agent, exception : Exception?) : Nil | |
sync(:trap, agent, exception) | |
end | |
def terminate : Nil | |
info(:terminate) | |
@mailbox.close | |
end | |
handle_info(:trap, agent : Agent, exception : Exception?) { nil } | |
handle_info(:terminate) { nil } | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: gen_server_example | |
version: 0.2.0 | |
dependencies: | |
syn: | |
github: ysbaddaden/syn |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment