/* * Copyright 2016 higherfrequencytrading.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.openhft.chronicle.network; import net.openhft.chronicle.bytes.Bytes; import net.openhft.chronicle.core.Jvm; import net.openhft.chronicle.core.annotation.Nullable; import net.openhft.chronicle.core.io.Closeable; import net.openhft.chronicle.network.api.TcpHandler; import net.openhft.chronicle.network.connection.WireOutPublisher; import net.openhft.chronicle.wire.*; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static net.openhft.chronicle.network.connection.CoreFields.reply; import static net.openhft.chronicle.wire.WireType.BINARY; import static net.openhft.chronicle.wire.WireType.DELTA_BINARY; import static net.openhft.chronicle.wire.WriteMarshallable.EMPTY; public abstract class WireTcpHandler<T extends NetworkContext> implements TcpHandler, NetworkContextManager<T> { private static final int SIZE_OF_SIZE = 4; private static final Logger LOG = LoggerFactory.getLogger(WireTcpHandler.class); // this is the point at which it is worth doing more work to get more data. @NotNull protected Wire outWire; long lastWritePosition = 0; long writeBps; long bytesReadCount; int socketPollCount; volatile long lastMonitor; @NotNull private Wire inWire; private boolean recreateWire; @Nullable private WireType wireType; private WireOutPublisher publisher; private T nc; private volatile boolean closed; private boolean isAcceptor; private long lastReadRemaining; private static void logYaml(@NotNull final DocumentContext dc) { if (YamlLogging.showServerWrites() || YamlLogging.showServerReads()) try { LOG.info("\nDocumentContext:\n" + Wires.fromSizePrefixedBlobs(dc)); } catch (Exception e) { Jvm.warn().on(WireOutPublisher.class, "\nServer Sends ( corrupted ) :\n" + dc.wire().bytes().toDebugString()); } } private static void logYaml(@NotNull final WireOut outWire) { if (YamlLogging.showServerWrites()) try { LOG.info("\nServer Sends:\n" + Wires.fromSizePrefixedBlobs((Wire) outWire)); } catch (Exception e) { Jvm.warn().on(WireOutPublisher.class, "\nServer Sends ( corrupted ) :\n" + outWire.bytes().toDebugString()); } } public boolean isAcceptor() { return this.isAcceptor; } public void wireType(@NotNull WireType wireType) { if (wireType == BINARY) wireType = DELTA_BINARY.isAvailable() ? DELTA_BINARY : BINARY; this.wireType = wireType; if (publisher != null) publisher.wireType(wireType); } public WireOutPublisher publisher() { return publisher; } public void publisher(@NotNull WireOutPublisher publisher) { this.publisher = publisher; if (wireType() != null) publisher.wireType(wireType()); } public void isAcceptor(boolean isAcceptor) { this.isAcceptor = isAcceptor; } @Override public void process(@NotNull Bytes in, @NotNull Bytes out, NetworkContext nc) { if (closed) return; WireType wireType = wireType(); if (wireType == null) wireType = in.readByte(in.readPosition() + 4) < 0 ? WireType.BINARY : WireType.TEXT; checkWires(in, out, wireType); // we assume that if any bytes were in lastOutBytesRemaining the sc.write() would have been // called and this will fail, if the other end has lost its connection if (outWire.bytes().writePosition() != lastWritePosition) { onBytesWritten(); // NOTE you can not use remaining as the buffer maybe resized writeBps += (lastWritePosition - outWire.bytes().writePosition()); } socketPollCount++; bytesReadCount += (in.readRemaining() - lastReadRemaining); long now = System.currentTimeMillis(); if (now > lastMonitor + 10000) { final NetworkStatsListener networkStatsListener = nc().networkStatsListener(); if (networkStatsListener != null) { if (lastMonitor == 0) { networkStatsListener.onNetworkStats(0, 0, 0); } else { networkStatsListener.onNetworkStats(writeBps / 10, bytesReadCount / 10, socketPollCount / 10); writeBps = bytesReadCount = socketPollCount = 0; } } lastMonitor = now; } if (publisher != null) publisher.applyAction(outWire); if (in.readRemaining() >= SIZE_OF_SIZE) onRead0(); else onWrite(outWire); lastWritePosition = outWire.bytes().writePosition(); lastReadRemaining = inWire.bytes().readRemaining(); } protected void onBytesWritten() { } @Override public void onEndOfConnection(boolean heartbeatTimeOut) { final NetworkStatsListener networkStatsListener = nc().networkStatsListener(); if (networkStatsListener != null) networkStatsListener.onNetworkStats(-1, -1, -1); if (publisher != null) publisher.close(); } protected void onWrite(@NotNull WireOut out) { } /** * process all messages in this batch, provided there is plenty of output space. */ private void onRead0() { assert inWire.startUse(); ensureCapacity(); try { while (!inWire.bytes().isEmpty()) { try (DocumentContext dc = inWire.readingDocument()) { if (!dc.isPresent()) return; try { if (YamlLogging.showServerReads()) logYaml(dc); onRead(dc, outWire); onWrite(outWire); } catch (Exception e) { Jvm.warn().on(getClass(), "inWire=" + inWire.getClass() + ",yaml=" + Wires.fromSizePrefixedBlobs(dc), e); } } } } finally { assert inWire.endUse(); } } private void ensureCapacity() { @NotNull final Bytes<?> bytes = inWire.bytes(); if (bytes.readRemaining() >= 4) { final long pos = bytes.readPosition(); int length = bytes.readInt(pos); final long size = pos + Wires.SPB_HEADER_SIZE * 2 + Wires.lengthOf(length); if (size > bytes.realCapacity()) { resizeInWire(size); } } } private void resizeInWire(long size) { @NotNull final Bytes<?> bytes = inWire.bytes(); if (size > bytes.realCapacity()) { Jvm.debug().on(getClass(), Integer.toHexString(System.identityHashCode(bytes)) + " resized to: " + size); bytes.ensureCapacity(size); } } private void logYaml(long start) { if (YamlLogging.showServerReads() && !inWire.bytes().isEmpty()) { String s = Wires.fromSizePrefixedBlobs(inWire.bytes(), start, inWire.bytes() .readLimit()); LOG.info("handler=" + this.getClass().getSimpleName() + ", read:\n" + s); } } protected void checkWires(Bytes in, Bytes out, @NotNull WireType wireType) { if (recreateWire) { recreateWire = false; inWire = wireType.apply(in); outWire = wireType.apply(out); return; } if (inWire == null) { inWire = wireType.apply(in); recreateWire = false; } assert inWire.startUse(); if (inWire.bytes() != in) { inWire = wireType.apply(in); recreateWire = false; } assert inWire.endUse(); boolean replace = outWire == null; if (!replace) { assert outWire.startUse(); replace = outWire.bytes() != out; assert outWire.endUse(); } if (replace) { outWire = wireType.apply(out); recreateWire = false; } } /** * Process an incoming request */ public WireType wireType() { return this.wireType; } /** * @param in the wire to be processed * @param out the result of processing the {@code in} */ protected abstract void onRead(@NotNull DocumentContext in, @NotNull WireOut out); /** * write and exceptions and rolls back if no data was written */ protected void writeData(@NotNull Bytes inBytes, @NotNull WriteMarshallable c) { outWire.writeDocument(false, out -> { final long readPosition = inBytes.readPosition(); final long position = outWire.bytes().writePosition(); try { c.writeMarshallable(outWire); } catch (Throwable t) { inBytes.readPosition(readPosition); if (LOG.isInfoEnabled()) LOG.info("While reading " + inBytes.toDebugString(), " processing wire " + c, t); outWire.bytes().writePosition(position); outWire.writeEventName(() -> "exception").throwable(t); } // write 'reply : {} ' if no data was sent if (position == outWire.bytes().writePosition()) { outWire.writeEventName(reply).marshallable(EMPTY); } }); logYaml(outWire); } /** * write and exceptions and rolls back if no data was written */ protected void writeData(boolean isNotComplete, @NotNull Bytes inBytes, @NotNull WriteMarshallable c) { @NotNull final WriteMarshallable marshallable = out -> { final long readPosition = inBytes.readPosition(); final long position = outWire.bytes().writePosition(); try { c.writeMarshallable(outWire); } catch (Throwable t) { inBytes.readPosition(readPosition); if (LOG.isInfoEnabled()) LOG.info("While reading " + inBytes.toDebugString(), " processing wire " + c, t); outWire.bytes().writePosition(position); outWire.writeEventName(() -> "exception").throwable(t); } // write 'reply : {} ' if no data was sent if (position == outWire.bytes().writePosition()) { outWire.writeEventName(reply).marshallable(EMPTY); } }; if (isNotComplete) outWire.writeNotCompleteDocument(false, marshallable); else outWire.writeDocument(false, marshallable); logYaml(outWire); } public final void nc(T nc) { this.nc = nc; if (!closed) onInitialize(); } public T nc() { return nc; } protected abstract void onInitialize(); @Override public void close() { closed = true; Closeable.closeQuietly(nc); } protected void publish(WriteMarshallable w) { publisher.put("", w); } }