Skip to content

Commit d49fa27

Browse files
committed
realtime: more cleanup (WIP)
1 parent b1c646e commit d49fa27

File tree

1 file changed

+62
-49
lines changed

1 file changed

+62
-49
lines changed

src/main/scala/ru/org/linux/realtime/RealtimeEventHub.scala

Lines changed: 62 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@
1616
package ru.org.linux.realtime
1717

1818
import akka.Done
19-
import akka.actor.typed.Scheduler
19+
import akka.actor.typed.{ActorRef, Behavior, PostStop, Scheduler}
2020
import akka.actor.typed.scaladsl.AskPattern.Askable
21-
import akka.actor.{Actor, ActorLogging, ActorRef, ActorSystem, PoisonPill, Props, SupervisorStrategy, Terminated, Timers}
21+
import akka.actor.typed.scaladsl.Behaviors
22+
import akka.actor.{Actor, ActorLogging, ActorSystem, Props, SupervisorStrategy, Terminated, Timers}
2223
import akka.util.Timeout
2324
import com.typesafe.scalalogging.StrictLogging
2425
import org.springframework.beans.factory.annotation.Qualifier
@@ -41,21 +42,24 @@ import scala.concurrent.duration.*
4142
import scala.jdk.CollectionConverters.*
4243
import scala.util.control.NonFatal
4344

