214 lines
7.4 KiB
Text
Executable file
214 lines
7.4 KiB
Text
Executable file
--- OVERVIEW ---
|
|
Scala Actors Behavior Module
|
|
|
|
Implements Erlang-style Behaviors for Scala; Supervisor, GenericServer, GenericEvent and GenericFiniteStateMachine allowing creating fault-tolerant actor-based enterprise systems.
|
|
|
|
The implementation consists of four main abstractions;
|
|
|
|
* Supervisor -- The Supervisor manages hierarchies of Scala actors and provides fault-tolerance in terms of different restart
|
|
semantics. The configuration and semantics is almost a 1-1 port of the Erlang Supervisor implementation, explained
|
|
here: http://www.erlang.org/doc/design_principles/sup_princ.html, read this document in order to understand how to
|
|
configure the Supervisor properly.
|
|
|
|
* GenericServer -- The GenericServer (which subclasses Actor) is a trait that forms the base for a server to be managed by a Supervisor.
|
|
The GenericServer is wrapped by a GenericServerContainer instance providing a necessary indirection needed to be able to
|
|
fully manage the life-cycle of the GenericServer.
|
|
|
|
* GenericEvent -- TBD
|
|
|
|
* GenericFiniteStateMachine -- TBD
|
|
|
|
--- CHECK OUT ---
|
|
The SCM system used is Git.
|
|
|
|
1. Download and install Git (google git).
|
|
2. Invoke 'git clone git://github.com/jboner/scala-otp.git'.
|
|
|
|
--- BUILD ---
|
|
The build system used is Maven.
|
|
|
|
1. Download and install Maven 2.
|
|
2. Step into the root dir 'scala-otp'.
|
|
3. Invoke 'mvn install'
|
|
|
|
This will build the project, run all tests, create a jar and upload it to your local Maven repository ready for use.
|
|
|
|
--- RUNTIME DEPENDENCIES ---
|
|
1. Scala 2.7.1-final
|
|
2. SLF4J 1.5.2
|
|
3. LogBack Classic 0.9.9
|
|
|
|
--- USAGE ---
|
|
Here is a small step-by-step runnable tutorial on how to create a server, configure it, use it, hotswap its
|
|
implementation etc. For more details on the API, look at the code or the tests.
|
|
|
|
You can find this code in the sample.scala file in the root directory. Run it by invoking 'scala -cp
|
|
target/scala-behavior-0.1-SNAPSHOT.jar:<path to slf4j and logback jars> sample.scala'
|
|
|
|
// =============================================
|
|
// 1. Import statements and Server messages
|
|
|
|
import scala.actors._
|
|
import scala.actors.Actor._
|
|
|
|
import scala.actors.behavior._
|
|
import scala.actors.behavior.Helpers._
|
|
|
|
sealed abstract class SampleMessage
|
|
case object Ping extends SampleMessage
|
|
case object Pong extends SampleMessage
|
|
case object OneWay extends SampleMessage
|
|
case object Die extends SampleMessage
|
|
|
|
// =============================================
|
|
// 2. Create the GenericServer by extending the GenericServer trait and override the 'body' method
|
|
|
|
class SampleServer extends GenericServer {
|
|
|
|
// This method implements the core server logic and naturally has to be overridden
|
|
override def body: PartialFunction[Any, Unit] = {
|
|
case Ping =>
|
|
println("Received Ping"); reply(Pong)
|
|
|
|
case OneWay =>
|
|
println("Received OneWay")
|
|
|
|
case Die =>
|
|
println("Received Die..dying...")
|
|
throw new RuntimeException("Received Die message")
|
|
}
|
|
|
|
// GenericServer also has some callback life-cycle methods, such as init(..) and shutdown(..)
|
|
}
|
|
|
|
// =============================================
|
|
// 3. Wrap our SampleServer in a GenericServerContainer and give it a name to be able to refer to it later.
|
|
|
|
object sampleServer1 extends GenericServerContainer("sample1", () => new SampleServer)
|
|
object sampleServer2 extends GenericServerContainer("sample2", () => new SampleServer)
|
|
|
|
// =============================================
|
|
// 4. Create a Supervisor configuration (and a SupervisorFactory) that is configuring our SampleServer (takes a list of
|
|
'Worker' configurations, one or many)
|
|
|
|
object factory extends SupervisorFactory {
|
|
override protected def getSupervisorConfig: SupervisorConfig = {
|
|
SupervisorConfig(
|
|
RestartStrategy(AllForOne, 3, 10000),
|
|
Worker(
|
|
sampleServer1,
|
|
LifeCycle(Permanent, 1000)) ::
|
|
Worker(
|
|
sampleServer2,
|
|
LifeCycle(Permanent, 1000)) ::
|
|
Nil)
|
|
}
|
|
}
|
|
|
|
// =============================================
|
|
// 5. Create a new Supervisor with the custom factory
|
|
|
|
val supervisor = factory.newSupervisor
|
|
|
|
// =============================================
|
|
// 6. Start the Supervisor (which starts the server(s))
|
|
|
|
supervisor ! Start
|
|
|
|
// =============================================
|
|
// 7. Try to send a one way asyncronous message to our servers
|
|
|
|
sampleServer1 ! OneWay
|
|
|
|
// Try to get sampleServer2 from the Supervisor before sending a message
|
|
supervisor.getServer("sample2") match {
|
|
case Some(server2) => server2 ! OneWay
|
|
case None => println("server [sample2] could not be found")
|
|
}
|
|
|
|
// =============================================
|
|
// 8. Try to send an asyncronous message - receive a future - wait 100 ms (time-out) for the reply
|
|
|
|
val future = sampleServer1 !! Ping
|
|
val reply1 = future.receiveWithin(100) match {
|
|
case Some(reply) =>
|
|
println("Received reply: " + reply)
|
|
case None =>
|
|
println("Did not get a reply witin 100 ms")
|
|
}
|
|
|
|
// =============================================
|
|
// 9. Try to send a message (Die) telling the server to kill itself (throw an exception)
|
|
|
|
sampleServer1 ! Die
|
|
|
|
// =============================================
|
|
// 10. Send an asyncronous message and wait on a future. If it times out -> use error handler (in this case throw an
|
|
exception). It is likely that this call will time out since the server is in the middle of recovering from failure.
|
|
|
|
val reply2 = try {
|
|
sampleServer1 !!! (Ping, throw new RuntimeException("Time-out"), 10) // time out is set to 10 ms (very low on purpose)
|
|
|
|
} catch { case e => println("Expected exception: " + e.toString); Pong }
|
|
|
|
// =============================================
|
|
// 11. Server should be up again. Try the same call again
|
|
|
|
val reply3 = try {
|
|
sampleServer1 !!! (Ping, throw new RuntimeException("Time-out"), 1000)
|
|
} catch { case e => println("Expected exception: " + e.toString); Pong }
|
|
|
|
// Also check server number 2
|
|
sampleServer2 ! Ping
|
|
|
|
// =============================================
|
|
// 11. Try to hotswap the server implementation
|
|
|
|
sampleServer1.hotswap(Some({
|
|
case Ping =>
|
|
println("Hotswapped Ping")
|
|
}))
|
|
|
|
// =============================================
|
|
// 12. Try the hotswapped server out
|
|
|
|
sampleServer1 ! Ping
|
|
|
|
// =============================================
|
|
// 13. Hotswap again
|
|
|
|
sampleServer1.hotswap(Some({
|
|
case Pong =>
|
|
println("Hotswapped again, now doing Pong")
|
|
reply(Ping)
|
|
}))
|
|
|
|
// =============================================
|
|
// 14. Send an asyncronous message that will wait on a future. Method returns an Option[T] => if Some(result) -> return
|
|
result, if None -> print out an info message (or throw an exception or do whatever you like...)
|
|
|
|
val reply4 = (sampleServer1 !!! Pong).getOrElse({println("Time out when sending Pong"); Ping})
|
|
|
|
// Same invocation with pattern matching syntax.
|
|
|
|
val reply5 = sampleServer1 !!! Pong match {
|
|
case Some(result) => result
|
|
case None => println("Time out when sending Pong"); Ping
|
|
}
|
|
|
|
// =============================================
|
|
// 15. Hotswap back to original implementation by passing in None
|
|
|
|
sampleServer1.hotswap(None)
|
|
|
|
// =============================================
|
|
// 16. Test the final hotswap by sending an async message
|
|
|
|
sampleServer1 ! Ping
|
|
|
|
// =============================================
|
|
// 17. Shut down the supervisor and its server(s)
|
|
|
|
supervisor ! Stop
|
|
|
|
|