improve AbstractActor, #21717

* Receive class that wraps PartialFunction, to avoid
  scary scala types
* move AbstractActorContext to AbstractActor.ActorContext
* converting docs, many, many UntypedActor
* removing UntypedActor docs
* add unit test for ReceiveBuilder
* MiMa filters
* consistent use of getContext(), self(), sender()
* rename cross references
* migration guide
* skip samples for now
* improve match type safetyi, add matchUnchecked
  * the `? extends P` caused code like this to compile:
    `match(String.class, (Integer i) -> {})`
  * added matchUnchecked, since it can still be useful (um, convenient)
    to be able to do:
    `matchUnchecked(List.class, (List<String> list) -> {})`
* eleminate some scala.Option
  * preRestart
  * findChild
  * ActorIdentity.getActorRef
This commit is contained in:
Patrik Nordwall 2016-12-13 10:59:29 +01:00
parent 3617fe8b41
commit 4bd6b7aab1
157 changed files with 3290 additions and 8882 deletions

View file

@ -9,7 +9,7 @@ import java.util.LinkedList;
import java.util.Queue;
import akka.actor.ActorRef;
import akka.actor.UntypedActor;
import akka.actor.AbstractActor;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import akka.io.Tcp.CommandFailed;
@ -19,14 +19,13 @@ import akka.io.Tcp.Received;
import akka.io.Tcp.Write;
import akka.io.Tcp.WritingResumed;
import akka.io.TcpMessage;
import akka.japi.Procedure;
import akka.util.ByteString;
//#echo-handler
public class EchoHandler extends UntypedActor {
public class EchoHandler extends AbstractActor {
final LoggingAdapter log = Logging
.getLogger(getContext().system(), getSelf());
.getLogger(getContext().system(), self());
final ActorRef connection;
final InetSocketAddress remote;
@ -35,6 +34,13 @@ public class EchoHandler extends UntypedActor {
public static final long HIGH_WATERMARK = MAX_STORED * 5 / 10;
public static final long LOW_WATERMARK = MAX_STORED * 2 / 10;
private long transferred;
private int storageOffset = 0;
private long stored = 0;
private Queue<ByteString> storage = new LinkedList<ByteString>();
private boolean suspended = false;
private static class Ack implements Event {
public final int ack;
public Ack(int ack) {
@ -45,6 +51,8 @@ public class EchoHandler extends UntypedActor {
public EchoHandler(ActorRef connection, InetSocketAddress remote) {
this.connection = connection;
this.remote = remote;
writing = writing();
// sign death pact: this actor stops when the connection is closed
getContext().watch(connection);
@ -52,138 +60,136 @@ public class EchoHandler extends UntypedActor {
// start out in optimistic write-through mode
getContext().become(writing);
}
@Override
public Receive createReceive() {
return writing;
}
private final Procedure<Object> writing = new Procedure<Object>() {
@Override
public void apply(Object msg) throws Exception {
if (msg instanceof Received) {
final ByteString data = ((Received) msg).data();
connection.tell(TcpMessage.write(data, new Ack(currentOffset())), getSelf());
private final Receive writing;
private Receive writing() {
return receiveBuilder()
.match(Received.class, msg -> {
final ByteString data = msg.data();
connection.tell(TcpMessage.write(data, new Ack(currentOffset())), self());
buffer(data);
} else if (msg instanceof Integer) {
acknowledge((Integer) msg);
} else if (msg instanceof CommandFailed) {
final Write w = (Write) ((CommandFailed) msg).cmd();
connection.tell(TcpMessage.resumeWriting(), getSelf());
})
.match(Integer.class, msg -> {
acknowledge(msg);
})
.match(CommandFailed.class, msg -> {
final Write w = (Write) msg.cmd();
connection.tell(TcpMessage.resumeWriting(), self());
getContext().become(buffering((Ack) w.ack()));
} else if (msg instanceof ConnectionClosed) {
final ConnectionClosed cl = (ConnectionClosed) msg;
if (cl.isPeerClosed()) {
})
.match(ConnectionClosed.class, msg -> {
if (msg.isPeerClosed()) {
if (storage.isEmpty()) {
getContext().stop(getSelf());
getContext().stop(self());
} else {
getContext().become(closing);
getContext().become(closing());
}
}
}
}
};
})
.build();
}
//#buffering
protected Procedure<Object> buffering(final Ack nack) {
return new Procedure<Object>() {
final static class BufferingState {
int toAck = 10;
boolean peerClosed = false;
}
protected Receive buffering(final Ack nack) {
final BufferingState state = new BufferingState();
return receiveBuilder()
.match(Received.class, msg -> {
buffer(msg.data());
private int toAck = 10;
private boolean peerClosed = false;
})
.match(WritingResumed.class, msg -> {
writeFirst();
@Override
public void apply(Object msg) throws Exception {
if (msg instanceof Received) {
buffer(((Received) msg).data());
})
.match(ConnectionClosed.class, msg -> {
if (msg.isPeerClosed())
state.peerClosed = true;
else
getContext().stop(self());
} else if (msg instanceof WritingResumed) {
writeFirst();
})
.match(Integer.class, ack -> {
acknowledge(ack);
} else if (msg instanceof ConnectionClosed) {
if (((ConnectionClosed) msg).isPeerClosed())
peerClosed = true;
else
getContext().stop(getSelf());
if (ack >= nack.ack) {
// otherwise it was the ack of the last successful write
} else if (msg instanceof Integer) {
final int ack = (Integer) msg;
acknowledge(ack);
if (storage.isEmpty()) {
if (state.peerClosed)
getContext().stop(self());
else
getContext().become(writing);
if (ack >= nack.ack) {
// otherwise it was the ack of the last successful write
if (storage.isEmpty()) {
if (peerClosed)
getContext().stop(getSelf());
} else {
if (state.toAck > 0) {
// stay in ACK-based mode for a short while
writeFirst();
--state.toAck;
} else {
// then return to NACK-based again
writeAll();
if (state.peerClosed)
getContext().become(closing());
else
getContext().become(writing);
} else {
if (toAck > 0) {
// stay in ACK-based mode for a short while
writeFirst();
--toAck;
} else {
// then return to NACK-based again
writeAll();
if (peerClosed)
getContext().become(closing);
else
getContext().become(writing);
}
}
}
}
}
};
})
.build();
}
//#buffering
//#closing
protected Procedure<Object> closing = new Procedure<Object>() {
@Override
public void apply(Object msg) throws Exception {
if (msg instanceof CommandFailed) {
protected Receive closing() {
return receiveBuilder()
.match(CommandFailed.class, msg -> {
// the command can only have been a Write
connection.tell(TcpMessage.resumeWriting(), getSelf());
getContext().become(closeResend, false);
} else if (msg instanceof Integer) {
acknowledge((Integer) msg);
connection.tell(TcpMessage.resumeWriting(), self());
getContext().become(closeResend(), false);
})
.match(Integer.class, msg -> {
acknowledge(msg);
if (storage.isEmpty())
getContext().stop(getSelf());
}
}
};
getContext().stop(self());
})
.build();
}
protected Procedure<Object> closeResend = new Procedure<Object>() {
@Override
public void apply(Object msg) throws Exception {
if (msg instanceof WritingResumed) {
protected Receive closeResend() {
return receiveBuilder()
.match(WritingResumed.class, msg -> {
writeAll();
getContext().unbecome();
} else if (msg instanceof Integer) {
acknowledge((Integer) msg);
}
}
};
})
.match(Integer.class, msg -> {
acknowledge(msg);
})
.build();
}
//#closing
//#storage-omitted
@Override
public void onReceive(Object msg) throws Exception {
// this method is not used due to become()
}
@Override
public void postStop() {
log.info("transferred {} bytes from/to [{}]", transferred, remote);
}
private long transferred;
private int storageOffset = 0;
private long stored = 0;
private Queue<ByteString> storage = new LinkedList<ByteString>();
private boolean suspended = false;
//#helpers
protected void buffer(ByteString data) {
storage.add(data);
@ -191,11 +197,11 @@ public class EchoHandler extends UntypedActor {
if (stored > MAX_STORED) {
log.warning("drop connection to [{}] (buffer overrun)", remote);
getContext().stop(getSelf());
getContext().stop(self());
} else if (stored > HIGH_WATERMARK) {
log.debug("suspending reading at {}", currentOffset());
connection.tell(TcpMessage.suspendReading(), getSelf());
connection.tell(TcpMessage.suspendReading(), self());
suspended = true;
}
}
@ -211,7 +217,7 @@ public class EchoHandler extends UntypedActor {
if (suspended && stored < LOW_WATERMARK) {
log.debug("resuming reading");
connection.tell(TcpMessage.resumeReading(), getSelf());
connection.tell(TcpMessage.resumeReading(), self());
suspended = false;
}
}
@ -224,12 +230,12 @@ public class EchoHandler extends UntypedActor {
protected void writeAll() {
int i = 0;
for (ByteString data : storage) {
connection.tell(TcpMessage.write(data, new Ack(storageOffset + i++)), getSelf());
connection.tell(TcpMessage.write(data, new Ack(storageOffset + i++)), self());
}
}
protected void writeFirst() {
connection.tell(TcpMessage.write(storage.peek(), new Ack(storageOffset)), getSelf());
connection.tell(TcpMessage.write(storage.peek(), new Ack(storageOffset)), self());
}
//#storage-omitted

View file

@ -9,7 +9,7 @@ import java.net.InetSocketAddress;
import akka.actor.ActorRef;
import akka.actor.Props;
import akka.actor.SupervisorStrategy;
import akka.actor.UntypedActor;
import akka.actor.AbstractActor;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import akka.io.Tcp;
@ -19,10 +19,10 @@ import akka.io.Tcp.CommandFailed;
import akka.io.Tcp.Connected;
import akka.io.TcpMessage;
public class EchoManager extends UntypedActor {
public class EchoManager extends AbstractActor {
final LoggingAdapter log = Logging
.getLogger(getContext().system(), getSelf());
.getLogger(getContext().system(), self());
final Class<?> handlerClass;
@ -41,41 +41,42 @@ public class EchoManager extends UntypedActor {
final ActorRef tcpManager = Tcp.get(getContext().system()).manager();
//#manager
tcpManager.tell(
TcpMessage.bind(getSelf(), new InetSocketAddress("localhost", 0), 100),
getSelf());
TcpMessage.bind(self(), new InetSocketAddress("localhost", 0), 100),
self());
}
@Override
public void postRestart(Throwable arg0) throws Exception {
// do not restart
getContext().stop(getSelf());
getContext().stop(self());
}
@Override
public void onReceive(Object msg) throws Exception {
if (msg instanceof Bound) {
log.info("listening on [{}]", ((Bound) msg).localAddress());
} else if (msg instanceof Tcp.CommandFailed) {
final CommandFailed failed = (CommandFailed) msg;
if (failed.cmd() instanceof Bind) {
log.warning("cannot bind to [{}]", ((Bind) failed.cmd()).localAddress());
getContext().stop(getSelf());
} else {
log.warning("unknown command failed [{}]", failed.cmd());
}
} else
if (msg instanceof Connected) {
final Connected conn = (Connected) msg;
log.info("received connection from [{}]", conn.remoteAddress());
final ActorRef connection = getSender();
final ActorRef handler = getContext().actorOf(
Props.create(handlerClass, connection, conn.remoteAddress()));
//#echo-manager
connection.tell(TcpMessage.register(handler,
true, // <-- keepOpenOnPeerClosed flag
true), getSelf());
//#echo-manager
}
public Receive createReceive() {
return receiveBuilder()
.match(Bound.class, msg -> {
log.info("listening on [{}]", msg.localAddress());
})
.match(Tcp.CommandFailed.class, failed -> {
if (failed.cmd() instanceof Bind) {
log.warning("cannot bind to [{}]", ((Bind) failed.cmd()).localAddress());
getContext().stop(self());
} else {
log.warning("unknown command failed [{}]", failed.cmd());
}
})
.match(Connected.class, conn -> {
log.info("received connection from [{}]", conn.remoteAddress());
final ActorRef connection = sender();
final ActorRef handler = getContext().actorOf(
Props.create(handlerClass, connection, conn.remoteAddress()));
//#echo-manager
connection.tell(TcpMessage.register(handler,
true, // <-- keepOpenOnPeerClosed flag
true), self());
//#echo-manager
})
.build();
}
}

View file

@ -15,7 +15,7 @@ import java.net.InetSocketAddress;
import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import akka.actor.Props;
import akka.actor.UntypedActor;
import akka.actor.AbstractActor;
import akka.io.Tcp;
import akka.io.Tcp.Bound;
import akka.io.Tcp.CommandFailed;
@ -34,7 +34,7 @@ public class IODocTest extends AbstractJavaTest {
static
//#server
public class Server extends UntypedActor {
public class Server extends AbstractActor {
final ActorRef manager;
@ -49,25 +49,28 @@ public class IODocTest extends AbstractJavaTest {
@Override
public void preStart() throws Exception {
final ActorRef tcp = Tcp.get(getContext().system()).manager();
tcp.tell(TcpMessage.bind(getSelf(),
new InetSocketAddress("localhost", 0), 100), getSelf());
tcp.tell(TcpMessage.bind(self(),
new InetSocketAddress("localhost", 0), 100), self());
}
@Override
public void onReceive(Object msg) throws Exception {
if (msg instanceof Bound) {
manager.tell(msg, getSelf());
} else if (msg instanceof CommandFailed) {
getContext().stop(getSelf());
} else if (msg instanceof Connected) {
final Connected conn = (Connected) msg;
manager.tell(conn, getSelf());
final ActorRef handler = getContext().actorOf(
Props.create(SimplisticHandler.class));
getSender().tell(TcpMessage.register(handler), getSelf());
}
public Receive createReceive() {
return receiveBuilder()
.match(Bound.class, msg -> {
manager.tell(msg, self());
})
.match(CommandFailed.class, msg -> {
getContext().stop(self());
})
.match(Connected.class, conn -> {
manager.tell(conn, self());
final ActorRef handler = getContext().actorOf(
Props.create(SimplisticHandler.class));
sender().tell(TcpMessage.register(handler), self());
})
.build();
}
}
@ -75,23 +78,26 @@ public class IODocTest extends AbstractJavaTest {
static
//#simplistic-handler
public class SimplisticHandler extends UntypedActor {
public class SimplisticHandler extends AbstractActor {
@Override
public void onReceive(Object msg) throws Exception {
if (msg instanceof Received) {
final ByteString data = ((Received) msg).data();
System.out.println(data);
getSender().tell(TcpMessage.write(data), getSelf());
} else if (msg instanceof ConnectionClosed) {
getContext().stop(getSelf());
}
public Receive createReceive() {
return receiveBuilder()
.match(Received.class, msg -> {
final ByteString data = msg.data();
System.out.println(data);
sender().tell(TcpMessage.write(data), self());
})
.match(ConnectionClosed.class, msg -> {
getContext().stop(self());
})
.build();
}
}
//#simplistic-handler
static
//#client
public class Client extends UntypedActor {
public class Client extends AbstractActor {
final InetSocketAddress remote;
final ActorRef listener;
@ -105,44 +111,43 @@ public class IODocTest extends AbstractJavaTest {
this.listener = listener;
final ActorRef tcp = Tcp.get(getContext().system()).manager();
tcp.tell(TcpMessage.connect(remote), getSelf());
tcp.tell(TcpMessage.connect(remote), self());
}
@Override
public void onReceive(Object msg) throws Exception {
if (msg instanceof CommandFailed) {
listener.tell("failed", getSelf());
getContext().stop(getSelf());
} else if (msg instanceof Connected) {
listener.tell(msg, getSelf());
getSender().tell(TcpMessage.register(getSelf()), getSelf());
getContext().become(connected(getSender()));
}
public Receive createReceive() {
return receiveBuilder()
.match(CommandFailed.class, msg -> {
listener.tell("failed", self());
getContext().stop(self());
})
.match(Connected.class, msg -> {
listener.tell(msg, self());
sender().tell(TcpMessage.register(self()), self());
getContext().become(connected(sender()));
})
.build();
}
private Procedure<Object> connected(final ActorRef connection) {
return new Procedure<Object>() {
@Override
public void apply(Object msg) throws Exception {
if (msg instanceof ByteString) {
connection.tell(TcpMessage.write((ByteString) msg), getSelf());
} else if (msg instanceof CommandFailed) {
// OS kernel socket buffer was full
} else if (msg instanceof Received) {
listener.tell(((Received) msg).data(), getSelf());
} else if (msg.equals("close")) {
connection.tell(TcpMessage.close(), getSelf());
} else if (msg instanceof ConnectionClosed) {
getContext().stop(getSelf());
}
}
};
private Receive connected(final ActorRef connection) {
return receiveBuilder()
.match(ByteString.class, msg -> {
connection.tell(TcpMessage.write((ByteString) msg), self());
})
.match(CommandFailed.class, msg -> {
// OS kernel socket buffer was full
})
.match(Received.class, msg -> {
listener.tell(msg.data(), self());
})
.matchEquals("close", msg -> {
connection.tell(TcpMessage.close(), self());
})
.match(ConnectionClosed.class, msg -> {
getContext().stop(self());
})
.build();
}
}

View file

@ -9,7 +9,7 @@ import java.util.LinkedList;
import java.util.Queue;
import akka.actor.ActorRef;
import akka.actor.UntypedActor;
import akka.actor.AbstractActor;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import akka.io.Tcp.ConnectionClosed;
@ -20,10 +20,10 @@ import akka.japi.Procedure;
import akka.util.ByteString;
//#simple-echo-handler
public class SimpleEchoHandler extends UntypedActor {
public class SimpleEchoHandler extends AbstractActor {
final LoggingAdapter log = Logging
.getLogger(getContext().system(), getSelf());
.getLogger(getContext().system(), self());
final ActorRef connection;
final InetSocketAddress remote;
@ -41,38 +41,42 @@ public class SimpleEchoHandler extends UntypedActor {
}
@Override
public void onReceive(Object msg) throws Exception {
if (msg instanceof Received) {
final ByteString data = ((Received) msg).data();
buffer(data);
connection.tell(TcpMessage.write(data, ACK), getSelf());
// now switch behavior to waiting for acknowledgement
getContext().become(buffering, false);
} else if (msg instanceof ConnectionClosed) {
getContext().stop(getSelf());
}
public Receive createReceive() {
return receiveBuilder()
.match(Received.class, msg -> {
final ByteString data = msg.data();
buffer(data);
connection.tell(TcpMessage.write(data, ACK), self());
// now switch behavior to waiting for acknowledgement
getContext().become(buffering(), false);
})
.match(ConnectionClosed.class, msg -> {
getContext().stop(self());
})
.build();
}
private final Procedure<Object> buffering = new Procedure<Object>() {
@Override
public void apply(Object msg) throws Exception {
if (msg instanceof Received) {
buffer(((Received) msg).data());
private final Receive buffering() {
return receiveBuilder()
.match(Received.class, msg -> {
buffer(msg.data());
} else if (msg == ACK) {
})
.match(Event.class, msg -> msg == ACK, msg -> {
acknowledge();
} else if (msg instanceof ConnectionClosed) {
if (((ConnectionClosed) msg).isPeerClosed()) {
})
.match(ConnectionClosed.class, msg -> {
if (msg.isPeerClosed()) {
closing = true;
} else {
// could also be ErrorClosed, in which case we just give up
getContext().stop(getSelf());
getContext().stop(self());
}
}
}
};
})
.build();
}
//#storage-omitted
public void postStop() {
@ -95,11 +99,11 @@ public class SimpleEchoHandler extends UntypedActor {
if (stored > maxStored) {
log.warning("drop connection to [{}] (buffer overrun)", remote);
getContext().stop(getSelf());
getContext().stop(self());
} else if (stored > highWatermark) {
log.debug("suspending reading");
connection.tell(TcpMessage.suspendReading(), getSelf());
connection.tell(TcpMessage.suspendReading(), self());
suspended = true;
}
}
@ -111,18 +115,18 @@ public class SimpleEchoHandler extends UntypedActor {
if (suspended && stored < lowWatermark) {
log.debug("resuming reading");
connection.tell(TcpMessage.resumeReading(), getSelf());
connection.tell(TcpMessage.resumeReading(), self());
suspended = false;
}
if (storage.isEmpty()) {
if (closing) {
getContext().stop(getSelf());
getContext().stop(self());
} else {
getContext().unbecome();
}
} else {
connection.tell(TcpMessage.write(storage.peek(), ACK), getSelf());
connection.tell(TcpMessage.write(storage.peek(), ACK), self());
}
}
//#simple-helpers

View file

@ -4,9 +4,9 @@ import java.util.concurrent.CountDownLatch;
import akka.actor.ActorRef;
import akka.actor.Terminated;
import akka.actor.UntypedActor;
import akka.actor.AbstractActor;
public class Watcher extends UntypedActor {
public class Watcher extends AbstractActor {
static public class Watch {
final ActorRef target;
@ -22,13 +22,16 @@ public class Watcher extends UntypedActor {
}
@Override
public void onReceive(Object msg) throws Exception {
if (msg instanceof Watch) {
getContext().watch(((Watch) msg).target);
} else if (msg instanceof Terminated) {
latch.countDown();
if (latch.getCount() == 0) getContext().stop(getSelf());
}
public Receive createReceive() {
return receiveBuilder()
.match(Watch.class, msg -> {
getContext().watch(msg.target);
})
.match(Terminated.class, msg -> {
latch.countDown();
if (latch.getCount() == 0) getContext().stop(self());
})
.build();
}
}