8
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

RubyAdvent Calendar 2023

Day 1

RactorでGenServerっぽいものを作ってみた記録

Last updated at Posted at 2023-12-10

この記事は何

Rubyには3.0からRactorという機能がexperimentalで入っています。
これは文字通りRubyでActorモデルを実装することを可能にしたものです。
この記事では、このRactorを用いて、ElixirやErlangのOTPの一つであるGenServerを作ってみた記録記事です。

Ractorとは

Ractorとは、Ruby 3.0から導入された新しい並行抽象化の機能です。
今までのThreadなどの仕組みとは異なり、GVLの制限を受けずに文字通り並列の処理をRubyで実現することが可能になります。

詳しくは以下の記事をご覧ください。

OTPとは

OTP (Open Telegraph Protocol)とは、分散システムや並列処理をErlangやElixirで実現するためのフレームワークです。このOTPではいわゆるアクターモデルを用いた実装がされています。

OTPの一つとして、GenServerがあります。
GenServerとは、関数型言語であるErlangやElixirで状態を保持するために用いるパターンです。
GenServerでは、状態を変数ではなく、プロセスベースで保持します。

詳しくはこちらをご覧ください。

実装例

今回はこのErlangやElixirで用いられるGenServerを、同じくアクターモデルを実現するRactorを用いて実装していきたいと思います。

今回は以下のように実装を行なってみました。

class RactorGenServer
  def call(command, data = nil)
    ractor.send([command, data])
    ractor.take
  end

  def cast(command, data = nil)
    ractor.send([command, data])
  end

  private

  def ractor
    @ractor ||= Ractor.new do
      handle_message = ->(message, current_state) do
        case message
        in [:enqueue, data]
          {
            status: :ok,
            state: [*current_state, data],
            value: nil
          }
        in [:dequeue, nil]
          if current_state.empty?
            { status: :error,
              state: current_state,
              value: nil
            }
          else
            {
              status: :ok,
              state: current_state[1..-1],
              value: current_state.first
            }
          end
        end
      end

      state = []

      loop do
        message = Ractor.receive

        handle_message.call(message, state) => { status:, state:, value: }

        raise StandardError, "error" if status == :error

        Ractor.yield(value) if value
      end
    end
  end
end

# 使用例
gen_server = RactorGenServer.new
gen_server.cast(:enqueue, "item1")
gen_server.cast(:enqueue, "item2")
puts gen_server.call(:dequeue) # => "item1"
puts gen_server.call(:dequeue) # => "item2"

このような実装をすることで、Ractorのプロセス単位で状態を保持することが可能になります。
OTPでは、分散システムで扱われる想定のための実装も含まれていますが、今回はそこまで実装はしておらず、あくまでプロセス単位で状態を保持できるところまで実装してみました。

今回はRactorの使い方の模索の一環として、OTPのGenServerっぽいものを作ってみました。

まだだいぶ荒削りな実装なので、
よりOTPの挙動に近い実装のアイディアなどある方はぜひシェアいただけると嬉しいです。

8
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?