summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLi Haoyi <haoyi.sg@gmail.com>2019-11-04 08:05:29 +0800
committerLi Haoyi <haoyi.sg@gmail.com>2019-11-04 08:05:29 +0800
commit1b93048876f590ccf157639a4e1aec61dc6bb9f9 (patch)
tree9ac0a4a097e0cad81f4a461ead5f9f0899b7511a
parentaaca9ae2a09364fd8ae5bf85e080734cec351657 (diff)
downloadcask-1b93048876f590ccf157639a4e1aec61dc6bb9f9.tar.gz
cask-1b93048876f590ccf157639a4e1aec61dc6bb9f9.tar.bz2
cask-1b93048876f590ccf157639a4e1aec61dc6bb9f9.zip
split out definition and usage examples in actor readme
-rw-r--r--docs/pages/4 - Cask Actors.md102
1 files changed, 59 insertions, 43 deletions
diff --git a/docs/pages/4 - Cask Actors.md b/docs/pages/4 - Cask Actors.md
index 1b899e4..241caf6 100644
--- a/docs/pages/4 - Cask Actors.md
+++ b/docs/pages/4 - Cask Actors.md
@@ -106,7 +106,22 @@ val logPath = os.pwd / "out" / "scratch" / "log.txt"
val oldPath = os.pwd / "out" / "scratch" / "log-old.txt"
val logger = new Logger(logPath, oldPath, rotateSize = 50)
+```
+
+In the above example, we are defining a single `Logger` actor class, which we
+are instantiating once as `val logger`. We can now send as many messages as we
+want via `logger.send`: while the processing of a message make take some time
+(here are are both writing to disk, as well as providing
+[log-rotation](https://en.wikipedia.org/wiki/Log_rotation) to avoid the logfile
+growing in size forever) the fact that it's in a separate actor means the
+processing happens in the background without slowing down the main logic of your
+program. Cask Actors process messages one at a time, so by putting the file
+write-and-rotate logic inside an Actor we can be sure to avoid race conditions
+that may arise due to multiple threads mangling the same file at once.
+
+Here's the result of sending messages to the actor:
+```scala
logger.send("I am cow")
logger.send("hear me moo")
logger.send("I weight twice as much as you")
@@ -124,17 +139,6 @@ os.read.lines(oldPath) ==> Seq("Comes from liquids from my udder")
os.read.lines(logPath) ==> Seq("I am cow, I am cow", "Hear me moo, moooo")
```
-In the above example, we are defining a single `Logger` actor class, which we
-are instantiating once as `val logger`. We can now send as many messages as we
-want via `logger.send`: while the processing of a message make take some time
-(here are are both writing to disk, as well as providing
-[log-rotation](https://en.wikipedia.org/wiki/Log_rotation) to avoid the logfile
-growing in size forever) the fact that it's in a separate actor means the
-processing happens in the background without slowing down the main logic of your
-program. Cask Actors process messages one at a time, so by putting the file
-write-and-rotate logic inside an Actor we can be sure to avoid race conditions
-that may arise due to multiple threads mangling the same file at once.
-
Using Actors is ideal for scenarios where the dataflow is one way: e.g. when
logging, you only write logs, and never need to wait for the results of
processing them.
@@ -237,7 +241,20 @@ val oldPath = os.pwd / "out" / "scratch" / "log-old.txt"
val writer = new Writer(logPath, oldPath, rotateSize = 50)
val logger = new Logger(writer)
+```
+
+Although we have added another Base64 encoding step to the logging process, this
+new step lives in a separate actor from the original write-to-disk step, and
+both of these can run in parallel as well as in parallel with the main logic. By
+constructing our data processing flows using Actors, we can take advantage of
+pipeline parallelism to distribute the processing over multiple threads and CPU
+cores, so adding steps to the pipeline neither slows it down nor does it slow
+down the execution of the main program.
+
+We can send messages to this actor and verify that it writes lines to the log
+file base64 encoded:
+```scala
logger.send("I am cow")
logger.send("hear me moo")
logger.send("I weight twice as much as you")
@@ -260,14 +277,6 @@ decodeFile(oldPath) ==> Seq("Comes from liquids from my udder")
decodeFile(logPath) ==> Seq("I am cow, I am cow", "Hear me moo, moooo")
```
-Although we have added another Base64 encoding step to the logging process, this
-new step lives in a separate actor from the original write-to-disk step, and
-both of these can run in parallel as well as in parallel with the main logic. By
-constructing our data processing flows using Actors, we can take advantage of
-pipeline parallelism to distribute the processing over multiple threads and CPU
-cores, so adding steps to the pipeline neither slows it down nor does it slow
-down the execution of the main program.
-
You can imagine adding additional stages to this actor pipeline, to perform
other sorts of processing, and have those additional stages running in parallel
as well.
@@ -325,7 +334,21 @@ val oldPath = os.pwd / "out" / "scratch" / "log-old.txt"
val writer = new Writer(logPath, oldPath)
val logger = new Logger(writer, rotateSize = 50)
+```
+
+Here the `Logger` actor takes incoming log lines and decides when it needs to
+trigger a log rotation, while sending both the log lines and rotation commands
+as `Text` and `Rotate` commands to the `Writer` batch actor which handles
+batches of these messages via its `runBatch` method. `Writer` filters through
+the list of incoming messages to decide what it needs to do: either there are
+zero `Rotate` commands and it simply appends all incoming `Text`s to the log
+file, or there are one-or-more `Rotate` commands it needs to do a log rotation,
+writing the batched messages once to the log file pre- and post-rotation.
+
+We can send messages to the logger and verify that it behaves the same as the
+`SimpleActor` example earlier:
+```scala
logger.send("I am cow")
logger.send("hear me moo")
logger.send("I weight twice as much as you")
@@ -341,15 +364,6 @@ os.read.lines(oldPath) ==> Seq("Comes from liquids from my udder")
os.read.lines(logPath) ==> Seq("I am cow, I am cow", "Hear me moo, moooo")
```
-Here the `Logger` actor takes incoming log lines and decides when it needs to
-trigger a log rotation, while sending both the log lines and rotation commands
-as `Text` and `Rotate` commands to the `Writer` batch actor which handles
-batches of these messages via its `runBatch` method. `Writer` filters through
-the list of incoming messages to decide what it needs to do: either there are
-zero `Rotate` commands and it simply appends all incoming `Text`s to the log
-file, or there are one-or-more `Rotate` commands it needs to do a log rotation,
-writing the batched messages once to the log file pre- and post-rotation.
-
Using a `BatchActor` here helps reduce the number of writes to the filesystem:
no matter how many messages get queued up, our batch actor only makes two
writes. Furthermore, if there are more than two `Rotate` commands in the same
@@ -397,7 +411,21 @@ implicit val ac = new Context.Test()
val logPath = os.pwd / "out" / "scratch" / "log.txt"
val logger = new Logger(logPath, java.time.Duration.ofMillis(50))
+```
+In this example, we use `StateMachineActor` to define a `Logger` actor with two
+states `Idle` and `Buffering`.
+
+This actor starts out with its `initalState = Idle()`. When it receives a `Text`
+message, it schedules a `Flush` message to be sent 50 milliseconds in the
+future, and transitions into the `Buffering` state. While in `Buffering`, any
+additional `Text` messages are simply accumulated onto the buffer, until the
+`Flush` is received again and all the buffered messages are flushed to disk.
+Each group of messages is written as a single line, separated by newlines (just
+so we can see the effect of the batching in the output). The output is as
+follows:
+
+```scala
logger.send(Text("I am cow"))
logger.send(Text("hear me moo"))
Thread.sleep(100)
@@ -418,21 +446,9 @@ os.read.lines(logPath) ==> Seq(
)
```
-In this example, we use `StateMachineActor` to define a `Logger` actor with two
-states `Idle` and `Buffering`.
-
-This actor starts out with its `initalState = Idle()`. When it receives a `Text`
-message, it schedules a `Flush` message to be sent 50 milliseconds in the
-future, and transitions into the `Buffering` state. While in `Buffering`, any
-additional `Text` messages are simply accumulated onto the buffer, until the
-`Flush` is received again and all the buffered messages are flushed to disk.
-Each group of messages is written as a single line, separated by newlines (just
-so we can see the effect of the batching in the output)
-
-You can see that we send the text messages to the `logger` in three groups
-separated by 100 millisecond waits, and as a result the final log file ends up
-having three lines of logs each of which contains multiple messages buffered
-together.
+You can see that when sending the text messages to the `logger` in three groups
+separated by 100 millisecond waits, the final log file ends up having three
+lines of logs each of which contains multiple messages buffered together.
In general, `StateMachineActor` is very useful in cases where there are multiple
distinct states which an Actor can be in, as it forces you explicitly define the