/*
* 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.connection;
import net.openhft.chronicle.bytes.Bytes;
import net.openhft.chronicle.core.Jvm;
import net.openhft.chronicle.core.threads.InvalidEventHandlerException;
import net.openhft.chronicle.network.TcpEventHandler;
import net.openhft.chronicle.wire.*;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Created by peter.lawrey on 09/07/2015.
*/
public class VanillaWireOutPublisher implements WireOutPublisher {
private static final Logger LOG = LoggerFactory.getLogger(VanillaWireOutPublisher.class);
private final Bytes<ByteBuffer> bytes;
private volatile boolean closed;
private Wire wire;
@NotNull
private List<WireOutConsumer> consumers = new CopyOnWriteArrayList<>();
private int consumerIndex;
public VanillaWireOutPublisher(@NotNull WireType wireType) {
this.closed = false;
bytes = Bytes.elasticByteBuffer(TcpChannelHub.TCP_BUFFER);
final WireType wireType0 = wireType == WireType.DELTA_BINARY ? WireType.BINARY : wireType;
wire = wireType0.apply(bytes);
}
/**
* Apply waiting messages and return false if there was none.
*
* @param bytes buffer to write to.
*/
@Override
public void applyAction(@NotNull Bytes bytes) {
if (this.bytes.readRemaining() > 0) {
synchronized (lock()) {
if (YamlLogging.showServerWrites())
logBuffer();
bytes.write(this.bytes);
this.bytes.clear();
}
}
}
private void logBuffer() {
long pos = this.bytes.readPosition();
try {
while (wire.bytes().readRemaining() > 0) {
try (DocumentContext dc = wire.readingDocument()) {
Bytes<?> bytes = wire.bytes();
if (!dc.isPresent()) {
bytes.readPosition(bytes.readLimit());
return;
}
LOG.info("Server Sends aync event:\n" + Wires.fromSizePrefixedBlobs(dc));
bytes.readPosition(bytes.readLimit());
}
}
} finally {
this.bytes.readPosition(pos);
}
}
/**
* Apply waiting messages and return false if there was none.
*
* @param outWire buffer to write to.
*/
@Override
public void applyAction(@NotNull WireOut outWire) {
applyAction(outWire.bytes());
for (int y = 1; y < 1000; y++) {
long pos = outWire.bytes().writePosition();
for (int i = 0; i < consumers.size(); i++) {
if (outWire.bytes().writePosition() > TcpEventHandler.TCP_BUFFER)
return;
if (isClosed())
return;
WireOutConsumer c = next();
try {
c.accept(outWire);
} catch (InvalidEventHandlerException e) {
consumers.remove(c);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
Jvm.warn().on(getClass(), e);
return;
}
}
if (pos == outWire.bytes().writePosition())
return;
}
Jvm.warn().on(getClass(), new IllegalStateException("loop when too long"));
}
@Override
public void addWireConsumer(WireOutConsumer wireOutConsumer) {
consumers.add(wireOutConsumer);
}
@Override
public boolean removeBytesConsumer(WireOutConsumer wireOutConsumer) {
return consumers.remove(wireOutConsumer);
}
/**
* round robins - the consumers, we should only write when the buffer is empty, as we can't
* guarantee that we will have enough space to add more data to the out wire.
*
* @return the Marshallable that you are writing to
*/
private WireOutConsumer next() {
if (consumerIndex >= consumers.size())
consumerIndex = 0;
return consumers.get(consumerIndex++);
}
@Override
public void put(final Object key, @NotNull WriteMarshallable event) {
if (closed) {
Jvm.debug().on(getClass(), "message ignored as closed");
return;
}
// writes the data and its size
synchronized (lock()) {
assert wire.startUse();
try {
final long start = wire.bytes().writePosition();
event.writeMarshallable(wire);
if (YamlLogging.showServerWrites()) {
long rp = wire.bytes().readPosition();
long rl = wire.bytes().readLimit();
long wl = wire.bytes().writeLimit();
try {
long len = wire.bytes().writePosition() - start;
wire.bytes().readPositionRemaining(start, len);
String message = Wires.fromSizePrefixedBlobs(wire);
LOG.info("Server is about to send async event:" + message);
} finally {
wire.bytes().writeLimit(wl).readLimit(rl).readPosition(rp);
}
}
} finally {
assert wire.endUse();
}
}
}
@Override
public boolean isClosed() {
return closed;
}
private Object lock() {
return this;
}
@Override
public synchronized void close() {
closed = true;
clear();
}
public boolean canTakeMoreData() {
synchronized (lock()) {
assert wire.startUse();
try {
return wire.bytes().writePosition() < TcpChannelHub.TCP_BUFFER / 2; // don't attempt
// to fill the buffer completely.
} finally {
assert wire.endUse();
}
}
}
@Override
public void wireType(@NotNull WireType wireType) {
final WireType wireType0 = wireType == WireType.DELTA_BINARY ? WireType.BINARY : wireType;
if (WireType.valueOf(wire) == wireType0)
return;
synchronized (lock()) {
wire = wireType0.apply(bytes);
}
}
@Override
public void clear() {
synchronized (lock()) {
wire.clear();
}
}
@Override
public boolean isEmpty() {
synchronized (lock()) {
return bytes.isEmpty();
}
}
@NotNull
@Override
public String toString() {
return "VanillaWireOutPublisher{" +
", closed=" + closed +
", " + wire.getClass().getSimpleName() + "=" + bytes +
'}';
}
}