CE-14 moved map code into the map project

This commit is contained in:
Rob Austin
2015-03-12 15:27:51 +00:00
parent 05f4af53fd
commit f2d4d213fe
6 changed files with 2699 additions and 0 deletions
@@ -0,0 +1,26 @@
/*
* Copyright 2014 Higher Frequency Trading
*
* http://www.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.map;
/**
* Created by Rob Austin
*/
public interface ChannelFactory {
void createChannel(short channelID);
}
@@ -0,0 +1,164 @@
/*
* Copyright 2014 Higher Frequency Trading
*
* http://www.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.map;
import net.openhft.chronicle.hash.ChronicleHashStatelessClientBuilder;
import net.openhft.lang.MemoryUnit;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* @author Rob Austin.
*/
public final class ClientWiredChronicleMapStatelessClientBuilder<K, V> implements
ChronicleHashStatelessClientBuilder<ClientWiredChronicleMapStatelessClientBuilder<K, V>,
ChronicleMap<K, V>>,
MapBuilder<ClientWiredChronicleMapStatelessClientBuilder<K, V>> {
ClientWiredStatelessClientTcpConnectionHub hub;
private Class keyClass;
private Class valueClass;
private byte localIdentifier;
private short channelID;
private boolean doHandShaking;
public ClientWiredChronicleMapStatelessClientBuilder(InetSocketAddress remoteAddress, Class keyClass, Class valueClass, short channelID) {
this.keyClass = keyClass;
this.valueClass = valueClass;
this.remoteAddress = remoteAddress;
this.channelID = channelID;
}
public ClientWiredChronicleMapStatelessClientBuilder(ClientWiredStatelessClientTcpConnectionHub hub, Class keyClass, Class valueClass, short channelID) {
this.keyClass = keyClass;
this.valueClass = valueClass;
this.hub = hub;
this.channelID = channelID;
}
// This method is meaningful, despite you could call of().create(), because Java 7 doesn't
// infer type parameters if case of chained calls, like this; and then you couldn't omit class
// name. Then it is
// ChronicleMapStatelessClientBuilder.<Integer, Integer>.of(addr).create()
// vs
// createClientOf(addr) // static import
// -- the second is really shorter.
private InetSocketAddress remoteAddress;
private boolean putReturnsNull = false;
private boolean removeReturnsNull = false;
private long timeoutMs = TimeUnit.SECONDS.toMillis(10);
private String name;
private int tcpBufferSize = (int) MemoryUnit.KILOBYTES.toBytes(64);
private final AtomicBoolean used = new AtomicBoolean(false);
InetSocketAddress remoteAddress() {
return remoteAddress;
}
@Override
public ClientWiredChronicleMapStatelessClientBuilder<K, V> putReturnsNull(boolean putReturnsNull) {
this.putReturnsNull = putReturnsNull;
return this;
}
boolean putReturnsNull() {
return putReturnsNull;
}
@Override
public ClientWiredChronicleMapStatelessClientBuilder<K, V> removeReturnsNull(boolean removeReturnsNull) {
this.removeReturnsNull = removeReturnsNull;
return this;
}
boolean removeReturnsNull() {
return removeReturnsNull;
}
@Override
public ClientWiredChronicleMapStatelessClientBuilder<K, V> timeout(long timeout, TimeUnit units) {
this.timeoutMs = units.toMillis(timeout);
return this;
}
long timeoutMs() {
return timeoutMs;
}
@Override
public ClientWiredChronicleMapStatelessClientBuilder<K, V> name(String name) {
this.name = name;
return this;
}
String name() {
return name;
}
@Override
public ClientWiredChronicleMapStatelessClientBuilder<K, V> tcpBufferSize(int tcpBufferSize) {
this.tcpBufferSize = tcpBufferSize;
return this;
}
int tcpBufferSize() {
return tcpBufferSize;
}
@Override
public ChronicleMap<K, V> create() throws IOException {
// todo clean this up
if (hub == null)
hub = new ClientWiredStatelessClientTcpConnectionHub(this, localIdentifier, doHandShaking);
if (!used.getAndSet(true)) {
return new ClientWiredStatelessChronicleMap<K, V>(this, keyClass, valueClass, channelID);
} else {
throw new IllegalStateException(
"A stateless client has already been created using this builder. " +
"Create a new ChronicleMapStatelessClientBuilder " +
"to create a new stateless client");
}
}
public void identifier(byte identifier) {
this.localIdentifier = identifier;
}
public short channelID() {
return channelID;
}
public ClientWiredStatelessClientTcpConnectionHub hub() {
return hub;
}
}
@@ -0,0 +1,943 @@
/*
* Copyright 2014 Higher Frequency Trading
*
* http://www.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.map;
import net.openhft.chronicle.hash.function.SerializableFunction;
import net.openhft.chronicle.map.MapWireHandlerBuilder.Fields;
import net.openhft.chronicle.wire.Marshallable;
import net.openhft.chronicle.wire.Wire;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Predicate;
import static java.util.Collections.emptyList;
import static net.openhft.chronicle.map.ClientWiredStatelessChronicleMap.EventId.*;
/**
* @author Rob Austin.
*/
class ClientWiredStatelessChronicleMap<K, V> implements ChronicleMap<K, V>, Cloneable, ChannelFactory {
private static final Logger LOG = LoggerFactory.getLogger(ClientWiredStatelessChronicleMap.class);
private final ClientWiredStatelessClientTcpConnectionHub hub;
protected Class<K> kClass;
protected Class<V> vClass;
private boolean putReturnsNull;
private boolean removeReturnsNull;
private short channelID;
public ClientWiredStatelessChronicleMap(@NotNull final ClientWiredChronicleMapStatelessClientBuilder config,
@NotNull final Class kClass,
@NotNull final Class vClass,
short channelID) {
this.channelID = channelID;
hub = config.hub;
this.putReturnsNull = config.putReturnsNull();
this.removeReturnsNull = config.removeReturnsNull();
this.kClass = kClass;
this.vClass = vClass;
}
@SuppressWarnings("UnusedDeclaration")
void identifier(int localIdentifier) {
hub.localIdentifier = localIdentifier;
}
@Override
public void getAll(File toFile) throws IOException {
JsonSerializer.getAll(toFile, this, emptyList());
}
@Override
public void putAll(File fromFile) throws IOException {
JsonSerializer.putAll(fromFile, this, emptyList());
}
@Override
public V newValueInstance() {
return VanillaChronicleMap.newInstance(vClass, false);
}
@Override
public K newKeyInstance() {
return VanillaChronicleMap.newInstance(kClass, true);
}
@Override
public Class<K> keyClass() {
return kClass;
}
@Override
public boolean forEachEntryWhile(Predicate<? super MapKeyContext<K, V>> predicate) {
// TODO implement!
throw new UnsupportedOperationException();
}
@Override
public void forEachEntry(Consumer<? super MapKeyContext<K, V>> action) {
// TODO implement!
throw new UnsupportedOperationException();
}
public String serverApplicationVersion() {
return hub.serverApplicationVersion(channelID);
}
@Override
public void close() {
// todo add ref count
}
@Override
public Class<V> valueClass() {
return vClass;
}
@NotNull
public File file() {
throw new UnsupportedOperationException();
}
@SuppressWarnings("NullableProblems")
public V putIfAbsent(K key, V value) {
if (key == null || value == null)
throw new NullPointerException();
return proxyReturnObject(PUT_IF_ABSENT.toString(), key, value, vClass);
}
@SuppressWarnings("NullableProblems")
public boolean remove(Object key, Object value) {
if (key == null)
throw new NullPointerException();
return value != null && proxyReturnBoolean(REMOVE_WITH_VALUE.toString(), (K) key, (V) value);
}
@SuppressWarnings("NullableProblems")
public boolean replace(K key, V oldValue, V newValue) {
if (key == null || oldValue == null || newValue == null)
throw new NullPointerException();
return proxyReturnBoolean(REPLACE_WITH_OLD_AND_NEW_VALUE.toString(), key, oldValue, newValue);
}
@SuppressWarnings("NullableProblems")
public V replace(K key, V value) {
if (key == null || value == null)
throw new NullPointerException();
return proxyReturnObject(REPLACE.toString(), key, value, vClass);
}
public int size() {
return (int) longSize();
}
/**
* calling this method should be avoided at all cost, as the entire {@code object} is
* serialized. This equals can be used to compare map that extends ChronicleMap. So two
* Chronicle Maps that contain the same data are considered equal, even if the instances of the
* chronicle maps were of different types
*
* @param object the object that you are comparing against
* @return true if the contain the same data
*/
@Override
public boolean equals(@Nullable Object object) {
if (this == object) return true;
if (object == null || object.getClass().isAssignableFrom(Map.class))
return false;
final Map<? extends K, ? extends V> that = (Map<? extends K, ? extends V>) object;
final int size = size();
if (that.size() != size)
return false;
final Set<Map.Entry<K, V>> entries = entrySet();
return that.entrySet().equals(entries);
}
@Override
public int hashCode() {
return proxyReturnInt(HASH_CODE.toString());
}
@NotNull
public String toString() {
return hub.proxyReturnString(TO_STRING.toString(), channelID);
}
@NotNull
public String serverPersistedDataVersion() {
return hub.proxyReturnString(PERSISTED_DATA_VERSION.toString(), channelID);
}
public boolean isEmpty() {
return proxyReturnBoolean(IS_EMPTY.toString());
}
public boolean containsKey(Object key) {
return proxyReturnBooleanK(CONTAINS_KEY.toString(), (K) key);
}
@NotNull
private NullPointerException keyNotNullNPE() {
return new NullPointerException("key can not be null");
}
public boolean containsValue(Object value) {
return proxyReturnBooleanV(CONTAINS_VALUE.toString(), (V) value);
}
public long longSize() {
return proxyReturnLong(LONG_SIZE.toString());
}
@Override
public MapKeyContext<K, V> context(K key) {
throw new UnsupportedOperationException("Contexts are not supported by stateless clients");
}
public V get(Object key) {
return proxyReturnObject(vClass, GET.toString(), (K) key);
}
@Nullable
public V getUsing(K key, V usingValue) {
throw new UnsupportedOperationException();
}
@NotNull
public V acquireUsing(@NotNull K key, V usingValue) {
throw new UnsupportedOperationException(
"acquireUsing() is not supported for stateless clients");
}
@NotNull
@Override
public MapKeyContext<K, V> acquireContext(@NotNull K key, @NotNull V usingValue) {
throw new UnsupportedOperationException("Contexts are not supported by stateless clients");
}
public V remove(Object key) {
if (key == null)
throw keyNotNullNPE();
return proxyReturnObject(vClass, removeReturnsNull ? REMOVE_WITHOUT_ACC.toString() : REMOVE.toString(), (K) key);
}
@Override
public void createChannel(short channelID) {
proxyReturnVoid(
() -> {
writeField(Fields.METHOD_NAME, "CREATE_CHANNEL");
hub.outWire().write(Fields.ARG_1).int16(channelID);
});
}
public void putAll(@NotNull Map<? extends K, ? extends V> map) {
final long startTime = System.currentTimeMillis();
long transactionId = hub.nextUniqueTransaction(startTime);
Set<? extends Map.Entry<? extends K, ? extends V>> entries = map.entrySet();
Iterator<? extends Map.Entry<? extends K, ? extends V>> iterator = entries.iterator();
boolean hasNext;
do {
hasNext = iterator.hasNext();
hub.outBytesLock().lock();
try {
assert hub.outBytesLock().isHeldByCurrentThread();
assert !hub.inBytesLock().isHeldByCurrentThread();
Wire wire = hub.outWire();
assert hub.outBytesLock().isHeldByCurrentThread();
hub.markSize(wire);
hub.startTime(startTime);
wire.write(Fields.TRANSACTION_ID).int64(transactionId);
wire.write(Fields.TIME_STAMP).int64(startTime);
wire.write(Fields.CHANNEL_ID).int16(channelID);
writeField(Fields.METHOD_NAME, "PUT_ALL");
hub.outWire().write(Fields.HAS_NEXT).bool(hasNext);
if (hasNext) {
Map.Entry<? extends K, ? extends V> e = iterator.next();
writeField(Fields.ARG_1, e.getKey());
writeField(Fields.ARG_2, e.getValue());
}
hub.writeSocket(hub.outWire());
} finally {
hub.outBytesLock().unlock();
}
} while (hasNext);
// wait for the transaction id to be received, this will only be received once the
// last chunk has been processed
readVoid(transactionId, startTime);
}
public V put(K key, V value) {
if (key == null || value == null)
throw new NullPointerException();
return proxyReturnObject(putReturnsNull ? PUT_WITHOUT_ACC.toString() : PUT.toString(), key, value, vClass);
}
@Nullable
public <R> R getMapped(@Nullable K key, @NotNull SerializableFunction<? super V, R> function) {
throw new UnsupportedOperationException();
}
@Nullable
@Override
public V putMapped(@Nullable K key, @NotNull UnaryOperator<V> unaryOperator) {
throw new UnsupportedOperationException();
}
public void clear() {
proxyReturnVoid(() -> writeField(Fields.METHOD_NAME, CLEAR.toString()));
}
public Set<K> keySet() {
final long startTime = System.currentTimeMillis();
final Set<K> result = new HashSet<K>();
// send
final long transactionId = proxySend("KEY_SET", startTime);
assert !hub.outBytesLock().isHeldByCurrentThread();
final long timeoutTime = startTime + hub.timeoutMs;
for (; ; ) {
// receive
hub.inBytesLock().lock();
try {
final Wire wireIn = hub.proxyReply(timeoutTime, transactionId);
if (wireIn.read(Fields.HAS_NEXT).bool()) {
K k = readK(Fields.RESULT, wireIn, null);
result.add(k);
} else
break;
} finally {
hub.inBytesLock().unlock();
}
}
return result;
}
public Collection<V> values() {
final long startTime = System.currentTimeMillis();
final Set<V> result = new HashSet<V>();
// send
final long transactionId = proxySend("VALUES", startTime);
assert !hub.outBytesLock().isHeldByCurrentThread();
final long timeoutTime = startTime + hub.timeoutMs;
for (; ; ) {
// receive
hub.inBytesLock().lock();
try {
final Wire wireIn = hub.proxyReply(timeoutTime, transactionId);
if (wireIn.read(Fields.HAS_NEXT).bool()) {
V v = readV(Fields.RESULT, wireIn, null);
result.add(v);
} else
break;
} finally {
hub.inBytesLock().unlock();
}
}
return result;
}
@NotNull
public Set<Map.Entry<K, V>> entrySet() {
final long startTime = System.currentTimeMillis();
final Map<K, V> result = new HashMap<K, V>();
// send
final long transactionId = proxySend("ENTRY_SET", startTime);
assert !hub.outBytesLock().isHeldByCurrentThread();
final long timeoutTime = startTime + hub.timeoutMs;
for (; ; ) {
// receive
hub.inBytesLock().lock();
try {
final Wire wireIn = hub.proxyReply(timeoutTime, transactionId);
if (wireIn.read(Fields.HAS_NEXT).bool()) {
result.put(
readK(Fields.RESULT_KEY, wireIn, null),
readV(Fields.RESULT_VALUE, wireIn, null));
} else
break;
} finally {
hub.inBytesLock().unlock();
}
}
return result.entrySet();
}
/**
* @param callback each entry is passed to the callback
*/
void entrySet(@NotNull MapEntryCallback<K, V> callback) {
throw new UnsupportedOperationException();
}
private long readLong(long transactionId, long startTime) {
assert !hub.outBytesLock().isHeldByCurrentThread();
final long timeoutTime = startTime + hub.timeoutMs;
// receive
hub.inBytesLock().lock();
try {
return hub.proxyReply(timeoutTime, transactionId).read(Fields.RESULT).int64();
} finally {
hub.inBytesLock().unlock();
}
}
private void writeField(Fields fieldName, Object value) {
writeField(fieldName, value, hub.outWire());
}
private void writeField(Fields fieldName, Object value, Wire wire) {
assert hub.outBytesLock().isHeldByCurrentThread();
assert !hub.inBytesLock().isHeldByCurrentThread();
if (value instanceof Byte)
wire.write(fieldName).int8((Byte) value);
else if (value instanceof Character)
wire.write(fieldName).text(((Character) value).toString());
else if (value instanceof Short)
wire.write(fieldName).int16((Short) value);
else if (value instanceof Integer)
wire.write(fieldName).int32((Integer) value);
else if (value instanceof Long)
wire.write(fieldName).int64((Long) value);
else if (value instanceof CharSequence) {
wire.write(fieldName).text((CharSequence) value);
} else if (value instanceof Marshallable) {
wire.write(fieldName).marshallable((Marshallable) value);
} else {
throw new IllegalStateException("type=" + value.getClass() + " is unsupported, it must either be of type Marshallable or CharSequence");
}
}
private V readValue(Fields argName, long transactionId, long startTime, final V usingValue) {
assert !hub.outBytesLock().isHeldByCurrentThread();
long timeoutTime = startTime + hub.timeoutMs;
hub.inBytesLock().lock();
try {
final Wire wireIn = hub.proxyReply(timeoutTime, transactionId);
if (wireIn.read(Fields.RESULT_IS_NULL).bool())
return null;
return readV(argName, wireIn, usingValue);
} finally {
hub.inBytesLock().unlock();
}
}
private V readV(Fields argName, Wire wireIn, V usingValue) {
if (StringBuilder.class.isAssignableFrom(vClass)) {
wireIn.read(argName).text((StringBuilder) usingValue);
return usingValue;
} else if (Marshallable.class.isAssignableFrom(vClass)) {
if (usingValue == null)
try {
V v = vClass.newInstance();
wireIn.read(argName).marshallable((Marshallable) v);
return v;
} catch (Exception e) {
throw new RuntimeException(e);
}
wireIn.read(argName).marshallable((Marshallable) usingValue);
return usingValue;
} else if (String.class.isAssignableFrom(vClass)) {
//noinspection unchecked
return (V) wireIn.read(argName).text();
} else if (Long.class.isAssignableFrom(vClass)) {
//noinspection unchecked
return (V) (Long) wireIn.read(argName).int64();
} else if (Double.class.isAssignableFrom(vClass)) {
//noinspection unchecked
return (V) (Double) wireIn.read(argName).float64();
} else if (Integer.class.isAssignableFrom(vClass)) {
//noinspection unchecked
return (V) (Integer) wireIn.read(argName).int32();
} else if (Float.class.isAssignableFrom(vClass)) {
//noinspection unchecked
return (V) (Float) wireIn.read(argName).float32();
} else if (Short.class.isAssignableFrom(vClass)) {
//noinspection unchecked
return (V) (Short) wireIn.read(argName).int16();
} else if (Character.class.isAssignableFrom(vClass)) {
//noinspection unchecked
final String text = wireIn.read(argName).text();
if (text == null || text.length() == 0)
return null;
return (V) (Character) text.charAt(0);
} else if (Byte.class.isAssignableFrom(vClass)) {
//noinspection unchecked
return (V) (Byte) wireIn.read(argName).int8();
} else {
throw new IllegalStateException("unsupported type");
}
}
private K readK(Fields argName, Wire wireIn, K usingValue) {
if (StringBuilder.class.isAssignableFrom(kClass)) {
wireIn.read(argName).text((StringBuilder) usingValue);
return usingValue;
} else if (Marshallable.class.isAssignableFrom(kClass)) {
if (usingValue == null)
try {
K v = kClass.newInstance();
wireIn.read(argName).marshallable((Marshallable) v);
return v;
} catch (Exception e) {
throw new RuntimeException(e);
}
wireIn.read(argName).marshallable((Marshallable) usingValue);
return usingValue;
} else if (String.class.isAssignableFrom(vClass)) {
//noinspection unchecked
return (K) wireIn.read(argName).text();
} else {
throw new IllegalStateException("unsupported type");
}
}
private boolean readBoolean(long transactionId, long startTime) {
assert !hub.outBytesLock().isHeldByCurrentThread();
long timeoutTime = startTime + hub.timeoutMs;
// receive
hub.inBytesLock().lock();
try {
final Wire wireIn = hub.proxyReply(timeoutTime, transactionId);
return wireIn.read(Fields.RESULT).bool();
} finally {
hub.inBytesLock().unlock();
}
}
private int readInt(long transactionId, long startTime) {
assert !hub.outBytesLock().isHeldByCurrentThread();
long timeoutTime = startTime + hub.timeoutMs;
// receive
hub.inBytesLock().lock();
try {
final Wire wireIn = hub.proxyReply(timeoutTime, transactionId);
return wireIn.read(Fields.RESULT).int32();
} finally {
hub.inBytesLock().unlock();
}
}
@SuppressWarnings("SameParameterValue")
private boolean proxyReturnBoolean(@NotNull final String methodName, K key, V value) {
final long startTime = System.currentTimeMillis();
long transactionId;
hub.outBytesLock().lock();
try {
transactionId = hub.writeHeader(startTime, channelID, hub.outWire());
writeField(Fields.METHOD_NAME, methodName);
writeField(Fields.ARG_1, key);
writeField(Fields.ARG_2, value);
hub.writeSocket(hub.outWire());
} finally {
hub.outBytesLock().unlock();
}
return readBoolean(transactionId, startTime);
}
@SuppressWarnings("SameParameterValue")
private boolean proxyReturnBoolean(@NotNull final String methodName, K key, V value1, V value2) {
final long startTime = System.currentTimeMillis();
long transactionId;
hub.outBytesLock().lock();
try {
transactionId = hub.writeHeader(startTime, channelID, hub.outWire());
writeField(Fields.METHOD_NAME, methodName);
writeField(Fields.ARG_1, value1);
writeField(Fields.ARG_2, value2);
hub.writeSocket(hub.outWire());
} finally {
hub.outBytesLock().unlock();
}
return readBoolean(transactionId, startTime);
}
@SuppressWarnings("SameParameterValue")
private boolean proxyReturnBooleanV(@NotNull final String methodName, V value) {
final long startTime = System.currentTimeMillis();
return readBoolean(proxySend(methodName, startTime, value), startTime);
}
@SuppressWarnings("SameParameterValue")
private boolean proxyReturnBooleanK(@NotNull final String methodName, K key) {
final long startTime = System.currentTimeMillis();
return readBoolean(proxySend(methodName, startTime, key), startTime);
}
@SuppressWarnings("SameParameterValue")
private long proxyReturnLong(@NotNull final String methodName) {
final long startTime = System.currentTimeMillis();
long transactionId;
hub.outBytesLock().lock();
try {
transactionId = hub.writeHeader(startTime, channelID, hub.outWire());
writeField(Fields.METHOD_NAME, methodName);
hub.writeSocket(hub.outWire());
} finally {
hub.outBytesLock().unlock();
}
return readLong(transactionId, startTime);
}
@SuppressWarnings("SameParameterValue")
private boolean proxyReturnBoolean(@NotNull final String methodName) {
final long startTime = System.currentTimeMillis();
long transactionId;
hub.outBytesLock().lock();
try {
transactionId = hub.writeHeader(startTime, channelID, hub.outWire());
writeField(Fields.METHOD_NAME, methodName);
hub.writeSocket(hub.outWire());
} finally {
hub.outBytesLock().unlock();
}
return readBoolean(transactionId, startTime);
}
@SuppressWarnings("SameParameterValue")
private void proxyReturnVoid(Runnable r) {
final long startTime = System.currentTimeMillis();
long transactionId;
hub.outBytesLock().lock();
try {
transactionId = hub.writeHeader(startTime, channelID, hub.outWire());
r.run();
hub.writeSocket(hub.outWire());
} finally {
hub.outBytesLock().unlock();
}
readVoid(transactionId, startTime);
}
@SuppressWarnings("SameParameterValue")
private int proxyReturnInt(@NotNull final String methodName) {
final long startTime = System.currentTimeMillis();
long transactionId;
hub.outBytesLock().lock();
try {
transactionId = hub.writeHeader(startTime, channelID, hub.outWire());
writeField(Fields.METHOD_NAME, methodName);
hub.writeSocket(hub.outWire());
} finally {
hub.outBytesLock().unlock();
}
return readInt(transactionId, startTime);
}
@Nullable
private <R> R proxyReturnObject(@NotNull final String methodName, K key, V value, Class<V> resultType) {
final long startTime = System.currentTimeMillis();
long transactionId;
hub.outBytesLock().lock();
try {
assert hub.outBytesLock().isHeldByCurrentThread();
assert !hub.inBytesLock().isHeldByCurrentThread();
transactionId = hub.writeHeader(startTime, channelID, hub.outWire());
writeField(Fields.METHOD_NAME, methodName);
writeField(Fields.ARG_1, key);
writeField(Fields.ARG_2, value);
hub.writeSocket(hub.outWire());
} finally {
hub.outBytesLock().unlock();
}
if (eventReturnsNull(methodName))
return null;
if (resultType == vClass)
return (R) readValue(Fields.RESULT, transactionId, startTime, null);
else
throw new UnsupportedOperationException("class of type class=" + resultType + " is not " +
"supported");
}
@Nullable
private <R> R proxyReturnObject(Class<R> rClass, @NotNull final String methodName, Object key) {
final long startTime = System.currentTimeMillis();
long transactionId;
transactionId = proxySend(methodName, startTime, key);
if (eventReturnsNull(methodName))
return null;
if (rClass == vClass)
return (R) readValue(Fields.RESULT, transactionId, startTime, null);
else
throw new UnsupportedOperationException("class of type class=" + rClass + " is not " +
"supported");
}
private long proxySend(String methodName, long startTime, Object arg1) {
long transactionId;
hub.outBytesLock().lock();
try {
transactionId = hub.writeHeader(startTime, channelID, hub.outWire());
writeField(Fields.METHOD_NAME, methodName);
writeField(Fields.ARG_1, arg1);
hub.writeSocket(hub.outWire());
} finally {
hub.outBytesLock().unlock();
}
return transactionId;
}
private long proxySend(String methodName, long startTime) {
long transactionId;
hub.outBytesLock().lock();
try {
transactionId = hub.writeHeader(startTime, channelID, hub.outWire());
writeField(Fields.METHOD_NAME, methodName);
hub.writeSocket(hub.outWire());
} finally {
hub.outBytesLock().unlock();
}
return transactionId;
}
private boolean eventReturnsNull(@NotNull String methodName) {
switch (methodName) {
case "PUT_ALL_WITHOUT_ACC":
case "PUT_WITHOUT_ACC":
case "REMOVE_WITHOUT_ACC":
return true;
default:
return false;
}
}
private void readVoid(long transactionId, long startTime) {
long timeoutTime = startTime + hub.timeoutMs;
// receive
hub.inBytesLock().lock();
try {
hub.proxyReply(timeoutTime, transactionId);
} finally {
hub.inBytesLock().unlock();
}
}
static enum EventId {
HEARTBEAT,
STATEFUL_UPDATE,
LONG_SIZE,
SIZE,
IS_EMPTY,
CONTAINS_KEY,
CONTAINS_VALUE,
GET,
PUT,
PUT_WITHOUT_ACC,
REMOVE,
REMOVE_WITHOUT_ACC,
CLEAR,
KEY_SET,
VALUES,
ENTRY_SET,
REPLACE,
REPLACE_WITH_OLD_AND_NEW_VALUE,
PUT_IF_ABSENT,
REMOVE_WITH_VALUE,
TO_STRING,
APPLICATION_VERSION,
PERSISTED_DATA_VERSION,
PUT_ALL,
PUT_ALL_WITHOUT_ACC,
HASH_CODE,
MAP_FOR_KEY,
PUT_MAPPED,
KEY_BUILDER,
VALUE_BUILDER
}
class Entry implements Map.Entry<K, V> {
final K key;
final V value;
/**
* Creates new entry.
*/
Entry(K k1, V v) {
value = v;
key = k1;
}
public final K getKey() {
return key;
}
public final V getValue() {
return value;
}
public final V setValue(V newValue) {
V oldValue = value;
ClientWiredStatelessChronicleMap.this.put(getKey(), newValue);
return oldValue;
}
public final boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
final Map.Entry e = (Map.Entry) o;
final Object k1 = getKey();
final Object k2 = e.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
return false;
}
public final int hashCode() {
return (key == null ? 0 : key.hashCode()) ^
(value == null ? 0 : value.hashCode());
}
@NotNull
public final String toString() {
return getKey() + "=" + getValue();
}
}
}
@@ -0,0 +1,648 @@
/*
* Copyright 2014 Higher Frequency Trading
*
* http://www.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.map;
import com.sun.jdi.connect.spi.ClosedConnectionException;
import net.openhft.chronicle.bytes.Bytes;
import net.openhft.chronicle.hash.RemoteCallTimeoutException;
import net.openhft.chronicle.hash.impl.util.BuildVersion;
import net.openhft.chronicle.hash.impl.util.CloseablesManager;
import net.openhft.chronicle.network2.event.EventGroup;
import net.openhft.chronicle.wire.TextWire;
import net.openhft.chronicle.wire.Wire;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;
import static net.openhft.chronicle.map.AbstractChannelReplicator.SIZE_OF_SIZE;
import static net.openhft.chronicle.map.ClientWiredStatelessChronicleMap.EventId.APPLICATION_VERSION;
import static net.openhft.chronicle.map.MapWireHandlerBuilder.Fields.*;
/**
* Created by Rob Austin
*/
public class ClientWiredStatelessClientTcpConnectionHub {
private static final Logger LOG = LoggerFactory.getLogger(StatelessChronicleMap.class);
protected final String name;
protected final InetSocketAddress remoteAddress;
protected final long timeoutMs;
protected final int tcpBufferSize;
private final ReentrantLock inBytesLock = new ReentrantLock(true);
private final ReentrantLock outBytesLock = new ReentrantLock();
@NotNull
private final AtomicLong transactionID = new AtomicLong(0);
private final ClientWiredChronicleMapStatelessClientBuilder config;
@Nullable
protected CloseablesManager closeables;
private final Wire outWire = new TextWire(Bytes.elasticByteBuffer());
long largestChunkSoFar = 0;
private final Wire intWire = new TextWire(Bytes.elasticByteBuffer());
// used by the enterprise version
protected int localIdentifier;
private SocketChannel clientChannel;
// this is a transaction id and size that has been read by another thread.
private volatile long parkedTransactionId;
private volatile long parkedTransactionTimeStamp;
private long limitOfLast = 0;
// set up in the header
private long startTime;
private boolean doHandShaking;
public ClientWiredStatelessClientTcpConnectionHub(ClientWiredChronicleMapStatelessClientBuilder config, byte localIdentifier, boolean doHandShaking) {
this.localIdentifier = localIdentifier;
this.doHandShaking = doHandShaking;
this.remoteAddress = config.remoteAddress();
this.tcpBufferSize = config.tcpBufferSize();
this.config = config;
this.name = config.name();
this.timeoutMs = config.timeoutMs();
attemptConnect(remoteAddress);
checkVersion(config.channelID());
}
private synchronized void attemptConnect(final InetSocketAddress remoteAddress) {
// ensures that the excising connection are closed
closeExisting();
try {
SocketChannel socketChannel = AbstractChannelReplicator.openSocketChannel(closeables);
if (socketChannel.connect(remoteAddress)) {
clientChannel = socketChannel;
}
} catch (IOException e) {
if (closeables != null) closeables.closeQuietly();
clientChannel = null;
}
}
ReentrantLock inBytesLock() {
return inBytesLock;
}
ReentrantLock outBytesLock() {
return outBytesLock;
}
protected void checkVersion(short channelID) {
final String serverVersion = serverApplicationVersion(channelID);
final String clientVersion = clientVersion();
if (!serverVersion.equals(clientVersion)) {
LOG.warn("DIFFERENT CHRONICLE-MAP VERSIONS: The Chronicle-Map-Server and " +
"Stateless-Client are on different " +
"versions, " +
" we suggest that you use the same version, server=" + serverApplicationVersion(channelID) + ", " +
"client=" + clientVersion);
}
}
private void checkTimeout(long timeoutTime) {
if (timeoutTime < System.currentTimeMillis())
throw new RemoteCallTimeoutException();
}
protected synchronized void lazyConnect(final long timeoutMs,
final InetSocketAddress remoteAddress) {
if (clientChannel != null)
return;
if (LOG.isDebugEnabled())
LOG.debug("attempting to connect to " + remoteAddress + " ,name=" + name);
SocketChannel result;
long timeoutAt = System.currentTimeMillis() + timeoutMs;
for (; ; ) {
checkTimeout(timeoutAt);
// ensures that the excising connection are closed
closeExisting();
try {
result = AbstractChannelReplicator.openSocketChannel(closeables);
if (!result.connect(remoteAddress)) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
continue;
}
result.socket().setTcpNoDelay(true);
if (doHandShaking)
break;
} catch (IOException e) {
if (closeables != null) closeables.closeQuietly();
} catch (Exception e) {
if (closeables != null) closeables.closeQuietly();
throw e;
}
}
clientChannel = result;
checkVersion(config.channelID());
}
/**
* closes the existing connections and establishes a new closeables
*/
protected void closeExisting() {
// ensure that any excising connection are first closed
if (closeables != null)
closeables.closeQuietly();
closeables = new CloseablesManager();
}
public synchronized void close() {
if (closeables != null)
closeables.closeQuietly();
closeables = null;
clientChannel = null;
}
/**
* the transaction id are generated as unique timestamps
*
* @param time in milliseconds
* @return a unique transactionId
*/
long nextUniqueTransaction(long time) {
long id = time * TcpReplicator.TIMESTAMP_FACTOR;
for (; ; ) {
long old = transactionID.get();
if (old >= id) id = old + 1;
if (transactionID.compareAndSet(old, id))
break;
}
return id;
}
@NotNull
public String serverApplicationVersion(short channelID) {
TextWire wire = new TextWire(Bytes.elasticByteBuffer());
String result = proxyReturnString(APPLICATION_VERSION.toString(), channelID, wire);
return (result == null) ? "" : result;
}
@SuppressWarnings("WeakerAccess")
@NotNull
String clientVersion() {
return BuildVersion.version();
}
/**
* sends data to the server via TCP/IP
*
* @param wire the {@code wire} containing the outbound data
*/
void writeSocket(@NotNull final Wire wire) {
assert outBytesLock().isHeldByCurrentThread();
assert !inBytesLock().isHeldByCurrentThread();
final long timeoutTime = startTime + this.timeoutMs;
try {
for (; ; ) {
if (clientChannel == null)
lazyConnect(timeoutMs, remoteAddress);
try {
writeLength(wire);
// send out all the bytes
writeSocket(wire, timeoutTime);
break;
} catch (@NotNull java.nio.channels.ClosedChannelException | ClosedConnectionException e) {
checkTimeout(timeoutTime);
lazyConnect(timeoutMs, remoteAddress);
}
}
} catch (IOException e) {
close();
throw new IORuntimeException(e);
} catch (Exception e) {
close();
throw e;
}
}
private void writeLength(Wire outWire) {
assert outBytesLock().isHeldByCurrentThread();
long position = outWire.bytes().position();
if (position > Integer.MAX_VALUE || position < Integer.MIN_VALUE)
throw new IllegalStateException("message too large");
long pos = outWire.bytes().position();
try {
outWire.bytes().reset();
int size = (int) (position - outWire.bytes().position());
outWire.bytes().writeUnsignedShort(size - SIZE_OF_SIZE);
} finally {
outWire.bytes().position(pos);
}
}
protected Wire proxyReply(long timeoutTime, final long transactionId) {
assert inBytesLock().isHeldByCurrentThread();
try {
final Wire wire = proxyReplyThrowable(timeoutTime, transactionId);
// handle an exception if the message contains the IS_EXCEPTION field
if (wire.read(() -> "IS_EXCEPTION").bool()) {
final String text = wire.read(() -> "EXCEPTION").text();
throw new RuntimeException(text);
}
return wire;
} catch (IOException e) {
close();
throw new IORuntimeException(e);
} catch (RuntimeException e) {
close();
throw e;
} catch (Exception e) {
close();
throw new RuntimeException(e);
} catch (AssertionError e) {
LOG.error("name=" + name, e);
throw e;
}
}
private Wire proxyReplyThrowable(long timeoutTime, long transactionId) throws IOException {
assert inBytesLock().isHeldByCurrentThread();
for (; ; ) {
// read the next item from the socket
if (parkedTransactionId == 0) {
assert parkedTransactionTimeStamp == 0;
// if we have processed all the bytes that we have read in
if (inWireByteBuffer().position() == intWire.bytes().position())
inWireClear();
// todo change the size to include the meta data bit
// reads just the size
readSocket(SIZE_OF_SIZE, timeoutTime);
final int messageSize = intWire.bytes().readUnsignedShort(intWire.bytes().position());
assert messageSize > 0 : "Invalid message size " + messageSize;
assert messageSize < 16 << 20 : "Invalid message size " + messageSize;
final int remainingBytes0 = messageSize;
readSocket(remainingBytes0, timeoutTime);
intWire.bytes().skip(SIZE_OF_SIZE);
intWire.bytes().limit(intWire.bytes().position() + messageSize);
System.out.println("\n--------------------------------\n" +
"client read:\n\n" + Bytes.toDebugString(intWire.bytes()));
long transactionId0 = intWire.read(() -> "TRANSACTION_ID").int64();
// check the transaction id is reasonable
/* assert transactionId0 > 1410000000000L * TcpReplicator.TIMESTAMP_FACTOR :
"TransactionId too small " + transactionId0 + " messageSize " + messageSize;
assert transactionId0 < 2100000000000L * TcpReplicator.TIMESTAMP_FACTOR :
"TransactionId too large " + transactionId0 + " messageSize " + messageSize;
*/
// if the transaction id is for this thread process it
if (transactionId0 == transactionId) {
clearParked();
return intWire;
} else {
// if the transaction id is not for this thread, park it
// and allow another thread to pick it up
parkedTransactionTimeStamp = System.currentTimeMillis();
parkedTransactionId = transactionId0;
pause();
continue;
}
}
// the transaction id was read by another thread, but is for this thread, process it
if (parkedTransactionId == transactionId) {
clearParked();
return intWire;
}
// time out the old transaction id
if (System.currentTimeMillis() - timeoutTime >
parkedTransactionTimeStamp) {
LOG.error("name=" + name, new IllegalStateException("Skipped Message with " +
"transaction-id=" +
parkedTransactionTimeStamp +
", this can occur when you have another thread which has called the " +
"stateless client and terminated abruptly before the message has been " +
"returned from the server and hence consumed by the other thread."));
// read the the next message
clearParked();
pause();
}
}
}
/**
* clears the wire and its underlying byte buffer
*/
private void inWireClear() {
inWireByteBuffer().clear();
intWire.bytes().clear();
}
private void clearParked() {
assert inBytesLock().isHeldByCurrentThread();
parkedTransactionId = 0;
parkedTransactionTimeStamp = 0;
}
private void pause() {
assert !outBytesLock().isHeldByCurrentThread();
assert inBytesLock().isHeldByCurrentThread();
/// don't call inBytesLock.isHeldByCurrentThread() as it not atomic
inBytesLock().unlock();
// allows another thread to enter hear
inBytesLock().lock();
}
/**
* reads up to the number of byte in {@code requiredNumberOfBytes} from the socket
*
* @param requiredNumberOfBytes the number of bytes to read
* @param timeoutTime timeout in milliseconds
* @return bytes read from the TCP/IP socket
* @throws java.io.IOException socket failed to read data
*/
@SuppressWarnings("UnusedReturnValue")
private void readSocket(int requiredNumberOfBytes, long timeoutTime) throws IOException {
assert inBytesLock().isHeldByCurrentThread();
ByteBuffer buffer = inWireByteBuffer();
buffer.limit(buffer.position() + requiredNumberOfBytes);
long start = buffer.position();
while (buffer.position() - start < requiredNumberOfBytes) {
int len = clientChannel.read(buffer);
if (len == -1)
throw new IORuntimeException("Disconnection to server");
checkTimeout(timeoutTime);
}
}
private ByteBuffer inWireByteBuffer() {
return (ByteBuffer) intWire.bytes().underlyingObject();
}
/**
* writes the bytes to the socket
*
* @param outWire the data that you wish to write
* @param timeoutTime how long before a we timeout
* @throws java.io.IOException
*/
private void writeSocket(Wire outWire, long timeoutTime) throws IOException {
assert outBytesLock().isHeldByCurrentThread();
assert !inBytesLock().isHeldByCurrentThread();
long outBytesPosition = outWire.bytes().position();
// if we have other threads waiting to send and the buffer is not full, let the other threads
// write to the buffer
if (outBytesLock().hasQueuedThreads() &&
outBytesPosition + largestChunkSoFar <= tcpBufferSize) {
return;
}
final ByteBuffer outBuffer = (ByteBuffer) outWire.bytes().underlyingObject();
outBuffer.limit((int) outWire.bytes().position());
outBuffer.position(SIZE_OF_SIZE);
if (EventGroup.IS_DEBUG ) {
System.out.println("\n--------------------------------------------\n" +
"client wrote:\n\n" + Bytes.toDebugString(outBuffer));
}
outBuffer.position(0);
upateLargestChunkSoFarSize(outBuffer);
while (outBuffer.remaining() > 0) {
int len = clientChannel.write(outBuffer);
if (len == -1)
throw new IORuntimeException("Disconnection to server");
// if we have queued threads then we don't have to write all the bytes as the other
// threads will write the remains bytes.
if (outBuffer.remaining() > 0 && outBytesLock().hasQueuedThreads() &&
outBuffer.remaining() + largestChunkSoFar <= tcpBufferSize) {
if (LOG.isDebugEnabled())
LOG.debug("continuing - without all the data being written to the buffer as " +
"it will be written by the next thread");
outBuffer.compact();
outWire.bytes().limit(outBuffer.limit());
outWire.bytes().position(outBuffer.position());
return;
}
checkTimeout(timeoutTime);
}
outBuffer.clear();
outWire.bytes().clear();
}
/**
* calculates the size of each chunk
*
* @param outBuffer
*/
private void upateLargestChunkSoFarSize(ByteBuffer outBuffer) {
int sizeOfThisChunk = (int) (outBuffer.limit() - limitOfLast);
if (largestChunkSoFar < sizeOfThisChunk)
largestChunkSoFar = sizeOfThisChunk;
limitOfLast = outBuffer.limit();
}
private long proxySend(@NotNull final String methodName,
final long startTime,
short channelID,
@NotNull final Wire wire) {
assert outBytesLock().isHeldByCurrentThread();
assert !inBytesLock().isHeldByCurrentThread();
// send
outBytesLock().lock();
try {
long transactionId = writeHeader(startTime, channelID, wire);
wire.write(() -> "METHOD_NAME").text(methodName);
writeSocket(wire);
return transactionId;
} finally {
outBytesLock().unlock();
}
}
@SuppressWarnings("SameParameterValue")
@Nullable
String proxyReturnString(@NotNull final String messageId, short channelID) {
return proxyReturnString(messageId, channelID, outWire);
}
@SuppressWarnings("SameParameterValue")
@Nullable
String proxyReturnString(@NotNull final String messageId, short channelID, Wire outWire) {
final long startTime = System.currentTimeMillis();
long transactionId;
outBytesLock().lock();
try {
transactionId = proxySend(messageId, startTime, channelID, outWire);
} finally {
outBytesLock().unlock();
}
long timeoutTime = startTime + this.timeoutMs;
// receive
inBytesLock().lock();
try {
return proxyReply(timeoutTime, transactionId).read(() -> "RESULT").text();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
inBytesLock().unlock();
}
}
Wire outWire() {
assert outBytesLock().isHeldByCurrentThread();
return outWire;
}
long writeHeader(long startTime, short channelID, Wire wire) {
assert outBytesLock().isHeldByCurrentThread();
markSize(wire);
startTime(startTime);
long transactionId = nextUniqueTransaction(startTime);
wire.write(TYPE).text("MAP");
wire.write(TRANSACTION_ID).int64(transactionId);
wire.write(TIME_STAMP).int64(startTime);
wire.write(CHANNEL_ID).int16(channelID);
return transactionId;
}
/**
* mark the location of the outWire size
*
* @param outWire
*/
void markSize(Wire outWire) {
assert outBytesLock().isHeldByCurrentThread();
// this is where the size will go
outWire.bytes().mark();
// skip the 2 bytes for the size
outWire.bytes().skip(2);
}
void startTime(long startTime) {
this.startTime = startTime;
}
}
@@ -0,0 +1,857 @@
/*
* Copyright 2014 Higher Frequency Trading
*
* http://www.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.map;
/**
* Created by Rob Austin
*/
import net.openhft.chronicle.bytes.Bytes;
import net.openhft.chronicle.hash.ChronicleHashInstanceBuilder;
import net.openhft.chronicle.hash.impl.util.BuildVersion;
import net.openhft.chronicle.hash.replication.ReplicationHub;
import net.openhft.chronicle.network2.WireHandler;
import net.openhft.chronicle.network2.event.EventGroup;
import net.openhft.chronicle.wire.ValueIn;
import net.openhft.chronicle.wire.Wire;
import net.openhft.chronicle.wire.WireKey;
import net.openhft.lang.io.ByteBufferBytes;
import net.openhft.lang.io.DirectStore;
import net.openhft.lang.io.IByteBufferBytes;
import net.openhft.lang.io.NativeBytes;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.PrintWriter;
import java.io.StreamCorruptedException;
import java.io.StringWriter;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import static net.openhft.chronicle.map.AbstractChannelReplicator.SIZE_OF_SIZE;
import static net.openhft.chronicle.map.MapWireHandlerBuilder.Fields.*;
/**
* @author Rob Austin.
*/
class MapWireHandler<K, V> implements WireHandler {
private static final Logger LOG = LoggerFactory.getLogger(MapWireHandler.class);
private final NativeBytes inLangBytes = new NativeBytes(0, 0);
@NotNull
private final OutMessageAdapter outMessageAdapter = new OutMessageAdapter();
@NotNull
private final ArrayList<BytesChronicleMap> bytesChronicleMaps = new ArrayList<>();
@NotNull
private final ByteBuffer inLanByteBuffer = ByteBuffer.allocateDirect(64);
private final StringBuilder methodName = new StringBuilder();
private final Supplier<ChronicleHashInstanceBuilder<ChronicleMap<K, V>>> chronicleHashInstanceBuilder;
private Wire inWire = null;
private Wire outWire = null;
private final Consumer writeElement = new Consumer<Iterator<byte[]>>() {
@Override
public void accept(Iterator<byte[]> iterator) {
outWire.write(RESULT);
outWire.bytes().write(iterator.next());
}
};
private byte localIdentifier;
private long timestamp;
private short channelId;
private List<Replica> channelList;
private ReplicationHub hub;
private byte remoteIdentifier;
private final Consumer writeEntry = new Consumer<Iterator<Map.Entry<byte[], byte[]>>>() {
@Override
public void accept(Iterator<Map.Entry<byte[], byte[]>> iterator) {
final Map.Entry<byte[], byte[]> entry = iterator.next();
outWire.write(RESULT_KEY);
outWire.bytes().write(entry.getKey());
outWire.write(RESULT_VALUE);
outWire.bytes().write(entry.getValue());
}
};
private Runnable out = null;
private SelectionKey key = null;
public MapWireHandler(@NotNull final Supplier<ChronicleHashInstanceBuilder<ChronicleMap<K, V>>> chronicleHashInstanceBuilder,
@NotNull final ReplicationHub hub,
byte localIdentifier,
@NotNull final List<Replica> channelList) {
this(chronicleHashInstanceBuilder, hub);
this.channelList = channelList;
this.localIdentifier = localIdentifier;
}
public MapWireHandler(Supplier<ChronicleHashInstanceBuilder<ChronicleMap<K, V>>> chronicleHashInstanceBuilder, ReplicationHub hub) {
this.chronicleHashInstanceBuilder = chronicleHashInstanceBuilder;
this.hub = hub;
}
private Map<Long, Runnable> incompleteWork = new HashMap<>();
@Override
public void process(Wire in, Wire out) throws StreamCorruptedException {
// if(LOG.isDebugEnabled()) {
System.out.println("--------------------------------------------\nserver read:\n\n" + Bytes.toDebugString(in.bytes()));
// }
this.inWire = in;
this.outWire = out;
onEvent();
}
private void writeChunked(long transactionId,
@NotNull final Function<Map, Iterator<byte[]>> function,
@NotNull final Consumer<Iterator<byte[]>> c) {
final BytesChronicleMap m = bytesMap(channelId);
final Iterator<byte[]> iterator = function.apply(m);
// this allows us to write more data than the buffer will allow
out = () -> {
// each chunk has its own transaction-id
outWire.write(TRANSACTION_ID).int64(transactionId);
write(map -> {
boolean hasNext = iterator.hasNext();
outWire.write(HAS_NEXT).bool(hasNext);
if (hasNext)
c.accept(iterator);
else
// setting out to NULL denotes that there are no more chunks
out = null;
});
};
out.run();
}
@SuppressWarnings("UnusedReturnValue")
void onEvent() {
// it is assumed by this point that the buffer has all the bytes in it for this message
long transactionId = inWire.read(TRANSACTION_ID).int64();
timestamp = inWire.read(TIME_STAMP).int64();
channelId = inWire.read(CHANNEL_ID).int16();
inWire.read(METHOD_NAME).text(methodName);
if ("PUT_WITHOUT_ACC".contentEquals(methodName)) {
writeVoid(bytesMap -> {
final net.openhft.lang.io.Bytes reader = toReader(inWire, ARG_1, ARG_2);
bytesMap.put(reader, reader, timestamp, identifier());
});
return;
}
try {
if ("KEY_SET".contentEquals(methodName)) {
writeChunked(transactionId, map -> map.keySet().iterator(), writeElement);
return;
}
if ("VALUES".contentEquals(methodName)) {
writeChunked(transactionId, map -> map.values().iterator(), writeElement);
return;
}
if ("ENTRY_SET".contentEquals(methodName)) {
writeChunked(transactionId, map -> map.entrySet().iterator(), writeEntry);
return;
}
if ("PUT_ALL".contentEquals(methodName)) {
putAll(transactionId);
return;
}
// write the transaction id
outWire.write(() -> "TRANSACTION_ID").int64(transactionId);
if ("CREATE_CHANNEL".contentEquals(methodName)) {
writeVoid(() -> {
short channelId1 = inWire.read(ARG_1).int16();
chronicleHashInstanceBuilder.get().replicatedViaChannel(hub.createChannel(channelId1)).create();
return null;
});
return;
}
if ("REMOTE_IDENTIFIER".contentEquals(methodName)) {
this.remoteIdentifier = inWire.read(RESULT).int8();
return;
}
if ("LONG_SIZE".contentEquals(methodName)) {
write(b -> outWire.write(RESULT).int64(b.longSize()));
return;
}
if ("IS_EMPTY".contentEquals(methodName)) {
write(b -> outWire.write(RESULT).bool(b.isEmpty()));
return;
}
if ("CONTAINS_KEY".contentEquals(methodName)) {
write(b -> outWire.write(RESULT).
bool(b.containsKey(toReader(inWire, ARG_1))));
return;
}
if ("CONTAINS_VALUE".contentEquals(methodName)) {
write(b -> outWire.write(RESULT).
bool(b.containsKey(toReader(inWire, ARG_1))));
return;
}
if ("GET".contentEquals(methodName)) {
writeValueUsingDelegate(map -> {
byte[] key1 = this.toByteArray(inWire, ARG_1);
return map.get(key1);
});
return;
}
if ("PUT".contentEquals(methodName)) {
writeValue(b -> {
final net.openhft.lang.io.Bytes reader = toReader(inWire, ARG_1, ARG_2);
return b.put(reader, reader, timestamp, identifier());
});
return;
}
if ("REMOVE".contentEquals(methodName)) {
writeValue(b -> b.remove(toReader(inWire, ARG_1)));
return;
}
if ("CLEAR".contentEquals(methodName)) {
writeVoid(BytesChronicleMap::clear);
return;
}
if ("REPLACE".contentEquals(methodName)) {
writeValue(bytesMap -> {
final net.openhft.lang.io.Bytes reader = toReader(inWire, ARG_1, ARG_2);
return bytesMap.replace(reader, reader);
});
return;
}
if ("REPLACE_WITH_OLD_AND_NEW_VALUE".contentEquals(methodName)) {
writeValue(bytesMap -> {
final net.openhft.lang.io.Bytes reader = toReader(inWire, ARG_1, ARG_2, ARG_3);
return bytesMap.replace(reader, reader);
});
return;
}
if ("PUT_IF_ABSENT".contentEquals(methodName)) {
writeValue(bytesMap -> {
final net.openhft.lang.io.Bytes reader = toReader(inWire, ARG_1, ARG_2);
return bytesMap.putIfAbsent(reader, reader, timestamp, identifier());
});
return;
}
if ("REMOVE_WITH_VALUE".contentEquals(methodName)) {
write(bytesMap -> {
final net.openhft.lang.io.Bytes reader = toReader(inWire, ARG_1, ARG_2);
outWire.write(RESULT).bool(bytesMap.remove(reader, reader, timestamp, identifier()));
});
return;
}
if ("TO_STRING".contentEquals(methodName)) {
write(b -> outWire.write(RESULT).text(b.toString()));
return;
}
if ("APPLICATION_VERSION".contentEquals(methodName)) {
write(b -> outWire.write(RESULT).text(applicationVersion()));
return;
}
if ("PERSISTED_DATA_VERSION".contentEquals(methodName)) {
write(b -> outWire.write(RESULT).text(persistedDataVersion()));
return;
}
if ("HASH_CODE".contentEquals(methodName)) {
write(b -> outWire.write(RESULT).int32(b.hashCode()));
return;
}
throw new IllegalStateException("unsupported event=" + methodName);
} finally {
// if (len > 4)
if (EventGroup.IS_DEBUG) {
long len = outWire.bytes().position() - SIZE_OF_SIZE;
System.out.println("--------------------------------------------\nserver wrote:\n\n" + Bytes.toDebugString(outWire.bytes(), SIZE_OF_SIZE, len));
}
}
}
private byte[] toByteArray(net.openhft.lang.io.Bytes bytes) {
if (bytes == null || bytes.remaining() == 0)
return new byte[]{};
if (bytes.remaining() > Integer.MAX_VALUE)
throw new BufferOverflowException();
byte[] result = new byte[(int) bytes.remaining()];
bytes.write(result);
return result;
}
private byte[] toBytes(WireKey fieldName) {
final Wire wire = inWire;
System.out.println(Bytes.toDebugString(inWire.bytes()));
final ValueIn read = wire.read(fieldName);
final long l = read.readLength();
if (l > Integer.MAX_VALUE)
throw new BufferOverflowException();
final int fieldLength = (int) l;
final long endPos = wire.bytes().position() + fieldLength;
final long limit = wire.bytes().limit();
try {
byte[] bytes = new byte[fieldLength];
wire.bytes().read(bytes);
return bytes;
} finally {
wire.bytes().position(endPos);
wire.bytes().limit(limit);
}
}
private void putAll(long transactionId) {
final BytesChronicleMap bytesMap = bytesMap(MapWireHandler.this.channelId);
if (bytesMap == null)
return;
Runnable runnable = incompleteWork.get(transactionId);
if (runnable != null) {
runnable.run();
return;
}
runnable = new Runnable() {
// we should try and collect the data and then apply it atomically as quickly possible
final Map<byte[], byte[]> collectData = new HashMap<>();
@Override
public void run() {
if (inWire.read(HAS_NEXT).bool()) {
collectData.put(toBytes(ARG_1), toBytes(ARG_2));
} else {
// the old code assumed that all the data would fit into a single buffer
// this assumption is invalid
if (!collectData.isEmpty()) {
bytesMap.delegate.putAll((Map) collectData);
incompleteWork.remove(transactionId);
outWire.write(TRANSACTION_ID).int64(transactionId);
// todo handle the case where there is an exception
outWire.write(IS_EXCEPTION).bool(false);
}
}
}
};
incompleteWork.put(transactionId, runnable);
runnable.run();
}
private byte identifier() {
// if the client provides a remote identifier then we will use that otherwise we will use the local identifier
return remoteIdentifier == 0 ? localIdentifier : remoteIdentifier;
}
@NotNull
private CharSequence persistedDataVersion() {
final BytesChronicleMap bytesChronicleMap = bytesMap(channelId);
if (bytesChronicleMap == null)
return "";
return bytesChronicleMap.persistedDataVersion();
}
@NotNull
private CharSequence applicationVersion() {
return BuildVersion.version();
}
/**
* creates a lang buffer that holds just the payload of the args
*
* @param wire the inbound wire
* @param args the key names of the {@code wire} args
* @return a new lang buffer containing the bytes of the args
*/
private net.openhft.lang.io.Bytes toReader(@NotNull Wire wire, @NotNull WireKey... args) {
final long inSize = wire.bytes().limit();
final net.openhft.lang.io.Bytes bytes = DirectStore.allocate(inSize).bytes();
// copy the bytes to the reader
for (final WireKey field : args) {
final ValueIn read = wire.read(field);
final long fieldLength = read.readLength();
final long endPos = wire.bytes().position() + fieldLength;
final long limit = wire.bytes().limit();
try {
final Bytes source = wire.bytes();
source.limit(endPos);
// write the size
bytes.writeStopBit(source.remaining());
while (source.remaining() > 0) {
if (source.remaining() >= 8)
bytes.writeLong(source.readLong());
else
bytes.writeByte(source.readByte());
}
} finally {
wire.bytes().position(endPos);
wire.bytes().limit(limit);
}
}
return bytes.flip();
}
/**
* creates a lang buffer that holds just the payload of the args
*
* @param wire the inbound wire
* @return a new lang buffer containing the bytes of the args
*/
// todo remove this method - just added to get it to work for now
private byte[] toByteArray(@NotNull Wire wire, @NotNull WireKey field) {
final ValueIn read = wire.read(field);
final long fieldLength = read.readLength();
final long endPos = wire.bytes().position() + fieldLength;
final long limit = wire.bytes().limit();
byte[] result = new byte[]{};
try {
final Bytes source = wire.bytes();
source.limit(endPos);
if (source.remaining() > Integer.MAX_VALUE)
throw new BufferOverflowException();
// write the size
result = new byte[(int) source.remaining()];
source.read(result);
} finally {
wire.bytes().position(endPos);
wire.bytes().limit(limit);
}
return result;
}
/**
* gets the map for this channel id
*
* @param channelId the ID of the map
* @return the chronicle map with this {@code channelId}
*/
@NotNull
private ReplicatedChronicleMap map(short channelId) {
// todo this cast is a bit of a hack, improve later
final ReplicatedChronicleMap replicas =
(ReplicatedChronicleMap) channelList.get(channelId);
if (replicas != null)
return replicas;
throw new IllegalStateException();
}
/**
* this is used to push the data straight into the entry in memory
*
* @param channelId the ID of the map
* @return a BytesChronicleMap used to update the memory which holds the chronicle map
*/
@Nullable
private BytesChronicleMap bytesMap(short channelId) {
final BytesChronicleMap bytesChronicleMap = (channelId < bytesChronicleMaps.size())
? bytesChronicleMaps.get(channelId)
: null;
if (bytesChronicleMap != null)
return bytesChronicleMap;
// grow the array
for (int i = bytesChronicleMaps.size(); i <= channelId; i++) {
bytesChronicleMaps.add(null);
}
final ReplicatedChronicleMap delegate = map(channelId);
final BytesChronicleMap element = new BytesChronicleMap(delegate);
bytesChronicleMaps.set(channelId, element);
return element;
}
@SuppressWarnings("SameReturnValue")
private void writeValue(final Function<BytesChronicleMap, net.openhft.lang.io.Bytes> f) {
write(b -> {
byte[] fromBytes = toByteArray(f.apply(b));
boolean isNull = fromBytes.length == 0;
outWire.write(RESULT_IS_NULL).bool(isNull);
if (isNull)
return;
outWire.write(RESULT);
outWire.bytes().write(fromBytes);
});
}
@SuppressWarnings("SameReturnValue")
private void writeValueUsingDelegate(final Function<ChronicleMap<byte[], byte[]>, byte[]> f) {
write(b -> {
byte[] result = f.apply((ChronicleMap) b.delegate);
boolean isNull = result == null;
outWire.write(RESULT_IS_NULL).bool(isNull);
if (isNull)
return;
isNull = result.length == 0;
outWire.write(RESULT);
outWire.bytes().write(result);
});
}
private void writeValueTcpRep(@NotNull Consumer<BytesChronicleMap> process) {
final BytesChronicleMap bytesMap = bytesMap(channelId);
if (bytesMap == null) {
LOG.error("no map for channelId=" + channelId + " can be found.");
return;
}
inLanByteBuffer.clear();
inLangBytes.clear();
outMessageAdapter.markStartOfMessage();
bytesMap.output = outMessageAdapter.outBuffer;
try {
process.accept(bytesMap);
} catch (Exception e) {
// the idea of wire is that is platform independent,
// so we wil have to send the exception as a String
outWire.write(IS_EXCEPTION).bool(true);
outWire.write(EXCEPTION).text(toString(e));
LOG.error("", e);
return;
}
outMessageAdapter.accept(outWire);
}
@SuppressWarnings("SameReturnValue")
private void write(@NotNull Consumer<BytesChronicleMap> c) {
final BytesChronicleMap bytesMap = bytesMap(channelId);
if (bytesMap == null) {
LOG.error("no map for channelId=" + channelId + " can be found.");
return;
}
bytesMap.output = null;
outWire.bytes().mark();
outWire.write(IS_EXCEPTION).bool(false);
try {
c.accept(bytesMap);
} catch (Exception e) {
outWire.bytes().reset();
// the idea of wire is that is platform independent,
// so we wil have to send the exception as a String
outWire.write(IS_EXCEPTION).bool(true);
outWire.write(EXCEPTION).text(toString(e));
LOG.error("", e);
return;
}
}
@SuppressWarnings("SameReturnValue")
private void writeVoid(@NotNull Callable r) {
final BytesChronicleMap bytesMap = bytesMap(channelId);
if (bytesMap == null) {
LOG.error("no map for channelId=" + channelId + " can be found.");
return;
}
bytesMap.output = null;
outWire.bytes().mark();
outWire.write(IS_EXCEPTION).bool(false);
try {
r.call();
} catch (Exception e) {
outWire.bytes().reset();
// the idea of wire is that is platform independent,
// so we wil have to send the exception as a String
outWire.write(IS_EXCEPTION).bool(true);
outWire.write(EXCEPTION).text(toString(e));
LOG.error("", e);
return;
}
}
/**
* only used for debugging
*/
@SuppressWarnings("UnusedDeclaration")
private void showOutWire() {
System.out.println("pos=" + outWire.bytes().position() + ",bytes=" + Bytes.toDebugString(outWire.bytes(), 0, outWire.bytes().position()));
}
@SuppressWarnings("SameReturnValue")
private void writeVoid(@NotNull Consumer<BytesChronicleMap> process) {
// skip 4 bytes where we will write the size
final BytesChronicleMap bytesMap = bytesMap(channelId);
if (bytesMap == null) {
LOG.error("no map for channelId=" + channelId + " can be found.");
return;
}
bytesMap.output = null;
try {
process.accept(bytesMap);
} catch (Exception e) {
LOG.error("", e);
}
}
/**
* converts the exception into a String, so that it can be sent to c# clients
*/
private String toString(@NotNull Throwable t) {
final StringWriter sw = new StringWriter();
final PrintWriter pw = new PrintWriter(sw);
t.printStackTrace(pw);
return sw.toString();
}
public void localIdentifier(byte localIdentifier) {
this.localIdentifier = localIdentifier;
}
/**
* used to send data to the wired stateless client, this class converts/adapts a chronicle map
* lang-bytes output into chronicle-bytes
*/
private static class OutMessageAdapter {
@NotNull
private final IByteBufferBytes outLangBytes = ByteBufferBytes.wrap(ByteBuffer.allocate(1024));
private Bytes outChronBytes = Bytes.wrap(outLangBytes.buffer());
// chronicle map will write its result in here
final MapIOBuffer outBuffer = new MapIOBuffer() {
@Override
public void ensureBufferSize(long l) {
if (outLangBytes.remaining() >= l)
return;
long size = outLangBytes.capacity() + l;
if (size > Integer.MAX_VALUE)
throw new BufferOverflowException();
// record the current position and limit
long position = outLangBytes.position();
// create a new buffer and copy the data into it
outLangBytes.clear();
final IByteBufferBytes newOutLangBytes = ByteBufferBytes.wrap(ByteBuffer.allocate((int) size));
newOutLangBytes.write(outLangBytes);
outChronBytes = Bytes.wrap(outLangBytes.buffer());
newOutLangBytes.limit(newOutLangBytes.capacity());
newOutLangBytes.position(position);
outChronBytes.limit(outChronBytes.capacity());
outChronBytes.position(position);
}
@NotNull
@Override
public net.openhft.lang.io.Bytes in() {
return outLangBytes;
}
};
private long startChunk;
void clear() {
outChronBytes.clear();
outLangBytes.clear();
}
/**
* adapts the chronicle out lang bytes to chroncile bytes
*
* @param wire the wire that we wish to append data to
*/
void accept(@NotNull Wire wire) {
wire.write(IS_EXCEPTION).bool(false);
// flips calls flip on this message so that we can read it
flipMessage();
// set the chron-bytes and the lang-bytes to be the same
outChronBytes.position(outLangBytes.position());
outChronBytes.limit(outLangBytes.limit());
// is Null
boolean isNull = outChronBytes.readBoolean();
// read the size - not used
outChronBytes.readStopBit();
wire.write(RESULT_IS_NULL).bool(isNull);
if (!isNull) {
// write the result
wire.write(RESULT);
wire.bytes().write(outChronBytes);
}
}
private void flipMessage() {
long position = outLangBytes.position();
outLangBytes.position(startChunk);
outLangBytes.limit(position);
}
/**
* marks the start of the message
*/
public void markStartOfMessage() {
startChunk = outLangBytes.position();
}
}
}
@@ -0,0 +1,61 @@
/*
* Copyright 2014 Higher Frequency Trading
*
* http://www.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.map;
import net.openhft.chronicle.hash.ChronicleHashInstanceBuilder;
import net.openhft.chronicle.hash.replication.ReplicationHub;
import net.openhft.chronicle.network2.WireHandler;
import net.openhft.chronicle.wire.WireKey;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.function.Supplier;
/**
* Created by Rob Austin
*/
public class MapWireHandlerBuilder {
public static <K, V> WireHandler of(
@NotNull final Supplier<ChronicleHashInstanceBuilder<ChronicleMap<K, V>>> chronicleHashInstanceBuilder,
@NotNull final ReplicationHub hub,
byte localIdentifier,
@NotNull final List<Replica> channelList) {
return new MapWireHandler<K, V>(chronicleHashInstanceBuilder, hub, localIdentifier, channelList);
}
public static enum Fields implements WireKey {
HAS_NEXT,
TIME_STAMP,
CHANNEL_ID,
METHOD_NAME,
TYPE,
TRANSACTION_ID,
RESULT,
RESULT_KEY,
RESULT_VALUE,
ARG_1,
ARG_2,
ARG_3,
IS_EXCEPTION,
EXCEPTION,
RESULT_IS_NULL
}
}