+str #15588,#17229 Java 6 Synchronous File Sink / Source
These are synchronous implementations, because we need to be Java 6 compatible while developing on 2.3.x. However asynchronous implementations using AsynchronousFileChannel will come soon for JDK7 users. + ActorPublisher/Subscriber now manage stopping of the actor + added documentation on configuring dispatcher for File IO + properly handle if source file does not exist + file sink / source come with default io dispatcher > verified no actors are leaking > exceptions are caught and onErrored properly + moved files to akka.stream.io + Added OutputStreamSink and InputStreamSource
This commit is contained in:
parent
a1639c4312
commit
cebd9bf1ae
37 changed files with 1581 additions and 86 deletions
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package docs.stream.io
|
||||
|
||||
import java.io.File
|
||||
|
||||
import akka.stream._
|
||||
import akka.stream.io.SynchronousFileSource
|
||||
import akka.stream.io.SynchronousFileSink
|
||||
import akka.stream.testkit.AkkaSpec
|
||||
import akka.stream.testkit.StreamTestKit
|
||||
import akka.util.ByteString
|
||||
|
||||
class StreamFileDocSpec extends AkkaSpec(StreamTestKit.UnboundedMailboxConfig) {
|
||||
|
||||
implicit val ec = system.dispatcher
|
||||
implicit val mat = ActorFlowMaterializer()
|
||||
|
||||
// silence sysout
|
||||
def println(s: String) = ()
|
||||
|
||||
val file = File.createTempFile(getClass.getName, ".tmp")
|
||||
|
||||
override def afterTermination() = file.delete()
|
||||
|
||||
{
|
||||
//#file-source
|
||||
import akka.stream.io._
|
||||
//#file-source
|
||||
}
|
||||
|
||||
{
|
||||
//#file-source
|
||||
val file = new File("example.csv")
|
||||
//#file-source
|
||||
}
|
||||
|
||||
"read data from a file" in {
|
||||
//#file-source
|
||||
def handle(b: ByteString): Unit //#file-source
|
||||
= ()
|
||||
|
||||
//#file-source
|
||||
|
||||
SynchronousFileSource(file)
|
||||
.runForeach((chunk: ByteString) ⇒ handle(chunk))
|
||||
//#file-source
|
||||
}
|
||||
|
||||
"configure dispatcher in code" in {
|
||||
//#custom-dispatcher-code
|
||||
SynchronousFileSink(file)
|
||||
.withAttributes(ActorOperationAttributes.dispatcher("custom-file-io-dispatcher"))
|
||||
//#custom-dispatcher-code
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +1,22 @@
|
|||
/**
|
||||
* Copyright (C) 2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package docs.stream
|
||||
package docs.stream.io
|
||||
|
||||
import java.net.InetSocketAddress
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import akka.actor.ActorSystem
|
||||
|
||||
import akka.stream._
|
||||
import akka.stream.scaladsl.StreamTcp._
|
||||
import akka.stream.scaladsl._
|
||||
import akka.stream.stage.{ PushStage, SyncDirective, Context }
|
||||
import akka.stream.stage.Context
|
||||
import akka.stream.stage.PushStage
|
||||
import akka.stream.stage.SyncDirective
|
||||
import akka.stream.testkit.AkkaSpec
|
||||
import akka.testkit.TestProbe
|
||||
import akka.util.ByteString
|
||||
import cookbook.RecipeParseLines
|
||||
import docs.stream.cookbook.RecipeParseLines
|
||||
import docs.utils.TestUtils
|
||||
import StreamTcp._
|
||||
|
||||
import scala.concurrent.Future
|
||||
|
||||
|
|
@ -4,19 +4,22 @@
|
|||
Working with streaming IO
|
||||
#########################
|
||||
|
||||
Akka Streams provides a way of handling TCP connections with Streams.
|
||||
Akka Streams provides a way of handling File IO and TCP connections with Streams.
|
||||
While the general approach is very similar to the `Actor based TCP handling`_ using Akka IO,
|
||||
by using Akka Streams you are freed of having to manually react to back-pressure signals,
|
||||
as the library does it transparently for you.
|
||||
|
||||
.. _Actor based TCP handling: http://doc.akka.io/docs/akka/current/scala/io-tcp.html
|
||||
|
||||
Streaming TCP
|
||||
=============
|
||||
|
||||
Accepting connections: Echo Server
|
||||
==================================
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
In order to implement a simple EchoServer we ``bind`` to a given address, which returns a ``Source[IncomingConnection]``,
|
||||
which will emit an :class:`IncomingConnection` element for each new connection that the Server should handle:
|
||||
|
||||
.. includecode:: code/docs/stream/StreamTcpDocSpec.scala#echo-server-simple-bind
|
||||
.. includecode:: code/docs/stream/io/StreamTcpDocSpec.scala#echo-server-simple-bind
|
||||
|
||||
Next, we simply handle *each* incoming connection using a :class:`Flow` which will be used as the processing stage
|
||||
to handle and emit ByteStrings from and to the TCP Socket. Since one :class:`ByteString` does not have to necessarily
|
||||
|
|
@ -24,7 +27,7 @@ correspond to exactly one line of text (the client might be sending the line in
|
|||
recipe from the :ref:`cookbook-parse-lines-scala` Akka Streams Cookbook recipe to chunk the inputs up into actual lines of text.
|
||||
In this example we simply add exclamation marks to each incoming text message and push it through the flow:
|
||||
|
||||
.. includecode:: code/docs/stream/StreamTcpDocSpec.scala#echo-server-simple-handle
|
||||
.. includecode:: code/docs/stream/io/StreamTcpDocSpec.scala#echo-server-simple-handle
|
||||
|
||||
Notice that while most building blocks in Akka Streams are reusable and freely shareable, this is *not* the case for the
|
||||
incoming connection Flow, since it directly corresponds to an existing, already accepted connection its handling can
|
||||
|
|
@ -42,13 +45,13 @@ We can then test the TCP server by sending data to the TCP Socket using ``netcat
|
|||
Hello World!!!
|
||||
|
||||
Connecting: REPL Client
|
||||
=======================
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
In this example we implement a rather naive Read Evaluate Print Loop client over TCP.
|
||||
Let's say we know a server has exposed a simple command line interface over TCP,
|
||||
and would like to interact with it using Akka Streams over TCP. To open an outgoing connection socket we use
|
||||
the ``outgoingConnection`` method:
|
||||
|
||||
.. includecode:: code/docs/stream/StreamTcpDocSpec.scala#repl-client
|
||||
.. includecode:: code/docs/stream/io/StreamTcpDocSpec.scala#repl-client
|
||||
|
||||
The ``repl`` flow we use to handle the server interaction first prints the servers response, then awaits on input from
|
||||
the command line (this blocking call is used here just for the sake of simplicity) and converts it to a
|
||||
|
|
@ -61,7 +64,7 @@ a separate mapAsync step and have a way to let the server write more data than o
|
|||
these improvements however are left as exercise for the reader.
|
||||
|
||||
Avoiding deadlocks and liveness issues in back-pressured cycles
|
||||
===============================================================
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
When writing such end-to-end back-pressured systems you may sometimes end up in a situation of a loop,
|
||||
in which *either side is waiting for the other one to start the conversation*. One does not need to look far
|
||||
to find examples of such back-pressure loops. In the two examples shown previously, we always assumed that the side we
|
||||
|
|
@ -80,7 +83,7 @@ Thankfully in most situations finding the right spot to start the conversation i
|
|||
to the protocol we are trying to implement using Streams. In chat-like applications, which our examples resemble,
|
||||
it makes sense to make the Server initiate the conversation by emitting a "hello" message:
|
||||
|
||||
.. includecode:: code/docs/stream/StreamTcpDocSpec.scala#welcome-banner-chat-server
|
||||
.. includecode:: code/docs/stream/io/StreamTcpDocSpec.scala#welcome-banner-chat-server
|
||||
|
||||
The way we constructed a :class:`Flow` using a :class:`PartialFlowGraph` is explained in detail in
|
||||
:ref:`constructing-sources-sinks-flows-from-partial-graphs-scala`, however the basic concepts is rather simple–
|
||||
|
|
@ -93,3 +96,28 @@ logic in Flows and attaching those to :class:`StreamIO` in order to implement yo
|
|||
In this example both client and server may need to close the stream based on a parsed command command - ``BYE`` in the case
|
||||
of the server, and ``q`` in the case of the client. This is implemented by using a custom :class:`PushStage`
|
||||
(see :ref:`stream-using-push-pull-stage-scala`) which completes the stream once it encounters such command.
|
||||
|
||||
Streaming File IO
|
||||
=================
|
||||
|
||||
Akka Streams provide simple Sources and Sinks that can work with :class:`ByteString` instances to perform IO operations
|
||||
on files.
|
||||
|
||||
.. note::
|
||||
Since the current version of Akka (``2.3.x``) needs to support JDK6, the currently provided File IO implementations
|
||||
are not able to utilise Asynchronous File IO operations, as these were introduced in JDK7 (and newer).
|
||||
Once Akka is free to require JDK8 (from ``2.4.x``) these implementations will be updated to make use of the
|
||||
new NIO APIs (i.e. :class:`AsynchronousFileChannel`).
|
||||
|
||||
Streaming data from a file is as easy as defining a `SynchronousFileSource` given a target file, and an optional
|
||||
``chunkSize`` which determines the buffer size determined as one "element" in such stream:
|
||||
|
||||
.. includecode:: code/docs/stream/io/StreamFileDocSpec.scala#file-source
|
||||
|
||||
Please note that these processing stages are backed by Actors and by default are configured to run on a pre-configured
|
||||
threadpool-backed dispatcher dedicated for File IO. This is very important as it isolates the blocking file IO operations from the rest
|
||||
of the ActorSystem allowing each dispatcher to be utilised in the most efficient way. If you want to configure a custom
|
||||
dispatcher for file IO operations globally, you can do so by changing the ``akka.strea.file-io-dispatcher``,
|
||||
or for a specific stage by spefifying a custom Dispatcher in code, like this:
|
||||
|
||||
.. includecode:: code/docs/stream/io/StreamFileDocSpec.scala#custom-dispatcher-code
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue