├── project ├── build.properties └── build.sbt ├── .gitignore ├── examples ├── chat-full-stack │ ├── public │ │ ├── stylesheets │ │ │ ├── main.css │ │ │ └── chatbox.css │ │ ├── images │ │ │ └── favicon.png │ │ └── javascripts │ │ │ ├── .gitignore │ │ │ └── md5-min.js │ ├── app │ │ ├── actors │ │ │ ├── Messages.scala │ │ │ ├── ChatEntryPoint.scala │ │ │ └── ChatManager.scala │ │ ├── controllers │ │ │ └── Application.scala │ │ └── views │ │ │ ├── main.scala.html │ │ │ └── index.scala.html │ ├── README │ ├── scalajs │ │ └── build.sbt │ ├── .gitignore │ ├── build.sbt │ ├── cscommon │ │ └── models │ │ │ ├── Models.scala │ │ │ ├── Messages.scala │ │ │ └── RegisterPicklers.scala │ ├── conf │ │ ├── routes │ │ └── application.conf │ └── test │ │ ├── IntegrationSpec.scala │ │ └── ApplicationSpec.scala ├── webworkers │ ├── src │ │ └── main │ │ │ └── scala │ │ │ └── org │ │ │ └── scalajs │ │ │ └── examples │ │ │ └── webworkers │ │ │ ├── Greeting.scala │ │ │ ├── PicklerRegistrations.scala │ │ │ ├── WebWorkerMain.scala │ │ │ └── Main.scala │ ├── build.sbt │ ├── worker.js │ └── index-fastopt.html └── faulttolerance │ ├── build.sbt │ ├── index-fastopt.html │ └── src │ └── main │ └── scala │ └── org │ └── scalajs │ └── examples │ └── faulttolerance │ └── FaultHandlingDocSample.scala ├── actors ├── src │ ├── test │ │ └── scala │ │ │ └── org │ │ │ └── scalajs │ │ │ └── actors │ │ │ └── test │ │ │ ├── ActorsTest.scala │ │ │ ├── helloactors │ │ │ └── HelloActorsTest.scala │ │ │ └── ActorsTestFramework.scala │ └── main │ │ ├── scala │ │ └── akka │ │ │ ├── actor │ │ │ ├── Guardian.scala │ │ │ ├── Envelope.scala │ │ │ ├── Props.scala │ │ │ ├── Cancellable.scala │ │ │ ├── Address.scala │ │ │ ├── dungeon │ │ │ │ ├── ReceiveTimeout.scala │ │ │ │ ├── Dispatch.scala │ │ │ │ ├── Children.scala │ │ │ │ ├── ChildrenContainer.scala │ │ │ │ ├── DeathWatch.scala │ │ │ │ └── FaultHandling.scala │ │ │ ├── ChildRestartStats.scala │ │ │ ├── VirtualPathContainer.scala │ │ │ ├── LocalActorRef.scala │ │ │ ├── DeadLetterActorRef.scala │ │ │ ├── ActorRefFactory.scala │ │ │ ├── ActorContext.scala │ │ │ ├── StdMessages.scala │ │ │ ├── Scheduler.scala │ │ │ ├── Exceptions.scala │ │ │ ├── ActorSystem.scala │ │ │ ├── Actor.scala │ │ │ ├── ActorRef.scala │ │ │ └── ActorPath.scala │ │ │ ├── event │ │ │ ├── EventStream.scala │ │ │ ├── LoggingReceive.scala │ │ │ └── Logging.scala │ │ │ ├── scalajs │ │ │ ├── jsapi │ │ │ │ ├── WebSocket.scala │ │ │ │ ├── Worker.scala │ │ │ │ ├── Events.scala │ │ │ │ └── Timers.scala │ │ │ ├── webworkers │ │ │ │ ├── DedicatedPicklers.scala │ │ │ │ ├── WorkerActorRef.scala │ │ │ │ ├── WebWorkersActorSystem.scala │ │ │ │ └── WebWorkerRouter.scala │ │ │ └── wsclient │ │ │ │ └── ClientProxy.scala │ │ │ ├── util │ │ │ ├── Collections.scala │ │ │ ├── Helpers.scala │ │ │ ├── JSTimeoutTask.scala │ │ │ ├── JSIntervalTask.scala │ │ │ ├── JSTimeoutThenInterval.scala │ │ │ ├── JSQueue.scala │ │ │ ├── JSMap.scala │ │ │ └── Timeout.scala │ │ │ ├── Exceptions.scala │ │ │ ├── dispatch │ │ │ ├── Mailboxes.scala │ │ │ ├── sysmsg │ │ │ │ └── SystemMessage.scala │ │ │ ├── MessageQueue.scala │ │ │ ├── MessageDispatcher.scala │ │ │ └── Mailbox.scala │ │ │ └── pattern │ │ │ └── PipeTo.scala │ │ └── wscommon │ │ └── akka │ │ └── scalajs │ │ └── wscommon │ │ └── AbstractProxy.scala └── build.sbt ├── akka-websocket-bridge ├── build.sbt └── src │ └── main │ └── scala │ └── akka │ └── scalajs │ └── wsserver │ ├── ActorWebSocket.scala │ └── ServerProxy.scala ├── LICENSE └── README.md /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.5 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .cache 3 | .classpath 4 | .project 5 | .settings/ 6 | -------------------------------------------------------------------------------- /examples/chat-full-stack/public/stylesheets/main.css: -------------------------------------------------------------------------------- 1 | .status-hidden { 2 | display: none; 3 | } 4 | -------------------------------------------------------------------------------- /examples/chat-full-stack/app/actors/Messages.scala: -------------------------------------------------------------------------------- 1 | package actors 2 | 3 | case class NewConnection() 4 | -------------------------------------------------------------------------------- /examples/chat-full-stack/public/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjrd/scala-js-actors/HEAD/examples/chat-full-stack/public/images/favicon.png -------------------------------------------------------------------------------- /examples/webworkers/src/main/scala/org/scalajs/examples/webworkers/Greeting.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.examples.webworkers 2 | 3 | case class Greeting(who: String) 4 | -------------------------------------------------------------------------------- /examples/chat-full-stack/README: -------------------------------------------------------------------------------- 1 | This is your new Play application 2 | ===================================== 3 | 4 | This file will be packaged with your application, when using `play dist`. 5 | -------------------------------------------------------------------------------- /examples/webworkers/build.sbt: -------------------------------------------------------------------------------- 1 | import ScalaJSKeys._ 2 | 3 | scalaJSSettings 4 | 5 | name := "Scala.js actors examples - webworkers" 6 | 7 | normalizedName := "scalajs-actors-example-webworkers" 8 | -------------------------------------------------------------------------------- /examples/faulttolerance/build.sbt: -------------------------------------------------------------------------------- 1 | import ScalaJSKeys._ 2 | 3 | scalaJSSettings 4 | 5 | name := "Scala.js actors examples - fault tolerance" 6 | 7 | normalizedName := "scalajs-actors-example-faulttolerance" 8 | -------------------------------------------------------------------------------- /actors/src/test/scala/org/scalajs/actors/test/ActorsTest.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.actors.test 2 | 3 | import scala.scalajs.js 4 | import js.Dynamic.global 5 | 6 | trait ActorsTest extends scala.scalajs.test.Test 7 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/actor/Guardian.scala: -------------------------------------------------------------------------------- 1 | package akka.actor 2 | 3 | class Guardian extends Actor { 4 | def receive = { 5 | case msg => 6 | Console.err.println(s"guardian received message $msg") 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/chat-full-stack/app/actors/ChatEntryPoint.scala: -------------------------------------------------------------------------------- 1 | package actors 2 | 3 | import akka.actor._ 4 | import akka.scalajs.wsserver._ 5 | 6 | class ChatEntryPoint extends Actor { 7 | def receive = { 8 | case _ => ??? 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/chat-full-stack/scalajs/build.sbt: -------------------------------------------------------------------------------- 1 | name := "Scala.js actors examples - chat client" 2 | 3 | normalizedName := "scalajs-actors-example-chat-client" 4 | 5 | libraryDependencies += 6 | "org.scala-lang.modules.scalajs" %%% "scalajs-jquery" % "0.6" 7 | -------------------------------------------------------------------------------- /examples/chat-full-stack/.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | project/project 3 | project/target 4 | target 5 | tmp 6 | .history 7 | dist 8 | /.idea 9 | /*.iml 10 | /out 11 | /.idea_modules 12 | /.classpath 13 | /.project 14 | /RUNNING_PID 15 | /.settings 16 | .target/ 17 | bin/ 18 | -------------------------------------------------------------------------------- /examples/chat-full-stack/build.sbt: -------------------------------------------------------------------------------- 1 | name := "scalajs-actors-examples-chat" 2 | 3 | libraryDependencies ++= Seq( 4 | "org.scalajs" %% "scalajs-pickling-play-json" % "0.3.1", 5 | "org.webjars" %% "webjars-play" % "2.3.0", 6 | "org.webjars" % "jquery" % "2.1.1" 7 | ) 8 | -------------------------------------------------------------------------------- /examples/webworkers/worker.js: -------------------------------------------------------------------------------- 1 | var console = console || { 2 | log: function() {}, 3 | error: function() {} 4 | }; 5 | console.log("Worker loading ...") 6 | importScripts( 7 | "./target/scala-2.10/scalajs-actors-example-webworkers-fastopt.js"); 8 | WebWorkerMain().main(); 9 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/event/EventStream.scala: -------------------------------------------------------------------------------- 1 | package akka.event 2 | 3 | class EventStream { 4 | import Logging._ 5 | 6 | def publish(event: LogEvent): Unit = { 7 | Console.err.println(s"Event: $event") 8 | } 9 | 10 | def publish(event: Any): Unit = { 11 | Console.err.println(s"Event: $event") 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /examples/webworkers/src/main/scala/org/scalajs/examples/webworkers/PicklerRegistrations.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.examples.webworkers 2 | 3 | import org.scalajs.spickling._ 4 | 5 | object PicklerRegistrations { 6 | def registerTo(registry: BasePicklerRegistry): Unit = { 7 | import registry.register 8 | 9 | register[Greeting] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /actors/build.sbt: -------------------------------------------------------------------------------- 1 | import ScalaJSKeys._ 2 | 3 | scalaJSSettings 4 | 5 | name := "Scala.js actors" 6 | 7 | libraryDependencies += "org.scalajs" %%% "scalajs-pickling" % "0.3.1" 8 | 9 | libraryDependencies += "org.scala-lang.modules.scalajs" %% "scalajs-test-bridge" % scalaJSVersion % "test" 10 | 11 | scalaJSTestFramework in Test := "org.scalajs.actors.test.ActorsTestFramework" 12 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/scalajs/jsapi/WebSocket.scala: -------------------------------------------------------------------------------- 1 | package akka.scalajs.jsapi 2 | 3 | import scala.scalajs.js 4 | 5 | class WebSocket(url: js.String) extends js.Object with EventTarget { 6 | def send(message: js.String): Unit = ??? 7 | 8 | def close(code: js.Number, reason: js.String): Unit = ??? 9 | def close(code: js.Number): Unit = ??? 10 | def close(): Unit = ??? 11 | } 12 | -------------------------------------------------------------------------------- /project/build.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scala-lang.modules.scalajs" % "scalajs-sbt-plugin" % "0.5.4") 2 | 3 | // Comment to get more information during initialization 4 | logLevel := Level.Warn 5 | 6 | // The Typesafe repository 7 | resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/" 8 | 9 | // Use the Play sbt plugin for Play projects 10 | addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.3.4") 11 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/actor/Envelope.scala: -------------------------------------------------------------------------------- 1 | package akka.actor 2 | 3 | final case class Envelope private (val message: Any, val sender: ActorRef) 4 | 5 | object Envelope { 6 | def apply(message: Any, sender: ActorRef, system: ActorSystem): Envelope = { 7 | if (message == null) throw new InvalidMessageException("Message is null") 8 | new Envelope(message, if (sender ne Actor.noSender) sender else system.deadLetters) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/util/Collections.scala: -------------------------------------------------------------------------------- 1 | package akka.util 2 | 3 | import scala.collection.immutable 4 | 5 | object Collections { 6 | 7 | case object EmptyImmutableSeq extends immutable.Seq[Nothing] { 8 | override final def iterator = Iterator.empty 9 | override final def apply(idx: Int): Nothing = 10 | throw new java.lang.IndexOutOfBoundsException(idx.toString) 11 | override final def length: Int = 0 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /examples/chat-full-stack/cscommon/models/Models.scala: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | case class User(nick: String, gravatarHash: String) 4 | 5 | object User { 6 | val System = User("", "cd2aba324ee144fbe4066e0e2ee9966a") // Scala.js' gravatar 7 | val Nobody = User("", "") // better than null 8 | } 9 | 10 | case class Room(name: String) 11 | 12 | case class Message(user: User, text: String, 13 | timestamp: Long = System.currentTimeMillis()) 14 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/scalajs/jsapi/Worker.scala: -------------------------------------------------------------------------------- 1 | package akka.scalajs.jsapi 2 | 3 | import scala.scalajs.js 4 | 5 | trait WorkerConnection extends EventTarget { 6 | def postMessage(message: js.Any): Unit = ??? 7 | 8 | var onmessage: js.Function1[MessageEvent, _] = ??? 9 | } 10 | 11 | class Worker(url: js.String) extends WorkerConnection { 12 | def terminate(): Unit = ??? 13 | } 14 | 15 | object ParentWorkerConnection extends WorkerConnection with js.GlobalScope 16 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/util/Helpers.scala: -------------------------------------------------------------------------------- 1 | package akka.util 2 | 3 | import scala.annotation.tailrec 4 | 5 | object Helpers { 6 | 7 | final val base64chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+~" 8 | 9 | @tailrec 10 | def base64(l: Long, prefix: String = "$"): String = { 11 | val newPrefix = prefix + base64chars.charAt(l.toInt & 63) 12 | val next = l >>> 6 13 | if (next == 0) newPrefix 14 | else base64(next, newPrefix) 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /examples/chat-full-stack/public/javascripts/.gitignore: -------------------------------------------------------------------------------- 1 | /scalajs-actors-example-chat-client-extdeps.js 2 | /scalajs-actors-example-chat-client-extdeps.js.map 3 | /scalajs-actors-example-chat-client-intdeps.js 4 | /scalajs-actors-example-chat-client-intdeps.js.map 5 | /scalajs-actors-example-chat-client.js 6 | /scalajs-actors-example-chat-client.js.map 7 | /scalajs-actors-example-chat-client-fastopt.js 8 | /scalajs-actors-example-chat-client-fastopt.js.map 9 | /scalajs-actors-example-chat-client-opt.js 10 | /scalajs-actors-example-chat-client-opt.js.map 11 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/Exceptions.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2009-2013 Typesafe Inc. 3 | */ 4 | 5 | package akka 6 | 7 | /** 8 | * Akka base Exception. Each Exception gets: 9 | *
    10 | *
  • a uuid for tracking purposes
  • 11 | *
  • toString that includes exception name, message and uuid
  • 12 | *
13 | */ 14 | @SerialVersionUID(1L) 15 | class AkkaException(message: String, cause: Throwable) extends RuntimeException(message, cause) with Serializable { 16 | def this(msg: String) = this(msg, null) 17 | } 18 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/actor/Props.scala: -------------------------------------------------------------------------------- 1 | package akka.actor 2 | 3 | import scala.reflect.ClassTag 4 | 5 | final class Props(clazz: Class[_ <: Actor], creator: () => Actor) { 6 | private[akka] def newActor(): Actor = 7 | creator() 8 | } 9 | 10 | object Props { 11 | def apply[A <: Actor : ClassTag](creator: => A): Props = 12 | apply(implicitly[ClassTag[A]].runtimeClass.asInstanceOf[Class[_ <: Actor]], 13 | () => creator) 14 | 15 | def apply(clazz: Class[_ <: Actor], creator: () => Actor): Props = 16 | new Props(clazz, creator) 17 | } 18 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/actor/Cancellable.scala: -------------------------------------------------------------------------------- 1 | package akka.actor 2 | 3 | /** 4 | * Signifies something that can be cancelled. 5 | */ 6 | trait Cancellable { 7 | /** 8 | * Cancels this Cancellable and returns true if that was successful. 9 | * If this cancellable was (concurrently) cancelled already, then this method 10 | * will return false although isCancelled will return true. 11 | */ 12 | def cancel(): Boolean 13 | 14 | /** 15 | * Returns true if and only if this Cancellable has been successfully cancelled 16 | */ 17 | def isCancelled: Boolean 18 | } 19 | -------------------------------------------------------------------------------- /akka-websocket-bridge/build.sbt: -------------------------------------------------------------------------------- 1 | name := "Scala.js-Akka Websocket Bridge" 2 | 3 | normalizedName := "akka-websocket-bridge" 4 | 5 | resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/" 6 | 7 | resolvers += Resolver.url("scala-js-releases", 8 | url("http://dl.bintray.com/content/scala-js/scala-js-releases"))( 9 | Resolver.ivyStylePatterns) 10 | 11 | libraryDependencies ++= Seq( 12 | "com.typesafe.akka" %% "akka-actor" % "2.3.6", 13 | "com.typesafe.play" %% "play" % "2.3.4", 14 | "org.scalajs" %% "scalajs-pickling-play-json" % "0.3.1" 15 | ) 16 | -------------------------------------------------------------------------------- /examples/webworkers/index-fastopt.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Scala.js actors example with webworkers 5 | 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /examples/faulttolerance/index-fastopt.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Scala.js actors example with fault tolerance 5 | 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /examples/chat-full-stack/conf/routes: -------------------------------------------------------------------------------- 1 | # Routes 2 | # This file defines all application routes (Higher priority routes first) 3 | # ~~~~ 4 | 5 | # Home page 6 | GET / controllers.Application.indexDev 7 | GET /opt controllers.Application.indexOpt 8 | 9 | # Web socket entry point 10 | GET /chat-ws-entry controllers.Application.chatWSEntry 11 | 12 | # WebJars 13 | GET /webjars/*file controllers.WebJarAssets.at(file) 14 | 15 | # Map static resources from the /public folder to the /assets URL path 16 | GET /assets/*file controllers.Assets.at(path="/public", file) 17 | -------------------------------------------------------------------------------- /examples/chat-full-stack/cscommon/models/Messages.scala: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import akka.actor.ActorRef 4 | 5 | // Actions from users 6 | case class Connect(user: User) 7 | case class Join(room: Room) 8 | case object Leave 9 | case class SendMessage(message: Message) 10 | 11 | // Requests 12 | case class RequestPrivateChat(peer: User) 13 | case object AcceptPrivateChat 14 | case object RejectPrivateChat 15 | case object UserDoesNotExist 16 | 17 | // Notifications from server 18 | case class RoomListChanged(rooms: List[Room]) 19 | case class JoinedRoom(users: List[User]) 20 | case class UserJoined(user: User) 21 | case class UserLeft(user: User) 22 | case class ReceiveMessage(message: Message) 23 | -------------------------------------------------------------------------------- /examples/chat-full-stack/test/IntegrationSpec.scala: -------------------------------------------------------------------------------- 1 | import org.specs2.mutable._ 2 | import org.specs2.runner._ 3 | import org.junit.runner._ 4 | 5 | import play.api.test._ 6 | import play.api.test.Helpers._ 7 | 8 | /** 9 | * add your integration spec here. 10 | * An integration test will fire up a whole play application in a real (or headless) browser 11 | */ 12 | @RunWith(classOf[JUnitRunner]) 13 | class IntegrationSpec extends Specification { 14 | 15 | "Application" should { 16 | 17 | "work from within a browser" in new WithBrowser { 18 | 19 | browser.goTo("http://localhost:" + port) 20 | 21 | browser.pageSource must contain("Your new application is ready.") 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/scalajs/jsapi/Events.scala: -------------------------------------------------------------------------------- 1 | package akka.scalajs.jsapi 2 | 3 | import scala.scalajs.js 4 | 5 | trait EventTarget extends js.Object { 6 | def addEventListener(`type`: js.String, 7 | listener: js.Function1[Event, _], 8 | useCapture: js.Boolean): Unit = ??? 9 | def removeEventListener(`type`: js.String, 10 | listener: js.Function1[Event, _], 11 | useCapture: js.Boolean): Unit = ??? 12 | } 13 | 14 | class Event extends js.Object { 15 | 16 | } 17 | 18 | trait MessageEvent extends Event { 19 | val data: js.Dynamic = ??? 20 | } 21 | 22 | trait CloseEvent extends Event { 23 | val wasClean: js.Boolean = ??? 24 | val code: js.Number = ??? 25 | val reason: js.String = ??? 26 | } 27 | -------------------------------------------------------------------------------- /actors/src/test/scala/org/scalajs/actors/test/helloactors/HelloActorsTest.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.actors.test 2 | package helloactors 3 | 4 | import scala.scalajs.js 5 | import js.Dynamic.global 6 | 7 | import akka.actor._ 8 | 9 | case class Greeting(who: String) 10 | 11 | class GreetingActor extends Actor { 12 | def receive = { 13 | case Greeting(who) => global.console.log("Hello " + who) 14 | } 15 | } 16 | 17 | object HelloActorsTest extends ActorsTest { 18 | global.console.log("Starting test") 19 | val system = ActorSystem("MySystem") 20 | global.console.log("Actor system created") 21 | val greeter = system.actorOf(Props(new GreetingActor), name = "greeter") 22 | global.console.log("Actor created") 23 | greeter ! Greeting("Charlie Parker") 24 | } 25 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/util/JSTimeoutTask.scala: -------------------------------------------------------------------------------- 1 | package akka.util 2 | 3 | import scala.concurrent.duration.FiniteDuration 4 | 5 | import akka.scalajs.jsapi.Timers 6 | import akka.actor.Cancellable 7 | 8 | class JSTimeoutTask(delay: FiniteDuration, task: => Any) extends Cancellable { 9 | private[this] var underlying: Option[Timers.TimeoutID] = 10 | Some(Timers.setTimeout(delay)(task)) 11 | 12 | def isCancelled: Boolean = underlying.isEmpty 13 | 14 | def cancel(): Boolean = { 15 | if (isCancelled) false 16 | else { 17 | Timers.clearTimeout(underlying.get) 18 | underlying = None 19 | true 20 | } 21 | } 22 | } 23 | 24 | object JSTimeoutTask { 25 | def apply(duration: FiniteDuration)(task: => Any): JSTimeoutTask = 26 | new JSTimeoutTask(duration, task) 27 | } 28 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/util/JSIntervalTask.scala: -------------------------------------------------------------------------------- 1 | package akka.util 2 | 3 | import scala.concurrent.duration.FiniteDuration 4 | 5 | import akka.scalajs.jsapi.Timers 6 | import akka.actor.Cancellable 7 | 8 | class JSIntervalTask(interval: FiniteDuration, task: => Any) extends Cancellable { 9 | private[this] var underlying: Option[Timers.IntervalID] = 10 | Some(Timers.setInterval(interval)(task)) 11 | 12 | def isCancelled: Boolean = underlying.isEmpty 13 | 14 | def cancel(): Boolean = { 15 | if (isCancelled) false 16 | else { 17 | Timers.clearInterval(underlying.get) 18 | underlying = None 19 | true 20 | } 21 | } 22 | } 23 | 24 | object JSIntervalTask { 25 | def apply(interval: FiniteDuration)(task: => Any): JSIntervalTask = 26 | new JSIntervalTask(interval, task) 27 | } 28 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/util/JSTimeoutThenInterval.scala: -------------------------------------------------------------------------------- 1 | package akka.util 2 | 3 | import scala.concurrent.duration.{Duration, FiniteDuration} 4 | 5 | import akka.actor.Cancellable 6 | 7 | class JSTimeoutThenIntervalTask(initialDelay: FiniteDuration, 8 | interval: FiniteDuration, task: => Any) extends Cancellable { 9 | 10 | private[this] var underlying: Cancellable = JSTimeoutTask(initialDelay) { 11 | underlying = JSIntervalTask(interval) { 12 | task 13 | } 14 | task 15 | } 16 | 17 | def isCancelled: Boolean = underlying.isCancelled 18 | 19 | def cancel(): Boolean = underlying.cancel() 20 | } 21 | 22 | object JSTimeoutThenIntervalTask { 23 | def apply(initialDelay: FiniteDuration, interval: FiniteDuration)( 24 | task: => Any): JSTimeoutThenIntervalTask = 25 | new JSTimeoutThenIntervalTask(initialDelay, interval, task) 26 | } 27 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/util/JSQueue.scala: -------------------------------------------------------------------------------- 1 | package akka.util 2 | 3 | import scala.collection.mutable 4 | 5 | import scala.scalajs.js 6 | 7 | class JSQueue[A] { 8 | private[this] var queue: js.Array[A] = js.Array() 9 | private[this] var offset: Int = 0 10 | 11 | def size: Int = queue.length.toInt - offset 12 | 13 | def isEmpty: Boolean = queue.length.toInt == 0 14 | def nonEmpty: Boolean = queue.length.toInt != 0 15 | 16 | def enqueue(item: A): Unit = queue.push(item) 17 | 18 | def dequeue(): A = { 19 | if (queue.length.toInt == 0) 20 | throw new NoSuchElementException("queue empty") 21 | 22 | val item = queue(offset) 23 | offset += 1 24 | 25 | // shrink the underlying queue if necessary 26 | if (offset*2 >= queue.length.toInt) { 27 | queue = queue.slice(offset) 28 | offset = 0 29 | } 30 | 31 | item 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/chat-full-stack/test/ApplicationSpec.scala: -------------------------------------------------------------------------------- 1 | import org.specs2.mutable._ 2 | import org.specs2.runner._ 3 | import org.junit.runner._ 4 | 5 | import play.api.test._ 6 | import play.api.test.Helpers._ 7 | 8 | /** 9 | * Add your spec here. 10 | * You can mock out a whole application including requests, plugins etc. 11 | * For more information, consult the wiki. 12 | */ 13 | @RunWith(classOf[JUnitRunner]) 14 | class ApplicationSpec extends Specification { 15 | 16 | "Application" should { 17 | 18 | "send 404 on a bad request" in new WithApplication{ 19 | route(FakeRequest(GET, "/boum")) must beNone 20 | } 21 | 22 | "render the index page" in new WithApplication{ 23 | val home = route(FakeRequest(GET, "/")).get 24 | 25 | status(home) must equalTo(OK) 26 | contentType(home) must beSome.which(_ == "text/html") 27 | contentAsString(home) must contain ("Your new application is ready.") 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/dispatch/Mailboxes.scala: -------------------------------------------------------------------------------- 1 | package akka.dispatch 2 | 3 | import akka.actor._ 4 | import akka.dispatch.sysmsg.SystemMessage 5 | 6 | private[akka] class Mailboxes( 7 | deadLetters: ActorRef) { 8 | 9 | val deadLetterMailbox: Mailbox = new Mailbox(new MessageQueue { 10 | def enqueue(receiver: ActorRef, envelope: Envelope): Unit = envelope.message match { 11 | case _: DeadLetter => // actor subscribing to DeadLetter, drop it 12 | case msg => deadLetters.!(DeadLetter(msg, envelope.sender, receiver))(envelope.sender) 13 | } 14 | def dequeue() = null 15 | def hasMessages = false 16 | def numberOfMessages = 0 17 | def cleanUp(owner: ActorRef, deadLetters: MessageQueue): Unit = () 18 | }) { 19 | becomeClosed() 20 | override def systemEnqueue(receiver: ActorRef, handle: SystemMessage): Unit = 21 | deadLetters ! DeadLetter(handle, receiver, receiver) 22 | override def hasSystemMessages = false 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/actor/Address.scala: -------------------------------------------------------------------------------- 1 | package akka.actor 2 | 3 | final case class Address private[akka] ( 4 | system: String, worker: Option[String]) { 5 | 6 | /** 7 | * Returns true if this Address is only defined locally. It is not safe to 8 | * send locally scoped addresses to other workers. 9 | * See also [[org.scalajs.actors.Address#hasGlobalScope]]. 10 | */ 11 | def hasLocalScope: Boolean = worker.isEmpty 12 | 13 | /** 14 | * Returns true if this Address is usable globally. Unlike locally defined 15 | * addresses ([[org.scalajs.actors.Address#hasLocalScope]]), addresses of 16 | * global scope are safe to send to other workers, as they globally and 17 | * uniquely identify an addressable entity. 18 | */ 19 | def hasGlobalScope: Boolean = worker.isDefined 20 | 21 | } 22 | 23 | object Address { 24 | def apply(system: String): Address = 25 | new Address(system, None) 26 | def apply(system: String, worker: String): Address = 27 | new Address(system, Some(worker)) 28 | } 29 | -------------------------------------------------------------------------------- /examples/chat-full-stack/app/controllers/Application.scala: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import scala.language.postfixOps 4 | 5 | import scala.concurrent.duration._ 6 | 7 | import play.api._ 8 | import play.api.mvc._ 9 | import play.api.libs.json._ 10 | import play.api.libs.iteratee._ 11 | import play.api.libs.concurrent.Akka 12 | 13 | import akka.actor._ 14 | import akka.pattern.ask 15 | import akka.scalajs.wsserver.ActorWebSocket 16 | import actors._ 17 | 18 | object Application extends Controller { 19 | 20 | import play.api.Play.current 21 | 22 | implicit val timeout = akka.util.Timeout(5 seconds) 23 | implicit def ec = Akka.system.dispatcher 24 | 25 | val chatManager = Akka.system.actorOf(Props[ChatManager], name = "chat") 26 | 27 | def indexDev = Action { 28 | Ok(views.html.index(devMode = true)) 29 | } 30 | 31 | def indexOpt = Action { 32 | Ok(views.html.index(devMode = false)) 33 | } 34 | 35 | def chatWSEntry = ActorWebSocket { request => 36 | chatManager ? NewConnection() 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/util/JSMap.scala: -------------------------------------------------------------------------------- 1 | package akka.util 2 | 3 | import scala.collection.mutable.{ Map, MapLike } 4 | 5 | import scala.scalajs.js 6 | import js.annotation.JSBracketAccess 7 | 8 | final class JSMap[A] private () extends Map[String, A] 9 | with MapLike[String, A, JSMap[A]] { 10 | private[this] val dict: js.Dictionary[A] = js.Dictionary.empty[A] 11 | 12 | override def empty: JSMap[A] = new JSMap[A] 13 | 14 | override def get(key: String): Option[A] = { 15 | if (dict.hasOwnProperty(key)) Some(dict(key)) 16 | else None 17 | } 18 | 19 | override def +=(kv: (String, A)): this.type = { 20 | dict(kv._1) = kv._2 21 | this 22 | } 23 | 24 | override def -=(key: String): this.type = { 25 | dict.delete(key) 26 | this 27 | } 28 | 29 | override def iterator: Iterator[(String, A)] = { 30 | for { 31 | key <- js.Object.keys(dict).iterator 32 | } yield { 33 | (key, dict(key)) 34 | } 35 | } 36 | } 37 | 38 | object JSMap { 39 | def empty[A]: JSMap[A] = new JSMap[A] 40 | } 41 | -------------------------------------------------------------------------------- /examples/webworkers/src/main/scala/org/scalajs/examples/webworkers/WebWorkerMain.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.examples.webworkers 2 | 3 | import scala.scalajs.js 4 | import js.annotation.JSExport 5 | import js.Dynamic.global 6 | 7 | import org.scalajs.spickling.PicklerRegistry 8 | import akka.actor._ 9 | import akka.scalajs.webworkers.WebWorkerRouter 10 | 11 | class GreetingActor extends Actor { 12 | def receive = { 13 | case Greeting(who) => 14 | global.console.log("Hello " + who) 15 | sender ! Greeting("returning " + who) 16 | } 17 | } 18 | 19 | @JSExport 20 | object WebWorkerMain { 21 | println("Starting WebWorkerMain") 22 | PicklerRegistry.register[Greeting] 23 | 24 | WebWorkerRouter.setupAsChild() 25 | 26 | var system: ActorSystem = _ 27 | 28 | @JSExport 29 | def main(): Unit = { 30 | WebWorkerRouter.onInitialized { 31 | global.console.log("Worker initialized with address "+WebWorkerRouter.address) 32 | system = ActorSystem("WorkerSystem") 33 | val greeter = system.actorOf(Props(new GreetingActor), name = "greeter") 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/util/Timeout.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2009-2013 Typesafe Inc. 3 | */ 4 | 5 | package akka.util 6 | 7 | import language.implicitConversions 8 | 9 | import java.util.concurrent.TimeUnit 10 | import java.lang.{ Double => JDouble } 11 | import scala.concurrent.duration.{ Duration, FiniteDuration } 12 | 13 | case class Timeout(duration: FiniteDuration) { 14 | def this(timeout: Long) = this(Duration(timeout, TimeUnit.MILLISECONDS)) 15 | def this(length: Long, unit: TimeUnit) = this(Duration(length, unit)) 16 | } 17 | 18 | /** 19 | * A Timeout is a wrapper on top of Duration to be more specific about what the duration means. 20 | */ 21 | object Timeout { 22 | 23 | /** 24 | * A timeout with zero duration, will cause most requests to always timeout. 25 | */ 26 | val zero: Timeout = new Timeout(Duration.Zero) 27 | 28 | def apply(timeout: Long): Timeout = new Timeout(timeout) 29 | def apply(length: Long, unit: TimeUnit): Timeout = new Timeout(length, unit) 30 | 31 | implicit def durationToTimeout(duration: FiniteDuration): Timeout = 32 | new Timeout(duration) 33 | implicit def intToTimeout(timeout: Int): Timeout = new Timeout(timeout) 34 | implicit def longToTimeout(timeout: Long): Timeout = new Timeout(timeout) 35 | } 36 | -------------------------------------------------------------------------------- /examples/chat-full-stack/app/views/main.scala.html: -------------------------------------------------------------------------------- 1 | @(title: String)(content: Html) 2 | 3 | 4 | 5 | 6 | 7 | 8 | @title 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | @content 25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /akka-websocket-bridge/src/main/scala/akka/scalajs/wsserver/ActorWebSocket.scala: -------------------------------------------------------------------------------- 1 | package akka.scalajs.wsserver 2 | 3 | import akka.actor._ 4 | import akka.scalajs.wscommon.AbstractProxy 5 | 6 | import play.api._ 7 | import play.api.mvc._ 8 | import play.api.libs.json._ 9 | import play.api.libs.iteratee._ 10 | import play.api.libs.concurrent.Akka 11 | 12 | import scala.concurrent.Future 13 | import scala.concurrent.ExecutionContext.Implicits._ 14 | 15 | object ActorWebSocket { 16 | def apply(f: RequestHeader => Future[Any])/*: WebSocket[JsValue]*/ = { 17 | WebSocket.async[JsValue] { request => 18 | f(request).map(_.asInstanceOf[(Iteratee[JsValue, Unit], Enumerator[JsValue])]) 19 | } 20 | } 21 | 22 | def actorForWebSocketHandler(entryPointRef: ActorRef)( 23 | implicit context: ActorRefFactory): (Iteratee[JsValue, Unit], Enumerator[JsValue]) = { 24 | 25 | val (out, channel) = Concurrent.broadcast[JsValue] 26 | val serverProxy = context.actorOf( 27 | Props(classOf[ServerProxy], channel, entryPointRef)) 28 | 29 | // Forward incoming messages as messages to the proxy 30 | val in = Iteratee.foreach[JsValue] { 31 | msg => serverProxy ! AbstractProxy.IncomingMessage(msg) 32 | }.map { 33 | _ => serverProxy ! AbstractProxy.ConnectionClosed 34 | } 35 | 36 | (in, out) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/scalajs/webworkers/DedicatedPicklers.scala: -------------------------------------------------------------------------------- 1 | package akka.scalajs.webworkers 2 | 3 | import org.scalajs.spickling._ 4 | 5 | import akka.actor._ 6 | 7 | object DedicatedPicklers { 8 | implicit object ChildActorPathPickler extends Pickler[ChildActorPath] { 9 | def pickle[P](path: ChildActorPath)(implicit registry: PicklerRegistry, 10 | builder: PBuilder[P]): P = { 11 | builder.makeObject( 12 | ("root", registry.pickle(path.root)), 13 | ("elements", builder.makeArray( 14 | path.elements.map(builder.makeString(_)).toSeq: _*)), 15 | ("uid", builder.makeNumber(path.uid))) 16 | } 17 | } 18 | 19 | implicit object ChildActorPathUnpickler extends Unpickler[ChildActorPath] { 20 | def unpickle[P](pickle: P)(implicit registry: PicklerRegistry, 21 | reader: PReader[P]): ChildActorPath = { 22 | val root = registry.unpickle( 23 | reader.readObjectField(pickle, "root")).asInstanceOf[ActorPath] 24 | val elemsArray = reader.readObjectField(pickle, "elements") 25 | val elements = (0 until reader.readArrayLength(elemsArray)).map { 26 | i => reader.readString(reader.readArrayElem(elemsArray, i)) 27 | } 28 | val uid = reader.readNumber(reader.readObjectField(pickle, "uid")).toInt 29 | (root / elements).withUid(uid).asInstanceOf[ChildActorPath] 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/scalajs/webworkers/WorkerActorRef.scala: -------------------------------------------------------------------------------- 1 | package akka.scalajs.webworkers 2 | 3 | import scala.scalajs.js 4 | import org.scalajs.spickling.PicklerRegistry 5 | import org.scalajs.spickling.jsany._ 6 | 7 | import akka.actor._ 8 | import akka.dispatch.sysmsg.SystemMessage 9 | 10 | private[akka] class WorkerActorRef( 11 | system: WebWorkersActorSystem, 12 | val path: ActorPath) extends InternalActorRef { 13 | 14 | private[this] val pickledPath = PicklerRegistry.pickle(path) 15 | 16 | private[this] lazy val parent = 17 | if (path.isInstanceOf[RootActorPath]) Nobody 18 | else new WorkerActorRef(system, path.parent) 19 | 20 | def !(msg: Any)(implicit sender: ActorRef): Unit = { 21 | system.sendMessageAcrossWorkers(path.address, msg, this, sender) 22 | } 23 | 24 | // InternalActorRef API 25 | 26 | // TODO 27 | def start(): Unit = () 28 | def resume(causedByFailure: Throwable): Unit = () 29 | def suspend(): Unit = () 30 | def restart(cause: Throwable): Unit = () 31 | def stop(): Unit = () 32 | def sendSystemMessage(message: SystemMessage): Unit = () 33 | 34 | def getParent: InternalActorRef = parent 35 | 36 | override def provider: ActorRefProvider = system.asInstanceOf[ActorSystemImpl].provider 37 | 38 | def getChild(name: Iterator[String]): InternalActorRef = 39 | Actor.noSender 40 | 41 | def isLocal: Boolean = true 42 | } 43 | -------------------------------------------------------------------------------- /examples/webworkers/src/main/scala/org/scalajs/examples/webworkers/Main.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.examples.webworkers 2 | 3 | import scala.scalajs.js 4 | import js.annotation.JSExport 5 | import js.Dynamic.global 6 | 7 | import org.scalajs.spickling.PicklerRegistry 8 | import akka.actor._ 9 | import akka.scalajs.webworkers.WebWorkerRouter 10 | 11 | case object Start 12 | 13 | class GreetingResponseActor extends Actor { 14 | def receive = { 15 | case Start => 16 | global.console.log("Start") 17 | val root = RootActorPath(Address("WorkerSystem", Main.workerAddress)) 18 | val path = root / "user" / "greeter" 19 | context.system.sendToPath(path, Greeting("John Parker")) 20 | 21 | case Greeting(who) => 22 | global.console.log("Receiving back " + who) 23 | global.console.log("Wow! We made it back here! That's amazing!") 24 | } 25 | } 26 | 27 | @JSExport 28 | object Main { 29 | PicklerRegistry.register(Start) 30 | PicklerRegistry.register[Greeting] 31 | 32 | WebWorkerRouter.initializeAsRoot() 33 | global.console.log("Will now create worker") 34 | val workerAddress = WebWorkerRouter.createChild("worker.js") 35 | 36 | val system = ActorSystem("MainSystem") 37 | 38 | @JSExport 39 | def main(): Unit = { 40 | val greeter = system.actorOf(Props(new GreetingResponseActor), 41 | name = "greetingresponse") 42 | greeter ! Start 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /examples/chat-full-stack/cscommon/models/RegisterPicklers.scala: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import org.scalajs.spickling._ 4 | 5 | object RegisterPicklers { 6 | import PicklerRegistry.register 7 | 8 | // Utils 9 | register(Nil) 10 | register[::[Any]] 11 | 12 | // Models 13 | register[User] 14 | register[Room] 15 | register[Message] 16 | 17 | // Actions from users 18 | register[Connect] 19 | register[Join] 20 | register(Leave) 21 | register[SendMessage] 22 | 23 | // Requests 24 | register[RequestPrivateChat] 25 | register(AcceptPrivateChat) 26 | register(RejectPrivateChat) 27 | register(UserDoesNotExist) 28 | 29 | // Notifications from server 30 | register[RoomListChanged] 31 | register[JoinedRoom] 32 | register[UserJoined] 33 | register[UserLeft] 34 | register[ReceiveMessage] 35 | 36 | def registerPicklers(): Unit = () 37 | 38 | implicit object ConsPickler extends Pickler[::[Any]] { 39 | def pickle[P](value: ::[Any])(implicit registry: PicklerRegistry, 40 | builder: PBuilder[P]): P = { 41 | builder.makeArray(value.map(registry.pickle(_)): _*) 42 | } 43 | } 44 | 45 | implicit object ConsUnpickler extends Unpickler[::[Any]] { 46 | def unpickle[P](pickle: P)(implicit registry: PicklerRegistry, 47 | reader: PReader[P]): ::[Any] = { 48 | val len = reader.readArrayLength(pickle) 49 | assert(len > 0) 50 | ((0 until len).toList map { index => 51 | registry.unpickle(reader.readArrayElem(pickle, index)) 52 | }).asInstanceOf[::[Any]] 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 EPFL 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, 9 | this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | * Neither the name of the EPFL nor the names of its contributors 14 | may be used to endorse or promote products derived from this software 15 | without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 21 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /akka-websocket-bridge/src/main/scala/akka/scalajs/wsserver/ServerProxy.scala: -------------------------------------------------------------------------------- 1 | package akka.scalajs.wsserver 2 | 3 | import scala.collection.mutable 4 | import scala.concurrent.Future 5 | 6 | import akka.actor._ 7 | import akka.scalajs.wscommon._ 8 | 9 | import play.api.libs.json._ 10 | import play.api.libs.iteratee.Concurrent.Channel 11 | 12 | import org.scalajs.spickling._ 13 | import org.scalajs.spickling.playjson._ 14 | 15 | object ServerProxy { 16 | case object SendEntryPointRef 17 | } 18 | 19 | class ServerProxy(channelToClient: Channel[JsValue], 20 | entryPointRef: Future[ActorRef]) extends AbstractProxy { 21 | def this(channelToClient: Channel[JsValue], 22 | entryPointRef: ActorRef) = this(channelToClient, Future.successful(entryPointRef)) 23 | 24 | import AbstractProxy._ 25 | import ServerProxy._ 26 | 27 | type PickleType = JsValue 28 | implicit protected def pickleBuilder: PBuilder[PickleType] = PlayJsonPBuilder 29 | implicit protected def pickleReader: PReader[PickleType] = PlayJsonPReader 30 | 31 | private implicit def ec = context.dispatcher 32 | 33 | override def preStart() = { 34 | super.preStart() 35 | self ! SendEntryPointRef 36 | } 37 | 38 | override def postStop() = { 39 | super.postStop() 40 | channelToClient.end() 41 | } 42 | 43 | override def receive = super.receive.orElse[Any, Unit] { 44 | case SendEntryPointRef => 45 | entryPointRef foreach { ref => 46 | self ! SendToPeer(Welcome(ref)) 47 | } 48 | } 49 | 50 | override protected def sendPickleToPeer(pickle: PickleType): Unit = { 51 | channelToClient push pickle 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /actors/src/test/scala/org/scalajs/actors/test/ActorsTestFramework.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.actors.test 2 | 3 | import scala.scalajs.js 4 | import scala.scalajs.js.Dynamic.global 5 | import scala.scalajs.js.JavaScriptException 6 | 7 | import scala.scalajs.test._ 8 | 9 | object ActorsTestFramework extends TestFramework { 10 | def withEventQueue(body: => Unit): Unit = { 11 | val eventQueue = new scala.collection.mutable.Queue[js.Function0[_]] 12 | 13 | val oldSetTimeout = global.setTimeout 14 | val oldClearTimeout = global.clearTimeout 15 | val oldSetInterval = global.setInterval 16 | val oldClearInterval = global.clearInterval 17 | 18 | var lastID: js.Number = 0 19 | global.setTimeout = { (f: js.Function0[_], delay: js.Number) => 20 | eventQueue.enqueue(f) 21 | lastID += 1 22 | lastID 23 | } 24 | global.clearTimeout = { () => sys.error("Stub for clearTimeout") } 25 | global.setInterval = { () => sys.error("Stub for setInterval") } 26 | global.clearInterval = { () => sys.error("Stub for clearInterval") } 27 | 28 | try { 29 | body 30 | 31 | while (eventQueue.nonEmpty) { 32 | val event = eventQueue.dequeue() 33 | event() 34 | } 35 | } finally { 36 | global.setTimeout = oldSetTimeout 37 | global.clearTimeout = oldClearTimeout 38 | global.setInterval = oldSetInterval 39 | global.clearInterval = oldClearInterval 40 | } 41 | } 42 | 43 | override def runTests(testOutput: TestOutput, args: js.Array[String])( 44 | tests: js.Function0[Unit]): Unit = { 45 | withEventQueue { 46 | try { 47 | tests() 48 | } catch { 49 | case e: Throwable => testOutput.error(e.getMessage, e.getStackTrace) 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/scalajs/jsapi/Timers.scala: -------------------------------------------------------------------------------- 1 | package akka.scalajs.jsapi 2 | 3 | import scala.concurrent.duration.FiniteDuration 4 | 5 | import scala.scalajs.js 6 | import js.annotation.JSName 7 | 8 | class TimersBase extends js.Object 9 | 10 | object Timers extends TimersBase with js.GlobalScope { 11 | type TimeoutID = js.Number 12 | type IntervalID = js.Number 13 | 14 | @JSName("setTimeout") 15 | private[jsapi] def setTimeout_impl(func: js.Function0[_], 16 | delay: js.Number): TimeoutID = ??? 17 | 18 | @JSName("setInterval") 19 | private[jsapi] def setInterval_impl(func: js.Function0[_], 20 | interval: js.Number): IntervalID = ??? 21 | 22 | def clearTimeout(timeoutID: TimeoutID): Unit = ??? 23 | def clearInterval(intervalID: IntervalID): Unit = ??? 24 | } 25 | 26 | object TimersBase { 27 | import Timers.{TimeoutID, IntervalID} 28 | 29 | implicit class Ops(val self: Timers.type) extends AnyVal { 30 | def setTimeout(func: js.Function0[_], delay: js.Number): TimeoutID = 31 | Timers.setTimeout_impl(func, delay) 32 | def setTimeout(delay: js.Number)(body: => Any): TimeoutID = 33 | Timers.setTimeout_impl(() => body, delay) 34 | def setTimeout(delay: FiniteDuration)(body: => Any): TimeoutID = 35 | Timers.setTimeout_impl(() => body, delay.toMillis) 36 | 37 | def setInterval(func: js.Function0[_], interval: js.Number): IntervalID = 38 | Timers.setInterval_impl(func, interval) 39 | def setInterval(interval: js.Number)(body: => Any): IntervalID = 40 | Timers.setInterval_impl(() => body, interval) 41 | def setInterval(interval: FiniteDuration)(body: => Any): IntervalID = 42 | Timers.setInterval_impl(() => body, interval.toMillis) 43 | 44 | def setImmediate(body: => Any): Unit = 45 | setTimeout(0)(body) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/actor/dungeon/ReceiveTimeout.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2009-2013 Typesafe Inc. 3 | */ 4 | 5 | package akka.actor 6 | package dungeon 7 | 8 | import scala.concurrent.duration.{Duration, FiniteDuration} 9 | 10 | import ReceiveTimeout.emptyReceiveTimeoutData 11 | import akka.util.JSTimeoutTask 12 | 13 | private[akka] object ReceiveTimeout { 14 | final val emptyReceiveTimeoutData: (Duration, Cancellable) = 15 | (Duration.Undefined, ActorCell.emptyCancellable) 16 | } 17 | 18 | private[akka] trait ReceiveTimeout { this: ActorCell => 19 | 20 | import ReceiveTimeout._ 21 | import ActorCell._ 22 | 23 | private var receiveTimeoutData: (Duration, Cancellable) = 24 | emptyReceiveTimeoutData 25 | 26 | final def receiveTimeout: Duration = receiveTimeoutData._1 27 | 28 | final def setReceiveTimeout(timeout: Duration): Unit = 29 | receiveTimeoutData = receiveTimeoutData.copy(_1 = timeout) 30 | 31 | final def checkReceiveTimeout() { 32 | val recvtimeout = receiveTimeoutData 33 | //Only reschedule if desired and there are currently no more messages to be processed 34 | if (!mailbox.hasMessages) recvtimeout._1 match { 35 | case f: FiniteDuration => 36 | recvtimeout._2.cancel() //Cancel any ongoing future 37 | val task = system.scheduler.scheduleOnce(f, self, 38 | akka.actor.ReceiveTimeout)(this.dispatcher) 39 | receiveTimeoutData = (f, task) 40 | case _ => cancelReceiveTimeout() 41 | } 42 | else cancelReceiveTimeout() 43 | 44 | } 45 | 46 | final def cancelReceiveTimeout(): Unit = { 47 | if (receiveTimeoutData._2 ne emptyCancellable) { 48 | receiveTimeoutData._2.cancel() 49 | receiveTimeoutData = (receiveTimeoutData._1, emptyCancellable) 50 | } 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/event/LoggingReceive.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2009-2013 Typesafe Inc. 3 | */ 4 | package akka.event 5 | 6 | import language.existentials 7 | 8 | import akka.actor._ 9 | 10 | import Actor.Receive 11 | import Logging.Debug 12 | 13 | object LoggingReceive { 14 | 15 | /** 16 | * Wrap a Receive partial function in a logging enclosure, which sends a 17 | * debug message to the event bus each time before a message is matched. 18 | * This includes messages which are not handled. 19 | * 20 | *

21 |    * def receive = LoggingReceive {
22 |    *   case x => ...
23 |    * }
24 |    * 
25 | * 26 | * This method does NOT modify the given Receive unless 27 | * `akka.actor.debug.receive` is set in configuration. 28 | */ 29 | def apply(r: Receive)(implicit context: ActorContext): Receive = r match { 30 | case _: LoggingReceive => r 31 | case _ => 32 | //if (context.system.settings.AddLoggingReceive) 33 | new LoggingReceive(None, r) 34 | //else r 35 | } 36 | } 37 | 38 | /** 39 | * This decorator adds invocation logging to a Receive function. 40 | * @param source the log source, if not defined the actor of the context will be used 41 | */ 42 | class LoggingReceive(source: Option[AnyRef], r: Receive)( 43 | implicit context: ActorContext) extends Receive { 44 | 45 | def isDefinedAt(o: Any): Boolean = { 46 | val handled = r.isDefinedAt(o) 47 | //val (str, clazz) = LogSource.fromAnyRef( 48 | // source getOrElse context.asInstanceOf[ActorCell].actor) 49 | val str = context.asInstanceOf[ActorCell].actor.self.toString() 50 | val clazz = context.asInstanceOf[ActorCell].actor.getClass() 51 | context.system.eventStream.publish(Debug(str, clazz, 52 | "received " + (if (handled) "handled" else "unhandled") + " message " + o)) 53 | handled 54 | } 55 | 56 | def apply(o: Any): Unit = r(o) 57 | } 58 | -------------------------------------------------------------------------------- /examples/chat-full-stack/conf/application.conf: -------------------------------------------------------------------------------- 1 | # This is the main configuration file for the application. 2 | # ~~~~~ 3 | 4 | # Secret key 5 | # ~~~~~ 6 | # The secret key is used to secure cryptographics functions. 7 | # If you deploy your application to several instances be sure to use the same key! 8 | application.secret="F72wwt=xI:rhSjSDBux?4qpYtV54kSmIMg=D:]6c_t/fQKgVcJ`/GE0r^[hf8^q5" 9 | 10 | # The application languages 11 | # ~~~~~ 12 | application.langs="en" 13 | 14 | # Global object class 15 | # ~~~~~ 16 | # Define the Global object class for this application. 17 | # Default to Global in the root package. 18 | # application.global=Global 19 | 20 | # Router 21 | # ~~~~~ 22 | # Define the Router object to use for this application. 23 | # This router will be looked up first when the application is starting up, 24 | # so make sure this is the entry point. 25 | # Furthermore, it's assumed your route file is named properly. 26 | # So for an application router like `my.application.Router`, 27 | # you may need to define a router file `conf/my.application.routes`. 28 | # Default to Routes in the root package (and conf/routes) 29 | # application.router=my.application.Routes 30 | 31 | # Database configuration 32 | # ~~~~~ 33 | # You can declare as many datasources as you want. 34 | # By convention, the default datasource is named `default` 35 | # 36 | # db.default.driver=org.h2.Driver 37 | # db.default.url="jdbc:h2:mem:play" 38 | # db.default.user=sa 39 | # db.default.password="" 40 | 41 | # Evolutions 42 | # ~~~~~ 43 | # You can disable evolutions if needed 44 | # evolutionplugin=disabled 45 | 46 | # Logger 47 | # ~~~~~ 48 | # You can also configure logback (http://logback.qos.ch/), 49 | # by providing an application-logger.xml file in the conf directory. 50 | 51 | # Root logger: 52 | logger.root=ERROR 53 | 54 | # Logger used by the framework: 55 | logger.play=INFO 56 | 57 | # Logger provided to your application: 58 | logger.application=DEBUG 59 | 60 | 61 | akka.loglevel="DEBUG" 62 | akka.actor.debug.receive=on 63 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/actor/ChildRestartStats.scala: -------------------------------------------------------------------------------- 1 | package akka.actor 2 | 3 | import java.util.concurrent.TimeUnit 4 | 5 | /** 6 | * ChildRestartStats is the statistics kept by every parent Actor for every child Actor 7 | * and is used for SupervisorStrategies to know how to deal with problems that occur for the children. 8 | */ 9 | case class ChildRestartStats(child: ActorRef, 10 | var maxNrOfRetriesCount: Int = 0, 11 | var restartTimeWindowStartNanos: Long = 0L) { 12 | 13 | def uid: Int = child.path.uid 14 | 15 | //FIXME How about making ChildRestartStats immutable and then move these methods into the actual supervisor strategies? 16 | def requestRestartPermission(retriesWindow: (Option[Int], Option[Int])): Boolean = 17 | retriesWindow match { 18 | case (Some(retries), _) if retries < 1 => false 19 | case (Some(retries), None) => { maxNrOfRetriesCount += 1; maxNrOfRetriesCount <= retries } 20 | case (x, Some(window)) => retriesInWindowOkay(if (x.isDefined) x.get else 1, window) 21 | case (None, _) => true 22 | } 23 | 24 | private def retriesInWindowOkay(retries: Int, window: Int): Boolean = { 25 | /* 26 | * Simple window algorithm: window is kept open for a certain time 27 | * after a restart and if enough restarts happen during this time, it 28 | * denies. Otherwise window closes and the scheme starts over. 29 | */ 30 | val retriesDone = maxNrOfRetriesCount + 1 31 | val now = System.nanoTime 32 | val windowStart = 33 | if (restartTimeWindowStartNanos == 0) { 34 | restartTimeWindowStartNanos = now 35 | now 36 | } else restartTimeWindowStartNanos 37 | val insideWindow = (now - windowStart) <= TimeUnit.MILLISECONDS.toNanos(window) 38 | if (insideWindow) { 39 | maxNrOfRetriesCount = retriesDone 40 | retriesDone <= retries 41 | } else { 42 | maxNrOfRetriesCount = 1 43 | restartTimeWindowStartNanos = now 44 | true 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/pattern/PipeTo.scala: -------------------------------------------------------------------------------- 1 | package akka.pattern 2 | 3 | import language.implicitConversions 4 | 5 | import scala.concurrent.{ Future, ExecutionContext } 6 | import scala.util.{ Failure, Success } 7 | 8 | import akka.actor._ 9 | 10 | trait PipeToSupport { 11 | 12 | final class PipeableFuture[T](val future: Future[T])(implicit executionContext: ExecutionContext) { 13 | def pipeTo(recipient: ActorRef)(implicit sender: ActorRef = Actor.noSender): Future[T] = { 14 | future onComplete { 15 | case Success(r) => recipient ! r 16 | case Failure(f) => recipient ! Status.Failure(f) 17 | } 18 | future 19 | } 20 | /*def pipeToSelection(recipient: ActorSelection)(implicit sender: ActorRef = Actor.noSender): Future[T] = { 21 | future onComplete { 22 | case Success(r) => recipient ! r 23 | case Failure(f) => recipient ! Status.Failure(f) 24 | } 25 | future 26 | }*/ 27 | def to(recipient: ActorRef): PipeableFuture[T] = to(recipient, Actor.noSender) 28 | def to(recipient: ActorRef, sender: ActorRef): PipeableFuture[T] = { 29 | pipeTo(recipient)(sender) 30 | this 31 | } 32 | /*def to(recipient: ActorSelection): PipeableFuture[T] = to(recipient, Actor.noSender) 33 | def to(recipient: ActorSelection, sender: ActorRef): PipeableFuture[T] = { 34 | pipeToSelection(recipient)(sender) 35 | this 36 | }*/ 37 | } 38 | 39 | /** 40 | * Import this implicit conversion to gain the `pipeTo` method on [[scala.concurrent.Future]]: 41 | * 42 | * {{{ 43 | * import akka.pattern.pipe 44 | * 45 | * Future { doExpensiveCalc() } pipeTo nextActor 46 | * 47 | * or 48 | * 49 | * pipe(someFuture) to nextActor 50 | * 51 | * }}} 52 | * 53 | * The successful result of the future is sent as a message to the recipient, or 54 | * the failure is sent in a [[akka.actor.Status.Failure]] to the recipient. 55 | */ 56 | implicit def pipe[T](future: Future[T])(implicit executionContext: ExecutionContext): PipeableFuture[T] = new PipeableFuture(future) 57 | } 58 | 59 | object PipeTo extends PipeToSupport 60 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/dispatch/sysmsg/SystemMessage.scala: -------------------------------------------------------------------------------- 1 | package akka.dispatch 2 | package sysmsg 3 | 4 | import akka.actor._ 5 | 6 | private[akka] sealed trait SystemMessage extends PossiblyHarmful 7 | 8 | private[akka] sealed trait StashWhenWaitingForChildren 9 | 10 | private[akka] sealed trait StashWhenFailed 11 | 12 | // sent to self from Dispatcher.register 13 | private[akka] final case class Create( 14 | failure: Option[ActorInitializationException]) 15 | extends SystemMessage 16 | 17 | // sent to self from ActorCell.restart 18 | private[akka] final case class Recreate(cause: Throwable) 19 | extends SystemMessage with StashWhenWaitingForChildren 20 | 21 | // sent to self from ActorCell.suspend 22 | private[akka] final case class Suspend() 23 | extends SystemMessage with StashWhenWaitingForChildren 24 | 25 | // sent to self from ActorCell.resume 26 | private[akka] final case class Resume( 27 | causedByFailure: Throwable) extends 28 | SystemMessage with StashWhenWaitingForChildren 29 | 30 | // sent to self from ActorCell.stop 31 | private[akka] final case class Terminate() 32 | extends SystemMessage 33 | 34 | // sent to supervisor ActorRef from ActorCell.start 35 | private[akka] final case class Supervise( 36 | child: ActorRef, async: Boolean) 37 | extends SystemMessage 38 | 39 | // sent to establish a DeathWatch 40 | private[akka] final case class Watch( 41 | watchee: InternalActorRef, watcher: InternalActorRef) 42 | extends SystemMessage 43 | 44 | // sent to tear down a DeathWatch 45 | private[akka] final case class Unwatch( 46 | watchee: ActorRef, watcher: ActorRef) 47 | extends SystemMessage 48 | 49 | // switched into the mailbox to signal termination 50 | private[akka] case object NoMessage 51 | extends SystemMessage 52 | 53 | private[akka] final case class Failed( 54 | child: ActorRef, cause: Throwable, uid: Int) 55 | extends SystemMessage with StashWhenFailed with StashWhenWaitingForChildren 56 | 57 | private[akka] final case class DeathWatchNotification( 58 | actor: ActorRef, 59 | existenceConfirmed: Boolean, 60 | addressTerminated: Boolean) extends SystemMessage 61 | -------------------------------------------------------------------------------- /examples/chat-full-stack/app/views/index.scala.html: -------------------------------------------------------------------------------- 1 | @(devMode: Boolean) 2 | 3 | @main("Example chat application with Akka/JS") { 4 | 5 |
6 |

Actochat

7 |

An actor-base chat application with support for multiple rooms and 8 | private chats (1 to 1).

9 |
10 |
11 | 12 | 13 |
14 |
15 | 16 | 17 |
18 | 19 |
20 |
21 | 22 |
23 |
24 |
25 |
26 | 27 |
28 |
29 | 31 |
32 |
33 | 34 |
35 |
36 |
37 | 38 |
39 |
40 |
41 |
42 | 43 |
44 |
45 | 46 |
47 |
48 |
49 | 50 | @if(devMode) { 51 | 52 | } else { 53 | 54 | } 55 | 58 | 59 | } 60 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/actor/VirtualPathContainer.scala: -------------------------------------------------------------------------------- 1 | package akka.actor 2 | 3 | import akka.util.JSMap 4 | 5 | /** 6 | * Internal implementation detail used for paths like “/temp” 7 | * 8 | * INTERNAL API 9 | */ 10 | private[akka] class VirtualPathContainer( 11 | override val provider: ActorRefProvider, 12 | override val path: ActorPath, 13 | override val getParent: InternalActorRef/*, 14 | val log: LoggingAdapter*/) extends MinimalActorRef { 15 | 16 | private val children = JSMap.empty[InternalActorRef] 17 | 18 | def addChild(name: String, ref: InternalActorRef): Unit = { 19 | children.put(name, ref) match { 20 | case null ⇒ // okay 21 | case old ⇒ 22 | // this can happen from RemoteSystemDaemon if a new child is created 23 | // before the old is removed from RemoteSystemDaemon children 24 | //log.debug("{} replacing child {} ({} -> {})", path, name, old, ref) 25 | } 26 | } 27 | 28 | def removeChild(name: String): Unit = 29 | if (children.remove(name) eq null) 30 | () //log.warning("{} trying to remove non-child {}", path, name) 31 | 32 | /** 33 | * Remove a named child if it matches the ref. 34 | */ 35 | protected def removeChild(name: String, ref: ActorRef): Unit = { 36 | val current = getChild(name) 37 | if (current eq null) 38 | ()//log.warning("{} trying to remove non-child {}", path, name) 39 | else if (current == ref) 40 | children.remove(name) 41 | } 42 | 43 | def getChild(name: String): InternalActorRef = children(name) 44 | 45 | override def getChild(name: Iterator[String]): InternalActorRef = { 46 | if (name.isEmpty) this 47 | else { 48 | val n = name.next() 49 | if (n.isEmpty) this 50 | else children.get(n) match { 51 | case None => Nobody 52 | case Some(some) => 53 | if (name.isEmpty) some 54 | else some.getChild(name) 55 | } 56 | } 57 | } 58 | 59 | def hasChildren: Boolean = !children.isEmpty 60 | 61 | def foreachChild(f: ActorRef => Unit): Unit = { 62 | val iter = children.values.iterator 63 | while (iter.hasNext) f(iter.next) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/actor/LocalActorRef.scala: -------------------------------------------------------------------------------- 1 | package akka.actor 2 | 3 | import scala.scalajs.js 4 | 5 | import akka.dispatch._ 6 | import akka.dispatch.sysmsg.SystemMessage 7 | 8 | private[akka] class LocalActorRef( 9 | system: ActorSystem, 10 | val path: ActorPath, 11 | _parent: ActorRef, 12 | _props: Props, 13 | _dispatcher: MessageDispatcher) extends InternalActorRef { 14 | 15 | assert(path.uid != ActorCell.undefinedUid || path.isInstanceOf[RootActorPath], 16 | s"Trying to create a LocalActorRef with uid-less path $path") 17 | 18 | val actorCell: ActorCell = 19 | new ActorCell(system, _props, _dispatcher, this, _parent) 20 | actorCell.init(sendSupervise = _parent ne null) 21 | 22 | private[akka] def underlying = actorCell 23 | 24 | def !(msg: Any)(implicit sender: ActorRef): Unit = 25 | actorCell.sendMessage(Envelope(msg, sender, system)) 26 | 27 | // InternalActorRef API 28 | 29 | def start(): Unit = actorCell.start() 30 | def resume(causedByFailure: Throwable): Unit = actorCell.resume(causedByFailure) 31 | def suspend(): Unit = actorCell.suspend() 32 | def restart(cause: Throwable): Unit = actorCell.restart(cause) 33 | def stop(): Unit = actorCell.stop() 34 | def sendSystemMessage(message: SystemMessage): Unit = actorCell.sendSystemMessage(message) 35 | 36 | def getParent: InternalActorRef = _parent 37 | 38 | override def provider: ActorRefProvider = actorCell.provider 39 | 40 | /** 41 | * Method for looking up a single child beneath this actor. Override in order 42 | * to inject “synthetic” actor paths like “/temp”. 43 | * It is racy if called from the outside. 44 | */ 45 | def getSingleChild(name: String): InternalActorRef = { 46 | val (childName, uid) = ActorCell.splitNameAndUid(name) 47 | actorCell.childStatsByName(childName) match { 48 | case Some(crs: ChildRestartStats) if uid == ActorCell.undefinedUid || uid == crs.uid => 49 | crs.child.asInstanceOf[InternalActorRef] 50 | case _ => Nobody 51 | } 52 | } 53 | 54 | def getChild(name: Iterator[String]): InternalActorRef = 55 | Actor.noSender 56 | 57 | def isLocal: Boolean = true 58 | } 59 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/scalajs/wsclient/ClientProxy.scala: -------------------------------------------------------------------------------- 1 | package akka.scalajs.wsclient 2 | 3 | import akka.actor._ 4 | import akka.scalajs.wscommon._ 5 | 6 | import scala.scalajs.js 7 | import akka.scalajs.jsapi._ 8 | 9 | import org.scalajs.spickling._ 10 | import org.scalajs.spickling.jsany._ 11 | 12 | object ClientProxy { 13 | case object ConnectionError 14 | } 15 | 16 | case class WebSocketConnected(entryPointRef: ActorRef) 17 | 18 | class ClientProxy(wsUrl: String, connectedHandler: ActorRef) extends AbstractProxy { 19 | /** Will send the WebSocketConnected message to parent actor. */ 20 | def this(wsUrl: String) = this(wsUrl, null) 21 | 22 | import AbstractProxy._ 23 | import ClientProxy._ 24 | 25 | type PickleType = js.Any 26 | implicit protected def pickleBuilder: PBuilder[PickleType] = JSPBuilder 27 | implicit protected def pickleReader: PReader[PickleType] = JSPReader 28 | 29 | var webSocket: WebSocket = _ 30 | 31 | override def preStart() = { 32 | super.preStart() 33 | 34 | webSocket = new WebSocket(wsUrl) 35 | webSocket.addEventListener("message", { (event: Event) => 36 | self ! IncomingMessage(js.JSON.parse( 37 | event.asInstanceOf[MessageEvent].data.toString())) 38 | }, useCapture = false) 39 | webSocket.addEventListener("close", { (event: Event) => 40 | self ! ConnectionClosed 41 | }, useCapture = false) 42 | webSocket.addEventListener("error", { (event: Event) => 43 | self ! ConnectionError 44 | }, useCapture = false) 45 | } 46 | 47 | override def postStop() = { 48 | super.postStop() 49 | webSocket.close() 50 | } 51 | 52 | override def receive = super.receive.orElse[Any, Unit] { 53 | case ConnectionError => 54 | throw new akka.AkkaException("WebSocket connection error") 55 | } 56 | 57 | override def receiveFromPeer = super.receiveFromPeer.orElse[Any, Unit] { 58 | case Welcome(entryPointRef) => 59 | val msg = WebSocketConnected(entryPointRef) 60 | if (connectedHandler eq null) context.parent ! msg 61 | else connectedHandler ! msg 62 | } 63 | 64 | override protected def sendPickleToPeer(pickle: PickleType): Unit = { 65 | webSocket.send(js.JSON.stringify(pickle)) 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/dispatch/MessageQueue.scala: -------------------------------------------------------------------------------- 1 | package akka.dispatch 2 | 3 | import scala.annotation.tailrec 4 | 5 | import akka.actor._ 6 | import akka.util.JSQueue 7 | 8 | /** 9 | * A MessageQueue is one of the core components in forming a Mailbox. 10 | * The MessageQueue is where the normal messages that are sent to Actors will 11 | * be enqueued (and subsequently dequeued). 12 | */ 13 | trait MessageQueue { 14 | /** 15 | * Try to enqueue the message to this queue, or throw an exception. 16 | */ 17 | def enqueue(receiver: ActorRef, handle: Envelope): Unit // NOTE: receiver is used only in two places, but cannot be removed 18 | 19 | /** 20 | * Try to dequeue the next message from this queue, return null failing that. 21 | */ 22 | def dequeue(): Envelope 23 | 24 | /** 25 | * Should return the current number of messages held in this queue; may 26 | * always return 0 if no other value is available efficiently. Do not use 27 | * this for testing for presence of messages, use `hasMessages` instead. 28 | */ 29 | def numberOfMessages: Int 30 | 31 | /** 32 | * Indicates whether this queue is non-empty. 33 | */ 34 | def hasMessages: Boolean 35 | 36 | /** 37 | * Called when the mailbox this queue belongs to is disposed of. Normally it 38 | * is expected to transfer all remaining messages into the dead letter queue 39 | * which is passed in. The owner of this MessageQueue is passed in if 40 | * available (e.g. for creating DeadLetters()), “/deadletters” otherwise. 41 | */ 42 | def cleanUp(owner: ActorRef, deadLetters: MessageQueue): Unit 43 | } 44 | 45 | class NodeMessageQueue extends JSQueue[Envelope] with MessageQueue { 46 | 47 | final def enqueue(receiver: ActorRef, handle: Envelope): Unit = 48 | enqueue(handle) 49 | 50 | override def dequeue(): Envelope = 51 | if (isEmpty) null else super.dequeue() 52 | 53 | final def numberOfMessages: Int = size 54 | 55 | final def hasMessages: Boolean = nonEmpty 56 | 57 | @tailrec final def cleanUp(owner: ActorRef, deadLetters: MessageQueue): Unit = { 58 | val envelope = dequeue() 59 | if (envelope ne null) { 60 | deadLetters.enqueue(owner, envelope) 61 | cleanUp(owner, deadLetters) 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Actor system for Scala.js -- obsolete 2 | 3 | **This project is obsolete. It has been superseded by [Akka.js](https://github.com/akka-js/akka.js).** 4 | 5 | This project aims at providing an actor system for Scala.js, featuring: 6 | 7 | * Supervision 8 | * (Almost) transparent collaboration with actors in an Akka-based backend 9 | * Transparent collaboration with actors in several Web Workers 10 | 11 | It is currently a prototype, working well but still in a rough shape. Hence, 12 | it is not published anywhere yet. 13 | 14 | ## Build it and try the examples 15 | 16 | To build the libraries, use 17 | 18 | > package 19 | 20 | in an sbt console. 21 | 22 | ### Fault-tolerance example 23 | 24 | The fault-tolerance example is taken from Akka's documentation and showcases 25 | supervision, death watch notifications and the like. 26 | 27 | Build it with: 28 | 29 | > faultToleranceExample/fastOptJS 30 | 31 | then open `examples/faulttolerance/index-fastopt.html` in your browser and look 32 | at the Web console for the output. 33 | 34 | ### Web Workers example 35 | 36 | The Web Workers example demonstrates the communication between Web Workers. 37 | 38 | Build it with: 39 | 40 | > webworkersExample/fastOptJS 41 | 42 | then open `examples/webworkers/index-fastopt.html` in your browser and look at 43 | the Webconsole for the output. 44 | 45 | ### Chat example (with client-server communication) 46 | 47 | The Chat example is a full-stack Play/Akka/Scala.js application where client 48 | and server communicate transparently between Akka/JVM and "Akka/JS". 49 | 50 | Build the client then run the server with: 51 | 52 | > chatExampleScalaJS/fullOptJS 53 | > project chatExample 54 | [scalajs-actors-examples-chat] $ run 55 | 56 | then navigate to [http://localhost:9000/opt](http://localhost:9000/opt) with 57 | your browser. To have some fun, open multiple tabs (or multiple browsers) and 58 | start playing with the chat. 59 | 60 | ## Design documentation 61 | 62 | The best source of documentation for the design at large is 63 | [this report](http://lampwww.epfl.ch/~doeraene/scalajs-actors-design.pdf). 64 | 65 | ## License 66 | 67 | Scala.js actors is distributed under the 68 | [Scala License](http://www.scala-lang.org/license.html). 69 | -------------------------------------------------------------------------------- /examples/chat-full-stack/public/stylesheets/chatbox.css: -------------------------------------------------------------------------------- 1 | .conversation-wrap 2 | { 3 | box-shadow: -2px 0 3px #ddd; 4 | padding:0; 5 | max-height: 400px; 6 | overflow: auto; 7 | } 8 | .conversation 9 | { 10 | padding:5px; 11 | border-bottom:1px solid #ddd; 12 | margin:0; 13 | 14 | } 15 | 16 | .message-wrap 17 | { 18 | box-shadow: 0 0 3px #ddd; 19 | padding:0; 20 | 21 | } 22 | .msg 23 | { 24 | padding:5px; 25 | /*border-bottom:1px solid #ddd;*/ 26 | margin:0; 27 | } 28 | .msg-wrap 29 | { 30 | padding:10px; 31 | max-height: 400px; 32 | overflow: auto; 33 | 34 | } 35 | 36 | .time 37 | { 38 | color:#bfbfbf; 39 | } 40 | 41 | .send-wrap 42 | { 43 | border-top: 1px solid #eee; 44 | border-bottom: 1px solid #eee; 45 | padding:10px; 46 | /*background: #f8f8f8;*/ 47 | } 48 | 49 | .send-message 50 | { 51 | resize: none; 52 | } 53 | 54 | .highlight 55 | { 56 | background-color: #f7f7f9; 57 | border: 1px solid #e1e1e8; 58 | } 59 | 60 | .send-message-btn 61 | { 62 | border-top-left-radius: 0; 63 | border-top-right-radius: 0; 64 | border-bottom-left-radius: 0; 65 | 66 | border-bottom-right-radius: 0; 67 | } 68 | .btn-panel 69 | { 70 | background: #f7f7f9; 71 | } 72 | 73 | .btn-panel .btn 74 | { 75 | color:#b8b8b8; 76 | 77 | transition: 0.2s all ease-in-out; 78 | } 79 | 80 | .btn-panel .btn:hover 81 | { 82 | color:#666; 83 | background: #f8f8f8; 84 | } 85 | .btn-panel .btn:active 86 | { 87 | background: #f8f8f8; 88 | box-shadow: 0 0 1px #ddd; 89 | } 90 | 91 | .btn-panel-conversation .btn,.btn-panel-msg .btn 92 | { 93 | 94 | background: #f8f8f8; 95 | } 96 | .btn-panel-conversation .btn:first-child 97 | { 98 | border-right: 1px solid #ddd; 99 | } 100 | 101 | .msg-wrap .media-heading 102 | { 103 | color:#003bb3; 104 | font-weight: 700; 105 | } 106 | 107 | 108 | .msg-date 109 | { 110 | background: none; 111 | text-align: center; 112 | color:#aaa; 113 | border:none; 114 | box-shadow: none; 115 | border-bottom: 1px solid #ddd; 116 | } 117 | 118 | 119 | body::-webkit-scrollbar { 120 | width: 12px; 121 | } 122 | 123 | 124 | /* Let's get this party started */ 125 | ::-webkit-scrollbar { 126 | width: 6px; 127 | } 128 | 129 | /* Track */ 130 | ::-webkit-scrollbar-track { 131 | -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); 132 | /* -webkit-border-radius: 10px; 133 | border-radius: 10px;*/ 134 | } 135 | 136 | /* Handle */ 137 | ::-webkit-scrollbar-thumb { 138 | /* -webkit-border-radius: 10px; 139 | border-radius: 10px;*/ 140 | background:#ddd; 141 | -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.5); 142 | } 143 | ::-webkit-scrollbar-thumb:window-inactive { 144 | background: #ddd; 145 | } 146 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/actor/dungeon/Dispatch.scala: -------------------------------------------------------------------------------- 1 | package akka.actor 2 | package dungeon 3 | 4 | import scala.annotation.tailrec 5 | 6 | import scala.util.control.NonFatal 7 | import scala.util.control.Exception.Catcher 8 | 9 | import akka.dispatch._ 10 | import akka.dispatch.sysmsg._ 11 | 12 | private[akka] trait Dispatch { this: ActorCell => 13 | 14 | private var _mailbox: Mailbox = _ 15 | 16 | @inline final def mailbox: Mailbox = _mailbox 17 | 18 | final def swapMailbox(newMailbox: Mailbox): Mailbox = { 19 | val oldMailbox = mailbox 20 | _mailbox = newMailbox 21 | oldMailbox 22 | } 23 | 24 | final def hasMessages: Boolean = mailbox.hasMessages 25 | 26 | final def numberOfMessages: Int = mailbox.numberOfMessages 27 | 28 | final def isTerminated: Boolean = mailbox.isClosed 29 | 30 | /** 31 | * Initialize this cell, i.e. set up mailboxes and supervision. The UID must be 32 | * reasonably different from the previous UID of a possible actor with the same path, 33 | * which can be achieved by using ThreadLocalRandom.current.nextInt(). 34 | */ 35 | final def init(sendSupervise: Boolean): this.type = { 36 | /* 37 | * Create the mailbox and enqueue the Create() message to ensure that 38 | * this is processed before anything else. 39 | */ 40 | val mbox = dispatcher.createMailbox(this) 41 | 42 | swapMailbox(mbox) 43 | mailbox.setActor(this) 44 | 45 | mailbox.systemEnqueue(self, Create(None)) 46 | 47 | if (sendSupervise) { 48 | (parent: InternalActorRef).sendSystemMessage(Supervise(self, async = false)) 49 | } 50 | this 51 | } 52 | 53 | /** 54 | * Start this cell, i.e. attach it to the dispatcher. 55 | */ 56 | def start(): this.type = { 57 | // This call is expected to start off the actor by scheduling its mailbox. 58 | dispatcher.attach(this) 59 | this 60 | } 61 | 62 | private def handleException: Catcher[Unit] = { 63 | case NonFatal(e) ⇒ 64 | // TODO publish to eventStream 65 | //system.eventStream.publish(Error(e, self.path.toString, clazz(actor), "swallowing exception during message send")) 66 | Console.err.println(s"swallowing exception during message send: $e") 67 | } 68 | 69 | final def suspend(): Unit = try dispatcher.systemDispatch(this, Suspend()) catch handleException 70 | 71 | final def resume(causedByFailure: Throwable): Unit = try dispatcher.systemDispatch(this, Resume(causedByFailure)) catch handleException 72 | 73 | final def restart(cause: Throwable): Unit = try dispatcher.systemDispatch(this, Recreate(cause)) catch handleException 74 | 75 | final def stop(): Unit = try dispatcher.systemDispatch(this, Terminate()) catch handleException 76 | 77 | def sendMessage(msg: Envelope): Unit = 78 | try { 79 | dispatcher.dispatch(this, msg) 80 | } catch handleException 81 | 82 | def sendSystemMessage(message: SystemMessage): Unit = 83 | try dispatcher.systemDispatch(this, message) catch handleException 84 | 85 | } 86 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/event/Logging.scala: -------------------------------------------------------------------------------- 1 | package akka.event 2 | 3 | import language.existentials 4 | 5 | object Logging { 6 | 7 | /** 8 | * Marker trait for annotating LogLevel, which must be Int after erasure. 9 | */ 10 | case class LogLevel(asInt: Int) extends AnyVal { 11 | @inline final def >=(other: LogLevel): Boolean = asInt >= other.asInt 12 | @inline final def <=(other: LogLevel): Boolean = asInt <= other.asInt 13 | @inline final def >(other: LogLevel): Boolean = asInt > other.asInt 14 | @inline final def <(other: LogLevel): Boolean = asInt < other.asInt 15 | } 16 | 17 | /** 18 | * Log level in numeric form, used when deciding whether a certain log 19 | * statement should generate a log event. Predefined levels are ErrorLevel (1) 20 | * to DebugLevel (4). In case you want to add more levels, loggers need to 21 | * be subscribed to their event bus channels manually. 22 | */ 23 | final val ErrorLevel = LogLevel(1) 24 | final val WarningLevel = LogLevel(2) 25 | final val InfoLevel = LogLevel(3) 26 | final val DebugLevel = LogLevel(4) 27 | 28 | /** 29 | * Base type of LogEvents 30 | */ 31 | sealed trait LogEvent { 32 | /** 33 | * When this LogEvent was created according to System.currentTimeMillis 34 | */ 35 | val timestamp: Long = System.currentTimeMillis 36 | 37 | /** 38 | * The LogLevel of this LogEvent 39 | */ 40 | def level: LogLevel 41 | 42 | /** 43 | * The source of this event 44 | */ 45 | def logSource: String 46 | 47 | /** 48 | * The class of the source of this event 49 | */ 50 | def logClass: Class[_] 51 | 52 | /** 53 | * The message, may be any object or null. 54 | */ 55 | def message: Any 56 | } 57 | 58 | /** 59 | * For ERROR Logging 60 | */ 61 | case class Error(cause: Throwable, logSource: String, logClass: Class[_], message: Any = "") extends LogEvent { 62 | def this(logSource: String, logClass: Class[_], message: Any) = this(Error.NoCause, logSource, logClass, message) 63 | 64 | override def level = ErrorLevel 65 | } 66 | 67 | object Error { 68 | def apply(logSource: String, logClass: Class[_], message: Any) = 69 | new Error(NoCause, logSource, logClass, message) 70 | 71 | /** Null Object used for errors without cause Throwable */ 72 | object NoCause extends Throwable 73 | } 74 | def noCause = Error.NoCause 75 | 76 | /** 77 | * For WARNING Logging 78 | */ 79 | case class Warning(logSource: String, logClass: Class[_], message: Any = "") extends LogEvent { 80 | override def level = WarningLevel 81 | } 82 | 83 | /** 84 | * For INFO Logging 85 | */ 86 | case class Info(logSource: String, logClass: Class[_], message: Any = "") extends LogEvent { 87 | override def level = InfoLevel 88 | } 89 | 90 | /** 91 | * For DEBUG Logging 92 | */ 93 | case class Debug(logSource: String, logClass: Class[_], message: Any = "") extends LogEvent { 94 | override def level = DebugLevel 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/scalajs/webworkers/WebWorkersActorSystem.scala: -------------------------------------------------------------------------------- 1 | package akka.scalajs.webworkers 2 | 3 | import scala.scalajs.js 4 | import org.scalajs.spickling._ 5 | import org.scalajs.spickling.jsany._ 6 | 7 | import akka.actor._ 8 | 9 | private[akka] object WebWorkersActorSystem { 10 | private var _registrationsDone = false 11 | 12 | case class SendMessage(msg: Any, receiver: ActorRef, sender: ActorRef) 13 | 14 | def registerPicklers(): Unit = { 15 | if (_registrationsDone) 16 | return 17 | _registrationsDone = true 18 | 19 | import PicklerRegistry.register 20 | import DedicatedPicklers._ 21 | 22 | register[Some[Any]] 23 | register(None) 24 | 25 | register[RootActorPath] 26 | register[ChildActorPath] 27 | register[Address] 28 | 29 | register[SendMessage] 30 | } 31 | } 32 | 33 | private[akka] trait WebWorkersActorSystem extends ActorSystem { this: ActorSystemImpl => 34 | import WebWorkersActorSystem._ 35 | 36 | registerPicklers() 37 | 38 | WebWorkerRouter.registerSystem(name, this) 39 | 40 | private[webworkers] def workerAddress = { 41 | Address(name, WebWorkerRouter.address) 42 | } 43 | 44 | private[webworkers] def globalizePath(path: ActorPath): ActorPath = { 45 | if (path.address.hasGlobalScope) path 46 | else RootActorPath(workerAddress) / path.elements 47 | } 48 | 49 | private[webworkers] def sendMessageAcrossWorkers(destWorker: Address, 50 | msg: Any, receiver: ActorRef, sender: ActorRef): Unit = { 51 | WebWorkerRouter.postMessageTo(destWorker, 52 | picklerRegistry.pickle(SendMessage(msg, receiver, sender))) 53 | } 54 | 55 | // Handling messages arriving to my webworker 56 | 57 | private[webworkers] def deliverMessageFromRouter(pickle: js.Dynamic): Unit = { 58 | picklerRegistry.unpickle[js.Any](pickle) match { 59 | case SendMessage(msg, receiver, sender) => 60 | receiver.tell(msg, sender) 61 | } 62 | } 63 | 64 | /** Pickler registry which knows how to deal with ActorRefs. */ 65 | object picklerRegistry extends PicklerRegistry { 66 | def pickle[P](value: Any)(implicit builder: PBuilder[P], 67 | registry: PicklerRegistry): P = { 68 | value match { 69 | case ref: ActorRef => 70 | val globalPath = globalizePath(ref.path) 71 | builder.makeObject(("ref", registry.pickle(globalPath))) 72 | 73 | case _ => 74 | PicklerRegistry.pickle(value) 75 | } 76 | } 77 | 78 | def unpickle[P](pickle: P)(implicit reader: PReader[P], 79 | registry: PicklerRegistry): Any = { 80 | pickle match { 81 | case null => null 82 | 83 | case _ if !reader.isUndefined(reader.readObjectField(pickle, "ref")) => 84 | val globalPath = registry.unpickle(reader.readObjectField( 85 | pickle, "ref")).asInstanceOf[ActorPath] 86 | if (globalPath.address == workerAddress) 87 | resolveLocalActorPath(globalPath).getOrElse(deadLetters) 88 | else 89 | new WorkerActorRef(WebWorkersActorSystem.this, globalPath) 90 | 91 | case _ => 92 | PicklerRegistry.unpickle(pickle) 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/actor/DeadLetterActorRef.scala: -------------------------------------------------------------------------------- 1 | package akka.actor 2 | 3 | import akka.event.EventStream 4 | import akka.dispatch.Mailbox 5 | import akka.dispatch.sysmsg._ 6 | 7 | /** 8 | * This special dead letter reference has a name: it is that which is returned 9 | * by a local look-up which is unsuccessful. 10 | * 11 | * INTERNAL API 12 | */ 13 | private[akka] class EmptyLocalActorRef( 14 | override val provider: ActorRefProvider, 15 | override val path: ActorPath, 16 | val eventStream: EventStream) extends MinimalActorRef { 17 | 18 | override def sendSystemMessage(message: SystemMessage): Unit = { 19 | //if (Mailbox.debug) println(s"ELAR $path having enqueued $message") 20 | specialHandle(message, provider.deadLetters) 21 | } 22 | 23 | override def !(message: Any)(implicit sender: ActorRef = Actor.noSender): Unit = message match { 24 | case null => throw new InvalidMessageException("Message is null") 25 | case d: DeadLetter => 26 | specialHandle(d.message, d.sender) // do NOT form endless loops, since deadLetters will resend! 27 | case _ if !specialHandle(message, sender) => 28 | //eventStream.publish(DeadLetter(message, if (sender eq Actor.noSender) provider.deadLetters else sender, this)) 29 | case _ => 30 | } 31 | 32 | protected def specialHandle(msg: Any, sender: ActorRef): Boolean = msg match { 33 | case w: Watch => 34 | if (w.watchee == this && w.watcher != this) 35 | w.watcher.sendSystemMessage( 36 | DeathWatchNotification(w.watchee, existenceConfirmed = false, addressTerminated = false)) 37 | true 38 | case _: Unwatch => true // Just ignore 39 | case Identify(messageId) => 40 | sender ! ActorIdentity(messageId, None) 41 | true 42 | /*case s: SelectChildName => 43 | s.identifyRequest match { 44 | case Some(identify) => sender ! ActorIdentity(identify.messageId, None) 45 | case None => 46 | //eventStream.publish(DeadLetter(s.wrappedMessage, if (sender eq Actor.noSender) provider.deadLetters else sender, this)) 47 | } 48 | true*/ 49 | case _ => false 50 | } 51 | } 52 | 53 | /** 54 | * Internal implementation of the dead letter destination: will publish any 55 | * received message to the eventStream, wrapped as [[akka.actor.DeadLetter]]. 56 | * 57 | * INTERNAL API 58 | */ 59 | private[akka] class DeadLetterActorRef(_provider: ActorRefProvider, 60 | _path: ActorPath, _eventStream: EventStream) 61 | extends EmptyLocalActorRef(_provider, _path, _eventStream) { 62 | 63 | override def !(message: Any)(implicit sender: ActorRef = this): Unit = message match { 64 | case null => throw new InvalidMessageException("Message is null") 65 | case Identify(messageId) => sender ! ActorIdentity(messageId, Some(this)) 66 | case d: DeadLetter => 67 | if (!specialHandle(d.message, d.sender)) 68 | ()//eventStream.publish(d) 69 | case _ => 70 | if (!specialHandle(message, sender)) 71 | ()//eventStream.publish(DeadLetter(message, if (sender eq Actor.noSender) provider.deadLetters else sender, this)) 72 | } 73 | 74 | override protected def specialHandle(msg: Any, sender: ActorRef): Boolean = msg match { 75 | case w: Watch => 76 | if (w.watchee != this && w.watcher != this) 77 | w.watcher.sendSystemMessage(DeathWatchNotification(w.watchee, 78 | existenceConfirmed = false, addressTerminated = false)) 79 | true 80 | case _ => super.specialHandle(msg, sender) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/actor/ActorRefFactory.scala: -------------------------------------------------------------------------------- 1 | package akka.actor 2 | 3 | import scala.annotation.implicitNotFound 4 | 5 | import scala.concurrent.ExecutionContext 6 | 7 | /** 8 | * Interface implemented by ActorSystem and ActorContext, the only two places 9 | * from which you can get fresh actors. 10 | */ 11 | @implicitNotFound("implicit ActorRefFactory required: if outside of an Actor you need an implicit ActorSystem, inside of an actor this should be the implicit ActorContext") 12 | trait ActorRefFactory { 13 | /** 14 | * INTERNAL API 15 | */ 16 | protected def systemImpl: ActorSystemImpl 17 | 18 | /** 19 | * INTERNAL API 20 | */ 21 | protected def provider: ActorRefProvider 22 | 23 | /** 24 | * Returns the default MessageDispatcher associated with this ActorRefFactory 25 | */ 26 | implicit def dispatcher: ExecutionContext 27 | 28 | /** 29 | * Father of all children created by this interface. 30 | * 31 | * INTERNAL API 32 | */ 33 | protected def guardian: InternalActorRef 34 | 35 | /** 36 | * INTERNAL API 37 | */ 38 | protected def lookupRoot: InternalActorRef 39 | 40 | /** 41 | * Create new actor as child of this context and give it an automatically 42 | * generated name (currently similar to base64-encoded integer count, 43 | * reversed and with “$” prepended, may change in the future). 44 | * 45 | * See [[akka.actor.Props]] for details on how to obtain a `Props` object. 46 | * 47 | * @throws akka.ConfigurationException if deployment, dispatcher 48 | * or mailbox configuration is wrong 49 | */ 50 | def actorOf(props: Props): ActorRef 51 | 52 | /** 53 | * Create new actor as child of this context with the given name, which must 54 | * not be null, empty or start with “$”. If the given name is already in use, 55 | * and `InvalidActorNameException` is thrown. 56 | * 57 | * See [[akka.actor.Props]] for details on how to obtain a `Props` object. 58 | * @throws akka.actor.InvalidActorNameException if the given name is 59 | * invalid or already in use 60 | * @throws akka.ConfigurationException if deployment, dispatcher 61 | * or mailbox configuration is wrong 62 | */ 63 | def actorOf(props: Props, name: String): ActorRef 64 | 65 | /** 66 | * Construct an [[akka.actor.ActorSelection]] from the given path, which is 67 | * parsed for wildcards (these are replaced by regular expressions 68 | * internally). No attempt is made to verify the existence of any part of 69 | * the supplied path, it is recommended to send a message and gather the 70 | * replies in order to resolve the matching set of actors. 71 | */ 72 | /*def actorSelection(path: String): ActorSelection = path match { 73 | case RelativeActorPath(elems) => 74 | if (elems.isEmpty) ActorSelection(provider.deadLetters, "") 75 | else if (elems.head.isEmpty) ActorSelection(provider.rootGuardian, elems.tail) 76 | else ActorSelection(lookupRoot, elems) 77 | case ActorPathExtractor(address, elems) => 78 | ActorSelection(provider.rootGuardianAt(address), elems) 79 | case _ => 80 | ActorSelection(provider.deadLetters, "") 81 | }*/ 82 | 83 | /** 84 | * Construct an [[akka.actor.ActorSelection]] from the given path, which is 85 | * parsed for wildcards (these are replaced by regular expressions 86 | * internally). No attempt is made to verify the existence of any part of 87 | * the supplied path, it is recommended to send a message and gather the 88 | * replies in order to resolve the matching set of actors. 89 | */ 90 | /*def actorSelection(path: ActorPath): ActorSelection = 91 | ActorSelection(provider.rootGuardianAt(path.address), path.elements)*/ 92 | 93 | /** 94 | * Stop the actor pointed to by the given [[akka.actor.ActorRef]]; this is 95 | * an asynchronous operation, i.e. involves a message send. 96 | */ 97 | def stop(actor: ActorRef): Unit 98 | } 99 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/actor/ActorContext.scala: -------------------------------------------------------------------------------- 1 | package akka.actor 2 | 3 | import scala.collection.immutable 4 | import scala.concurrent.ExecutionContext 5 | import scala.concurrent.duration.Duration 6 | 7 | trait ActorContext extends ActorRefFactory { 8 | /** 9 | * Reference to this actor. 10 | */ 11 | def self: ActorRef 12 | 13 | /** 14 | * Retrieve the Props which were used to create this actor. 15 | */ 16 | def props: Props 17 | 18 | /** 19 | * Gets the current receive timeout. 20 | * When specified, the receive method should be able to handle a [[akka.actor.ReceiveTimeout]] message. 21 | */ 22 | def receiveTimeout: Duration 23 | 24 | /** 25 | * Defines the inactivity timeout after which the sending of a [[akka.actor.ReceiveTimeout]] message is triggered. 26 | * When specified, the receive function should be able to handle a [[akka.actor.ReceiveTimeout]] message. 27 | * 1 millisecond is the minimum supported timeout. 28 | * 29 | * Please note that the receive timeout might fire and enqueue the `ReceiveTimeout` message right after 30 | * another message was enqueued; hence it is '''not guaranteed''' that upon reception of the receive 31 | * timeout there must have been an idle period beforehand as configured via this method. 32 | * 33 | * Once set, the receive timeout stays in effect (i.e. continues firing repeatedly after inactivity 34 | * periods). Pass in `Duration.Undefined` to switch off this feature. 35 | */ 36 | def setReceiveTimeout(timeout: Duration): Unit 37 | 38 | /** 39 | * Changes the Actor's behavior to become the new 'Receive' (PartialFunction[Any, Unit]) handler. 40 | * This method acts upon the behavior stack as follows: 41 | * 42 | * - if `discardOld = true` it will replace the top element (i.e. the current behavior) 43 | * - if `discardOld = false` it will keep the current behavior and push the given one atop 44 | * 45 | * The default of replacing the current behavior has been chosen to avoid memory leaks in 46 | * case client code is written without consulting this documentation first (i.e. always pushing 47 | * new closures and never issuing an `unbecome()`) 48 | */ 49 | def become(behavior: Actor.Receive, discardOld: Boolean = true): Unit 50 | 51 | /** 52 | * Reverts the Actor behavior to the previous one in the hotswap stack. 53 | */ 54 | def unbecome(): Unit 55 | 56 | /** 57 | * Returns the sender 'ActorRef' of the current message. 58 | */ 59 | def sender: ActorRef 60 | 61 | /** 62 | * Returns all supervised children; this method returns a view (i.e. a lazy 63 | * collection) onto the internal collection of children. Targeted lookups 64 | * should be using `child` instead for performance reasons: 65 | * 66 | * {{{ 67 | * val badLookup = context.children find (_.path.name == "kid") 68 | * // should better be expressed as: 69 | * val goodLookup = context.child("kid") 70 | * }}} 71 | */ 72 | def children: immutable.Iterable[ActorRef] 73 | 74 | /** 75 | * Get the child with the given name if it exists. 76 | */ 77 | def child(name: String): Option[ActorRef] 78 | 79 | /** 80 | * Returns the dispatcher (MessageDispatcher) that is used for this Actor. 81 | * Importing this member will place an implicit ExecutionContext in scope. 82 | */ 83 | implicit def dispatcher: ExecutionContext 84 | 85 | /** 86 | * The system that the actor belongs to. 87 | * Importing this member will place an implicit ExecutionContext in scope. 88 | */ 89 | implicit def system: ActorSystem 90 | 91 | /** 92 | * Returns the supervising parent ActorRef. 93 | */ 94 | def parent: ActorRef 95 | 96 | /** 97 | * Registers this actor as a Monitor for the provided ActorRef. 98 | * This actor will receive a Terminated(subject) message when watched 99 | * actor is terminated. 100 | * @return the provided ActorRef 101 | */ 102 | def watch(subject: ActorRef): ActorRef 103 | 104 | /** 105 | * Unregisters this actor as Monitor for the provided ActorRef. 106 | * @return the provided ActorRef 107 | */ 108 | def unwatch(subject: ActorRef): ActorRef 109 | } 110 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/actor/StdMessages.scala: -------------------------------------------------------------------------------- 1 | package akka.actor 2 | 3 | /** 4 | * INTERNAL API 5 | * 6 | * Marker trait to show which Messages are automatically handled by Akka 7 | */ 8 | private[akka] trait AutoReceivedMessage 9 | 10 | /** 11 | * Marker trait to indicate that a message might be potentially harmful, 12 | * this is used to block messages coming in over remoting. 13 | */ 14 | trait PossiblyHarmful 15 | 16 | /** 17 | * A message all Actors will understand, that when processed will terminate 18 | * the Actor permanently. 19 | */ 20 | case object PoisonPill extends AutoReceivedMessage with PossiblyHarmful 21 | 22 | /** 23 | * A message all Actors will understand, that when processed will make the 24 | * Actor throw an ActorKilledException, which will trigger supervision. 25 | */ 26 | case object Kill extends AutoReceivedMessage with PossiblyHarmful 27 | 28 | /** 29 | * Internal Akka use only, used in implementation of system.stop(child). 30 | */ 31 | private[akka] case class StopChild(child: ActorRef) 32 | 33 | /** 34 | * A message all Actors will understand, that when processed will reply with 35 | * [[org.scalajs.actors.ActorIdentity]] containing the `ActorRef`. The 36 | * `messageId` is returned in the `ActorIdentity` message as `correlationId`. 37 | */ 38 | final case class Identify(messageId: Any) extends AutoReceivedMessage 39 | 40 | /** 41 | * Reply to [[org.scalajs.actors.Identify]]. Contains 42 | * `Some(ref)` with the `ActorRef` of the actor replying to the request or 43 | * `None` if no actor matched the request. 44 | * The `correlationId` is taken from the `messageId` in 45 | * the `Identify` message. 46 | */ 47 | final case class ActorIdentity(correlationId: Any, ref: Option[ActorRef]) 48 | 49 | /** 50 | * When Death Watch is used, the watcher will receive a Terminated(watched) 51 | * message when watched is terminated. 52 | * Terminated message can't be forwarded to another actor, since that actor 53 | * might not be watching the subject. Instead, if you need to forward Terminated 54 | * to another actor you should send the information in your own message. 55 | * 56 | * @param actor the watched actor that terminated 57 | * @param existenceConfirmed is false when the Terminated message was not sent 58 | * directly from the watched actor, but derived from another source, such as 59 | * when watching a non-local ActorRef, which might not have been resolved 60 | * @param addressTerminated the Terminated message was derived from 61 | * that the remote node hosting the watched actor was detected as unreachable 62 | */ 63 | final case class Terminated private[akka] (actor: ActorRef)( 64 | val existenceConfirmed: Boolean, 65 | val addressTerminated: Boolean) extends AutoReceivedMessage with PossiblyHarmful 66 | 67 | /** 68 | * When using ActorContext.setReceiveTimeout, the singleton instance of 69 | * ReceiveTimeout will be sent to the Actor when there hasn't been any message 70 | * for that long. 71 | */ 72 | case object ReceiveTimeout extends PossiblyHarmful 73 | 74 | /** 75 | * This message is published to the EventStream whenever an Actor receives a 76 | * message it doesn't understand. 77 | */ 78 | final case class UnhandledMessage(message: Any, sender: ActorRef, 79 | recipient: ActorRef) 80 | 81 | /** 82 | * Classes for passing status back to the sender. 83 | * Used for internal ACKing protocol. But exposed as utility class for 84 | * user-specific ACKing protocols as well. 85 | */ 86 | object Status { 87 | sealed trait Status extends Serializable 88 | 89 | /** 90 | * This class/message type is preferably used to indicate success of some 91 | * operation performed. 92 | */ 93 | case class Success(status: AnyRef) extends Status 94 | 95 | /** 96 | * This class/message type is preferably used to indicate failure of some 97 | * operation performed. 98 | * As an example, it is used to signal failure with AskSupport is used (ask/?). 99 | */ 100 | case class Failure(cause: Throwable) extends Status 101 | } 102 | 103 | /** 104 | * When a message is sent to an Actor that is terminated before receiving the 105 | * message, it will be sent as a DeadLetter to the ActorSystem's EventStream 106 | */ 107 | final case class DeadLetter(message: Any, sender: ActorRef, 108 | recipient: ActorRef) { 109 | require(sender ne null, "DeadLetter sender may not be null") 110 | require(recipient ne null, "DeadLetter recipient may not be null") 111 | } 112 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/actor/Scheduler.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2009-2013 Typesafe Inc. 3 | */ 4 | 5 | package akka.actor 6 | 7 | import scala.scalajs.js 8 | 9 | import scala.concurrent.ExecutionContext 10 | import scala.concurrent.duration._ 11 | 12 | import akka.util._ 13 | 14 | /** 15 | * This exception is thrown by Scheduler.schedule* when scheduling is not 16 | * possible, e.g. after shutting down the Scheduler. 17 | */ 18 | private case class SchedulerException(msg: String) extends ActorsException(msg) 19 | 20 | /** 21 | * An Akka scheduler service. This one needs one special behavior: if 22 | * Closeable, it MUST execute all outstanding tasks upon .close() in order 23 | * to properly shutdown all dispatchers. 24 | * 25 | * Furthermore, this timer service MUST throw IllegalStateException if it 26 | * cannot schedule a task. Once scheduled, the task MUST be executed. If 27 | * executed upon close(), the task may execute before its timeout. 28 | * 29 | * Scheduler implementation are loaded reflectively at ActorSystem start-up 30 | * with the following constructor arguments: 31 | * 1) the system’s com.typesafe.config.Config (from system.settings.config) 32 | * 2) a akka.event.LoggingAdapter 33 | * 3) a java.util.concurrent.ThreadFactory 34 | */ 35 | trait Scheduler { 36 | /** 37 | * Schedules a message to be sent repeatedly with an initial delay and 38 | * frequency. E.g. if you would like a message to be sent immediately and 39 | * thereafter every 500ms you would set delay=Duration.Zero and 40 | * interval=Duration(500, TimeUnit.MILLISECONDS) 41 | * 42 | * Java & Scala API 43 | */ 44 | final def schedule( 45 | initialDelay: FiniteDuration, 46 | interval: FiniteDuration, 47 | receiver: ActorRef, 48 | message: Any)(implicit executor: ExecutionContext, 49 | sender: ActorRef = Actor.noSender): Cancellable = 50 | schedule(initialDelay, interval) { 51 | receiver ! message 52 | //if (receiver.isTerminated) 53 | // throw new SchedulerException("timer active for terminated actor") 54 | } 55 | 56 | /** 57 | * Schedules a function to be run repeatedly with an initial delay and a 58 | * frequency. E.g. if you would like the function to be run after 2 seconds 59 | * and thereafter every 100ms you would set delay = Duration(2, TimeUnit.SECONDS) 60 | * and interval = Duration(100, TimeUnit.MILLISECONDS) 61 | * 62 | * Scala API 63 | */ 64 | def schedule( 65 | initialDelay: FiniteDuration, 66 | interval: FiniteDuration)(f: => Unit)( 67 | implicit executor: ExecutionContext): Cancellable 68 | 69 | /** 70 | * Schedules a message to be sent once with a delay, i.e. a time period that has 71 | * to pass before the message is sent. 72 | * 73 | * Java & Scala API 74 | */ 75 | final def scheduleOnce(delay: FiniteDuration, receiver: ActorRef, 76 | message: Any)(implicit executor: ExecutionContext, 77 | sender: ActorRef = Actor.noSender): Cancellable = { 78 | scheduleOnce(delay) { 79 | receiver ! message 80 | } 81 | } 82 | 83 | /** 84 | * Schedules a function to be run once with a delay, i.e. a time period that has 85 | * to pass before the function is run. 86 | * 87 | * Scala API 88 | */ 89 | def scheduleOnce(delay: FiniteDuration)(f: => Unit)( 90 | implicit executor: ExecutionContext): Cancellable 91 | 92 | /** 93 | * Schedules a Runnable to be run once with a delay, i.e. a time period that 94 | * has to pass before the runnable is executed. 95 | * 96 | * Java & Scala API 97 | */ 98 | final def scheduleOnce(delay: FiniteDuration, runnable: Runnable)( 99 | implicit executor: ExecutionContext): Cancellable = { 100 | scheduleOnce(delay)(runnable.run()) 101 | } 102 | 103 | /** 104 | * The maximum supported task frequency of this scheduler, i.e. the inverse 105 | * of the minimum time interval between executions of a recurring task, in Hz. 106 | */ 107 | def maxFrequency: Double 108 | 109 | } 110 | 111 | class EventLoopScheduler extends Scheduler { 112 | 113 | def schedule( 114 | initialDelay: FiniteDuration, 115 | interval: FiniteDuration)(f: => Unit)( 116 | implicit executor: ExecutionContext): Cancellable = { 117 | JSTimeoutThenIntervalTask(initialDelay, interval)(f) 118 | } 119 | 120 | def scheduleOnce(delay: FiniteDuration)(f: => Unit)( 121 | implicit executor: ExecutionContext): Cancellable = { 122 | JSTimeoutTask(delay)(f) 123 | } 124 | 125 | def maxFrequency: Double = 1.0 / 0.0004 // as per HTML spec 126 | 127 | } 128 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/dispatch/MessageDispatcher.scala: -------------------------------------------------------------------------------- 1 | package akka.dispatch 2 | 3 | import scala.annotation.tailrec 4 | 5 | import scala.concurrent.duration.Duration 6 | import scala.concurrent.ExecutionContext 7 | 8 | import akka.actor._ 9 | import akka.dispatch.sysmsg._ 10 | import akka.scalajs.jsapi.Timers 11 | 12 | class MessageDispatcher( 13 | val mailboxes: Mailboxes) extends ExecutionContext { 14 | 15 | /** 16 | * Creates and returns a mailbox for the given actor. 17 | */ 18 | protected[akka] def createMailbox(actor: ActorCell): Mailbox = 19 | new Mailbox(new NodeMessageQueue) 20 | 21 | /** 22 | * Attaches the specified actor instance to this dispatcher, which includes 23 | * scheduling it to run for the first time (Create() is expected to have 24 | * been enqueued by the ActorCell upon mailbox creation). 25 | */ 26 | final def attach(actor: ActorCell): Unit = { 27 | register(actor) 28 | registerForExecution(actor.mailbox, false, true) 29 | } 30 | 31 | /** 32 | * Detaches the specified actor instance from this dispatcher 33 | */ 34 | final def detach(actor: ActorCell): Unit = { 35 | unregister(actor) 36 | /*try unregister(actor) 37 | finally ifSensibleToDoSoThenScheduleShutdown()*/ 38 | } 39 | 40 | /** 41 | * If you override it, you must call it. But only ever once. See "attach" 42 | * for the only invocation. 43 | * 44 | * INTERNAL API 45 | */ 46 | protected[akka] def register(actor: ActorCell) { 47 | //addInhabitants(+1) 48 | } 49 | 50 | /** 51 | * If you override it, you must call it. But only ever once. See "detach" 52 | * for the only invocation 53 | * 54 | * INTERNAL API 55 | */ 56 | protected[akka] def unregister(actor: ActorCell) { 57 | //addInhabitants(-1) 58 | val mailBox = actor.swapMailbox(mailboxes.deadLetterMailbox) 59 | mailBox.becomeClosed() 60 | mailBox.cleanUp() 61 | } 62 | 63 | /** 64 | * After the call to this method, the dispatcher mustn't begin any new message processing for the specified reference 65 | */ 66 | protected[akka] def suspend(actor: ActorCell): Unit = { 67 | val mbox = actor.mailbox 68 | if ((mbox.actor eq actor) && (mbox.dispatcher eq this)) 69 | mbox.suspend() 70 | } 71 | 72 | /* 73 | * After the call to this method, the dispatcher must begin any new message processing for the specified reference 74 | */ 75 | protected[akka] def resume(actor: ActorCell): Unit = { 76 | val mbox = actor.mailbox 77 | if ((mbox.actor eq actor) && (mbox.dispatcher eq this) && mbox.resume()) 78 | registerForExecution(mbox, false, false) 79 | } 80 | 81 | /** 82 | * Will be called when the dispatcher is to queue an invocation for execution 83 | * 84 | * INTERNAL API 85 | */ 86 | protected[akka] def systemDispatch(receiver: ActorCell, invocation: SystemMessage) = { 87 | val mbox = receiver.mailbox 88 | mbox.systemEnqueue(receiver.self, invocation) 89 | registerForExecution(mbox, false, true) 90 | } 91 | 92 | /** 93 | * Will be called when the dispatcher is to queue an invocation for execution 94 | * 95 | * INTERNAL API 96 | */ 97 | protected[akka] def dispatch(receiver: ActorCell, invocation: Envelope) = { 98 | val mbox = receiver.mailbox 99 | val s = receiver.self 100 | mbox.enqueue(s, invocation) 101 | registerForExecution(mbox, true, false) 102 | } 103 | 104 | /** 105 | * Suggest to register the provided mailbox for execution 106 | * 107 | * INTERNAL API 108 | */ 109 | protected[akka] def registerForExecution(mbox: Mailbox, 110 | hasMessageHint: Boolean, hasSystemMessageHint: Boolean): Boolean = { 111 | if (mbox.canBeScheduledForExecution(hasMessageHint, hasSystemMessageHint)) { 112 | if (mbox.setAsScheduled()) { 113 | execute(mbox) 114 | true 115 | } else false 116 | } else false 117 | } 118 | 119 | // TODO make these configurable 120 | /** 121 | * INTERNAL API 122 | */ 123 | protected[akka] def throughput: Int = 10 124 | 125 | /** 126 | * INTERNAL API 127 | */ 128 | protected[akka] def throughputDeadlineTime: Duration = 129 | Duration.fromNanos(1000) 130 | 131 | /** 132 | * INTERNAL API 133 | */ 134 | @inline protected[akka] final val isThroughputDeadlineTimeDefined = 135 | throughputDeadlineTime.toMillis > 0 136 | 137 | // ExecutionContext API 138 | 139 | override def execute(runnable: Runnable): Unit = { 140 | Timers.setImmediate { 141 | runnable.run() 142 | } 143 | } 144 | 145 | override def reportFailure(t: Throwable): Unit = { 146 | // TODO publish to even stream 147 | Console.err.println(s"dispatcher.reportFailure($t)") 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /examples/chat-full-stack/public/javascripts/md5-min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message 3 | * Digest Algorithm, as defined in RFC 1321. 4 | * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009 5 | * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet 6 | * Distributed under the BSD License 7 | * See http://pajhome.org.uk/crypt/md5 for more info. 8 | */ 9 | var hexcase=0;function hex_md5(a){return rstr2hex(rstr_md5(str2rstr_utf8(a)))}function hex_hmac_md5(a,b){return rstr2hex(rstr_hmac_md5(str2rstr_utf8(a),str2rstr_utf8(b)))}function md5_vm_test(){return hex_md5("abc").toLowerCase()=="900150983cd24fb0d6963f7d28e17f72"}function rstr_md5(a){return binl2rstr(binl_md5(rstr2binl(a),a.length*8))}function rstr_hmac_md5(c,f){var e=rstr2binl(c);if(e.length>16){e=binl_md5(e,c.length*8)}var a=Array(16),d=Array(16);for(var b=0;b<16;b++){a[b]=e[b]^909522486;d[b]=e[b]^1549556828}var g=binl_md5(a.concat(rstr2binl(f)),512+f.length*8);return binl2rstr(binl_md5(d.concat(g),512+128))}function rstr2hex(c){try{hexcase}catch(g){hexcase=0}var f=hexcase?"0123456789ABCDEF":"0123456789abcdef";var b="";var a;for(var d=0;d>>4)&15)+f.charAt(a&15)}return b}function str2rstr_utf8(c){var b="";var d=-1;var a,e;while(++d>>6)&31),128|(a&63))}else{if(a<=65535){b+=String.fromCharCode(224|((a>>>12)&15),128|((a>>>6)&63),128|(a&63))}else{if(a<=2097151){b+=String.fromCharCode(240|((a>>>18)&7),128|((a>>>12)&63),128|((a>>>6)&63),128|(a&63))}}}}}return b}function rstr2binl(b){var a=Array(b.length>>2);for(var c=0;c>5]|=(b.charCodeAt(c/8)&255)<<(c%32)}return a}function binl2rstr(b){var a="";for(var c=0;c>5]>>>(c%32))&255)}return a}function binl_md5(p,k){p[k>>5]|=128<<((k)%32);p[(((k+64)>>>9)<<4)+14]=k;var o=1732584193;var n=-271733879;var m=-1732584194;var l=271733878;for(var g=0;g>16)+(d>>16)+(c>>16);return(b<<16)|(c&65535)}function bit_rol(a,b){return(a<>>(32-b))}; -------------------------------------------------------------------------------- /actors/src/main/scala/akka/actor/Exceptions.scala: -------------------------------------------------------------------------------- 1 | package akka.actor 2 | 3 | import scala.annotation.tailrec 4 | 5 | /** Base class for actors exceptions. */ 6 | class ActorsException(message: String, 7 | cause: Throwable) extends Exception(message, cause) { 8 | def this(message: String) = this(message, null) 9 | } 10 | 11 | /** 12 | * IllegalActorStateException is thrown when a core invariant in the Actor 13 | * implementation has been violated. 14 | * For instance, if you try to create an Actor that doesn't extend Actor. 15 | */ 16 | final case class IllegalActorStateException private[akka] ( 17 | message: String) extends ActorsException(message) 18 | 19 | /** 20 | * ActorKilledException is thrown when an Actor receives the 21 | * [[org.scalajs.actors.Kill]] message 22 | */ 23 | final case class ActorKilledException private[akka] ( 24 | message: String) extends ActorsException(message) 25 | 26 | /** 27 | * An InvalidActorNameException is thrown when you try to convert something, 28 | * usually a String, to an Actor name which doesn't validate. 29 | */ 30 | final case class InvalidActorNameException( 31 | message: String) extends ActorsException(message) 32 | 33 | /** 34 | * An ActorInitializationException is thrown when the the initialization logic 35 | * for an Actor fails. 36 | * 37 | * There is an extractor which works for ActorInitializationException and its 38 | * subtypes: 39 | * 40 | * {{{ 41 | * ex match { 42 | * case ActorInitializationException(actor, message, cause) => ... 43 | * } 44 | * }}} 45 | */ 46 | class ActorInitializationException protected (actor: ActorRef, 47 | message: String, cause: Throwable) 48 | extends ActorsException(message, cause) { 49 | def getActor(): ActorRef = actor 50 | } 51 | 52 | object ActorInitializationException { 53 | private[akka] def apply(actor: ActorRef, message: String, cause: Throwable = null): ActorInitializationException = 54 | new ActorInitializationException(actor, message, cause) 55 | private[akka] def apply(message: String): ActorInitializationException = 56 | new ActorInitializationException(null, message, null) 57 | def unapply(ex: ActorInitializationException): Option[(ActorRef, String, Throwable)] = 58 | Some((ex.getActor, ex.getMessage, ex.getCause)) 59 | } 60 | 61 | /** 62 | * A PreRestartException is thrown when the preRestart() method failed; this 63 | * exception is not propagated to the supervisor, as it originates from the 64 | * already failed instance, hence it is only visible as log entry on the event 65 | * stream. 66 | * 67 | * @param actor is the actor whose preRestart() hook failed 68 | * @param cause is the exception thrown by that actor within preRestart() 69 | * @param originalCause is the exception which caused the restart in the first place 70 | * @param messageOption is the message which was optionally passed into preRestart() 71 | */ 72 | final case class PreRestartException private[akka] (actor: ActorRef, 73 | cause: Throwable, originalCause: Throwable, messageOption: Option[Any]) 74 | extends ActorInitializationException(actor, 75 | "exception in preRestart(" + 76 | (if (originalCause == null) "null" else originalCause.getClass) + ", " + 77 | (messageOption match { case Some(m: AnyRef) => m.getClass; case _ => "None" }) + 78 | ")", cause) 79 | 80 | /** 81 | * A PostRestartException is thrown when constructor or postRestart() method 82 | * fails during a restart attempt. 83 | * 84 | * @param actor is the actor whose constructor or postRestart() hook failed 85 | * @param cause is the exception thrown by that actor within preRestart() 86 | * @param originalCause is the exception which caused the restart in the first place 87 | */ 88 | final case class PostRestartException private[akka] (actor: ActorRef, 89 | cause: Throwable, originalCause: Throwable) 90 | extends ActorInitializationException(actor, 91 | "exception post restart (" + ( 92 | if (originalCause == null) "null" 93 | else originalCause.getClass) + ")", cause) 94 | 95 | /** 96 | * This is an extractor for retrieving the original cause (i.e. the first 97 | * failure) from a [[org.scalajs.actors.PostRestartException]]. In the face of 98 | * multiple “nested” restarts it will walk the origCause-links until it arrives 99 | * at a non-PostRestartException type. 100 | */ 101 | object OriginalRestartException { 102 | def unapply(ex: PostRestartException): Option[Throwable] = { 103 | @tailrec def rec(ex: PostRestartException): Option[Throwable] = ex match { 104 | case PostRestartException(_, _, e: PostRestartException) => rec(e) 105 | case PostRestartException(_, _, e) => Some(e) 106 | } 107 | rec(ex) 108 | } 109 | } 110 | 111 | /** 112 | * InvalidMessageException is thrown when an invalid message is sent to an 113 | * Actor. 114 | * Currently only `null` is an invalid message. 115 | */ 116 | final case class InvalidMessageException private[akka] ( 117 | message: String) extends ActorsException(message) 118 | 119 | /** 120 | * A DeathPactException is thrown by an Actor that receives a 121 | * Terminated(someActor) message that it doesn't handle itself, effectively 122 | * crashing the Actor and escalating to the supervisor. 123 | */ 124 | final case class DeathPactException private[akka] (dead: ActorRef) 125 | extends ActorsException("Monitored actor [" + dead + "] terminated") 126 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/actor/ActorSystem.scala: -------------------------------------------------------------------------------- 1 | package akka.actor 2 | 3 | import scala.concurrent.Future 4 | 5 | import akka.dispatch._ 6 | import akka.event._ 7 | 8 | object ActorSystem { 9 | 10 | /** 11 | * Creates a new ActorSystem with the name "default", 12 | * obtains the current ClassLoader by first inspecting the current threads' getContextClassLoader, 13 | * then tries to walk the stack to find the callers class loader, then falls back to the ClassLoader 14 | * associated with the ActorSystem class. 15 | * Then it loads the default reference configuration using the ClassLoader. 16 | */ 17 | def apply(): ActorSystem = apply("default") 18 | 19 | /** 20 | * Creates a new ActorSystem with the specified name, 21 | * obtains the current ClassLoader by first inspecting the current threads' getContextClassLoader, 22 | * then tries to walk the stack to find the callers class loader, then falls back to the ClassLoader 23 | * associated with the ActorSystem class. 24 | * Then it loads the default reference configuration using the ClassLoader. 25 | */ 26 | def apply(name: String): ActorSystem = { 27 | new ActorSystemImpl(new Settings(name)).start() 28 | } 29 | 30 | /** 31 | * Settings are the overall ActorSystem Settings which also provides a convenient access to the Config object. 32 | * 33 | * For more detailed information about the different possible configuration options, look in the Akka Documentation under "Configuration" 34 | * 35 | * @see The Typesafe Config Library API Documentation 36 | */ 37 | class Settings(final val name: String) { 38 | 39 | final val LogDeadLetters: Int = 0 40 | final val LogDeadLettersDuringShutdown: Boolean = false 41 | 42 | final val AddLoggingReceive: Boolean = true 43 | final val DebugAutoReceive: Boolean = false 44 | final val DebugLifecycle: Boolean = true 45 | final val DebugEventStream: Boolean = false 46 | final val DebugUnhandledMessage: Boolean = false 47 | 48 | override def toString: String = s"Settings($name)" 49 | 50 | } 51 | } 52 | 53 | abstract class ActorSystem(val settings: ActorSystem.Settings) extends ActorRefFactory { 54 | val name: String = settings.name 55 | 56 | def scheduler: Scheduler 57 | def eventStream: EventStream 58 | def provider: ActorRefProvider 59 | 60 | def deadLetters: ActorRef 61 | 62 | def shutdown(): Unit 63 | 64 | def sendToPath(path: ActorPath, message: Any)( 65 | implicit sender: ActorRef = Actor.noSender): Unit 66 | } 67 | 68 | class ActorSystemImpl(_settings: ActorSystem.Settings) 69 | extends ActorSystem(_settings) 70 | with akka.scalajs.webworkers.WebWorkersActorSystem { 71 | 72 | protected def systemImpl = this 73 | 74 | def actorOf(props: Props): ActorRef = 75 | guardian.actorCell.actorOf(props) 76 | def actorOf(props: Props, name: String): ActorRef = 77 | guardian.actorCell.actorOf(props, name) 78 | 79 | def stop(actor: ActorRef): Unit = { 80 | val path = actor.path 81 | val guard = guardian.path 82 | val sys = systemGuardian.path 83 | path.parent match { 84 | case `guard` => guardian ! StopChild(actor) 85 | case `sys` => systemGuardian ! StopChild(actor) 86 | case _ => actor.asInstanceOf[InternalActorRef].stop() 87 | } 88 | } 89 | 90 | val scheduler: Scheduler = new EventLoopScheduler 91 | val eventStream: EventStream = new EventStream 92 | val provider: ActorRefProvider = new LocalActorRefProvider( 93 | name, settings, eventStream) 94 | 95 | def deadLetters: ActorRef = provider.deadLetters 96 | 97 | val mailboxes: Mailboxes = new Mailboxes(deadLetters) 98 | val dispatcher: MessageDispatcher = new MessageDispatcher(mailboxes) 99 | 100 | def terminationFuture: Future[Unit] = provider.terminationFuture 101 | def lookupRoot: InternalActorRef = provider.rootGuardian 102 | def guardian: LocalActorRef = provider.guardian 103 | def systemGuardian: LocalActorRef = provider.systemGuardian 104 | 105 | def /(actorName: String): ActorPath = guardian.path / actorName 106 | def /(path: Iterable[String]): ActorPath = guardian.path / path 107 | 108 | private lazy val _start: this.type = { 109 | // the provider is expected to start default loggers, LocalActorRefProvider does this 110 | provider.init(this) 111 | //if (settings.LogDeadLetters > 0) 112 | // logDeadLetterListener = Some(systemActorOf(Props[DeadLetterListener], "deadLetterListener")) 113 | //registerOnTermination(stopScheduler()) 114 | //loadExtensions() 115 | //if (LogConfigOnStart) logConfiguration() 116 | this 117 | } 118 | 119 | def start(): this.type = _start 120 | 121 | def shutdown(): Unit = { 122 | guardian.stop() 123 | } 124 | 125 | override def sendToPath(path: ActorPath, message: Any)( 126 | implicit sender: ActorRef): Unit = { 127 | // FIXME The existence of this method is a hack! Need to find a solution. 128 | new akka.scalajs.webworkers.WorkerActorRef(this, path) ! message 129 | } 130 | 131 | def resolveLocalActorPath(path: ActorPath): Option[ActorRef] = { 132 | println(path.elements) 133 | //Some(provider.resolveActorRef(provider.rootPath / path.elements)) 134 | val result = path.elements.tail.foldLeft(provider.guardian) { (parent, childName) => 135 | parent.actorCell.child(childName) match { 136 | case Some(child: LocalActorRef) => child 137 | case x => 138 | println(s"$parent of name ${parent.path}.child($childName) = $x") 139 | return None 140 | } 141 | } 142 | Some(result) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /actors/src/main/wscommon/akka/scalajs/wscommon/AbstractProxy.scala: -------------------------------------------------------------------------------- 1 | package akka.scalajs.wscommon 2 | 3 | import scala.collection.mutable 4 | 5 | import akka.actor._ 6 | import akka.scalajs.wscommon._ 7 | 8 | import org.scalajs.spickling._ 9 | 10 | object AbstractProxy { 11 | // Messages sent across the network 12 | case class Welcome(entryPoint: ActorRef) 13 | case class SendMessage(msg: Any, receiver: ActorRef, sender: ActorRef) 14 | case class ForeignTerminated(ref: ActorRef) 15 | 16 | // Local messages 17 | case class IncomingMessage(pickle: Any) // JsValue/js.Any = P 18 | case object ConnectionClosed 19 | case class SendToPeer(message: Any) 20 | 21 | /** Register the messages sent across the network to the pickler registry. */ 22 | private lazy val _registerPicklers: Unit = { 23 | import PicklerRegistry.register 24 | register[Welcome] 25 | register[SendMessage] 26 | register[ForeignTerminated] 27 | } 28 | 29 | private def registerPicklers(): Unit = _registerPicklers 30 | } 31 | 32 | /** Common between [[akka.scalajs.wsserver.ServerProxy]] and 33 | * [[akka.scalajs.wsclient.ClientProxy]]. 34 | */ 35 | abstract class AbstractProxy extends Actor { 36 | 37 | import AbstractProxy._ 38 | 39 | type PickleType 40 | implicit protected def pickleBuilder: PBuilder[PickleType] 41 | implicit protected def pickleReader: PReader[PickleType] 42 | 43 | registerPicklers() 44 | protected val picklerRegistry: PicklerRegistry = 45 | new ActorRefAwarePicklerRegistry(this) 46 | 47 | private[this] var _nextLocalID: Long = 0 48 | private def nextLocalID(): String = { 49 | _nextLocalID += 1 50 | _nextLocalID.toString 51 | } 52 | 53 | private val localIDs = mutable.Map.empty[ActorRef, String] 54 | private val localIDsRev = mutable.Map.empty[String, ActorRef] 55 | 56 | private val foreignIDs = mutable.Map.empty[ActorRef, String] 57 | private val foreignIDsRev = mutable.Map.empty[String, ActorRef] 58 | 59 | def receive = { 60 | case IncomingMessage(pickle) => 61 | val msg = picklerRegistry.unpickle(pickle.asInstanceOf[PickleType]) 62 | receiveFromPeer(msg) 63 | 64 | case ConnectionClosed => 65 | context.stop(self) 66 | 67 | case SendToPeer(message) => 68 | sendToPeer(message) 69 | 70 | case Terminated(ref) => 71 | if (localIDs.contains(ref)) { 72 | sendToPeer(ForeignTerminated(ref)) // do this *before* altering localIDs 73 | localIDs.remove(ref).foreach(localIDsRev -= _) 74 | } 75 | foreignIDs -= ref 76 | 77 | case ForeignTerminated(ref) => 78 | context.stop(ref) 79 | } 80 | 81 | protected def receiveFromPeer: Receive = { 82 | case m @ SendMessage(message, receiver, sender) => 83 | receiver.tell(message, sender) 84 | 85 | case ForeignTerminated(ref) => 86 | context.stop(ref) 87 | } 88 | 89 | protected def sendToPeer(msg: Any): Unit = { 90 | val pickle = picklerRegistry.pickle(msg) 91 | sendPickleToPeer(pickle) 92 | } 93 | 94 | protected def sendPickleToPeer(pickle: PickleType): Unit 95 | 96 | private[wscommon] def pickleActorRef[P](ref: ActorRef)( 97 | implicit builder: PBuilder[P]): P = { 98 | val (side, id) = if (context.children.exists(_ == ref)) { 99 | /* This is a proxy actor for an actor on the client. 100 | * We need to unbox it to recover the ID the client gave to us for it. 101 | */ 102 | ("receiver", foreignIDs(ref)) 103 | } else { 104 | /* This is an actor on the server (or somewhere else). 105 | * The client will have to make a proxy for this one with an ID we choose. 106 | */ 107 | val id = localIDs.getOrElseUpdate(ref, { 108 | context.watch(ref) 109 | val id = nextLocalID() 110 | localIDsRev += id -> ref 111 | id 112 | }) 113 | ("sender", id) 114 | } 115 | builder.makeObject( 116 | ("side", builder.makeString(side)), 117 | ("id", builder.makeString(id))) 118 | } 119 | 120 | private[wscommon] def unpickleActorRef[P](pickle: P)( 121 | implicit reader: PReader[P]): ActorRef = { 122 | val side = reader.readString(reader.readObjectField(pickle, "side")) 123 | val id = reader.readString(reader.readObjectField(pickle, "id")) 124 | 125 | side match { 126 | case "receiver" => 127 | localIDsRev.getOrElse(id, context.system.deadLetters) 128 | 129 | case "sender" => 130 | foreignIDsRev.getOrElse(id, { 131 | // we don't have a proxy yet, make one 132 | val ref = context.watch(context.actorOf(Props(new ForeignActorProxy))) 133 | foreignIDs += ref -> id 134 | foreignIDsRev += id -> ref 135 | ref 136 | }) 137 | } 138 | } 139 | } 140 | 141 | private class ForeignActorProxy extends Actor { 142 | import AbstractProxy._ 143 | 144 | def receive = { 145 | case message => 146 | context.parent ! SendToPeer(SendMessage(message, self, sender)) 147 | } 148 | } 149 | 150 | /** My pickler registry with hooks for pickling and unpickling ActorRefs. */ 151 | private class ActorRefAwarePicklerRegistry(proxy: AbstractProxy) extends PicklerRegistry { 152 | val base = PicklerRegistry 153 | 154 | override def pickle[P](value: Any)(implicit builder: PBuilder[P], 155 | registry: PicklerRegistry): P = { 156 | value match { 157 | case ref: ActorRef => builder.makeObject(("ref", proxy.pickleActorRef(ref))) 158 | case _ => base.pickle(value) 159 | } 160 | } 161 | 162 | override def unpickle[P](pickle: P)(implicit reader: PReader[P], 163 | registry: PicklerRegistry): Any = { 164 | if (reader.isNull(pickle)) { 165 | null 166 | } else { 167 | val refData = reader.readObjectField(pickle, "ref") 168 | if (!reader.isUndefined(refData)) 169 | proxy.unpickleActorRef(refData) 170 | else 171 | base.unpickle(pickle) 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/actor/dungeon/Children.scala: -------------------------------------------------------------------------------- 1 | package akka.actor 2 | package dungeon 3 | 4 | import scala.annotation.tailrec 5 | import scala.collection.{ immutable, mutable } 6 | 7 | import akka.util.Helpers 8 | 9 | import ActorPath.ElementRegex 10 | 11 | private[akka] trait Children { this: ActorCell => 12 | 13 | import ChildrenContainer._ 14 | 15 | private var childrenRefs: ChildrenContainer = EmptyChildrenContainer 16 | 17 | final def children: immutable.Iterable[ActorRef] = 18 | childrenRefs.children 19 | 20 | final def childStatsByName(name: String): Option[ChildRestartStats] = 21 | childrenRefs.getByName(name) 22 | 23 | final def child(name: String): Option[ActorRef] = 24 | childStatsByName(name).map(_.child) 25 | 26 | final def childStatsByRef(actor: ActorRef): Option[ChildRestartStats] = 27 | childrenRefs.getByRef(actor) 28 | 29 | final def childByRef(actor: ActorRef): Option[ActorRef] = 30 | childStatsByRef(actor).map(_.child) 31 | 32 | final def isChild(actor: ActorRef): Boolean = 33 | childrenRefs.isChild(actor) 34 | 35 | protected def getAllChildStats: immutable.Iterable[ChildRestartStats] = 36 | childrenRefs.stats 37 | 38 | def actorOf(props: Props): ActorRef = 39 | makeChild(this, props, randomName(), async = false, systemService = false) 40 | def actorOf(props: Props, name: String): ActorRef = 41 | makeChild(this, props, checkName(name), async = false, systemService = false) 42 | 43 | private[akka] def attachChild(props: Props, systemService: Boolean): ActorRef = 44 | makeChild(this, props, randomName(), async = true, systemService = systemService) 45 | private[akka] def attachChild(props: Props, name: String, systemService: Boolean): ActorRef = 46 | makeChild(this, props, checkName(name), async = true, systemService = systemService) 47 | 48 | private var _nextName: Long = _ // default = 0L 49 | final protected def randomName(): String = { 50 | val current = _nextName 51 | _nextName += 1 52 | Helpers.base64(current) 53 | } 54 | 55 | final def stop(actor: ActorRef): Unit = { 56 | if (childrenRefs.isChild(actor)) { 57 | childrenRefs = childrenRefs.shallDie(actor) 58 | } 59 | actor.asInstanceOf[InternalActorRef].stop() 60 | } 61 | 62 | final def checkChildNameAvailable(name: String): Unit = { 63 | if (childrenRefs.isChild(name)) 64 | throw new InvalidActorNameException(s"actor name [$name] is not unique!") 65 | } 66 | 67 | final protected def setChildrenTerminationReason( 68 | reason: ChildrenContainer.SuspendReason): Boolean = { 69 | childrenRefs match { 70 | case c: ChildrenContainer.TerminatingChildrenContainer => 71 | childrenRefs = c.copy(reason = reason) 72 | true 73 | case _ => false 74 | } 75 | } 76 | 77 | final protected def setTerminated(): Unit = 78 | childrenRefs = TerminatedChildrenContainer 79 | 80 | /* 81 | * ActorCell-internal API 82 | */ 83 | 84 | protected def isNormal = childrenRefs.isNormal 85 | 86 | protected def isTerminating = childrenRefs.isTerminating 87 | 88 | private[akka] def initChild(child: ActorRef): Unit = 89 | childrenRefs = childrenRefs.add(child.path.name, ChildRestartStats(child)) 90 | 91 | protected def waitingForChildrenOrNull = childrenRefs match { 92 | case TerminatingChildrenContainer(_, _, w: WaitingForChildren) => w 93 | case _ => null 94 | } 95 | 96 | protected def suspendChildren(exceptFor: Set[ActorRef] = Set.empty): Unit = 97 | childrenRefs.stats foreach { 98 | case ChildRestartStats(child, _, _) if !(exceptFor contains child) => 99 | child.asInstanceOf[InternalActorRef].suspend() 100 | case _ => 101 | } 102 | 103 | protected def resumeChildren(causedByFailure: Throwable, perp: ActorRef): Unit = 104 | childrenRefs.stats foreach { 105 | case ChildRestartStats(child, _, _) => 106 | child.asInstanceOf[InternalActorRef].resume( 107 | if (perp == child) causedByFailure else null) 108 | } 109 | 110 | protected def removeChildAndGetStateChange(child: ActorRef): Option[SuspendReason] = { 111 | childrenRefs match { // The match must be performed BEFORE the removeChild 112 | case TerminatingChildrenContainer(_, _, reason) => 113 | childrenRefs = childrenRefs.remove(child) 114 | childrenRefs match { 115 | case _: TerminatingChildrenContainer => None 116 | case _ => Some(reason) 117 | } 118 | case _ => 119 | childrenRefs = childrenRefs.remove(child) 120 | None 121 | } 122 | } 123 | 124 | /* 125 | * Private helpers 126 | */ 127 | 128 | private def checkName(name: String): String = { 129 | name match { 130 | case null => throw new InvalidActorNameException("actor name must not be null") 131 | case "" => throw new InvalidActorNameException("actor name must not be empty") 132 | case ElementRegex() => name 133 | case _ => throw new InvalidActorNameException(s"illegal actor name [$name], must conform to $ElementRegex") 134 | } 135 | } 136 | 137 | private def makeChild(cell: ActorCell, props: Props, name: String, 138 | async: Boolean, systemService: Boolean): ActorRef = { 139 | 140 | /* 141 | * in case we are currently terminating, fail external attachChild requests 142 | * (internal calls cannot happen anyway because we are suspended) 143 | */ 144 | if (isTerminating) { 145 | throw new IllegalStateException( 146 | "cannot create children while terminating or terminated") 147 | } else { 148 | checkChildNameAvailable(name) 149 | val childPath = new ChildActorPath(cell.self.path, name)(ActorCell.newUid()) 150 | val child = cell.provider.actorOf(cell.systemImpl, props, cell.self, 151 | childPath, systemService = systemService, async = async) 152 | // mailbox==null during RoutedActorCell constructor, where suspends are queued otherwise 153 | if (mailbox ne null) for (_ <- 1 to mailbox.suspendCount) child.suspend() 154 | initChild(child) 155 | child.start() 156 | child 157 | } 158 | 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /examples/chat-full-stack/app/actors/ChatManager.scala: -------------------------------------------------------------------------------- 1 | package actors 2 | 3 | import scala.language.postfixOps 4 | 5 | import scala.concurrent.duration._ 6 | 7 | import akka.actor._ 8 | import akka.actor.SupervisorStrategy.{Stop, Escalate} 9 | import akka.event.LoggingReceive 10 | import akka.pattern.{ask, pipe} 11 | import akka.util.Timeout 12 | import akka.scalajs.wsserver._ 13 | 14 | import models._ 15 | 16 | class ChatManager extends Actor with ActorLogging { 17 | RegisterPicklers.registerPicklers() 18 | 19 | val roomsManager = context.actorOf(Props[RoomsManager], name = "rooms") 20 | val usersManager = context.actorOf( 21 | Props(classOf[UsersManager], roomsManager), name = "users") 22 | 23 | roomsManager ! RoomsManager.SetUsersManager(usersManager) 24 | 25 | def receive = LoggingReceive { 26 | case m @ NewConnection() => 27 | log.info("chat manager, new connection") 28 | usersManager.forward(m) 29 | } 30 | } 31 | 32 | object RoomsManager { 33 | case class SetUsersManager(ref: ActorRef) 34 | case class UserJoin(user: User, room: Room) 35 | case object AskRoomList 36 | } 37 | class RoomsManager extends Actor with ActorLogging { 38 | var usersManager: ActorRef = context.system.deadLetters 39 | var openRooms = Map.empty[ActorRef, Room] 40 | var openRoomsRev = Map.empty[Room, ActorRef] 41 | 42 | def receive = LoggingReceive { 43 | case RoomsManager.SetUsersManager(ref) => 44 | usersManager = ref 45 | 46 | case m @ RoomsManager.UserJoin(user, room) => 47 | val roomManager = openRoomsRev.getOrElse(room, { 48 | val man = context.watch(context.actorOf( 49 | Props(classOf[RoomManager], room))) 50 | openRooms += man -> room 51 | openRoomsRev += room -> man 52 | sendRoomListChanged() 53 | man 54 | }) 55 | roomManager.forward(m) 56 | 57 | case RoomsManager.AskRoomList => 58 | sender ! RoomListChanged(openRooms.values.toList) 59 | 60 | case Terminated(roomManager) => 61 | openRooms.get(sender) foreach { room => 62 | openRooms -= sender 63 | openRoomsRev -= room 64 | sendRoomListChanged() 65 | } 66 | } 67 | 68 | override val supervisorStrategy = 69 | OneForOneStrategy() { 70 | case _: Exception => Stop 71 | case t => 72 | super.supervisorStrategy.decider.applyOrElse(t, (_: Any) => Escalate) 73 | } 74 | 75 | def sendRoomListChanged(): Unit = { 76 | usersManager ! RoomListChanged(openRooms.values.toList) 77 | } 78 | } 79 | 80 | class RoomManager(val room: Room) extends Actor with ActorLogging { 81 | var attendingUsers = Map.empty[ActorRef, User] 82 | 83 | def receive = LoggingReceive { 84 | case RoomsManager.UserJoin(user, _) => 85 | attendingUsers.keys foreach { 86 | _ ! UserJoined(user) 87 | } 88 | attendingUsers += sender -> user 89 | context.watch(sender) 90 | sender ! JoinedRoom(attendingUsers.values.toList) 91 | 92 | case Leave => 93 | userLeave(sender) 94 | context.unwatch(sender) 95 | 96 | case SendMessage(message) => 97 | // simulation of an exploit 98 | if (message.text == "exploit!kill-room") 99 | throw new Exception(s"room $room was killed by exploit!") 100 | 101 | sendToAllAttending(ReceiveMessage(message)) 102 | 103 | case Terminated(ref) => 104 | userLeave(ref) 105 | } 106 | 107 | def userLeave(ref: ActorRef): Unit = { 108 | attendingUsers.get(ref) foreach { user => 109 | attendingUsers -= ref 110 | sendToAllAttending(UserLeft(user)) 111 | 112 | if (attendingUsers.isEmpty) 113 | context.stop(self) 114 | } 115 | } 116 | 117 | def sendToAllAttending(msg: Any): Unit = 118 | attendingUsers.keys.foreach(_ ! msg) 119 | } 120 | 121 | object UsersManager { 122 | case class GetUserManager(user: User) 123 | } 124 | class UsersManager(val roomsManager: ActorRef) extends Actor with ActorLogging { 125 | var connectedUsers = Map.empty[ActorRef, User] 126 | 127 | def receive = LoggingReceive { 128 | case m @ NewConnection() => 129 | context.watch(context.actorOf( 130 | Props(classOf[UserManager], roomsManager))).forward(m) 131 | 132 | case m @ Connect(user) => 133 | connectedUsers += sender -> user 134 | 135 | case m @ RoomListChanged(rooms) => 136 | context.children.foreach(_ ! m) 137 | 138 | case UsersManager.GetUserManager(user) => 139 | connectedUsers.find(_._2 == user).fold { 140 | sender ! None 141 | } { 142 | x => sender ! Some(x._1) 143 | } 144 | 145 | case Terminated(child) => 146 | connectedUsers -= child 147 | } 148 | } 149 | 150 | object UserManager { 151 | case class ReceiveRequestPrivateChat(peer: User) 152 | } 153 | class UserManager(val roomsManager: ActorRef) extends Actor with ActorLogging { 154 | var peer: ActorRef = context.system.deadLetters 155 | var user: User = User.Nobody 156 | 157 | def receive = LoggingReceive { 158 | case m @ NewConnection() => 159 | sender ! ActorWebSocket.actorForWebSocketHandler(self) 160 | context.children.foreach(context.watch(_)) 161 | 162 | case Connect(user) => 163 | peer = sender 164 | this.user = user 165 | context.watch(peer) 166 | context.parent ! Connect(user) 167 | roomsManager ! RoomsManager.AskRoomList 168 | 169 | case m @ RoomListChanged(rooms) => 170 | peer ! m 171 | 172 | case m @ Join(room: Room) => 173 | roomsManager.forward(RoomsManager.UserJoin(user, room)) 174 | 175 | case m @ RequestPrivateChat(peer) => 176 | implicit val timeout = new Timeout(3 seconds) 177 | implicit val ec = context.dispatcher 178 | val sender = this.sender 179 | val f = (context.parent ? UsersManager.GetUserManager(peer)) 180 | f.onSuccess { 181 | case Some(ref: ActorRef) => 182 | ref.tell(UserManager.ReceiveRequestPrivateChat(user), sender) 183 | case None => sender ! UserDoesNotExist 184 | } 185 | f.onFailure { 186 | case _ => sender ! UserDoesNotExist 187 | } 188 | 189 | case m @ UserManager.ReceiveRequestPrivateChat(chatPeer) => 190 | peer.forward(RequestPrivateChat(chatPeer)) 191 | 192 | case Terminated(peerOrProxy) => 193 | context.stop(self) 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/actor/Actor.scala: -------------------------------------------------------------------------------- 1 | package akka.actor 2 | 3 | import scala.annotation.unchecked.uncheckedStable 4 | 5 | object Actor { 6 | /** 7 | * Type alias representing a Receive-expression for Akka Actors. 8 | */ 9 | type Receive = PartialFunction[Any, Unit] 10 | 11 | /** 12 | * emptyBehavior is a Receive-expression that matches no messages at all, ever. 13 | */ 14 | @SerialVersionUID(1L) 15 | object emptyBehavior extends Receive { 16 | def isDefinedAt(x: Any) = false 17 | def apply(x: Any) = throw new UnsupportedOperationException("Empty behavior apply()") 18 | } 19 | 20 | /** 21 | * Default placeholder (null) used for "!" to indicate that there is no sender of the message, 22 | * that will be translated to the receiving system's deadLetters. 23 | */ 24 | final val noSender: ActorRef = null 25 | } 26 | 27 | trait Actor { 28 | 29 | import Actor._ 30 | 31 | // to make type Receive known in subclasses without import 32 | type Receive = Actor.Receive 33 | 34 | private[this] var _context: ActorContext = { 35 | val contextStack = ActorCell.contextStack 36 | if ((contextStack.isEmpty) || (contextStack.head eq null)) 37 | throw ActorInitializationException( 38 | s"You cannot create an instance of [${getClass.getName}] explicitly using the constructor (new). " + 39 | "You have to use one of the 'actorOf' factory methods to create a new actor. See the documentation.") 40 | val c = contextStack.head 41 | ActorCell.contextStack = null :: contextStack 42 | c 43 | } 44 | 45 | private[this] var _self: ActorRef = context.self 46 | 47 | private[akka] final def setActorFields(context: ActorContext, 48 | self: ActorRef): Unit = { 49 | this._context = context 50 | this._self = self 51 | } 52 | 53 | /** 54 | * Stores the context for this actor, including self, and sender. 55 | * It is implicit to support operations such as `forward`. 56 | * 57 | * WARNING: Only valid within the Actor itself, so do not close over it and 58 | * publish it to other threads! 59 | */ 60 | implicit final def context: ActorContext = _context 61 | 62 | /** 63 | * The 'self' field holds the ActorRef for this actor. 64 | *

65 | * Can be used to send messages to itself: 66 | *

 67 |    * self ! message
 68 |    * 
69 | */ 70 | implicit final def self: ActorRef = _self 71 | 72 | /** 73 | * The reference sender Actor of the last received message. 74 | * Is defined if the message was sent from another Actor, 75 | * else `deadLetters` in [[akka.actor.ActorSystem]]. 76 | * 77 | * WARNING: Only valid within the Actor itself, so do not close over it and 78 | * publish it to other threads! 79 | */ 80 | final def sender: ActorRef = context.sender 81 | 82 | /** 83 | * This defines the initial actor behavior, it must return a partial function 84 | * with the actor logic. 85 | */ 86 | def receive: Actor.Receive 87 | 88 | /** 89 | * User overridable definition the strategy to use for supervising 90 | * child actors. 91 | */ 92 | def supervisorStrategy: SupervisorStrategy = SupervisorStrategy.defaultStrategy 93 | 94 | // Life-cycle hooks 95 | 96 | /** 97 | * User overridable callback. 98 | *

99 | * Is called when an Actor is started. 100 | * Actors are automatically started asynchronously when created. 101 | * Empty default implementation. 102 | */ 103 | def preStart(): Unit = () 104 | 105 | /** 106 | * User overridable callback. 107 | *

108 | * Is called asynchronously after 'actor.stop()' is invoked. 109 | * Empty default implementation. 110 | */ 111 | def postStop(): Unit = () 112 | 113 | /** 114 | * User overridable callback: '''By default it disposes of all children and 115 | * then calls `postStop()`.''' 116 | * @param reason the Throwable that caused the restart to happen 117 | * @param message optionally the current message the actor processed when failing, if applicable 118 | *

119 | * Is called on a crashed Actor right BEFORE it is restarted to allow clean 120 | * up of resources before Actor is terminated. 121 | */ 122 | def preRestart(reason: Throwable, message: Option[Any]): Unit = { 123 | context.children foreach { child => 124 | context.unwatch(child) 125 | context.stop(child) 126 | } 127 | postStop() 128 | } 129 | 130 | /** 131 | * User overridable callback: By default it calls `preStart()`. 132 | * @param reason the Throwable that caused the restart to happen 133 | *

134 | * Is called right AFTER restart on the newly created Actor to allow 135 | * reinitialization after an Actor crash. 136 | */ 137 | def postRestart(reason: Throwable): Unit = { 138 | preStart() 139 | } 140 | 141 | // Unhandled message 142 | 143 | /** 144 | * User overridable callback. 145 | *

146 | * Is called when a message isn't handled by the current behavior of the actor 147 | * by default it fails with either a [[akka.actor.DeathPactException]] (in 148 | * case of an unhandled [[akka.actor.Terminated]] message) or publishes an [[akka.actor.UnhandledMessage]] 149 | * to the actor's system's [[akka.event.EventStream]] 150 | */ 151 | def unhandled(message: Any): Unit = { 152 | message match { 153 | case Terminated(dead) => throw new DeathPactException(dead) 154 | case _ => 155 | context.system.eventStream.publish(UnhandledMessage(message, sender, self)) 156 | } 157 | } 158 | } 159 | 160 | /** 161 | * Scala API: Mix in ActorLogging into your Actor to easily obtain a reference to a logger, 162 | * which is available under the name "log". 163 | * 164 | * {{{ 165 | * class MyActor extends Actor with ActorLogging { 166 | * def receive = { 167 | * case "pigdog" => log.info("We've got yet another pigdog on our hands") 168 | * } 169 | * } 170 | * }}} 171 | */ 172 | trait ActorLogging { this: Actor => 173 | //val log = akka.event.Logging(context.system, this) 174 | object log { 175 | import akka.event.Logging._ 176 | private def publish(event: LogEvent) = context.system.eventStream.publish(event) 177 | private def myClass = ActorLogging.this.getClass 178 | 179 | def debug(msg: String): Unit = publish(Debug(self.toString, myClass, msg)) 180 | def info(msg: String): Unit = publish(Info(self.toString, myClass, msg)) 181 | def warning(msg: String): Unit = publish(Warning(self.toString, myClass, msg)) 182 | def error(msg: String): Unit = publish(Error(self.toString, myClass, msg)) 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/actor/ActorRef.scala: -------------------------------------------------------------------------------- 1 | package akka.actor 2 | 3 | import scala.language.implicitConversions 4 | 5 | import akka.dispatch.sysmsg.SystemMessage 6 | 7 | /** 8 | * Immutable and serializable handle to an actor, which may or may not reside 9 | * on the local host or inside the same [[org.scalajs.actors.ActorSystem]]. An ActorRef 10 | * can be obtained from an [[org.scalajs.actors.ActorRefFactory]], an interface which 11 | * is implemented by ActorSystem and [[org.scalajs.actors.ActorContext]]. This means 12 | * actors can be created top-level in the ActorSystem or as children of an 13 | * existing actor, but only from within that actor. 14 | * 15 | * ActorRefs can be freely shared among actors by message passing. Message 16 | * passing conversely is their only purpose. 17 | * 18 | * ActorRef does not have a method for terminating the actor it points to, use 19 | * [[akka.actor.ActorRefFactory]]`.stop(ref)`, or send a [[akka.actor.PoisonPill]], 20 | * for this purpose. 21 | * 22 | * Two actor references are compared equal when they have the same path and point to 23 | * the same actor incarnation. A reference pointing to a terminated actor doesn't compare 24 | * equal to a reference pointing to another (re-created) actor with the same path. 25 | * Actor references acquired with `actorFor` do not always include the full information 26 | * about the underlying actor identity and therefore such references do not always compare 27 | * equal to references acquired with `actorOf`, `sender`, or `context.self`. 28 | * 29 | * If you need to keep track of actor references in a collection and do not care 30 | * about the exact actor incarnation you can use the ``ActorPath`` as key because 31 | * the unique id of the actor is not taken into account when comparing actor paths. 32 | */ 33 | abstract class ActorRef { internalRef: InternalActorRef => 34 | 35 | def path: ActorPath 36 | 37 | /** 38 | * Sends a one-way asynchronous message. E.g. fire-and-forget semantics. 39 | *

40 | * 41 | * If invoked from within an actor then the actor reference is implicitly passed on as the implicit 'sender' argument. 42 | *

43 | * 44 | * This actor 'sender' reference is then available in the receiving actor in the 'sender' member variable, 45 | * if invoked from within an Actor. If not then no sender is available. 46 | *

 47 |    *   actor ! message
 48 |    * 
49 | *

50 | */ 51 | def !(message: Any)(implicit sender: ActorRef = Actor.noSender): Unit 52 | 53 | def tell(message: Any, sender: ActorRef): Unit = this.!(message)(sender) 54 | 55 | /** 56 | * Forwards the message and passes the original sender actor as the sender. 57 | * 58 | * Works with '!' and '?'/'ask'. 59 | */ 60 | def forward(message: Any)(implicit context: ActorContext): Unit = 61 | this.!(message)(context.sender) 62 | 63 | /** 64 | * Comparison takes path and the unique id of the actor cell into account. 65 | */ 66 | final def compareTo(other: ActorRef) = { 67 | val x = this.path compareTo other.path 68 | if (x == 0) { 69 | if (this.path.uid < other.path.uid) -1 70 | else if (this.path.uid == other.path.uid) 0 71 | else 1 72 | } 73 | else x 74 | } 75 | 76 | final override def hashCode: Int = { 77 | if (path.uid == ActorCell.undefinedUid) path.hashCode 78 | else path.uid 79 | } 80 | 81 | /** 82 | * Equals takes path and the unique id of the actor cell into account. 83 | */ 84 | final override def equals(that: Any): Boolean = that match { 85 | case other: ActorRef => path.uid == other.path.uid && path == other.path 86 | case _ => false 87 | } 88 | 89 | override def toString: String = 90 | if (path.uid == ActorCell.undefinedUid) s"Actor[${path}]" 91 | else s"Actor[${path}#${path.uid}]" 92 | } 93 | 94 | /** 95 | * Internal trait for assembling all the functionality needed internally on 96 | * ActorRefs. NOTE THAT THIS IS NOT A STABLE EXTERNAL INTERFACE! 97 | */ 98 | private[akka] abstract class InternalActorRef extends ActorRef { 99 | /* 100 | * Actor life-cycle management, invoked only internally (in response to user requests via ActorContext). 101 | */ 102 | def start(): Unit 103 | def resume(causedByFailure: Throwable): Unit 104 | def suspend(): Unit 105 | def restart(cause: Throwable): Unit 106 | def stop(): Unit 107 | def sendSystemMessage(message: SystemMessage): Unit 108 | 109 | /** 110 | * Get a reference to the actor ref provider which created this ref. 111 | */ 112 | def provider: ActorRefProvider 113 | 114 | /** 115 | * Obtain parent of this ref; used by getChild for ".." paths. 116 | */ 117 | def getParent: InternalActorRef 118 | 119 | /** 120 | * Obtain ActorRef by possibly traversing the actor tree or looking it up at 121 | * some provider-specific location. This method shall return the end result, 122 | * i.e. not only the next step in the look-up; this will typically involve 123 | * recursive invocation. A path element of ".." signifies the parent, a 124 | * trailing "" element must be disregarded. If the requested path does not 125 | * exist, return Nobody. 126 | */ 127 | def getChild(name: Iterator[String]): InternalActorRef 128 | 129 | /** 130 | * Scope: if this ref points to an actor which resides within the same 131 | * JS VM, i.e., whose mailbox is directly reachable etc. 132 | */ 133 | def isLocal: Boolean 134 | } 135 | 136 | private[akka] object InternalActorRef { 137 | // TODO Not sure this is a good idea ... 138 | implicit def fromActorRef(ref: ActorRef): InternalActorRef = 139 | ref.asInstanceOf[InternalActorRef] 140 | } 141 | 142 | /** 143 | * Trait for ActorRef implementations where all methods contain default stubs. 144 | * 145 | * INTERNAL API 146 | */ 147 | private[akka] trait MinimalActorRef extends InternalActorRef { 148 | 149 | override def start(): Unit = () 150 | override def suspend(): Unit = () 151 | override def resume(causedByFailure: Throwable): Unit = () 152 | override def stop(): Unit = () 153 | override def restart(cause: Throwable): Unit = () 154 | 155 | override def !(message: Any)(implicit sender: ActorRef = Actor.noSender): Unit = () 156 | 157 | override def sendSystemMessage(message: SystemMessage): Unit = () 158 | 159 | override def getParent: InternalActorRef = Nobody 160 | override def getChild(names: Iterator[String]): InternalActorRef = 161 | if (names.forall(_.isEmpty)) this else Nobody 162 | 163 | override def isLocal: Boolean = true 164 | } 165 | 166 | /** 167 | * This is an internal look-up failure token, not useful for anything else. 168 | */ 169 | private[akka] case object Nobody extends MinimalActorRef { 170 | override val path: RootActorPath = 171 | RootActorPath(Address("actors", "all-systems"), "/Nobody") 172 | 173 | override def provider = 174 | throw new UnsupportedOperationException("Nobody does not provide") 175 | } 176 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/actor/dungeon/ChildrenContainer.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2009-2013 Typesafe Inc. 3 | */ 4 | 5 | package akka.actor 6 | package dungeon 7 | 8 | import scala.collection.immutable 9 | 10 | import akka.util.JSMap 11 | import akka.util.Collections.EmptyImmutableSeq 12 | 13 | /** 14 | * INTERNAL API 15 | * 16 | * Unlike in Akka/JVM, instances of ChildrenContainer are mutable. 17 | * They will sometimes return themselves if possible to avoid unnecessary 18 | * allocations. Moreover, even when returning a different instance, the 19 | * internal states of the old and new instances may share mutable data. An 20 | * instance of ChildrenContrainer is therefore invalid after calling 21 | * any state transforming method on it (i.e., one of add(), remove() and 22 | * shallDie()). 23 | * 24 | * The only subclasses that are really immutable, and hence can be shared by 25 | * different [[org.scalajs.actors.cell.Children]], are the singletons 26 | * EmptyChildrenContainer and TerminatedChildrenContainer. 27 | */ 28 | private[akka] abstract class ChildrenContainer { 29 | 30 | def add(name: String, stats: ChildRestartStats): ChildrenContainer 31 | def remove(child: ActorRef): ChildrenContainer 32 | 33 | def getByName(name: String): Option[ChildRestartStats] 34 | def getByRef(actor: ActorRef): Option[ChildRestartStats] 35 | 36 | def isChild(name: String): Boolean = getByName(name).isDefined 37 | def isChild(actor: ActorRef): Boolean = getByRef(actor).isDefined 38 | 39 | def children: immutable.Iterable[ActorRef] 40 | def stats: immutable.Iterable[ChildRestartStats] 41 | 42 | def shallDie(actor: ActorRef): ChildrenContainer 43 | 44 | def isTerminating: Boolean = false 45 | def isNormal: Boolean = true 46 | } 47 | 48 | /** 49 | * INTERNAL API 50 | * 51 | * This object holds the classes performing the logic of managing the children 52 | * of an actor, hence they are intimately tied to ActorCell. 53 | */ 54 | private[akka] object ChildrenContainer { 55 | 56 | sealed trait SuspendReason 57 | case object UserRequest extends SuspendReason 58 | // careful with those system messages, all handling to be taking place in ActorCell.scala! 59 | case class Recreation(cause: Throwable) extends SuspendReason with WaitingForChildren 60 | case class Creation() extends SuspendReason with WaitingForChildren 61 | case object Termination extends SuspendReason 62 | 63 | trait WaitingForChildren 64 | 65 | abstract class EmptyChildrenContainer extends ChildrenContainer { 66 | override def add(name: String, stats: ChildRestartStats): ChildrenContainer = 67 | new NormalChildrenContainer((JSMap.empty[ChildRestartStats]) += name -> stats) 68 | override def remove(child: ActorRef): ChildrenContainer = this 69 | override def getByName(name: String): Option[ChildRestartStats] = None 70 | override def getByRef(actor: ActorRef): Option[ChildRestartStats] = None 71 | override def children: immutable.Iterable[ActorRef] = EmptyImmutableSeq 72 | override def stats: immutable.Iterable[ChildRestartStats] = EmptyImmutableSeq 73 | override def shallDie(actor: ActorRef): ChildrenContainer = this 74 | } 75 | 76 | /** 77 | * This is the empty container, shared among all leaf actors. 78 | */ 79 | object EmptyChildrenContainer extends EmptyChildrenContainer { 80 | override def toString = "no children" 81 | } 82 | 83 | /** 84 | * This is the empty container which is installed after the last child has 85 | * terminated while stopping; it is necessary to distinguish from the normal 86 | * empty state while calling handleChildTerminated() for the last time. 87 | */ 88 | object TerminatedChildrenContainer extends EmptyChildrenContainer { 89 | override def add(name: String, stats: ChildRestartStats): ChildrenContainer = this 90 | override def isTerminating: Boolean = true 91 | override def isNormal: Boolean = false 92 | override def toString = "terminated" 93 | } 94 | 95 | /** 96 | * Normal children container: we do have at least one child, but none of our 97 | * children are currently terminating (which is the time period between 98 | * calling context.stop(child) and processing the ChildTerminated() system 99 | * message). 100 | */ 101 | class NormalChildrenContainer( 102 | c: JSMap[ChildRestartStats]) extends ChildrenContainer { 103 | 104 | override def add(name: String, stats: ChildRestartStats): ChildrenContainer = { 105 | c(name) = stats 106 | this 107 | } 108 | 109 | override def remove(child: ActorRef): ChildrenContainer = { 110 | c -= child.path.name 111 | if (c.isEmpty) EmptyChildrenContainer 112 | else this 113 | } 114 | 115 | override def getByName(name: String): Option[ChildRestartStats] = 116 | c.get(name) 117 | 118 | override def getByRef(actor: ActorRef): Option[ChildRestartStats] = 119 | c.get(actor.path.name).filter(_.child == actor) 120 | 121 | override def children: immutable.Iterable[ActorRef] = 122 | c.values.map(_.child).toList 123 | 124 | override def stats: immutable.Iterable[ChildRestartStats] = 125 | c.values.toList 126 | 127 | override def shallDie(actor: ActorRef): ChildrenContainer = 128 | TerminatingChildrenContainer(c, Set(actor), UserRequest) 129 | 130 | override def toString = 131 | if (c.size > 20) c.size + " children" 132 | else c.mkString("children:\n ", "\n ", "") 133 | } 134 | 135 | /** 136 | * Waiting state: there are outstanding termination requests (i.e., 137 | * context.stop(child) was called but the corresponding ChildTerminated() 138 | * system message has not yet been processed). There could be no specific 139 | * reason (UserRequested), we could be Restarting or Terminating. 140 | * 141 | * Removing the last child which was supposed to be terminating will return 142 | * a different type of container, depending on whether or not children are 143 | * left and whether or not the reason was “Terminating”. 144 | */ 145 | case class TerminatingChildrenContainer( 146 | c: JSMap[ChildRestartStats], toDie: Set[ActorRef], reason: SuspendReason) 147 | extends ChildrenContainer { 148 | // TODO Test whether we can also turn toDie into a mutable set 149 | 150 | override def add(name: String, stats: ChildRestartStats): ChildrenContainer = { 151 | c(name) = stats 152 | this 153 | } 154 | 155 | override def remove(child: ActorRef): ChildrenContainer = { 156 | val t = toDie - child 157 | if (t.isEmpty) { 158 | reason match { 159 | case Termination => TerminatedChildrenContainer 160 | case _ => 161 | c -= child.path.name 162 | if (c.isEmpty) EmptyChildrenContainer 163 | else new NormalChildrenContainer(c) 164 | } 165 | } else { 166 | c -= child.path.name 167 | copy(toDie = t) 168 | } 169 | } 170 | 171 | override def getByName(name: String): Option[ChildRestartStats] = 172 | c.get(name) 173 | 174 | override def getByRef(actor: ActorRef): Option[ChildRestartStats] = 175 | c.get(actor.path.name).filter(_.child == actor) 176 | 177 | override def children: immutable.Iterable[ActorRef] = 178 | c.values.map(_.child).toList 179 | 180 | override def stats: immutable.Iterable[ChildRestartStats] = 181 | c.values.toList 182 | 183 | override def shallDie(actor: ActorRef): ChildrenContainer = 184 | copy(toDie = toDie + actor) 185 | 186 | override def isTerminating: Boolean = reason == Termination 187 | override def isNormal: Boolean = reason == UserRequest 188 | 189 | override def toString = 190 | if (c.size > 20) c.size + " children" 191 | else c.mkString("children (" + toDie.size + " terminating):\n ", "\n ", "\n") + toDie 192 | } 193 | 194 | } 195 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/dispatch/Mailbox.scala: -------------------------------------------------------------------------------- 1 | package akka.dispatch 2 | 3 | import scala.annotation.tailrec 4 | 5 | import scala.util.control.NonFatal 6 | 7 | import akka.actor._ 8 | import akka.dispatch.sysmsg.SystemMessage 9 | import akka.util.JSQueue 10 | 11 | private[akka] object Mailbox { 12 | 13 | private type Status = Int 14 | 15 | /* 16 | * The following assigned numbers CANNOT be changed without looking at the 17 | * code which uses them! 18 | */ 19 | 20 | // Primary status 21 | private final val Open = 0 // _status is not initialized in Mailbox, so default must be zero! Deliberately without type ascription to make it a compile-time constant 22 | private final val Closed = 1 // Deliberately without type ascription to make it a compile-time constant 23 | // Secondary status: Scheduled bit may be added to Open/Suspended 24 | private final val Scheduled = 2 // Deliberately without type ascription to make it a compile-time constant 25 | // Shifted by 2: the suspend count! 26 | private final val shouldScheduleMask = 3 27 | private final val shouldNotProcessMask = ~2 28 | private final val suspendMask = ~3 29 | private final val suspendUnit = 4 30 | } 31 | 32 | private[akka] class Mailbox(private[this] val messageQueue: MessageQueue) 33 | extends Runnable { 34 | import Mailbox._ 35 | 36 | private[this] var _actor: ActorCell = _ // = null 37 | private[this] var status: Status = _ // = initialize to 0 = Open 38 | private[this] val systemMessageQueue = new JSQueue[SystemMessage] 39 | 40 | def actor: ActorCell = _actor 41 | def setActor(actor: ActorCell): Unit = this._actor = actor 42 | 43 | def dispatcher: MessageDispatcher = actor.dispatcher 44 | 45 | /** 46 | * Try to enqueue the message to this queue, or throw an exception. 47 | */ 48 | def enqueue(receiver: ActorRef, msg: Envelope): Unit = 49 | messageQueue.enqueue(receiver, msg) 50 | 51 | /** 52 | * Try to dequeue the next message from this queue, return null failing that. 53 | */ 54 | def dequeue(): Envelope = messageQueue.dequeue() 55 | 56 | /** 57 | * Indicates whether this queue is non-empty. 58 | */ 59 | def hasMessages: Boolean = messageQueue.hasMessages 60 | 61 | /** 62 | * Should return the current number of messages held in this queue; may 63 | * always return 0 if no other value is available efficiently. Do not use 64 | * this for testing for presence of messages, use `hasMessages` instead. 65 | */ 66 | def numberOfMessages: Int = messageQueue.numberOfMessages 67 | 68 | def systemEnqueue(receiver: ActorRef, msg: SystemMessage): Unit = 69 | systemMessageQueue.enqueue(msg) 70 | 71 | /** 72 | * Indicates whether this mailbox has system messages. 73 | */ 74 | def hasSystemMessages: Boolean = systemMessageQueue.nonEmpty 75 | 76 | @inline 77 | final def shouldProcessMessage: Boolean = (status & shouldNotProcessMask) == 0 78 | 79 | @inline 80 | final def suspendCount: Int = status / suspendUnit 81 | 82 | @inline 83 | final def isSuspended: Boolean = (status & suspendMask) != 0 84 | 85 | @inline 86 | final def isClosed: Boolean = status == Closed 87 | 88 | @inline 89 | final def isScheduled: Boolean = (status & Scheduled) != 0 90 | 91 | /** 92 | * Reduce the suspend count by one. Caller does not need to worry about 93 | * whether status was Scheduled or not. 94 | * 95 | * @return true if the suspend count reached zero 96 | */ 97 | final def resume(): Boolean = { 98 | if (status == Closed) false 99 | else if (status < suspendUnit) true 100 | else { 101 | status -= suspendUnit 102 | status < suspendUnit 103 | } 104 | } 105 | 106 | /** 107 | * Increment the suspend count by one. Caller does not need to worry about 108 | * whether status was Scheduled or not. 109 | * 110 | * @return true if the previous suspend count was zero 111 | */ 112 | final def suspend(): Boolean = { 113 | if (status == Closed) false 114 | else { 115 | val s = status 116 | status = s + suspendUnit 117 | s < suspendUnit 118 | } 119 | } 120 | 121 | /** 122 | * set new primary status Closed. Caller does not need to worry about whether 123 | * status was Scheduled or not. 124 | */ 125 | final def becomeClosed(): Boolean = { 126 | if (status == Closed) false 127 | else { 128 | status = Closed 129 | true 130 | } 131 | } 132 | 133 | /** 134 | * Set Scheduled status, keeping primary status as is. 135 | */ 136 | final def setAsScheduled(): Boolean = { 137 | /* 138 | * Only try to add Scheduled bit if pure Open/Suspended, not Closed or with 139 | * Scheduled bit already set. 140 | */ 141 | val s = status 142 | if ((s & shouldScheduleMask) != Open) false 143 | else { 144 | status = s | Scheduled 145 | true 146 | } 147 | } 148 | 149 | /** 150 | * Reset Scheduled status, keeping primary status as is. 151 | */ 152 | final def setAsIdle(): Boolean = { 153 | status &= ~Scheduled 154 | true 155 | } 156 | 157 | final def canBeScheduledForExecution(hasMessageHint: Boolean, 158 | hasSystemMessageHint: Boolean): Boolean = status match { 159 | case Open | Scheduled => 160 | hasMessageHint || hasSystemMessageHint || hasSystemMessages || hasMessages 161 | case Closed => false 162 | case _ => hasSystemMessageHint || hasSystemMessages 163 | } 164 | 165 | final def run(): Unit = { 166 | try { 167 | if (!isClosed) { 168 | processAllSystemMessages() // First, deal with any system messages 169 | processMailbox() // Then deal with messages 170 | } 171 | } finally { 172 | setAsIdle() 173 | dispatcher.registerForExecution(this, false, false) 174 | } 175 | } 176 | 177 | /** 178 | * Process the messages in the mailbox 179 | */ 180 | @tailrec private final def processMailbox( 181 | left: Int = java.lang.Math.max(dispatcher.throughput, 1), 182 | deadlineNs: Long = if (dispatcher.isThroughputDeadlineTimeDefined == true) System.nanoTime + dispatcher.throughputDeadlineTime.toNanos else 0L): Unit = 183 | if (shouldProcessMessage) { 184 | val next = dequeue() 185 | if (next ne null) { 186 | actor invoke next 187 | processAllSystemMessages() 188 | if ((left > 1) && ((dispatcher.isThroughputDeadlineTimeDefined == false) || (System.nanoTime - deadlineNs) < 0)) 189 | processMailbox(left - 1, deadlineNs) 190 | } 191 | } 192 | 193 | /** 194 | * Will at least try to process all queued system messages: in case of 195 | * failure simply drop and go on to the next, because there is nothing to 196 | * restart here (failure is in ActorCell somewhere …). In case the mailbox 197 | * becomes closed (because of processing a Terminate message), dump all 198 | * already dequeued message to deadLetters. 199 | */ 200 | final def processAllSystemMessages() { 201 | while (systemMessageQueue.nonEmpty && !isClosed) { 202 | val msg = systemMessageQueue.dequeue() 203 | actor systemInvoke msg 204 | } 205 | 206 | /* 207 | * if we closed the mailbox, we must dump the remaining system messages 208 | * to deadLetters (this is essential for DeathWatch) 209 | */ 210 | val dlm = actor.dispatcher.mailboxes.deadLetterMailbox 211 | while (systemMessageQueue.nonEmpty) { 212 | val msg = systemMessageQueue.dequeue() 213 | try dlm.systemEnqueue(actor.self, msg) 214 | catch { 215 | case NonFatal(e) => 216 | // TODO publish to event stream 217 | /*actor.system.eventStream.publish( 218 | Error(e, actor.self.path.toString, this.getClass, 219 | "error while enqueuing " + msg + " to deadLetters: " + e.getMessage))*/ 220 | Console.err.println( 221 | s"error while enqueuing $msg to deadLetters: ${e.getMessage}") 222 | } 223 | } 224 | } 225 | 226 | /** 227 | * Overridable callback to clean up the mailbox, 228 | * called when an actor is unregistered. 229 | * By default it dequeues all system messages + messages and ships them to 230 | * the owning actors' systems' DeadLetterMailbox 231 | */ 232 | protected[dispatch] def cleanUp(): Unit = { 233 | if (actor ne null) { // actor is null for the deadLetterMailbox 234 | val dlm = actor.dispatcher.mailboxes.deadLetterMailbox 235 | while (systemMessageQueue.nonEmpty) { 236 | val msg = systemMessageQueue.dequeue() 237 | dlm.systemEnqueue(actor.self, msg) 238 | } 239 | 240 | /*if (messageQueue ne null) // needed for CallingThreadDispatcher, which never calls Mailbox.run() 241 | messageQueue.cleanUp(actor.self, actor.dispatcher.mailboxes.deadLetterMailbox.messageQueue)*/ 242 | } 243 | } 244 | 245 | } 246 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/actor/ActorPath.scala: -------------------------------------------------------------------------------- 1 | package akka.actor 2 | 3 | import scala.annotation.tailrec 4 | import scala.collection.immutable 5 | import java.lang.{ StringBuilder => JStringBuilder } 6 | 7 | object ActorPath { 8 | /** 9 | * Parse string as actor path; throws java.net.MalformedURLException if unable to do so. 10 | */ 11 | /*def fromString(s: String): ActorPath = s match { 12 | case ActorPathExtractor(addr, elems) => RootActorPath(addr) / elems 13 | case _ => throw new /*MalformedURLException*/IllegalArgumentException("cannot parse as ActorPath: " + s) 14 | }*/ 15 | 16 | /** 17 | * This Regular Expression is used to validate a path element (Actor Name). 18 | * Since Actors form a tree, it is addressable using an URL, therefore an Actor Name has to conform to: 19 | * http://www.ietf.org/rfc/rfc2396.txt 20 | */ 21 | val ElementRegex = 22 | """(?:[-\w:@&=+,.!~*'_;]|%\p{XDigit}{2})(?:[-\w:@&=+,.!~*'$_;]|%\p{XDigit}{2})*""".r 23 | 24 | private[akka] final val emptyActorPath: immutable.Iterable[String] = List("") 25 | } 26 | 27 | /** 28 | * Actor path is a unique path to an actor that shows the creation path 29 | * up through the actor tree to the root actor. 30 | * 31 | * ActorPath defines a natural ordering (so that ActorRefs can be put into 32 | * collections with this requirement); this ordering is intended to be as fast 33 | * as possible, which owing to the bottom-up recursive nature of ActorPath 34 | * is sorted by path elements FROM RIGHT TO LEFT, where RootActorPath > 35 | * ChildActorPath in case the number of elements is different. 36 | * 37 | * Two actor paths are compared equal when they have the same name and parent 38 | * elements, including the root address information. That does not necessarily 39 | * mean that they point to the same incarnation of the actor if the actor is 40 | * re-created with the same path. In other words, in contrast to how actor 41 | * references are compared the unique id of the actor is not taken into account 42 | * when comparing actor paths. 43 | */ 44 | sealed trait ActorPath extends Comparable[ActorPath] { 45 | /** 46 | * The Address under which this path can be reached; walks up the tree to 47 | * the RootActorPath. 48 | */ 49 | def address: Address 50 | 51 | /** 52 | * The name of the actor that this path refers to. 53 | */ 54 | def name: String 55 | 56 | /** 57 | * The path for the parent actor. 58 | */ 59 | def parent: ActorPath 60 | 61 | /** 62 | * Create a new child actor path. 63 | */ 64 | def /(child: String): ActorPath 65 | 66 | /** 67 | * Recursively create a descendant’s path by appending all child names. 68 | */ 69 | def /(child: Iterable[String]): ActorPath = 70 | (this /: child)((path, elem) => if (elem.isEmpty) path else path / elem) 71 | 72 | /** 73 | * Sequence of names for this path from root to this. Performance implication: has to allocate a list. 74 | */ 75 | def elements: immutable.Iterable[String] 76 | 77 | /** 78 | * Walk up the tree to obtain and return the RootActorPath. 79 | */ 80 | def root: RootActorPath 81 | 82 | def toSerializationFormat: String 83 | 84 | def toStringWithAddress(addr: Address): String 85 | 86 | def toSerializationFormatWithAddress(addr: Address): String 87 | 88 | /** 89 | * INTERNAL API 90 | * Unique identifier of the actor. Used for distinguishing 91 | * different incarnations of actors with same path (name elements). 92 | */ 93 | private[akka] def uid: Int 94 | 95 | /** 96 | * INTERNAL API 97 | * Creates a new ActorPath with same elements but with the specified `uid`. 98 | */ 99 | private[akka] def withUid(uid: Int): ActorPath 100 | } 101 | 102 | /** 103 | * Root of the hierarchy of ActorPaths. There is exactly one root per 104 | * ActorSystem and node (for remote-enabled or clustered systems). 105 | */ 106 | final case class RootActorPath(address: Address, name: String = "/") extends ActorPath { 107 | 108 | override def parent: ActorPath = this 109 | 110 | override def root: RootActorPath = this 111 | 112 | override def /(child: String): ActorPath = { 113 | val (childName, uid) = ActorCell.splitNameAndUid(child) 114 | new ChildActorPath(this, childName)(uid) 115 | } 116 | 117 | override def elements: immutable.Iterable[String] = ActorPath.emptyActorPath 118 | 119 | override val toString: String = address + name 120 | 121 | override val toSerializationFormat: String = toString 122 | 123 | override def toStringWithAddress(addr: Address): String = 124 | /*if (address.host.isDefined) address + name 125 | else*/ addr + name 126 | 127 | override def toSerializationFormatWithAddress(addr: Address): String = toStringWithAddress(addr) 128 | 129 | override def compareTo(other: ActorPath): Int = other match { 130 | case r: RootActorPath => toString compareTo r.toString // FIXME make this cheaper by comparing address and name in isolation 131 | case c: ChildActorPath => 1 132 | } 133 | 134 | /** 135 | * INTERNAL API 136 | */ 137 | override private[akka] def uid: Int = ActorCell.undefinedUid 138 | 139 | /** 140 | * INTERNAL API 141 | */ 142 | override private[akka] def withUid(uid: Int): ActorPath = 143 | if (uid == ActorCell.undefinedUid) this 144 | else throw new IllegalStateException( 145 | s"RootActorPath must have undefinedUid, [$uid != ${ActorCell.undefinedUid}") 146 | } 147 | 148 | final case class ChildActorPath private[akka] ( 149 | val parent: ActorPath, val name: String)( 150 | override private[akka] val uid: Int) extends ActorPath { 151 | 152 | if (name.indexOf('/') != -1) 153 | throw new IllegalArgumentException( 154 | "/ is a path separator and is not legal in ActorPath names: [%s]" format name) 155 | if (name.indexOf('#') != -1) 156 | throw new IllegalArgumentException( 157 | "# is a fragment separator and is not legal in ActorPath names: [%s]" format name) 158 | 159 | override def address: Address = root.address 160 | 161 | override def /(child: String): ActorPath = { 162 | val (childName, uid) = ActorCell.splitNameAndUid(child) 163 | new ChildActorPath(this, childName)(uid) 164 | } 165 | 166 | override def elements: immutable.Iterable[String] = { 167 | @tailrec 168 | def rec(p: ActorPath, acc: List[String]): immutable.Iterable[String] = p match { 169 | case r: RootActorPath => acc 170 | case _ => rec(p.parent, p.name :: acc) 171 | } 172 | rec(this, Nil) 173 | } 174 | 175 | override def root: RootActorPath = { 176 | @tailrec 177 | def rec(p: ActorPath): RootActorPath = p match { 178 | case r: RootActorPath => r 179 | case _ => rec(p.parent) 180 | } 181 | rec(this) 182 | } 183 | 184 | override private[akka] def withUid(uid: Int): ActorPath = 185 | if (uid == this.uid) this 186 | else new ChildActorPath(parent, name)(uid) 187 | 188 | override def toString: String = { 189 | buildToString(_.toString) 190 | } 191 | 192 | override def toSerializationFormat: String = toString() 193 | 194 | private def toStringLength: Int = toStringOffset + name.length 195 | 196 | private val toStringOffset: Int = parent match { 197 | case r: RootActorPath => r.address.toString.length + r.name.length 198 | case c: ChildActorPath => c.toStringLength + 1 199 | } 200 | 201 | override def toStringWithAddress(addr: Address): String = { 202 | buildToString(_.toStringWithAddress(addr)) 203 | } 204 | 205 | override def toSerializationFormatWithAddress(addr: Address): String = { 206 | toStringWithAddress(addr) 207 | } 208 | 209 | private def addressStringLengthDiff(addr: Address): Int = { 210 | val r = root 211 | /*if (r.address.host.isDefined) 0 212 | else*/ (addr.toString.length - r.address.toString.length) 213 | } 214 | 215 | /** 216 | * Optimized toString construction. Used by `toString`, `toSerializationFormat`, 217 | * and friends `WithAddress` 218 | * @param rootString function to construct the root element string 219 | */ 220 | private def buildToString(rootString: RootActorPath => String): String = { 221 | @tailrec 222 | def rec(p: ActorPath, suffix: String): String = p match { 223 | case r: RootActorPath => 224 | rootString(r) + suffix 225 | case c: ChildActorPath => 226 | rec(c.parent, c.name + "/" + suffix) 227 | } 228 | 229 | rec(this.parent, this.name) 230 | } 231 | 232 | override def compareTo(other: ActorPath): Int = { 233 | @tailrec 234 | def rec(left: ActorPath, right: ActorPath): Int = 235 | if (left eq right) 0 236 | else if (left.isInstanceOf[RootActorPath]) left compareTo right 237 | else if (right.isInstanceOf[RootActorPath]) -(right compareTo left) 238 | else { 239 | val x = left.name compareTo right.name 240 | if (x == 0) rec(left.parent, right.parent) 241 | else x 242 | } 243 | 244 | rec(this, other) 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/scalajs/webworkers/WebWorkerRouter.scala: -------------------------------------------------------------------------------- 1 | package akka.scalajs.webworkers 2 | 3 | import scala.scalajs.js 4 | import js.Dynamic.global 5 | import akka.scalajs.jsapi._ 6 | 7 | import akka.actor._ 8 | import akka.util.JSMap 9 | 10 | /** Router of the current web worker. 11 | * It provides a means to send messages to any actor system on any worker in 12 | * the same hierarchy. 13 | * Typically, the root of the hierarchy is the script on the page. The tree 14 | * follows the physical construction of workers. 15 | * Since there is exactly one router per worker, this is a top-level object. 16 | * 17 | * As a user, you need to call either initializeAsRoot() or setupAsChild(). 18 | * You can register blocks of code to be executed as soon as the router is 19 | * initialized with onInitialized(). 20 | */ 21 | object WebWorkerRouter { 22 | private var myAddress: String = "" 23 | private var nextChildAddress = 1 24 | private val children = JSMap.empty[Worker] 25 | private val systems = JSMap.empty[ActorSystemImpl] 26 | private var onInitializedEvents: List[() => _] = Nil 27 | 28 | def isInitialized: Boolean = myAddress != "" 29 | 30 | /** Returns the address of this router. 31 | * Requires this router to be initialized. 32 | */ 33 | def address: String = { 34 | assertInitialized() 35 | myAddress 36 | } 37 | 38 | /** Returns true iff this router is the root of its hierarchy. 39 | * Requires this router to be initialized. 40 | */ 41 | def isRoot: Boolean = { 42 | assertInitialized() 43 | myAddress == "/" 44 | } 45 | 46 | /** Returns the address of the parent of this router. 47 | * Requires this router to be initialized and not to be the root. 48 | */ 49 | def parentAddress: String = { 50 | assert(!isRoot, "Root router has no parent") 51 | val pos = myAddress.lastIndexOf('/', myAddress.length-2) 52 | myAddress.substring(0, pos+1) 53 | } 54 | 55 | private def assertInitialized(): Unit = { 56 | assert(isInitialized, 57 | "Operation cannot be executed when router is not initialized") 58 | } 59 | 60 | /** Initialize this router as the root of a hierarchy. 61 | * Pending onInitialized blocks are executed immediately. 62 | */ 63 | def initializeAsRoot(): Unit = { 64 | initializeWithAddress("/") 65 | } 66 | 67 | /** Setup this router as a child. 68 | * The router will listen from an initialization message coming from the 69 | * parent worker. 70 | */ 71 | def setupAsChild(): Unit = { 72 | val cruiseListener: js.Function1[js.Object, _] = { (event: js.Object) => 73 | val data = event.asInstanceOf[MessageEvent].data 74 | global.console.log("Child WebWorkerRouter receives: " + js.JSON.stringify(data)) 75 | if (!(!data.isWebWorkerRouterPostMessage)) { 76 | forwardMessage(data) 77 | } 78 | } 79 | var initializeListener: js.Function1[js.Object, _] = null 80 | initializeListener = { (event: js.Object) => 81 | val data = event.asInstanceOf[MessageEvent].data 82 | global.console.log("Child WebWorkerRouter receives: " + js.JSON.stringify(data)) 83 | if (!(!data.isWebWorkerRouterInitialize)) { 84 | ParentWorkerConnection.removeEventListener( 85 | "message", initializeListener, useCapture = false) 86 | ParentWorkerConnection.addEventListener( 87 | "message", cruiseListener, useCapture = false) 88 | 89 | val address = data.address.asInstanceOf[js.String] 90 | initializeWithAddress(address) 91 | } else if (!(!data.isWebWorkerRouterPostMessage)) { 92 | onInitialized { 93 | forwardMessage(data) 94 | } 95 | } 96 | } 97 | ParentWorkerConnection.addEventListener( 98 | "message", initializeListener, useCapture = false) 99 | } 100 | 101 | /** Execute a block of code when the router has been initialized. 102 | * If the router is already initialized, the block of code is executed 103 | * immediately. 104 | */ 105 | def onInitialized(body: => Unit): Unit = { 106 | if (isInitialized) { 107 | body 108 | } else { 109 | onInitializedEvents ::= (() => body) 110 | } 111 | } 112 | 113 | /** Posts a message to the given system at the given address. 114 | * If the router is not yet initialized, the message will be queued up and 115 | * sent when initialization is done. 116 | */ 117 | def postMessageTo(address: String, system: String, message: js.Any): Unit = { 118 | if (address == "") 119 | throw new IllegalArgumentException("Address must not be empty") 120 | if (system == "") 121 | throw new IllegalArgumentException("System must not be empty") 122 | 123 | onInitialized { 124 | if (address == myAddress) { 125 | postLocalMessageTo(system, message) 126 | } else { 127 | val data = js.Dynamic.literal( 128 | isWebWorkerRouterPostMessage = true, 129 | address = address, 130 | system = system, 131 | message = message) 132 | forwardMessage(data) 133 | } 134 | } 135 | } 136 | 137 | /** Posts a message to a system on the same router. 138 | * This works even when the router is not initialized. 139 | */ 140 | def postLocalMessageTo(system: String, message: js.Any): Unit = { 141 | Timers.setImmediate { 142 | deliverMessage(system, message) 143 | } 144 | } 145 | 146 | /** Posts a message to an actor system address. */ 147 | def postMessageTo(address: Address, message: js.Any): Unit = { 148 | if (address.hasLocalScope) 149 | postLocalMessageTo(address.system, message) 150 | else 151 | postMessageTo(address.worker.get, address.system, message) 152 | } 153 | 154 | /** Registers a worker as child of this one and returns its address. 155 | * If the given worker has already been registered to this router, 156 | * registerChild does nothing and returns the already attributed address of 157 | * that worker. 158 | * Requires this worker to be initialized. 159 | */ 160 | def registerChild(worker: Worker): String = { 161 | assertInitialized() 162 | 163 | children collectFirst { 164 | case (name, w) if w eq worker => myAddress + name + "/" 165 | } match { 166 | case Some(address) => address 167 | case None => registerNewChild(worker) 168 | } 169 | } 170 | 171 | /** Creates a child worker with the given script and registers it. 172 | * Requires this worker to be initialized. 173 | */ 174 | def createChild(script: String): String = { 175 | assertInitialized() 176 | val worker = new Worker(script) 177 | registerNewChild(worker) 178 | } 179 | 180 | private def registerNewChild(worker: Worker): String = { 181 | worker.addEventListener("message", { (event: js.Object) => 182 | val data = event.asInstanceOf[MessageEvent].data 183 | global.console.log("Parent WebWorkerRouter receives:", data) 184 | if (!(!data.isWebWorkerRouterPostMessage)) 185 | forwardMessage(data) 186 | }, useCapture = false) 187 | 188 | val childName = nextChildAddress.toString 189 | nextChildAddress += 1 190 | 191 | children(childName) = worker 192 | 193 | val address = myAddress + childName + "/" 194 | val initializeData = js.Dynamic.literal( 195 | isWebWorkerRouterInitialize = true, 196 | address = address) 197 | global.console.log("Parent WebWorkerRouter sends:", initializeData) 198 | worker.postMessage(initializeData) 199 | address 200 | } 201 | 202 | private[akka] def registerSystem(name: String, 203 | system: ActorSystemImpl): Unit = { 204 | systems(name) = system 205 | } 206 | 207 | private def initializeWithAddress(address: String): Unit = { 208 | if (isInitialized) 209 | throw new IllegalStateException( 210 | "Cannot initialize an already initialized router") 211 | 212 | myAddress = address 213 | val events = onInitializedEvents.reverse 214 | onInitializedEvents = Nil 215 | for (event <- events) 216 | event() 217 | } 218 | 219 | private def forwardMessage(data: js.Dynamic): Unit = { 220 | val address: String = data.address.asInstanceOf[js.String] 221 | 222 | if (address == myAddress) { 223 | // Arrived at destination 224 | deliverMessage(data.system.asInstanceOf[js.String], data.message) 225 | } else if (address.startsWith(myAddress)) { 226 | // Destination is one of my descendants 227 | val childName = address.substring(myAddress.length).split("/")(0) 228 | val childWorker = children(childName) 229 | global.console.log("Parent WebWorkerRouter sends:", data) 230 | childWorker.postMessage(data) 231 | } else { 232 | // Destination is not one of my descendants - forward to my parent 233 | global.console.log("Child WebWorkerRouter sends: " + js.JSON.stringify(data)) 234 | ParentWorkerConnection.postMessage(data) 235 | } 236 | } 237 | 238 | private def deliverMessage(system: String, message: js.Any): Unit = { 239 | val sys = systems(system) 240 | sys.deliverMessageFromRouter(message.asInstanceOf[js.Dynamic]) 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/actor/dungeon/DeathWatch.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2009-2013 Typesafe Inc. 3 | */ 4 | 5 | package akka.actor 6 | package dungeon 7 | 8 | import akka.dispatch.sysmsg.{ DeathWatchNotification, Watch, Unwatch } 9 | import scala.util.control.NonFatal 10 | 11 | import akka.event.Logging._ 12 | 13 | private[akka] trait DeathWatch { this: ActorCell => 14 | 15 | private var watching: Set[ActorRef] = ActorCell.emptyActorRefSet 16 | private var watchedBy: Set[ActorRef] = ActorCell.emptyActorRefSet 17 | private var terminatedQueued: Set[ActorRef] = ActorCell.emptyActorRefSet 18 | 19 | override final def watch(subject: ActorRef): ActorRef = subject match { 20 | case a: InternalActorRef => 21 | if (a != self && !watchingContains(a)) { 22 | maintainAddressTerminatedSubscription(a) { 23 | a.sendSystemMessage(Watch(a, self)) 24 | watching += a 25 | } 26 | } 27 | a 28 | } 29 | 30 | override final def unwatch(subject: ActorRef): ActorRef = subject match { 31 | case a: InternalActorRef => 32 | if (a != self && watchingContains(a)) { 33 | a.sendSystemMessage(Unwatch(a, self)) 34 | maintainAddressTerminatedSubscription(a) { 35 | watching = removeFromSet(a, watching) 36 | } 37 | } 38 | terminatedQueued = removeFromSet(a, terminatedQueued) 39 | a 40 | } 41 | 42 | protected def receivedTerminated(t: Terminated): Unit = 43 | if (terminatedQueued(t.actor)) { 44 | terminatedQueued -= t.actor // here we know that it is the SAME ref which was put in 45 | receiveMessage(t) 46 | } 47 | 48 | /** 49 | * When this actor is watching the subject of [[akka.actor.Terminated]] message 50 | * it will be propagated to user's receive. 51 | */ 52 | protected def watchedActorTerminated(actor: ActorRef, 53 | existenceConfirmed: Boolean, addressTerminated: Boolean): Unit = { 54 | 55 | if (isChild(actor)) 56 | handleChildTerminated(actor) 57 | if (watchingContains(actor)) { 58 | maintainAddressTerminatedSubscription(actor) { 59 | watching = removeFromSet(actor, watching) 60 | } 61 | if (!isTerminating) { 62 | self.tell(Terminated(actor)(existenceConfirmed, addressTerminated), actor) 63 | terminatedQueued += actor 64 | } 65 | } 66 | } 67 | 68 | // TODO this should be removed and be replaced with `watching.contains(subject)` 69 | // when all actor references have uid, i.e. actorFor is removed 70 | private def watchingContains(subject: ActorRef): Boolean = 71 | watching.contains(subject) || (subject.path.uid != ActorCell.undefinedUid && 72 | watching.contains(new UndefinedUidActorRef(subject))) 73 | 74 | // TODO this should be removed and be replaced with `set - subject` 75 | // when all actor references have uid, i.e. actorFor is removed 76 | private def removeFromSet(subject: ActorRef, set: Set[ActorRef]): Set[ActorRef] = 77 | if (subject.path.uid != ActorCell.undefinedUid) (set - subject) - new UndefinedUidActorRef(subject) 78 | else set filterNot (_.path == subject.path) 79 | 80 | protected def tellWatchersWeDied(actor: Actor): Unit = 81 | if (!watchedBy.isEmpty) { 82 | try { 83 | def sendTerminated(ifLocal: Boolean)(watcher: ActorRef): Unit = 84 | if ((watcher: InternalActorRef).isLocal == ifLocal) watcher.asInstanceOf[InternalActorRef].sendSystemMessage( 85 | DeathWatchNotification(self, existenceConfirmed = true, addressTerminated = false)) 86 | 87 | /* 88 | * It is important to notify the remote watchers first, otherwise RemoteDaemon might shut down, causing 89 | * the remoting to shut down as well. At this point Terminated messages to remote watchers are no longer 90 | * deliverable. 91 | * 92 | * The problematic case is: 93 | * 1. Terminated is sent to RemoteDaemon 94 | * 1a. RemoteDaemon is fast enough to notify the terminator actor in RemoteActorRefProvider 95 | * 1b. The terminator is fast enough to enqueue the shutdown command in the remoting 96 | * 2. Only at this point is the Terminated (to be sent remotely) enqueued in the mailbox of remoting 97 | * 98 | * If the remote watchers are notified first, then the mailbox of the Remoting will guarantee the correct order. 99 | */ 100 | watchedBy foreach sendTerminated(ifLocal = false) 101 | watchedBy foreach sendTerminated(ifLocal = true) 102 | } finally watchedBy = ActorCell.emptyActorRefSet 103 | } 104 | 105 | protected def unwatchWatchedActors(actor: Actor): Unit = 106 | if (!watching.isEmpty) { 107 | maintainAddressTerminatedSubscription() { 108 | try { 109 | watching foreach { 110 | case watchee: InternalActorRef => 111 | watchee.sendSystemMessage(Unwatch(watchee, self)) 112 | } 113 | } finally { 114 | watching = ActorCell.emptyActorRefSet 115 | terminatedQueued = ActorCell.emptyActorRefSet 116 | } 117 | } 118 | } 119 | 120 | protected def addWatcher(watchee: ActorRef, watcher: ActorRef): Unit = { 121 | val watcheeSelf = watchee == self 122 | val watcherSelf = watcher == self 123 | 124 | if (watcheeSelf && !watcherSelf) { 125 | if (!watchedBy.contains(watcher)) maintainAddressTerminatedSubscription(watcher) { 126 | watchedBy += watcher 127 | //if (system.settings.DebugLifecycle) publish(Debug(self.path.toString, clazz(actor), "now monitoring " + watcher)) 128 | } 129 | } else if (!watcheeSelf && watcherSelf) { 130 | watch(watchee) 131 | } else { 132 | publish(Warning(self.path.toString, clazz(actor), 133 | "BUG: illegal Watch(%s,%s) for %s".format(watchee, watcher, self))) 134 | } 135 | } 136 | 137 | protected def remWatcher(watchee: ActorRef, watcher: ActorRef): Unit = { 138 | val watcheeSelf = watchee == self 139 | val watcherSelf = watcher == self 140 | 141 | if (watcheeSelf && !watcherSelf) { 142 | if (watchedBy.contains(watcher)) maintainAddressTerminatedSubscription(watcher) { 143 | watchedBy -= watcher 144 | //if (system.settings.DebugLifecycle) publish(Debug(self.path.toString, clazz(actor), "stopped monitoring " + watcher)) 145 | } 146 | } else if (!watcheeSelf && watcherSelf) { 147 | unwatch(watchee) 148 | } else { 149 | publish(Warning(self.path.toString, clazz(actor), 150 | "BUG: illegal Unwatch(%s,%s) for %s".format(watchee, watcher, self))) 151 | } 152 | } 153 | 154 | protected def addressTerminated(address: Address): Unit = { 155 | // cleanup watchedBy since we know they are dead 156 | maintainAddressTerminatedSubscription() { 157 | for (a <- watchedBy; if a.path.address == address) watchedBy -= a 158 | } 159 | 160 | // send DeathWatchNotification to self for all matching subjects 161 | // that are not child with existenceConfirmed = false because we could have been watching a 162 | // non-local ActorRef that had never resolved before the other node went down 163 | // When a parent is watching a child and it terminates due to AddressTerminated 164 | // it is removed by sending DeathWatchNotification with existenceConfirmed = true to support 165 | // immediate creation of child with same name. 166 | for (a <- watching; if a.path.address == address) { 167 | (self: InternalActorRef).sendSystemMessage( 168 | DeathWatchNotification(a, existenceConfirmed = isChild(a), 169 | addressTerminated = true)) 170 | } 171 | } 172 | 173 | /** 174 | * Starts subscription to AddressTerminated if not already subscribing and the 175 | * block adds a non-local ref to watching or watchedBy. 176 | * Ends subscription to AddressTerminated if subscribing and the 177 | * block removes the last non-local ref from watching and watchedBy. 178 | */ 179 | private def maintainAddressTerminatedSubscription[T]( 180 | change: ActorRef = null)(block: => T): T = { 181 | def isNonLocal(ref: ActorRef) = ref match { 182 | case null ⇒ true 183 | case a: InternalActorRef if !a.isLocal => true 184 | case _ => false 185 | } 186 | 187 | if (isNonLocal(change)) { 188 | def hasNonLocalAddress: Boolean = 189 | ((watching exists isNonLocal) || (watchedBy exists isNonLocal)) 190 | val had = hasNonLocalAddress 191 | val result = block 192 | val has = hasNonLocalAddress 193 | if (had && !has) unsubscribeAddressTerminated() 194 | else if (!had && has) subscribeAddressTerminated() 195 | result 196 | } else { 197 | block 198 | } 199 | } 200 | 201 | private def unsubscribeAddressTerminated(): Unit = { 202 | // TODO 203 | //system.eventStream.unsubscribe(self, classOf[AddressTerminated]) 204 | } 205 | 206 | private def subscribeAddressTerminated(): Unit = { 207 | // TODO 208 | //system.eventStream.subscribe(self, classOf[AddressTerminated]) 209 | } 210 | 211 | } 212 | 213 | private[akka] class UndefinedUidActorRef(ref: ActorRef) extends MinimalActorRef { 214 | override val path = ref.path.withUid(ActorCell.undefinedUid) 215 | override def provider = throw new UnsupportedOperationException("UndefinedUidActorRef does not provide") 216 | } 217 | -------------------------------------------------------------------------------- /examples/faulttolerance/src/main/scala/org/scalajs/examples/faulttolerance/FaultHandlingDocSample.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.examples.faulttolerance 2 | 3 | import scala.language.postfixOps 4 | 5 | import akka.actor._ 6 | import akka.actor.SupervisorStrategy._ 7 | import akka.util.Timeout 8 | import akka.event.LoggingReceive 9 | import akka.pattern.Ask.ask 10 | import akka.pattern.PipeTo.pipe 11 | import scala.concurrent.duration._ 12 | 13 | import scala.scalajs.js 14 | import js.annotation.JSExport 15 | 16 | /*import org.scalajs.actors.pattern.{ ask, pipe } 17 | import com.typesafe.config.ConfigFactory*/ 18 | 19 | trait ActorLogging { 20 | 21 | object log { 22 | def log(msg: String): Unit = js.Dynamic.global.console.log(msg) 23 | def info(msg: String): Unit = js.Dynamic.global.console.info(msg) 24 | def error(msg: String): Unit = js.Dynamic.global.console.error(msg) 25 | } 26 | 27 | } 28 | 29 | /** 30 | * Runs the sample 31 | */ 32 | @JSExport 33 | object FaultHandlingDocSample { 34 | import Worker._ 35 | 36 | @JSExport 37 | def main(): Unit = { 38 | /*val config = ConfigFactory.parseString(""" 39 | akka.loglevel = "DEBUG" 40 | akka.actor.debug { 41 | receive = on 42 | lifecycle = on 43 | } 44 | """)*/ 45 | 46 | val system = ActorSystem("FaultToleranceSample") 47 | val worker = system.actorOf(Props(new Worker), name = "worker") 48 | val listener = system.actorOf(Props(new Listener), name = "listener") 49 | // start the work and listen on progress 50 | // note that the listener is used as sender of the tell, 51 | // i.e. it will receive replies from the worker 52 | worker.tell(Start, sender = listener) 53 | } 54 | } 55 | 56 | /** 57 | * Listens on progress from the worker and shuts down the system when enough 58 | * work has been done. 59 | */ 60 | class Listener extends Actor with ActorLogging { 61 | import Worker._ 62 | // If we don't get any progress within 15 seconds then the service is unavailable 63 | context.setReceiveTimeout(15 seconds) 64 | 65 | def receive = { 66 | case Progress(percent) => 67 | log.info(s"Current progress: $percent %") 68 | if (percent >= 100.0) { 69 | log.info("That's all, shutting down") 70 | context.system.shutdown() 71 | } 72 | 73 | case ReceiveTimeout => 74 | // No progress within 15 seconds, ServiceUnavailable 75 | log.error("Shutting down due to unavailable service") 76 | context.system.shutdown() 77 | } 78 | } 79 | 80 | object Worker { 81 | case object Start 82 | case object Do 83 | case class Progress(percent: Double) 84 | } 85 | 86 | /** 87 | * Worker performs some work when it receives the `Start` message. 88 | * It will continuously notify the sender of the `Start` message 89 | * of current ``Progress``. The `Worker` supervise the `CounterService`. 90 | */ 91 | class Worker extends Actor with ActorLogging { 92 | import Worker._ 93 | import CounterService._ 94 | implicit val askTimeout = Timeout(5 seconds) 95 | 96 | // Stop the CounterService child if it throws ServiceUnavailable 97 | override val supervisorStrategy = OneForOneStrategy() { 98 | case _: CounterService.ServiceUnavailable => Stop 99 | } 100 | 101 | // The sender of the initial Start message will continuously be notified 102 | // about progress 103 | var progressListener: Option[ActorRef] = None 104 | val counterService = context.actorOf(Props(new CounterService), name = "counter") 105 | val totalCount = 51 106 | implicit def dispatcher = context.dispatcher 107 | //import context.dispatcher // Use this Actors' Dispatcher as ExecutionContext 108 | 109 | def receive = LoggingReceive { 110 | case Start if progressListener.isEmpty => 111 | progressListener = Some(sender) 112 | context.system.scheduler.schedule(Duration.Zero, 1 second, self, Do) 113 | 114 | case Do => 115 | counterService ! Increment(1) 116 | counterService ! Increment(1) 117 | counterService ! Increment(1) 118 | 119 | // Send current progress to the initial sender 120 | counterService ? GetCurrentCount map { 121 | case CurrentCount(_, count) => Progress(100.0 * count / totalCount) 122 | } pipeTo progressListener.get 123 | } 124 | } 125 | 126 | object CounterService { 127 | case class Increment(n: Int) 128 | case object GetCurrentCount 129 | case class CurrentCount(key: String, count: Long) 130 | class ServiceUnavailable(msg: String) extends RuntimeException(msg) 131 | 132 | private case object Reconnect 133 | } 134 | 135 | /** 136 | * Adds the value received in `Increment` message to a persistent 137 | * counter. Replies with `CurrentCount` when it is asked for `CurrentCount`. 138 | * `CounterService` supervise `Storage` and `Counter`. 139 | */ 140 | class CounterService extends Actor { 141 | import CounterService._ 142 | import Counter._ 143 | import Storage._ 144 | 145 | // Restart the storage child when StorageException is thrown. 146 | // After 3 restarts within 5 seconds it will be stopped. 147 | override val supervisorStrategy = OneForOneStrategy(maxNrOfRetries = 3, 148 | withinTimeRange = 5 seconds) { 149 | case _: Storage.StorageException => Restart 150 | } 151 | 152 | val key = self.path.name 153 | var storage: Option[ActorRef] = None 154 | var counter: Option[ActorRef] = None 155 | var backlog = IndexedSeq.empty[(ActorRef, Any)] 156 | val MaxBacklog = 10000 157 | 158 | implicit def dispatcher = context.dispatcher 159 | //import context.dispatcher // Use this Actors' Dispatcher as ExecutionContext 160 | 161 | override def preStart() { 162 | initStorage() 163 | } 164 | 165 | /** 166 | * The child storage is restarted in case of failure, but after 3 restarts, 167 | * and still failing it will be stopped. Better to back-off than continuously 168 | * failing. When it has been stopped we will schedule a Reconnect after a delay. 169 | * Watch the child so we receive Terminated message when it has been terminated. 170 | */ 171 | def initStorage() { 172 | storage = Some(context.watch(context.actorOf(Props(new Storage), name = "storage"))) 173 | // Tell the counter, if any, to use the new storage 174 | counter foreach { _ ! UseStorage(storage) } 175 | // We need the initial value to be able to operate 176 | storage.get ! Get(key) 177 | } 178 | 179 | def receive = LoggingReceive { 180 | 181 | case Entry(k, v) if k == key && counter == None => 182 | // Reply from Storage of the initial value, now we can create the Counter 183 | val c = context.actorOf(Props(new Counter(key, v))) 184 | counter = Some(c) 185 | // Tell the counter to use current storage 186 | c ! UseStorage(storage) 187 | // and send the buffered backlog to the counter 188 | for ((replyTo, msg) <- backlog) c.tell(msg, sender = replyTo) 189 | backlog = IndexedSeq.empty 190 | 191 | case msg @ Increment(n) => forwardOrPlaceInBacklog(msg) 192 | 193 | case msg @ GetCurrentCount => forwardOrPlaceInBacklog(msg) 194 | 195 | case Terminated(actorRef) if Some(actorRef) == storage => 196 | // After 3 restarts the storage child is stopped. 197 | // We receive Terminated because we watch the child, see initStorage. 198 | storage = None 199 | // Tell the counter that there is no storage for the moment 200 | counter foreach { _ ! UseStorage(None) } 201 | // Try to re-establish storage after while 202 | context.system.scheduler.scheduleOnce(10 seconds, self, Reconnect) 203 | 204 | case Reconnect => 205 | // Re-establish storage after the scheduled delay 206 | initStorage() 207 | } 208 | 209 | def forwardOrPlaceInBacklog(msg: Any) { 210 | // We need the initial value from storage before we can start delegate to 211 | // the counter. Before that we place the messages in a backlog, to be sent 212 | // to the counter when it is initialized. 213 | counter match { 214 | case Some(c) => c forward msg 215 | case None => 216 | if (backlog.size >= MaxBacklog) 217 | throw new ServiceUnavailable( 218 | "CounterService not available, lack of initial value") 219 | backlog :+= (sender -> msg) 220 | } 221 | } 222 | 223 | } 224 | 225 | object Counter { 226 | case class UseStorage(storage: Option[ActorRef]) 227 | } 228 | 229 | /** 230 | * The in memory count variable that will send current 231 | * value to the `Storage`, if there is any storage 232 | * available at the moment. 233 | */ 234 | class Counter(key: String, initialValue: Long) extends Actor { 235 | import Counter._ 236 | import CounterService._ 237 | import Storage._ 238 | 239 | var count = initialValue 240 | var storage: Option[ActorRef] = None 241 | 242 | def receive = LoggingReceive { 243 | case UseStorage(s) => 244 | storage = s 245 | storeCount() 246 | 247 | case Increment(n) => 248 | count += n 249 | storeCount() 250 | 251 | case GetCurrentCount => 252 | sender ! CurrentCount(key, count) 253 | 254 | } 255 | 256 | def storeCount() { 257 | // Delegate dangerous work, to protect our valuable state. 258 | // We can continue without storage. 259 | storage foreach { _ ! Store(Entry(key, count)) } 260 | } 261 | 262 | } 263 | 264 | object Storage { 265 | case class Store(entry: Entry) 266 | case class Get(key: String) 267 | case class Entry(key: String, value: Long) 268 | class StorageException(msg: String) extends RuntimeException(msg) 269 | } 270 | 271 | /** 272 | * Saves key/value pairs to persistent storage when receiving `Store` message. 273 | * Replies with current value when receiving `Get` message. 274 | * Will throw StorageException if the underlying data store is out of order. 275 | */ 276 | class Storage extends Actor { 277 | import Storage._ 278 | 279 | val db = DummyDB 280 | 281 | def receive = LoggingReceive { 282 | case Store(Entry(key, count)) => db.save(key, count) 283 | case Get(key) => sender ! Entry(key, db.load(key).getOrElse(0L)) 284 | } 285 | } 286 | 287 | object DummyDB { 288 | import Storage.StorageException 289 | private var db = Map[String, Long]() 290 | 291 | def save(key: String, value: Long): Unit = synchronized { 292 | if (11 <= value && value <= 14) 293 | throw new StorageException("Simulated store failure " + value) 294 | db += (key -> value) 295 | } 296 | 297 | def load(key: String): Option[Long] = synchronized { 298 | db.get(key) 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /actors/src/main/scala/akka/actor/dungeon/FaultHandling.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2009-2013 Typesafe Inc. 3 | */ 4 | 5 | package akka.actor 6 | package dungeon 7 | 8 | import scala.collection.immutable 9 | import scala.concurrent.duration.Duration 10 | import scala.util.control.Exception._ 11 | import scala.util.control.NonFatal 12 | 13 | import akka.event.Logging 14 | import akka.event.Logging.{Error, Debug} 15 | import akka.dispatch.sysmsg._ 16 | 17 | private[akka] trait FaultHandling { this: ActorCell => 18 | 19 | /* ================= 20 | * T H E R U L E S 21 | * ================= 22 | * 23 | * Actors can be suspended for two reasons: 24 | * - they fail 25 | * - their supervisor gets suspended 26 | * 27 | * In particular they are not suspended multiple times because of cascading 28 | * own failures, i.e. while currentlyFailed() they do not fail again. In case 29 | * of a restart, failures in constructor/preStart count as new failures. 30 | */ 31 | 32 | private def suspendNonRecursive(): Unit = dispatcher suspend this 33 | 34 | private def resumeNonRecursive(): Unit = dispatcher resume this 35 | 36 | /* 37 | * have we told our supervisor that we Failed() and have not yet heard back? 38 | * (actually: we might have heard back but not yet acted upon it, in case of 39 | * a restart with dying children) 40 | * might well be replaced by ref to a Cancellable in the future (see #2299) 41 | */ 42 | private var _failed: ActorRef = null 43 | private def isFailed: Boolean = _failed != null 44 | private def setFailed(perpetrator: ActorRef): Unit = _failed = perpetrator 45 | private def clearFailed(): Unit = _failed = null 46 | private def perpetrator: ActorRef = _failed 47 | 48 | /** 49 | * Do re-create the actor in response to a failure. 50 | */ 51 | protected def faultRecreate(cause: Throwable): Unit = 52 | if (actor == null) { 53 | system.eventStream.publish(Error(self.path.toString, clazz(actor), 54 | "changing Recreate into Create after " + cause)) 55 | faultCreate() 56 | } else if (isNormal) { 57 | val failedActor = actor 58 | if (system.settings.DebugLifecycle) 59 | publish(Debug(self.path.toString, clazz(failedActor), "restarting")) 60 | if (failedActor ne null) { 61 | val optionalMessage = if (currentMessage ne null) Some(currentMessage.message) else None 62 | try { 63 | // if the actor fails in preRestart, we can do nothing but log it: it’s best-effort 64 | if (failedActor.context ne null) failedActor.preRestart(cause, optionalMessage) 65 | } catch handleNonFatalOrInterruptedException { e => 66 | val ex = new PreRestartException(self, e, cause, optionalMessage) 67 | publish(Error(ex, self.path.toString, clazz(failedActor), e.getMessage)) 68 | } finally { 69 | clearActorFields(failedActor) 70 | } 71 | } 72 | assert(mailbox.isSuspended, "mailbox must be suspended during restart") //, status=" + mailbox.status) 73 | if (!setChildrenTerminationReason(ChildrenContainer.Recreation(cause))) 74 | finishRecreate(cause, failedActor) 75 | } else { 76 | // need to keep that suspend counter balanced 77 | faultResume(causedByFailure = null) 78 | } 79 | 80 | /** 81 | * Do suspend the actor in response to a failure of a parent (i.e. the 82 | * “recursive suspend” feature). 83 | */ 84 | protected def faultSuspend(): Unit = { 85 | // done always to keep that suspend counter balanced 86 | suspendNonRecursive() 87 | suspendChildren() 88 | } 89 | 90 | /** 91 | * Do resume the actor in response to a failure. 92 | * 93 | * @param causedByFailure signifies if it was our own failure which 94 | * prompted this action. 95 | */ 96 | protected def faultResume(causedByFailure: Throwable): Unit = { 97 | if (actor == null) { 98 | system.eventStream.publish(Error(self.path.toString, clazz(actor), 99 | "changing Resume into Create after " + causedByFailure)) 100 | faultCreate() 101 | } else if (actor.context == null && causedByFailure != null) { 102 | system.eventStream.publish(Error(self.path.toString, clazz(actor), 103 | "changing Resume into Restart after " + causedByFailure)) 104 | faultRecreate(causedByFailure) 105 | } else { 106 | val perp = perpetrator 107 | // done always to keep that suspend counter balanced 108 | // must happen “atomically” 109 | try resumeNonRecursive() 110 | finally if (causedByFailure != null) clearFailed() 111 | resumeChildren(causedByFailure, perp) 112 | } 113 | } 114 | 115 | /** 116 | * Do create the actor in response to a failure. 117 | */ 118 | protected def faultCreate(): Unit = { 119 | assert(mailbox.isSuspended, "mailbox must be suspended during failed creation")//, status=" + mailbox.status) 120 | assert(perpetrator == self) 121 | 122 | setReceiveTimeout(Duration.Undefined) 123 | cancelReceiveTimeout 124 | 125 | // stop all children, which will turn childrenRefs into TerminatingChildrenContainer (if there are children) 126 | children foreach stop 127 | 128 | if (!setChildrenTerminationReason(ChildrenContainer.Creation())) 129 | finishCreate() 130 | } 131 | 132 | private def finishCreate(): Unit = { 133 | try resumeNonRecursive() 134 | finally clearFailed() 135 | try create(None) 136 | catch handleNonFatalOrInterruptedException { e => 137 | handleInvokeFailure(Nil, e) 138 | } 139 | } 140 | 141 | protected def terminate() { 142 | setReceiveTimeout(Duration.Undefined) 143 | cancelReceiveTimeout 144 | 145 | // prevent Deadletter(Terminated) messages 146 | unwatchWatchedActors(actor) 147 | 148 | // stop all children, which will turn childrenRefs into TerminatingChildrenContainer (if there are children) 149 | children foreach stop 150 | 151 | val wasTerminating = isTerminating 152 | 153 | if (setChildrenTerminationReason(ChildrenContainer.Termination)) { 154 | if (!wasTerminating) { 155 | // do not process normal messages while waiting for all children to terminate 156 | suspendNonRecursive() 157 | // do not propagate failures during shutdown to the supervisor 158 | setFailed(self) 159 | if (system.settings.DebugLifecycle) 160 | publish(Debug(self.path.toString, clazz(actor), "stopping")) 161 | } 162 | } else { 163 | setTerminated() 164 | finishTerminate() 165 | } 166 | } 167 | 168 | final def handleInvokeFailure( 169 | childrenNotToSuspend: immutable.Iterable[ActorRef], 170 | t: Throwable): Unit = { 171 | // prevent any further messages to be processed until the actor has been restarted 172 | if (!isFailed) try { 173 | suspendNonRecursive() 174 | // suspend children 175 | val skip: Set[ActorRef] = currentMessage match { 176 | case Envelope(Failed(_, _, _), child) => setFailed(child); Set(child) 177 | case _ => setFailed(self); Set.empty 178 | } 179 | suspendChildren(exceptFor = skip ++ childrenNotToSuspend) 180 | // tell supervisor 181 | parent.sendSystemMessage(Failed(self, t, uid)) 182 | } catch handleNonFatalOrInterruptedException { e => 183 | publish(Error(e, self.path.toString, clazz(actor), 184 | "emergency stop: exception in failure handling for " + t.getClass /*+ Logging.stackTraceFor(t)*/)) 185 | try children foreach stop 186 | finally finishTerminate() 187 | } 188 | } 189 | 190 | private def finishTerminate() { 191 | val a = actor 192 | /* The following order is crucial for things to work properly. Only change this if you're very confident and lucky. 193 | * 194 | * Please note that if a parent is also a watcher then ChildTerminated and Terminated must be processed in this 195 | * specific order. 196 | */ 197 | try if (a ne null) a.postStop() 198 | catch handleNonFatalOrInterruptedException { e => publish(Error(e, self.path.toString, clazz(a), e.getMessage)) } 199 | finally try dispatcher.detach(this) 200 | finally try parent.sendSystemMessage(DeathWatchNotification(self, existenceConfirmed = true, addressTerminated = false)) 201 | finally try tellWatchersWeDied(a) 202 | finally try unwatchWatchedActors(a) // stay here as we expect an emergency stop from handleInvokeFailure 203 | finally { 204 | if (system.settings.DebugLifecycle) 205 | publish(Debug(self.path.toString, clazz(a), "stopped")) 206 | 207 | clearActorFields(a) 208 | clearActorCellFields(this) 209 | actor = null 210 | } 211 | } 212 | 213 | private def finishRecreate(cause: Throwable, failedActor: Actor): Unit = { 214 | // need to keep a snapshot of the surviving children before the new actor instance creates new ones 215 | val survivors = children 216 | 217 | try { 218 | try resumeNonRecursive() 219 | finally clearFailed() // must happen in any case, so that failure is propagated 220 | 221 | val freshActor = newActor() 222 | actor = freshActor // this must happen before postRestart has a chance to fail 223 | if (freshActor eq failedActor) 224 | freshActor.setActorFields(this, self) // If the creator returns the same instance, we need to restore our nulled out fields. 225 | 226 | freshActor.postRestart(cause) 227 | if (system.settings.DebugLifecycle) 228 | publish(Debug(self.path.toString, clazz(freshActor), "restarted")) 229 | 230 | // only after parent is up and running again do restart the children which were not stopped 231 | survivors foreach (child => 232 | try child.asInstanceOf[InternalActorRef].restart(cause) 233 | catch handleNonFatalOrInterruptedException { e => 234 | publish(Error(e, self.path.toString, clazz(freshActor), "restarting " + child)) 235 | }) 236 | } catch handleNonFatalOrInterruptedException { e => 237 | clearActorFields(actor) // in order to prevent preRestart() from happening again 238 | handleInvokeFailure(survivors, new PostRestartException(self, e, cause)) 239 | } 240 | } 241 | 242 | final protected def handleFailure(f: Failed): Unit = { 243 | currentMessage = Envelope(f, f.child, system) 244 | childStatsByRef(f.child) match { 245 | /* 246 | * only act upon the failure, if it comes from a currently known child; 247 | * the UID protects against reception of a Failed from a child which was 248 | * killed in preRestart and re-created in postRestart 249 | */ 250 | case Some(stats) if stats.uid == f.uid => 251 | if (!actor.supervisorStrategy.handleFailure(this, f.child, f.cause, 252 | stats, getAllChildStats)) 253 | throw f.cause 254 | case Some(stats) => 255 | publish(Debug(self.path.toString, clazz(actor), 256 | "dropping Failed(" + f.cause + ") from old child " + f.child + 257 | " (uid=" + stats.uid + " != " + f.uid + ")")) 258 | case None => 259 | publish(Debug(self.path.toString, clazz(actor), 260 | "dropping Failed(" + f.cause + ") from unknown child " + f.child)) 261 | } 262 | } 263 | 264 | final protected def handleChildTerminated(child: ActorRef): Unit = { 265 | val status = removeChildAndGetStateChange(child) 266 | /* 267 | * if this fails, we do nothing in case of terminating/restarting state, 268 | * otherwise tell the supervisor etc. (in that second case, the match 269 | * below will hit the empty default case, too) 270 | */ 271 | if (actor != null) { 272 | try actor.supervisorStrategy.handleChildTerminated(this, child, children) 273 | catch handleNonFatalOrInterruptedException { e => 274 | publish(Error(e, self.path.toString, clazz(actor), "handleChildTerminated failed")) 275 | handleInvokeFailure(Nil, e) 276 | } 277 | } 278 | /* 279 | * if the removal changed the state of the (terminating) children container, 280 | * then we are continuing the previously suspended recreate/create/terminate action 281 | */ 282 | status match { 283 | case Some(c@ChildrenContainer.Recreation(cause)) => finishRecreate(cause, actor) 284 | case Some(c@ChildrenContainer.Creation()) => finishCreate() 285 | case Some(ChildrenContainer.Termination) => finishTerminate() 286 | case _ => 287 | } 288 | } 289 | 290 | final protected def handleNonFatalOrInterruptedException( 291 | thunk: (Throwable) => Unit): Catcher[Unit] = { 292 | case NonFatal(e) => 293 | thunk(e) 294 | } 295 | } 296 | --------------------------------------------------------------------------------