45+
4446
// TODO ignore list support
4547
// TODO fix face conditions on simultaneous posting comment, subscription and missing processing
4648
class RealtimeEventHub extends Actor with ActorLogging with Timers {
47-
private val topicSubscriptions: mutable.MultiDict[Int, ActorRef] = mutable.MultiDict[Int, ActorRef]()
48-
private val userSubscriptions: mutable.MultiDict[Int, ActorRef] = mutable.MultiDict[Int, ActorRef]()
49-
private val sessions = new mutable.HashMap[String, ActorRef]
49+
private val topicSubscriptions: mutable.MultiDict[Int, ActorRef[SessionProtocol]] = mutable.MultiDict[Int, ActorRef[SessionProtocol]]()
50+
private val userSubscriptions: mutable.MultiDict[Int, ActorRef[SessionProtocol]] = mutable.MultiDict[Int, ActorRef[SessionProtocol]]()
51+
private val sessions = new mutable.HashMap[String, ActorRef[SessionProtocol]]
5052
private var maxDataSize: Int = 0
5153

5254
timers.startTimerWithFixedDelay(Tick, Tick, 5.minutes)
5355

56+
import akka.actor.typed.scaladsl.adapter.*
57+
5458
override def supervisorStrategy: SupervisorStrategy = SupervisorStrategy.stoppingStrategy
5559

5660
override def receive: Receive = {
5761
case SessionStarted(session, user, replyTo) if !sessions.contains(session.getId) =>
58-
val actor = context.actorOf(RealtimeSessionActor.props(session))
62+
val actor: ActorRef[SessionProtocol] = context.spawnAnonymous(RealtimeSessionActor.behavior(session))
5963
context.watch(actor)
6064

6165
sessions += (session.getId -> actor)
@@ -77,7 +81,8 @@ class RealtimeEventHub extends Actor with ActorLogging with Timers {
7781
topicSubscriptions += (topic -> actor)
7882

7983
replyTo ! Done
80-
case Terminated(actorRef) =>
84+
case Terminated(untypedRef) =>
85+
val actorRef: ActorRef[SessionProtocol] = untypedRef
8186
log.debug(s"RealtimeSessionActor $actorRef terminated")
8287

8388
topicSubscriptions.sets.find(_._2.contains(actorRef)).foreach { case (msgid, _) =>
@@ -96,7 +101,7 @@ class RealtimeEventHub extends Actor with ActorLogging with Timers {
96101
sessions.get(id) foreach { actor =>
97102
log.debug("Session was terminated, stopping actor")
98103

99-
actor ! PoisonPill
104+
actor ! TerminateSession
100105
}
101106
case msg@NewComment(msgid, _) =>
102107
log.debug(s"New comment in topic $msgid")
@@ -117,16 +122,20 @@ class RealtimeEventHub extends Actor with ActorLogging with Timers {
117122
}
118123

119124
object RealtimeEventHub {
125+
sealed trait SessionProtocol
126+
120127
sealed trait Protocol
121128

122-
case class NewComment(msgid: Int, cid: Int) extends Protocol
123-
case class RefreshEvents(users: Set[Int]) extends Protocol
124-
private[realtime] case object Tick extends Protocol
129+
case class NewComment(msgid: Int, cid: Int) extends SessionProtocol with Protocol
130+
case class RefreshEvents(users: Set[Int]) extends SessionProtocol with Protocol
131+
private[realtime] case object Tick extends SessionProtocol with Protocol
125132

126-
private[realtime] case class SessionStarted(session: WebSocketSession, user: Option[Int], replyTo: akka.actor.typed.ActorRef[Done.type]) extends Protocol
127-
private[realtime] case class SubscribeTopic(session: WebSocketSession, topic: Int, replyTo: akka.actor.typed.ActorRef[Done.type]) extends Protocol
133+
private[realtime] case class SessionStarted(session: WebSocketSession, user: Option[Int], replyTo: ActorRef[Done.type]) extends Protocol
134+
private[realtime] case class SubscribeTopic(session: WebSocketSession, topic: Int, replyTo: ActorRef[Done.type]) extends Protocol
128135
private[realtime] case class SessionTerminated(session: String) extends Protocol
129136

137+
private[realtime] case object TerminateSession extends SessionProtocol
138+
130139
def props: Props = Props(new RealtimeEventHub())
131140

132141
private[realtime] def notifyComment(session: WebSocketSession, comment: Int): Unit = {
@@ -137,49 +146,53 @@ object RealtimeEventHub {
137146
session.sendMessage(new TextMessage(s"events-refresh"))
138147
}
139148

140-
def notifyEvents(realtimeEventHub: akka.actor.typed.ActorRef[RefreshEvents], users: java.lang.Iterable[Integer]): Unit = {
149+
def notifyEvents(realtimeEventHub: ActorRef[RefreshEvents], users: java.lang.Iterable[Integer]): Unit = {
141150
realtimeEventHub ! RefreshEvents(users.asScala.map(_.toInt).toSet)
142151
}
143152
}
144153

145-
class RealtimeSessionActor(session: WebSocketSession) extends Actor with ActorLogging with Timers {
146-
timers.startTimerWithFixedDelay(Tick, Tick, initialDelay = 5.seconds, delay = 1.minute)
147-
148-
override def receive: Receive = {
149-
case NewComment(_, cid) =>
150-
try {
151-
notifyComment(session, cid)
152-
} catch handleExceptions
153-
154-
case RefreshEvents(_) =>
155-
try {
156-
notifyEvent(session)
157-
} catch handleExceptions
158-
case Tick =>
159-
// log.debug("Sending keepalive")
160-
try {
161-
session.sendMessage(new PingMessage())
162-
} catch handleExceptions
163-
}
164-
165-
private def handleExceptions: PartialFunction[Throwable, Unit] = {
166-
case ex: IOException =>
167-
log.debug(s"Terminated by IOException ${ex.toString}")
168-
context.stop(self)
169-
}
154+
object RealtimeSessionActor {
155+
def behavior(session: WebSocketSession): Behavior[SessionProtocol] = Behaviors.setup { context =>
156+
Behaviors.withTimers { timers =>
157+
timers.startTimerWithFixedDelay(Tick, Tick, initialDelay = 5.seconds, delay = 1.minute)
158+
159+
def handleExceptions: PartialFunction[Throwable, Behavior[SessionProtocol]] = {
160+
case ex: IOException =>
161+
context.log.debug(s"Terminated by IOException ${ex.toString}")
162+
Behaviors.stopped
163+
}
170164

171-
@scala.throws[Exception](classOf[Exception])
172-
override def postStop(): Unit = {
173-
session.close()
165+
Behaviors.receiveMessage[SessionProtocol] {
166+
case NewComment(_, cid) =>
167+
try {
168+
notifyComment(session, cid)
169+
Behaviors.same
170+
} catch handleExceptions
171+
172+
case RefreshEvents(_) =>
173+
try {
174+
notifyEvent(session)
175+
Behaviors.same
176+
} catch handleExceptions
177+
case Tick =>
178+
// log.debug("Sending keepalive")
179+
try {
180+
session.sendMessage(new PingMessage())
181+
Behaviors.same
182+
} catch handleExceptions
183+
case TerminateSession =>
184+
Behaviors.stopped
185+
}.receiveSignal {
186+
case (_, PostStop) =>
187+
session.close()
188+
Behaviors.same
189+
}
190+
}
174191
}
175192
}
176193

177-
object RealtimeSessionActor {
178-
def props(session: WebSocketSession): Props = Props(new RealtimeSessionActor(session))
179-
}
180-
181194
@Service
182-
class RealtimeWebsocketHandler(@Qualifier("realtimeHubWS") hub: akka.actor.typed.ActorRef[Protocol],
195+
class RealtimeWebsocketHandler(@Qualifier("realtimeHubWS") hub: ActorRef[Protocol],
183196
topicDao: TopicDao, commentService: CommentReadService,
184197
actorSystem: ActorSystem) extends TextWebSocketHandler
185198
with StrictLogging {
@@ -259,10 +272,10 @@ class RealtimeWebsocketHandler(@Qualifier("realtimeHubWS") hub: akka.actor.typed
259272
@Configuration
260273
class RealtimeConfigurationBeans(actorSystem: ActorSystem) {
261274
@Bean(Array("realtimeHubWS"))
262-
def hub: akka.actor.typed.ActorRef[Protocol] = {
275+
def hub: ActorRef[Protocol] = {
263276
import akka.actor.typed.scaladsl.adapter.*
264277

265-
actorSystem.actorOf(RealtimeEventHub.props)
278+
actorSystem.actorOf(RealtimeEventHub.props, "realtimeHubWS")
266279
}
267280
}
268281

0 commit comments

Comments
 (0)