+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:
Konrad Malawski 2015-04-16 02:24:01 +02:00
parent a1639c4312
commit cebd9bf1ae
37 changed files with 1581 additions and 86 deletions

View file

@ -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
}
}

View file

@ -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

View file

@ -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