add BackpressureBuffer, see #3253

- also make a Write’s “ack” be a Tcp.Event (to suit pipelines)
- add stress test for BackpressureBuffer
- add it to SslTlsSupportSpec
- add it to the docs
This commit is contained in:
Roland 2013-05-26 10:58:55 +02:00
parent 025a91ecc2
commit ea5b79e562
15 changed files with 764 additions and 248 deletions

View file

@ -14,6 +14,7 @@ import akka.event.Logging;
import akka.event.LoggingAdapter;
import akka.io.Tcp.CommandFailed;
import akka.io.Tcp.ConnectionClosed;
import akka.io.Tcp.Event;
import akka.io.Tcp.Received;
import akka.io.Tcp.Write;
import akka.io.Tcp.WritingResumed;
@ -33,6 +34,13 @@ public class EchoHandler extends UntypedActor {
public static final long MAX_STORED = 100000000;
public static final long HIGH_WATERMARK = MAX_STORED * 5 / 10;
public static final long LOW_WATERMARK = MAX_STORED * 2 / 10;
private static class Ack implements Event {
public final int ack;
public Ack(int ack) {
this.ack = ack;
}
}
public EchoHandler(ActorRef connection, InetSocketAddress remote) {
this.connection = connection;
@ -50,7 +58,7 @@ public class EchoHandler extends UntypedActor {
public void apply(Object msg) throws Exception {
if (msg instanceof Received) {
final ByteString data = ((Received) msg).data();
connection.tell(TcpMessage.write(data, currentOffset()), getSelf());
connection.tell(TcpMessage.write(data, new Ack(currentOffset())), getSelf());
buffer(data);
} else if (msg instanceof Integer) {
@ -59,7 +67,7 @@ public class EchoHandler extends UntypedActor {
} else if (msg instanceof CommandFailed) {
final Write w = (Write) ((CommandFailed) msg).cmd();
connection.tell(TcpMessage.resumeWriting(), getSelf());
getContext().become(buffering((Integer) w.ack()));
getContext().become(buffering((Ack) w.ack()));
} else if (msg instanceof ConnectionClosed) {
final ConnectionClosed cl = (ConnectionClosed) msg;
@ -75,7 +83,7 @@ public class EchoHandler extends UntypedActor {
};
//#buffering
protected Procedure<Object> buffering(final int nack) {
protected Procedure<Object> buffering(final Ack nack) {
return new Procedure<Object>() {
private int toAck = 10;
@ -99,7 +107,7 @@ public class EchoHandler extends UntypedActor {
final int ack = (Integer) msg;
acknowledge(ack);
if (ack >= nack) {
if (ack >= nack.ack) {
// otherwise it was the ack of the last successful write
if (storage.isEmpty()) {
@ -216,12 +224,12 @@ public class EchoHandler extends UntypedActor {
protected void writeAll() {
int i = 0;
for (ByteString data : storage) {
connection.tell(TcpMessage.write(data, storageOffset + i++), getSelf());
connection.tell(TcpMessage.write(data, new Ack(storageOffset + i++)), getSelf());
}
}
protected void writeFirst() {
connection.tell(TcpMessage.write(storage.peek(), storageOffset), getSelf());
connection.tell(TcpMessage.write(storage.peek(), new Ack(storageOffset)), getSelf());
}
//#storage-omitted

View file

@ -13,6 +13,7 @@ import akka.actor.UntypedActor;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import akka.io.Tcp.ConnectionClosed;
import akka.io.Tcp.Event;
import akka.io.Tcp.Received;
import akka.io.TcpMessage;
import akka.japi.Procedure;
@ -85,7 +86,7 @@ public class SimpleEchoHandler extends UntypedActor {
private boolean suspended = false;
private boolean closing = false;
private final Object ACK = new Object();
private final Event ACK = new Event() {};
//#simple-helpers
protected void buffer(ByteString data) {

View file

@ -21,8 +21,13 @@ import akka.actor.UntypedActor;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import akka.io.AbstractPipelineContext;
import akka.io.BackpressureBuffer;
import akka.io.DelimiterFraming;
import akka.io.HasLogging;
import akka.io.PipelineStage;
import static akka.io.PipelineStage.sequence;
import akka.io.SslTlsSupport;
import akka.io.StringByteStringAdapter;
import akka.io.Tcp;
import akka.io.Tcp.Bound;
import akka.io.Tcp.Command;
@ -33,6 +38,8 @@ import akka.io.Tcp.Received;
import akka.io.TcpMessage;
import akka.io.TcpPipelineHandler;
import akka.io.TcpPipelineHandler.Init;
import akka.io.TcpPipelineHandler.WithinActorContext;
import akka.io.TcpReadWriteAdapter;
import akka.io.ssl.SslTlsSupportSpec;
import akka.testkit.AkkaSpec;
import akka.testkit.JavaTestKit;
@ -60,14 +67,8 @@ public class SslDocTest {
.tell(TcpMessage.connect(remote), getSelf());
}
class Context extends AbstractPipelineContext implements HasLogging {
@Override
public LoggingAdapter getLogger() {
return log;
}
}
Init<HasLogging, Command, Event> init = null;
// this will hold the pipeline handlers context
Init<WithinActorContext, String, String> init = null;
@Override
public void onReceive(Object msg) {
@ -79,33 +80,30 @@ public class SslDocTest {
final SSLEngine engine = sslContext.createSSLEngine(
remote.getHostName(), remote.getPort());
engine.setUseClientMode(true);
final SslTlsSupport ssl = new SslTlsSupport(engine);
// set up the context for communicating with TcpPipelineHandler
init = new Init<HasLogging, Command, Event>(ssl) {
@Override
public HasLogging makeContext(ActorContext ctx) {
return new Context();
}
};
// build pipeline and set up context for communicating with TcpPipelineHandler
init = TcpPipelineHandler.withLogger(log, sequence(sequence(sequence(sequence(
new StringByteStringAdapter("utf-8"),
new DelimiterFraming(1024, ByteString.fromString("\n"), true)),
new TcpReadWriteAdapter()),
new SslTlsSupport(engine)),
new BackpressureBuffer(1000, 10000, 1000000)));
// create handler for pipeline, setting ourselves as payload recipient
final ActorRef handler = getContext().actorOf(
TcpPipelineHandler.create(init, getSender(), getSelf()));
// register the SSL handler with the connection
getSender().tell(TcpMessage.register(handler), getSelf());
// and send a message across the SSL channel
handler.tell(
init.command(TcpMessage.write(ByteString.fromString("hello"))),
getSelf());
handler.tell(init.command("hello\n"), getSelf());
} else if (msg instanceof Init.Event) {
// unwrap TcpPipelineHandlers event into a Tcp.Event
final Event recv = init.event(msg);
if (recv instanceof Received) {
// and inform someone of the received payload
listener.tell(((Received) recv).data().utf8String(), getSelf());
}
final String recv = init.event(msg);
// and inform someone of the received payload
listener.tell(recv, getSelf());
}
}
}
@ -130,14 +128,8 @@ public class SslDocTest {
getSelf());
}
class Context extends AbstractPipelineContext implements HasLogging {
@Override
public LoggingAdapter getLogger() {
return log;
}
}
Init<HasLogging, Command, Event> init = null;
// this will hold the pipeline handlers context
Init<WithinActorContext, String, String> init = null;
@Override
public void onReceive(Object msg) {
@ -153,15 +145,15 @@ public class SslDocTest {
final SSLEngine engine = sslContext.createSSLEngine(
remote.getHostName(), remote.getPort());
engine.setUseClientMode(false);
final SslTlsSupport ssl = new SslTlsSupport(engine);
// set up the context for communicating with TcpPipelineHandler
init = new Init<HasLogging, Command, Event>(ssl) {
@Override
public HasLogging makeContext(ActorContext ctx) {
return new Context();
}
};
// build pipeline and set up context for communicating with TcpPipelineHandler
init = TcpPipelineHandler.withLogger(log, sequence(sequence(sequence(sequence(
new StringByteStringAdapter("utf-8"),
new DelimiterFraming(1024, ByteString.fromString("\n"), true)),
new TcpReadWriteAdapter()),
new SslTlsSupport(engine)),
new BackpressureBuffer(1000, 10000, 1000000)));
// create handler for pipeline, setting ourselves as payload recipient
final ActorRef handler = getContext().actorOf(
TcpPipelineHandler.create(init, getSender(), getSelf()));
@ -171,14 +163,11 @@ public class SslDocTest {
} else if (msg instanceof Init.Event) {
// unwrap TcpPipelineHandlers event to get a Tcp.Event
final Event recv = init.event(msg);
if (recv instanceof Received) {
// inform someone of the received message
listener.tell(((Received) recv).data().utf8String(), getSelf());
// and reply (sender is the SSL handler created above)
getSender().tell(init.command(
TcpMessage.write(ByteString.fromString("world"))), getSelf());
}
final String recv = init.event(msg);
// inform someone of the received message
listener.tell(recv, getSelf());
// and reply (sender is the SSL handler created above)
getSender().tell(init.command("world\n"), getSelf());
}
}
}
@ -201,9 +190,9 @@ public class SslDocTest {
assert getLastSender() == server;
final ActorRef client = system.actorOf(Props.create(SslClient.class, bound.localAddress(), ctx, getRef()));
expectMsgEquals("hello");
expectMsgEquals("hello\n");
assert getLastSender() == server;
expectMsgEquals("world");
expectMsgEquals("world\n");
assert getLastSender() == client;
}
};