16
16
package ru .org .linux .realtime
17
17
18
18
import akka .Done
19
- import akka .actor .typed .Scheduler
19
+ import akka .actor .typed .{ ActorRef , Behavior , PostStop , Scheduler }
20
20
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 }
22
23
import akka .util .Timeout
23
24
import com .typesafe .scalalogging .StrictLogging
24
25
import org .springframework .beans .factory .annotation .Qualifier
@@ -41,21 +42,24 @@ import scala.concurrent.duration.*
41
42
import scala .jdk .CollectionConverters .*
42
43
import scala .util .control .NonFatal
43
44
45
+
44
46
// TODO ignore list support
45
47
// TODO fix face conditions on simultaneous posting comment, subscription and missing processing
46
48
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 ] ]
50
52
private var maxDataSize : Int = 0
51
53
52
54
timers.startTimerWithFixedDelay(Tick , Tick , 5 .minutes)
53
55
56
+ import akka .actor .typed .scaladsl .adapter .*
57
+
54
58
override def supervisorStrategy : SupervisorStrategy = SupervisorStrategy .stoppingStrategy
55
59
56
60
override def receive : Receive = {
57
61
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))
59
63
context.watch(actor)
60
64
61
65
sessions += (session.getId -> actor)
@@ -77,7 +81,8 @@ class RealtimeEventHub extends Actor with ActorLogging with Timers {
77
81
topicSubscriptions += (topic -> actor)
78
82
79
83
replyTo ! Done
80
- case Terminated (actorRef) =>
84
+ case Terminated (untypedRef) =>
85
+ val actorRef : ActorRef [SessionProtocol ] = untypedRef
81
86
log.debug(s " RealtimeSessionActor $actorRef terminated " )
82
87
83
88
topicSubscriptions.sets.find(_._2.contains(actorRef)).foreach { case (msgid, _) =>
@@ -96,7 +101,7 @@ class RealtimeEventHub extends Actor with ActorLogging with Timers {
96
101
sessions.get(id) foreach { actor =>
97
102
log.debug(" Session was terminated, stopping actor" )
98
103
99
- actor ! PoisonPill
104
+ actor ! TerminateSession
100
105
}
101
106
case msg@ NewComment (msgid, _) =>
102
107
log.debug(s " New comment in topic $msgid" )
@@ -117,16 +122,20 @@ class RealtimeEventHub extends Actor with ActorLogging with Timers {
117
122
}
118
123
119
124
object RealtimeEventHub {
125
+ sealed trait SessionProtocol
126
+
120
127
sealed trait Protocol
121
128
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
125
132
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
128
135
private [realtime] case class SessionTerminated (session : String ) extends Protocol
129
136
137
+ private [realtime] case object TerminateSession extends SessionProtocol
138
+
130
139
def props : Props = Props (new RealtimeEventHub ())
131
140
132
141
private [realtime] def notifyComment (session : WebSocketSession , comment : Int ): Unit = {
@@ -137,49 +146,53 @@ object RealtimeEventHub {
137
146
session.sendMessage(new TextMessage (s " events-refresh " ))
138
147
}
139
148
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 = {
141
150
realtimeEventHub ! RefreshEvents (users.asScala.map(_.toInt).toSet)
142
151
}
143
152
}
144
153
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
+ }
170
164
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
+ }
174
191
}
175
192
}
176
193
177
- object RealtimeSessionActor {
178
- def props (session : WebSocketSession ): Props = Props (new RealtimeSessionActor (session))
179
- }
180
-
181
194
@ Service
182
- class RealtimeWebsocketHandler (@ Qualifier (" realtimeHubWS" ) hub : akka.actor.typed. ActorRef [Protocol ],
195
+ class RealtimeWebsocketHandler (@ Qualifier (" realtimeHubWS" ) hub : ActorRef [Protocol ],
183
196
topicDao : TopicDao , commentService : CommentReadService ,
184
197
actorSystem : ActorSystem ) extends TextWebSocketHandler
185
198
with StrictLogging {
@@ -259,10 +272,10 @@ class RealtimeWebsocketHandler(@Qualifier("realtimeHubWS") hub: akka.actor.typed
259
272
@ Configuration
260
273
class RealtimeConfigurationBeans (actorSystem : ActorSystem ) {
261
274
@ Bean (Array (" realtimeHubWS" ))
262
- def hub : akka.actor.typed. ActorRef [Protocol ] = {
275
+ def hub : ActorRef [Protocol ] = {
263
276
import akka .actor .typed .scaladsl .adapter .*
264
277
265
- actorSystem.actorOf(RealtimeEventHub .props)
278
+ actorSystem.actorOf(RealtimeEventHub .props, " realtimeHubWS " )
266
279
}
267
280
}
268
281
0 commit comments