Scala の限定継続2 - Swarmによるリモート継続

Scala 2.8 の限定継続用ライブラリ Swarm を使用すると、継続をリモートプロセスに転送する事ができるようになります。
という事で、Swarm を少し試してみました。

使用した環境は以下の通り。

  • Scala 2.8 Beta1
  • continuations プラグイン(正式名がよくわからない)
  • Swarm (c590f094f7e46674cc5effefb34f40dc43e5d415)

なお、continuations プラグインは最新の Revision:20864 でビルドに失敗するようになったので前回ビルドしたもの id:fits:20100207 を使いました。(Revision は未確認)

Swarm のビルド

まず、Swarm のソースを取得してビルドします。
なお、ビルドには Scala 2.8 と continuations プラグインのライブラリが必要です。

ソースは Git を使って取得します。

>git clone git://github.com/sanity/Swarm.git

ビルドは build.sh か Maven を使えば良さそうですが、今回は以下のような Ant のビルドファイルを自作してビルドしました。

build.xml
<project name="swarm" default="pack">
  <property environment="env"/>
  <property file="${basedir}/plugin.properties"/>
  <property name="dest.dir" value="build" />
  <property name="classes.dir" value="${dest.dir}/classes" />
  <path id="scala.classpath">
    <pathelement location="${scala.home}/lib/scala-compiler.jar"/>
    <pathelement location="${scala.home}/lib/scala-library.jar"/>
  </path>
  <taskdef resource="scala/tools/ant/antlib.xml" classpathref="scala.classpath"/>
  <target name="build">
    <mkdir dir="${classes.dir}" />
    <scalac srcdir="src/main/scala"
            destdir="${classes.dir}"
            includes="**/*.scala"
            addparams="-unchecked -Xpluginsdir ${continuation.home}/build/pack">
      <classpath>
        <path refid="scala.classpath"/>
        <pathelement location="${continuation.home}/build/pack/selectivecps-library.jar" />
      </classpath>
    </scalac>
  </target>
  <target name="pack" depends="build">
    <jar destfile="${dest.dir}/swarm.jar" basedir="${classes.dir}" />
  </target>
  ・・・
</project>
plugin.properties
scala.home=${env.SCALA_HOME}
continuation.home=D:/scala_continuations

continuation.home に continuations プラグインのソースコードのホームディレクトリを設定しています。

ビルドを実施すると build/swarm.jar ファイルが作成されます。

ビルド
>ant

Swarm を使ったサンプル1

それでは、継続の機能を使って処理の途中からリモート側で実行されるような簡単なサンプルを作ってみます。

まず、継続オブジェクトが転送されてくるのを待ち受ける方の処理を作成します。以下のように Swarm.listen に待ち受けるポート番号を指定するだけです。

ListenSample.scala
import swarm._

object ListenSample {
    def main(args: Array[String]) = {
        Swarm.listen(9998)
    }
}

次に、継続オブジェクトを転送する方の処理を作成します。
以下のように Swarm.spawn に Swarm.moveTo を内部で実行するメソッド(Unit => Bee)を渡します。

処理としては、Swarm.spawn 内で reset が、moveTo で shift が実行され、その際に継続オブジェクトがリモート転送されるため、"start proc" が MoveToSample のコンソールに、"end proc" が ListenSample のコンソールに出力されます。

MoveToSample.scala
import swarm._
import java.net.InetAddress

object MoveToSample {

    def main(args: Array[String]) = {
    //  Swarm.listen(9997)
        //listen の使用を避けるには myLocation を自前で設定する
        Swarm.myLocation = new Location(InetAddress.getLocalHost(), 0)
        Swarm.spawn(proc)
    }

    def proc(u: Unit) = {
        println("start proc")

        //サーバー側に転送
        Swarm.moveTo(new Location(InetAddress.getLocalHost(), 9998))

        //以降の処理がサーバー側で実行される
        println("end proc")
        NoBee()
    }
}

上記ソースファイルを以下のようにしてコンパイルします。(continuations プラグインを指定する点に注意)

コンパイル例
>scalac -Xplugin:selectivecps-plugin.jar -classpath selectivecps-library.jar;swarm.jar ListenSample.scala
>scalac -Xplugin:selectivecps-plugin.jar -classpath selectivecps-library.jar;swarm.jar MoveToSample.scala

それでは、動作確認です。
まず、以下のように ListenSample を実行し接続待ち状態にしておきます。

ListenSampleコンソール
>scala -classpath .;swarm.jar;selectivecps-library.jar ListenSample
9998 : Waiting for connection

次に、MoveToSample を実行すると以下のような結果になります。

MoveToSampleコンソール
>scala -classpath .;swarm.jar;selectivecps-library.jar MoveToSample
0 : Running task
start proc
0 : Move to
0 : Moving task to 9998
0 : Transmitting task to 9998
0 : Transmission complete

ListenSample は以下のような結果になり、転送されてきた継続オブジェクトを実行している事が確認できます。

サーバー側コンソール
>scala -classpath .;swarm.jar;selectivecps-library.jar ListenSample
9998 : Waiting for connection
9998 : Received connection
9998 : Executing continuation
9998 : Running task
end proc
9998 : Completed task
9998 : No more continuations to execute
9998 : Waiting for connection

なお、ListenSample 側では MoveToSample$$anonfun$proc$1.class をクラスパスに含めておく必要がある点に注意。(クラスファイルを別ディレクトリに配置している場合)

Swarm を使ったサンプル2

次に、ListenSample 側に送った継続オブジェクトを MoveToSample 側に送り返すサンプルを作成してみます。

ListenSample の変更は不要なので、以下のように moveTo を2度実行する MoveToSample2 を作成しました。

MoveToSample2.scala
import swarm._
import java.net.InetAddress

object MoveToSample2 {
    def main(args: Array[String]) = {
        Swarm.listen(9997)
        Swarm.spawn(proc)
    }

    def proc(u: Unit) = {
        //ローカル側(MoveToSample2)で実行
        var i = 0
        println("start proc : " + i)

        //リモート側(ListenSample)に転送
        Swarm.moveTo(new Location(InetAddress.getLocalHost(), 9998))

        //リモート側(ListenSample)で実行
        i = i + 5
        println("remote proc : " + i)

        //ローカル側(MoveToSample2)に転送
        Swarm.moveTo(new Location(InetAddress.getLocalHost(), 9997))

        //ローカル側(MoveToSample2)で実行
        i = i + 10
        println("end proc : " + i)
        NoBee()
    }
}

実行結果は以下のようになります。

ListenSample 側の結果
>scala -classpath .;swarm.jar;selectivecps-library.jar ListenSample
9998 : Waiting for connection
9998 : Received connection
9998 : Executing continuation
9998 : Running task
remote proc : 5
9998 : Move to
9998 : Moving task to 9997
9998 : Transmitting task to 9997
9998 : Transmission complete
9998 : Waiting for connection
MoveToSample2 側の結果
>scala -classpath .;swarm.jar;selectivecps-library.jar MoveToSample2
9997 : Waiting for connection
9997 : Running task
start proc : 0
9997 : Move to
9997 : Moving task to 9998
9997 : Transmitting task to 9998
9997 : Transmission complete
9997 : Received connection
9997 : Executing continuation
9997 : Running task
end proc : 15
9997 : Completed task
9997 : No more continuations to execute
9997 : Waiting for connection