-
Notifications
You must be signed in to change notification settings - Fork 3.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Provide public infrastructure for using a single actor as Flow
implementation
#16985
Comments
Could anyone comment on the question of whether there is a work-around currently? My use case: |
/cc @jrudolph |
Implementing the stream handling in an Actor is a complex endeavor, I’d rather get rid of ActorPublisher/ActorSubscriber than put more weight on them (yes, I do appreciate that sometimes we need them, but AFAICS only for Sinks and Sources). I think more complex transformation steps should be modeled using |
That's not going to be good enough.
To the outside this behaves exactly like a
This is very easily solved with a single actor "wrapping" the inner flow. Another (similar) use case, where I also need the "single-actor processor", is dynamic subscription. While we might want to support all this with special constructs in the future we have the need for these stream stages right now, rather in half a year. So we need a solution now! |
Also I need to note that |
@SmLin In most cases you should get away with an |
Closing this one as invalid: AsyncStage should be able to cover all cases, especially once we port InputBunch/OutputBunch to it. |
I'd like to reopen this discussion. I think my use case may be a bit different to @sirthias. We currently have integration that most people use in Play for handling websockets with actors, here's the docs: https://www.playframework.com/documentation/2.4.x/ScalaWebSockets#Handling-WebSockets-with-actors My hope was that I could deprecate everything in Play specific to handling WebSockets in Play with actors, and point users to something in Akka streams that replaces it. Not quite. So, firstly,
So, the interface that I'd like to see on def actorRef[In, Out](props: ActorRef => Props, onCompleteMessage: Any, bufferSize: Int, overflowStrategy: OverflowStrategy): Flow[In, Out, Unit] The Note that the above can almost be implemented today on top of the Akka streams API, except that it requires an intermediate materialization, making it only possible to use it once: def actorRef[In, Out](props: ActorRef => Props, onCompleteMessage: Any,
bufferSize: Int, overflowStrategy: OverflowStrategy)
(implicit factory: ActorRefFactory, mat: Materializer): Flow[In, Out, Unit] = {
val (outActor, publisher) = Source.actorRef[Out](bufferSize, overflowStrategy)
.toMat(Sink.publisher)(Keep.both).run()
Flow.wrap(
Sink.actorRef(factory.actorOf(props(outActor)), onCompleteMessage),
Source(publisher)
)(Keep.none)
} Perhaps the only additional thing here, is since the actor is being created by the method, it should also be automatically shutdown when the stream completes. |
Sorry for the long delay, the github notification somehow floated to the bottom of the pile. I think that we do indeed need to discuss this further, in particular which exact semantics we want to model—that will then inform our decision on which API to offer. |
We have StageActorRef now for GraphStageLogic, so using Actors anywhere near a Stream is both unneeded and possible :-) (i.e. when interfacing with existing APIs a GraphStage can now do so natively). |
One question though - when interacting with The example use case is the one everyone always uses in WebSockets 101 - a simple chat room. In a chatroom, it's fine to drop the connection if the client doesn't consume messages fast enough, and if it's a problem, you can place a limit on the rate at which any one client can send messages, dropping the connection when they exceed that, so back pressure is not needed. In node.js, implementing a simple chat room is only a handful of lines of code: var connections = {};
var count = 0;
ws.createServer(function(connection) {
var id = ++count;
connections[id] = connection;
connection.on("text", function(msg) {
connections.forEach(function(c) {
c.sendText(msg);
});
});
connection.on("close", function() {
delete connections[id];
});
}).listen(8000); This makes for a great code example, it's very easy to understand, and when people want to evaluate what server they are going to use to provide their websockets, they're going to look at things like this to see how simple it is. So what's our answer to that with Play using Akka streams (or Akka HTTP using Akka streams even)? The simplest I can come up with is this: class ChatRoom extends Actor {
var users: Set[ActorRef] = Nil
def receive = {
case msg: String =>
users.foreach(user => user ! msg)
case user: ActorRef =>
users += user
context.watch(user)
case Terminated(user) =>
users -= user
case Unit =>
// Ignore, we're already watching the stream that terminated
}
}
val chatRoom = system.actorOf(Props[ChatRoom])
def webSocket = WebSocket { _ =>
Flow.wrap(
Sink.actorRef[String](chatRoom, ()),
Source.actorRef[String](10, OverflowStrategy.fail)
) { (_, user) => chatRoom ! user }
} Aside from being more lines of code, this has the following problems:
So, does the new API make this very common and very simple use case for WebSockets simple to implement with Akka streams? Or do we need to keep this supplemental Akka streams API in Play to provide it? |
StageActorRef locally has all the semantics of an ActorRef, nothing more and nothing less (making it remote-capable is scheduled for later and not relevant for most use-cases). The answer to your questions is “yes”: GraphStage gives you—the library developer—all the tools to create a nice single-line abstraction that users can use to plug their Actor into a websocket flow. We can discuss details separately. |
Currently akka-stream provides me with
ActorPublisher
andActorSubscriber
traits that I can use to easily implement aSource
orSink
(resp.) manually by using these constructors:I suggest we add
as well as
to let me do the same for flows that are backed by a single actor.
Is there work-around that I can use with the current infrastructure to achieve the same effect?
(Other than splitting my processor logic into two actors, one for the
Sink
and one for theSource
side.)The text was updated successfully, but these errors were encountered: