/* * 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.bytes.ConnectionDroppedException; import net.openhft.chronicle.core.Jvm; import net.openhft.chronicle.core.io.IORuntimeException; import net.openhft.chronicle.core.util.ThrowingSupplier; import net.openhft.chronicle.core.util.Time; import net.openhft.chronicle.wire.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.Closeable; import java.util.Collection; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.Function; import static net.openhft.chronicle.network.connection.CoreFields.reply; /** * Created by Rob Austin */ public abstract class AbstractStatelessClient<E extends ParameterizeWireKey> implements Closeable { private static final Logger LOG = LoggerFactory.getLogger(AbstractStatelessClient.class); @NotNull protected final TcpChannelHub hub; @NotNull protected final String csp; private final long cid; /** * @param hub for this connection * @param cid used by proxies such as the entry-set * @param csp the uri of the request */ protected AbstractStatelessClient(@NotNull final TcpChannelHub hub, long cid, @NotNull final String csp) { this.cid = cid; this.csp = csp; this.hub = hub; } protected static <E extends ParameterizeWireKey> WriteValue toParameters(@NotNull final E eventId, @Nullable final Object... args) { return out -> { @NotNull final WireKey[] paramNames = eventId.params(); //args can be null, e.g. when get() is called from Reference. if (args == null) return; assert args.length == paramNames.length : "methodName=" + eventId + ", args.length=" + args.length + ", paramNames.length=" + paramNames.length; if (paramNames.length == 1) { out.object(args[0]); return; } out.marshallable(m -> { for (int i = 0; i < paramNames.length; i++) { @NotNull final ValueOut vo = m.write(paramNames[i]); vo.object(args[i]); } }); }; } @Nullable protected <R> R proxyReturnWireTypedObject( @NotNull final E eventId, @Nullable R usingValue, @NotNull final Class<R> resultType, @NotNull Object... args) { @Nullable Function<ValueIn, R> consumerIn = resultType == CharSequence.class && usingValue != null ? f -> { f.textTo((StringBuilder) usingValue); return usingValue; } : f -> f.object(resultType); return proxyReturnWireConsumerInOut(eventId, CoreFields.reply, toParameters(eventId, args), consumerIn); } @Nullable protected <R> R proxyReturnTypedObject( @NotNull final E eventId, @Nullable R usingValue, @NotNull final Class<R> resultType, @NotNull Object... args) { @NotNull Function<ValueIn, R> consumerIn = resultType == CharSequence.class && usingValue != null ? f -> { f.textTo((StringBuilder) usingValue); return usingValue; } : f -> f.object(resultType); return proxyReturnWireConsumerInOut(eventId, CoreFields.reply, toParameters(eventId, args), consumerIn); } @Nullable protected <R> R proxyReturnTypedObject( @NotNull final E eventId, @Nullable R usingValue, @NotNull final Class<R> resultType) { @NotNull Function<ValueIn, R> consumerIn = resultType == CharSequence.class && usingValue != null ? f -> { f.textTo((StringBuilder) usingValue); return usingValue; } : f -> f.object(resultType); return proxyReturnWireConsumerInOut(eventId, CoreFields.reply, x -> { }, consumerIn); } /** * this method will re attempt a number of times until successful,if connection is dropped to * the remote server the TcpChannelHub may ( if configured ) automatically failover to another * host. * * @param s the supply * @param <T> the type of supply * @return the result for s.get() */ protected <T> T attempt(@NotNull final ThrowingSupplier<T, TimeoutException> s) { @Nullable ConnectionDroppedException t = null; @Nullable TimeoutException te = null; for (int i = 1; i <= 20; i++) { try { return s.get(); } catch (ConnectionDroppedException e) { t = e; } catch (TimeoutException e) { te = e; } // pause then resend the request // do NOT make this value too small, we have to give time of the connection to be // re-established Jvm.pause(i * 25); } if (t != null) throw t; throw new ConnectionDroppedException(te); } @SuppressWarnings("SameParameterValue") protected long proxyReturnLong(@NotNull final WireKey eventId) { return proxyReturnWireConsumer(eventId, ValueIn::int64); } @SuppressWarnings("SameParameterValue") protected int proxyReturnInt(@NotNull final WireKey eventId) { return proxyReturnWireConsumer(eventId, ValueIn::int32); } protected int proxyReturnInt(@NotNull final E eventId, @NotNull Object... args) { return proxyReturnWireConsumerInOut(eventId, CoreFields.reply, toParameters(eventId, args), ValueIn::int32); } protected byte proxyReturnByte(@NotNull final WireKey eventId) { return proxyReturnWireConsumer(eventId, ValueIn::int8); } protected byte proxyReturnByte(@NotNull WireKey reply, @NotNull final WireKey eventId) { return proxyReturnWireConsumerInOut(eventId, reply, null, ValueIn::int8); } protected int proxyReturnUint16(@NotNull final WireKey eventId) { return proxyReturnWireConsumer(eventId, ValueIn::uint16); } protected <T> T proxyReturnWireConsumer(@NotNull final WireKey eventId, @NotNull final Function<ValueIn, T> consumer) { final long startTime = Time.currentTimeMillis(); return attempt(() -> readWire(sendEvent(startTime, eventId, null), startTime, CoreFields .reply, consumer)); } protected <T> T proxyReturnWireConsumerInOut(@NotNull final WireKey eventId, @NotNull final WireKey reply, @Nullable final WriteValue consumerOut, @NotNull final Function<ValueIn, T> consumerIn) { final long startTime = Time.currentTimeMillis(); return attempt(() -> readWire(sendEvent(startTime, eventId, consumerOut), startTime, reply, consumerIn)); } @SuppressWarnings("SameParameterValue") protected void proxyReturnVoid(@NotNull final WireKey eventId, @Nullable final WriteValue consumer) { final long startTime = Time.currentTimeMillis(); attempt(() -> readWire(sendEvent(startTime, eventId, consumer), startTime, CoreFields .reply, v -> v.marshallable(ReadMarshallable.DISCARD))); } @SuppressWarnings("SameParameterValue") protected void proxyReturnVoid(@NotNull final WireKey eventId) { proxyReturnVoid(eventId, null); } protected long sendEvent(final long startTime, @NotNull final WireKey eventId, @Nullable final WriteValue consumer) { long tid; if (hub.outBytesLock().isHeldByCurrentThread()) throw new IllegalStateException("Cannot view map while debugging"); try { final boolean success = hub.outBytesLock().tryLock(10, TimeUnit.SECONDS); if (!success) throw new IORuntimeException("failed to obtain write lock"); } catch (InterruptedException e) { throw new IORuntimeException(e); } try { tid = writeMetaDataStartTime(startTime); hub.outWire().writeDocument(false, wireOut -> { @NotNull final ValueOut valueOut = wireOut.writeEventName(eventId); if (consumer == null) valueOut.marshallable(WriteMarshallable.EMPTY); else consumer.writeValue(valueOut); }); hub.writeSocket(hub.outWire(), true); } finally { hub.outBytesLock().unlock(); } return tid; } /** * @param eventId the wire event id * @param consumer a function consume the wire * @param reattemptUponFailure if false - will only be sent if the connection is valid */ protected boolean sendEventAsync(@NotNull final WireKey eventId, @Nullable final WriteValue consumer, boolean reattemptUponFailure) { if (!reattemptUponFailure && !hub.isOpen()) return false; if (!reattemptUponFailure) { hub.lock(() -> quietSendEventAsyncWithoutLock(eventId, consumer)); return true; } attempt(() -> { hub.lock2(() -> quietSendEventAsyncWithoutLock(eventId, consumer), true, TryLock.LOCK); return true; }); return false; } /** * @param bytes the bytes to send * @param reattemptUponFailure if false - will only be sent if the connection is valid */ protected boolean sendBytes(@NotNull final Bytes bytes, boolean reattemptUponFailure) { if (reattemptUponFailure) hub.lock(hub::checkConnection); else if (!hub.isOpen()) return false; if (!reattemptUponFailure) { hub.lock(() -> quietSendBytesAsyncWithoutLock(bytes)); return true; } attempt(() -> { hub.lock(() -> quietSendBytesAsyncWithoutLock(bytes)); return true; }); return false; } private void quietSendEventAsyncWithoutLock(@NotNull final WireKey eventId, final WriteValue consumer) { try { sendEventAsyncWithoutLock(eventId, consumer); } catch (ConnectionDroppedException e) { if (LOG.isDebugEnabled()) Jvm.warn().on(getClass(), "", e); else LOG.info(e.toString()); } catch (IORuntimeException e) { // this can occur if the socket is not currently connected LOG.trace("socket is not currently connected.", e); } } private void quietSendBytesAsyncWithoutLock(@NotNull final Bytes bytes) { try { sendBytesAsyncWithoutLock(bytes); } catch (ConnectionDroppedException e) { if (Jvm.isDebug()) Jvm.debug().on(getClass(), e); else Jvm.debug().on(getClass(), e.toString()); } catch (IORuntimeException e) { // this can occur if the socket is not currently connected Jvm.debug().on(getClass(), "socket is not currently connected.", e); } } private void sendBytesAsyncWithoutLock(@NotNull final Bytes bytes) { writeAsyncMetaData(); hub.outWire().bytes().write(bytes); hub.writeSocket(hub.outWire(), true); } private void sendEventAsyncWithoutLock(@NotNull final WireKey eventId, @Nullable final WriteValue consumer) { writeAsyncMetaData(); hub.outWire().writeDocument(false, wireOut -> { @NotNull final ValueOut valueOut = wireOut.writeEventName(eventId); if (consumer == null) valueOut.marshallable(WriteMarshallable.EMPTY); else consumer.writeValue(valueOut); }); hub.writeSocket(hub.outWire(), true); } /** * @param startTime the start time of this transaction * @return the translation id ( which is sent to the server ) */ private long writeMetaDataStartTime(long startTime) { return hub.writeMetaDataStartTime(startTime, hub.outWire(), csp, cid); } /** * Useful for when you know the tid * * @param tid the tid transaction */ protected void writeMetaDataForKnownTID(long tid) { hub.writeMetaDataForKnownTID(tid, hub.outWire(), csp, cid); } /** * if async meta data is written, no response will be returned from the server */ private void writeAsyncMetaData() { hub.writeAsyncHeader(hub.outWire(), csp, cid); } private void checkIsData(@NotNull Wire wireIn) { @NotNull Bytes<?> bytes = wireIn.bytes(); int dataLen = bytes.readVolatileInt(); if (!Wires.isData(dataLen)) throw new IllegalStateException("expecting a data blob, from ->" + Bytes.toString (bytes, 0, bytes.readLimit())); } protected boolean readBoolean(long tid, long startTime) throws ConnectionDroppedException, TimeoutException { assert !hub.outBytesLock().isHeldByCurrentThread(); long timeoutTime = startTime + hub.timeoutMs; // receive final Wire wireIn = hub.proxyReply(timeoutTime, tid); checkIsData(wireIn); return readReply(wireIn, CoreFields.reply, ValueIn::bool); } private long readLong(long tid, long startTime) throws ConnectionDroppedException, TimeoutException { assert !hub.outBytesLock().isHeldByCurrentThread(); long timeoutTime = startTime + hub.timeoutMs; // receive final Wire wireIn = hub.proxyReply(timeoutTime, tid); checkIsData(wireIn); return readReply(wireIn, CoreFields.reply, ValueIn::int64); } private <R> R readReply(@NotNull WireIn wireIn, @NotNull WireKey replyId, @NotNull Function<ValueIn, R> function) { final StringBuilder eventName = Wires.acquireStringBuilder(); @NotNull final ValueIn event = wireIn.read(eventName); if (replyId.contentEquals(eventName)) return function.apply(event); if (CoreFields.exception.contentEquals(eventName)) { throw Jvm.rethrow(event.throwable(true)); } throw new UnsupportedOperationException("unknown event=" + eventName); } @SuppressWarnings("SameParameterValue") protected boolean proxyReturnBooleanWithArgs( @NotNull final E eventId, @NotNull final Object... args) { final long startTime = Time.currentTimeMillis(); return attempt(() -> readBoolean( sendEvent(startTime, eventId, toParameters(eventId, args)), startTime)); } @SuppressWarnings("SameParameterValue") protected long proxyReturnLongWithArgs( @NotNull final E eventId, @NotNull final Object... args) { final long startTime = Time.currentTimeMillis(); return attempt(() -> readLong( sendEvent(startTime, eventId, toParameters(eventId, args)), startTime)); } protected boolean proxyReturnBooleanWithSequence( @NotNull final E eventId, @NotNull final Collection sequence) { final long startTime = Time.currentTimeMillis(); return attempt(() -> readBoolean(sendEvent(startTime, eventId, out -> sequence.forEach(out::object)), startTime)); } @SuppressWarnings("SameParameterValue") protected boolean proxyReturnBoolean(@NotNull final WireKey eventId) { final long startTime = Time.currentTimeMillis(); return attempt(() -> readBoolean(sendEvent(startTime, eventId, null), startTime)); } private <T> T readWire(long tid, long startTime, @NotNull WireKey reply, @NotNull Function<ValueIn, T> c) throws ConnectionDroppedException, TimeoutException { assert !hub.outBytesLock().isHeldByCurrentThread(); final long timeoutTime = startTime + hub.timeoutMs; // receive final Wire wire = hub.proxyReply(timeoutTime, tid); checkIsData(wire); return readReply(wire, reply, c); } protected int readInt(long tid, long startTime) throws ConnectionDroppedException, TimeoutException { assert !hub.outBytesLock().isHeldByCurrentThread(); long timeoutTime = startTime + hub.timeoutMs; final Wire wireIn = hub.proxyReply(timeoutTime, tid); checkIsData(wireIn); return wireIn.read(reply).int32(); } @Override public void close() { hub.close(); } }