/** * TODO: Document me * * @author teck */ public class ServerMessageChannelImpl extends AbstractMessageChannel implements ServerMessageChannel { private static final TCLogger logger = TCLogging.getLogger(ServerMessageChannel.class); private final ChannelID sessionID; /** this is for the server it needs a session ID */ protected ServerMessageChannelImpl( ChannelID sessionID, TCMessageRouter router, TCMessageFactory msgFactory, final ServerID serverID, final ProductID productId) { super(router, logger, msgFactory, new ClientID(sessionID.toLong()), productId); this.sessionID = sessionID; setLocalNodeID(serverID); // server message channels should always be open initially synchronized (getStatus()) { channelOpened(); } } @Override public ChannelID getChannelID() { return sessionID; } @Override public NetworkStackID open() { throw new UnsupportedOperationException("Server channels don't support open()"); } @Override public NetworkStackID open(char[] password) { throw new UnsupportedOperationException("Server channels don't support open()"); } @Override public void reset() { throw new UnsupportedOperationException("Server channels don't support reset()"); } }
/** Utility for encoding/decoding DNA */ public abstract class BaseDNAEncodingImpl implements DNAEncodingInternal { // XXX: These warning thresholds should be done in a non-static way so they can be made // configurable // and architecture sensitive. private static final int WARN_THRESHOLD = 8 * 1000 * 1000; private static final int BOOLEAN_WARN = WARN_THRESHOLD / 1; private static final int BYTE_WARN = WARN_THRESHOLD / 1; private static final int CHAR_WARN = WARN_THRESHOLD / 2; private static final int DOUBLE_WARN = WARN_THRESHOLD / 8; private static final int FLOAT_WARN = WARN_THRESHOLD / 4; private static final int INT_WARN = WARN_THRESHOLD / 4; private static final int LONG_WARN = WARN_THRESHOLD / 8; private static final int SHORT_WARN = WARN_THRESHOLD / 2; private static final int REF_WARN = WARN_THRESHOLD / 4; static final byte LOGICAL_ACTION_TYPE = 1; static final byte PHYSICAL_ACTION_TYPE = 2; static final byte ARRAY_ELEMENT_ACTION_TYPE = 3; static final byte ENTIRE_ARRAY_ACTION_TYPE = 4; static final byte LITERAL_VALUE_ACTION_TYPE = 5; static final byte PHYSICAL_ACTION_TYPE_REF_OBJECT = 6; static final byte SUB_ARRAY_ACTION_TYPE = 7; private static final TCLogger logger = TCLogging.getLogger(BaseDNAEncodingImpl.class); protected static final byte TYPE_ID_REFERENCE = 1; protected static final byte TYPE_ID_BOOLEAN = 2; protected static final byte TYPE_ID_BYTE = 3; protected static final byte TYPE_ID_CHAR = 4; protected static final byte TYPE_ID_DOUBLE = 5; protected static final byte TYPE_ID_FLOAT = 6; protected static final byte TYPE_ID_INT = 7; protected static final byte TYPE_ID_LONG = 10; protected static final byte TYPE_ID_SHORT = 11; protected static final byte TYPE_ID_STRING = 12; protected static final byte TYPE_ID_STRING_BYTES = 13; protected static final byte TYPE_ID_ARRAY = 14; protected static final byte TYPE_ID_JAVA_LANG_CLASS = 15; protected static final byte TYPE_ID_JAVA_LANG_CLASS_HOLDER = 16; protected static final byte TYPE_ID_ENUM = 22; protected static final byte TYPE_ID_ENUM_HOLDER = 23; protected static final byte TYPE_ID_STRING_COMPRESSED = 25; // protected static final byte TYPE_ID_URL = 26; private static final byte ARRAY_TYPE_PRIMITIVE = 1; private static final byte ARRAY_TYPE_NON_PRIMITIVE = 2; private static final boolean STRING_COMPRESSION_ENABLED; protected static final boolean STRING_COMPRESSION_LOGGING_ENABLED; private static final int STRING_COMPRESSION_MIN_SIZE; private static final ObjectStringSerializer NULL_SERIALIZER = new NullObjectStringSerializer(); static { StringCompressionConfig config = ServiceUtil.loadService(StringCompressionConfig.class); STRING_COMPRESSION_ENABLED = config.enabled(); STRING_COMPRESSION_LOGGING_ENABLED = config.loggingEnabled(); STRING_COMPRESSION_MIN_SIZE = config.minSize(); } protected final ClassProvider classProvider; public BaseDNAEncodingImpl(ClassProvider classProvider) { this.classProvider = classProvider; } @Override public void encode(Object value, TCDataOutput output) { encode(value, output, NULL_SERIALIZER); } @Override public void encode(Object value, TCDataOutput output, ObjectStringSerializer serializer) { if (value == null) { // Normally Null values should have already been converted to null ObjectID, but this is not // true when there are // multiple versions of the same class in the cluster sharign data. value = ObjectID.NULL_ID; } final LiteralValues type = LiteralValues.valueFor(value); switch (type) { case ENUM: output.writeByte(TYPE_ID_ENUM); final Class<?> enumClass = ((Enum<?>) value).getDeclaringClass(); writeString(enumClass.getName(), output, serializer); writeString(((Enum<?>) value).name(), output, serializer); break; case ENUM_HOLDER: output.writeByte(TYPE_ID_ENUM_HOLDER); writeEnumInstance((EnumInstance) value, output, serializer); break; case JAVA_LANG_CLASS: output.writeByte(TYPE_ID_JAVA_LANG_CLASS); final Class<?> c = (Class<?>) value; writeString(c.getName(), output, serializer); break; case JAVA_LANG_CLASS_HOLDER: output.writeByte(TYPE_ID_JAVA_LANG_CLASS_HOLDER); writeClassInstance((ClassInstance) value, output, serializer); break; case BOOLEAN: output.writeByte(TYPE_ID_BOOLEAN); output.writeBoolean(((Boolean) value).booleanValue()); break; case BYTE: output.writeByte(TYPE_ID_BYTE); output.writeByte(((Byte) value).byteValue()); break; case CHARACTER: output.writeByte(TYPE_ID_CHAR); output.writeChar(((Character) value).charValue()); break; case DOUBLE: output.writeByte(TYPE_ID_DOUBLE); output.writeDouble(((Double) value).doubleValue()); break; case FLOAT: output.writeByte(TYPE_ID_FLOAT); output.writeFloat(((Float) value).floatValue()); break; case INTEGER: output.writeByte(TYPE_ID_INT); output.writeInt(((Integer) value).intValue()); break; case LONG: output.writeByte(TYPE_ID_LONG); output.writeLong(((Long) value).longValue()); break; case SHORT: output.writeByte(TYPE_ID_SHORT); output.writeShort(((Short) value).shortValue()); break; case STRING: final String s = (String) value; if (STRING_COMPRESSION_ENABLED && s.length() >= STRING_COMPRESSION_MIN_SIZE) { output.writeByte(TYPE_ID_STRING_COMPRESSED); writeCompressedString(s, output); } else { output.writeByte(TYPE_ID_STRING); writeString(s, output, serializer); } break; case STRING_BYTES: final UTF8ByteDataHolder utfBytes = (UTF8ByteDataHolder) value; output.writeByte(TYPE_ID_STRING_BYTES); serializer.writeStringBytes(output, utfBytes.getBytes()); break; case STRING_BYTES_COMPRESSED: final UTF8ByteCompressedDataHolder utfCompressedBytes = (UTF8ByteCompressedDataHolder) value; output.writeByte(TYPE_ID_STRING_COMPRESSED); output.writeInt(utfCompressedBytes.getUncompressedStringLength()); writeByteArray(utfCompressedBytes.getBytes(), output); output.writeInt(utfCompressedBytes.getStringLength()); output.writeInt(utfCompressedBytes.getStringHash()); break; case OBJECT_ID: output.writeByte(TYPE_ID_REFERENCE); output.writeLong(((ObjectID) value).toLong()); break; case ARRAY: encodeArray(value, output); break; case OBJECT: default: throw Assert.failure("Illegal type (" + type + "):" + value); } // unreachable } private void writeEnumInstance( EnumInstance value, TCDataOutput output, ObjectStringSerializer serializer) { writeStringBytes(value.getClassInstance().getName().getBytes(), output, serializer); writeStringBytes(value.getEnumName().getBytes(), output, serializer); } private void writeClassInstance( ClassInstance value, TCDataOutput output, ObjectStringSerializer serializer) { writeStringBytes(value.getName().getBytes(), output, serializer); } private void writeString(String string, TCDataOutput output, ObjectStringSerializer serializer) { try { writeStringBytes(string.getBytes("UTF-8"), output, serializer); } catch (UnsupportedEncodingException e) { throw new AssertionError(e); } } private void writeStringBytes( byte[] bytes, TCDataOutput output, ObjectStringSerializer serializer) { serializer.writeStringBytes(output, bytes); } private void writeCompressedString(String string, TCDataOutput output) { final byte[] uncompressed = StringCompressionUtil.stringToUncompressedBin(string); final CompressedData compressedInfo = StringCompressionUtil.compressBin(uncompressed); final byte[] compressed = compressedInfo.getCompressedData(); final int compressedSize = compressedInfo.getCompressedSize(); // XXX:: We are writing the original string's uncompressed byte[] length so that we save a // couple of copies when // decompressing output.writeInt(uncompressed.length); writeByteArray(compressed, 0, compressedSize, output); // write string metadata so we can avoid decompression on later L1s output.writeInt(string.length()); output.writeInt(string.hashCode()); if (STRING_COMPRESSION_LOGGING_ENABLED) { logger.info( "Compressed String of size : " + string.length() + " bytes : " + uncompressed.length + " to bytes : " + compressed.length); } } private void writeByteArray(byte[] bytes, int offset, int length, TCDataOutput output) { output.writeInt(length); output.write(bytes, offset, length); } private void writeByteArray(byte bytes[], TCDataOutput output) { output.writeInt(bytes.length); output.write(bytes); } /* This method is an optimized method for writing char array when no check is needed */ // private void writeCharArray(char[] chars, TCDataOutput output) { // output.writeInt(chars.length); // for (int i = 0, n = chars.length; i < n; i++) { // output.writeChar(chars[i]); // } // } protected byte[] readByteArray(TCDataInput input) throws IOException { final int length = input.readInt(); if (length >= BYTE_WARN) { logger.warn("Attempting to allocate a large byte array of size: " + length); } final byte[] array = new byte[length]; input.readFully(array); return array; } @Override public Object decode(TCDataInput input) throws IOException, ClassNotFoundException { return decode(input, NULL_SERIALIZER); } @Override public Object decode(TCDataInput input, ObjectStringSerializer serializer) throws IOException, ClassNotFoundException { final byte type = input.readByte(); switch (type) { case TYPE_ID_ENUM: return readEnum(input, type, serializer); case TYPE_ID_ENUM_HOLDER: return readEnum(input, type, serializer); case TYPE_ID_JAVA_LANG_CLASS: return readClass(input, type, serializer); case TYPE_ID_JAVA_LANG_CLASS_HOLDER: return readClass(input, type, serializer); case TYPE_ID_BOOLEAN: return Boolean.valueOf(input.readBoolean()); case TYPE_ID_BYTE: return Byte.valueOf(input.readByte()); case TYPE_ID_CHAR: return Character.valueOf(input.readChar()); case TYPE_ID_DOUBLE: return Double.valueOf(input.readDouble()); case TYPE_ID_FLOAT: return Float.valueOf(input.readFloat()); case TYPE_ID_INT: return Integer.valueOf(input.readInt()); case TYPE_ID_LONG: return Long.valueOf(input.readLong()); case TYPE_ID_SHORT: return Short.valueOf(input.readShort()); case TYPE_ID_STRING: return readString(input, type, serializer); case TYPE_ID_STRING_COMPRESSED: return readCompressedString(input); case TYPE_ID_STRING_BYTES: return readString(input, type, serializer); case TYPE_ID_REFERENCE: return new ObjectID(input.readLong()); case TYPE_ID_ARRAY: return decodeArray(input); default: throw Assert.failure("Illegal type (" + type + ")"); } // unreachable } // private char[] readCharArray(TCDataInput input) throws IOException { // int length = input.readInt(); // if (length >= CHAR_WARN) { // logger.warn("Attempting to allocate a large char array of size: " + length); // } // char[] array = new char[length]; // for (int i = 0, n = array.length; i < n; i++) { // array[i] = input.readChar(); // } // return array; // } @Override public void encodeArray(Object value, TCDataOutput output) { encodeArray(value, output, value == null ? -1 : Array.getLength(value)); } @Override public void encodeArray(Object value, TCDataOutput output, int length) { output.writeByte(TYPE_ID_ARRAY); if (value == null) { output.writeInt(-1); return; } else { output.writeInt(length); } final Class<?> type = value.getClass().getComponentType(); if (type.isPrimitive()) { output.writeByte(ARRAY_TYPE_PRIMITIVE); switch (primitiveClassMap.get(type)) { case TYPE_ID_BOOLEAN: encodeBooleanArray((boolean[]) value, output, length); break; case TYPE_ID_BYTE: encodeByteArray((byte[]) value, output, length); break; case TYPE_ID_CHAR: encodeCharArray((char[]) value, output, length); break; case TYPE_ID_SHORT: encodeShortArray((short[]) value, output, length); break; case TYPE_ID_INT: encodeIntArray((int[]) value, output, length); break; case TYPE_ID_LONG: encodeLongArray((long[]) value, output, length); break; case TYPE_ID_FLOAT: encodeFloatArray((float[]) value, output, length); break; case TYPE_ID_DOUBLE: encodeDoubleArray((double[]) value, output, length); break; default: throw Assert.failure("unknown primitive array type: " + type); } } else { output.writeByte(ARRAY_TYPE_NON_PRIMITIVE); encodeObjectArray((Object[]) value, output, length); } } private void encodeByteArray(byte[] value, TCDataOutput output, int length) { output.writeByte(TYPE_ID_BYTE); output.write(value, 0, length); } private void encodeObjectArray(Object[] value, TCDataOutput output, int length) { for (int i = 0; i < length; i++) { encode(value[i], output); } } private void encodeDoubleArray(double[] value, TCDataOutput output, int length) { output.writeByte(TYPE_ID_DOUBLE); for (int i = 0; i < length; i++) { output.writeDouble(value[i]); } } private void encodeFloatArray(float[] value, TCDataOutput output, int length) { output.writeByte(TYPE_ID_FLOAT); for (int i = 0; i < length; i++) { output.writeFloat(value[i]); } } private void encodeLongArray(long[] value, TCDataOutput output, int length) { output.writeByte(TYPE_ID_LONG); for (int i = 0; i < length; i++) { output.writeLong(value[i]); } } private void encodeIntArray(int[] value, TCDataOutput output, int length) { output.writeByte(TYPE_ID_INT); for (int i = 0; i < length; i++) { output.writeInt(value[i]); } } private void encodeShortArray(short[] value, TCDataOutput output, int length) { output.writeByte(TYPE_ID_SHORT); for (int i = 0; i < length; i++) { output.writeShort(value[i]); } } private void encodeCharArray(char[] value, TCDataOutput output, int length) { output.writeByte(TYPE_ID_CHAR); for (int i = 0; i < length; i++) { output.writeChar(value[i]); } } private void encodeBooleanArray(boolean[] value, TCDataOutput output, int length) { output.writeByte(TYPE_ID_BOOLEAN); for (int i = 0; i < length; i++) { output.writeBoolean(value[i]); } } private void checkSize(Class<?> type, int threshold, int len) { if (len >= threshold) { logger.warn( "Attempt to read a " + type + " array of len: " + len + "; threshold=" + threshold); } } private Object decodeArray(TCDataInput input) throws IOException, ClassNotFoundException { final int len = input.readInt(); if (len < 0) { return null; } final byte arrayType = input.readByte(); switch (arrayType) { case ARRAY_TYPE_PRIMITIVE: return decodePrimitiveArray(len, input); case ARRAY_TYPE_NON_PRIMITIVE: return decodeNonPrimitiveArray(len, input); default: throw Assert.failure("unknown array type: " + arrayType); } // unreachable } private Object[] decodeNonPrimitiveArray(int len, TCDataInput input) throws IOException, ClassNotFoundException { checkSize(Object.class, REF_WARN, len); final Object[] rv = new Object[len]; for (int i = 0, n = rv.length; i < n; i++) { rv[i] = decode(input); } return rv; } private Object decodePrimitiveArray(int len, TCDataInput input) throws IOException { final byte type = input.readByte(); switch (type) { case TYPE_ID_BOOLEAN: checkSize(Boolean.TYPE, BOOLEAN_WARN, len); return decodeBooleanArray(len, input); case TYPE_ID_BYTE: checkSize(Byte.TYPE, BYTE_WARN, len); return decodeByteArray(len, input); case TYPE_ID_CHAR: checkSize(Character.TYPE, CHAR_WARN, len); return decodeCharArray(len, input); case TYPE_ID_DOUBLE: checkSize(Double.TYPE, DOUBLE_WARN, len); return decodeDoubleArray(len, input); case TYPE_ID_FLOAT: checkSize(Float.TYPE, FLOAT_WARN, len); return decodeFloatArray(len, input); case TYPE_ID_INT: checkSize(Integer.TYPE, INT_WARN, len); return decodeIntArray(len, input); case TYPE_ID_LONG: checkSize(Long.TYPE, LONG_WARN, len); return decodeLongArray(len, input); case TYPE_ID_SHORT: checkSize(Short.TYPE, SHORT_WARN, len); return decodeShortArray(len, input); default: throw Assert.failure("unknown prim type: " + type); } // unreachable } private short[] decodeShortArray(int len, TCDataInput input) throws IOException { final short[] rv = new short[len]; for (int i = 0, n = rv.length; i < n; i++) { rv[i] = input.readShort(); } return rv; } private long[] decodeLongArray(int len, TCDataInput input) throws IOException { final long[] rv = new long[len]; for (int i = 0, n = rv.length; i < n; i++) { rv[i] = input.readLong(); } return rv; } private int[] decodeIntArray(int len, TCDataInput input) throws IOException { final int[] rv = new int[len]; for (int i = 0, n = rv.length; i < n; i++) { rv[i] = input.readInt(); } return rv; } private float[] decodeFloatArray(int len, TCDataInput input) throws IOException { final float[] rv = new float[len]; for (int i = 0, n = rv.length; i < n; i++) { rv[i] = input.readFloat(); } return rv; } private double[] decodeDoubleArray(int len, TCDataInput input) throws IOException { final double[] rv = new double[len]; for (int i = 0, n = rv.length; i < n; i++) { rv[i] = input.readDouble(); } return rv; } private char[] decodeCharArray(int len, TCDataInput input) throws IOException { final char[] rv = new char[len]; for (int i = 0, n = rv.length; i < n; i++) { rv[i] = input.readChar(); } return rv; } private byte[] decodeByteArray(int len, TCDataInput input) throws IOException { final byte[] rv = new byte[len]; if (len != 0) { final int read = input.read(rv, 0, len); if (read != len) { throw new IOException("read " + read + " bytes, expected " + len); } } return rv; } private boolean[] decodeBooleanArray(int len, TCDataInput input) throws IOException { final boolean[] rv = new boolean[len]; for (int i = 0, n = rv.length; i < n; i++) { rv[i] = input.readBoolean(); } return rv; } private Object readEnum(TCDataInput input, byte type, ObjectStringSerializer serializer) throws IOException, ClassNotFoundException { final UTF8ByteDataHolder name = new UTF8ByteDataHolder(readStringBytes(input, serializer)); final byte[] data = readStringBytes(input, serializer); if (useStringEnumRead(type)) { final Class<?> enumType = new ClassInstance(name).asClass(this.classProvider); final String enumName = new String(data, "UTF-8"); return makeEnum(enumType, enumName); } else { final ClassInstance clazzInstance = new ClassInstance(name); final UTF8ByteDataHolder enumName = new UTF8ByteDataHolder(data); return new EnumInstance(clazzInstance, enumName); } } @SuppressWarnings("unchecked") private static Enum<?> makeEnum(Class<?> enumType, String enumName) { return Enum.valueOf((Class<Enum>) enumType, enumName); } protected abstract boolean useStringEnumRead(byte type); protected abstract boolean useClassProvider(byte type, byte typeToCheck); private Object readClass(TCDataInput input, byte type, ObjectStringSerializer serializer) throws IOException, ClassNotFoundException { final UTF8ByteDataHolder name = new UTF8ByteDataHolder(readStringBytes(input, serializer)); if (useClassProvider(type, TYPE_ID_JAVA_LANG_CLASS)) { return new ClassInstance(name).asClass(this.classProvider); } else { return new ClassInstance(name); } } private Object readString(TCDataInput input, byte type, ObjectStringSerializer serializer) throws IOException { final byte[] data = readStringBytes(input, serializer); if (useUTF8String(type)) { // special case the empty string to save memory if (data.length == 0) { return ""; } return new String(data, "UTF-8"); } else { return new UTF8ByteDataHolder(data); } } private byte[] readStringBytes(TCDataInput input, ObjectStringSerializer serializer) throws IOException { return serializer.readStringBytes(input); } protected abstract boolean useUTF8String(byte type); protected Object readCompressedString(TCDataInput input) throws IOException { final int stringUncompressedByteLength = input.readInt(); final byte[] data = readByteArray(input); final int stringLength = input.readInt(); final int stringHash = input.readInt(); return new UTF8ByteCompressedDataHolder( data, stringUncompressedByteLength, stringLength, stringHash); } public static String inflateCompressedString(byte[] data, int length) { try { final ByteArrayInputStream bais = new ByteArrayInputStream(data); final InflaterInputStream iis = new InflaterInputStream(bais); final byte uncompressed[] = new byte[length]; int read; int offset = 0; while (length > 0 && (read = iis.read(uncompressed, offset, length)) != -1) { offset += read; length -= read; } iis.close(); Assert.assertEquals(0, length); return new String(uncompressed, "UTF-8"); } catch (final IOException e) { throw new AssertionError(e); } } private static final Map<Object, Byte> primitiveClassMap = new HashMap<Object, Byte>(); static { primitiveClassMap.put(java.lang.Boolean.TYPE, TYPE_ID_BOOLEAN); primitiveClassMap.put(java.lang.Byte.TYPE, TYPE_ID_BYTE); primitiveClassMap.put(java.lang.Character.TYPE, TYPE_ID_CHAR); primitiveClassMap.put(java.lang.Double.TYPE, TYPE_ID_DOUBLE); primitiveClassMap.put(java.lang.Float.TYPE, TYPE_ID_FLOAT); primitiveClassMap.put(java.lang.Integer.TYPE, TYPE_ID_INT); primitiveClassMap.put(java.lang.Long.TYPE, TYPE_ID_LONG); primitiveClassMap.put(java.lang.Short.TYPE, TYPE_ID_SHORT); } }
public class TCServerInfo extends AbstractTerracottaMBean implements TCServerInfoMBean, StateChangeListener { private static final TCLogger logger = TCLogging.getLogger(TCServerInfo.class); private static final boolean DEBUG = false; private static final MBeanNotificationInfo[] NOTIFICATION_INFO; static { final String[] notifTypes = new String[] {AttributeChangeNotification.ATTRIBUTE_CHANGE}; final String name = AttributeChangeNotification.class.getName(); final String description = "An attribute of this MBean has changed"; NOTIFICATION_INFO = new MBeanNotificationInfo[] {new MBeanNotificationInfo(notifTypes, name, description)}; } private final TCServer server; private final ProductInfo productInfo; private final String buildID; private final L2State l2State; private final StateChangeNotificationInfo stateChangeNotificationInfo; private long nextSequenceNumber; private final JVMMemoryManager manager; private final ObjectStatsRecorder objectStatsRecorder; public TCServerInfo( final TCServer server, final L2State l2State, final ObjectStatsRecorder objectStatsRecorder) throws NotCompliantMBeanException { super(TCServerInfoMBean.class, true); this.server = server; this.l2State = l2State; this.l2State.registerStateChangeListener(this); productInfo = ProductInfo.getInstance(); buildID = productInfo.buildID(); nextSequenceNumber = 1; stateChangeNotificationInfo = new StateChangeNotificationInfo(); manager = TCRuntime.getJVMMemoryManager(); this.objectStatsRecorder = objectStatsRecorder; } public ObjectStatsRecorder getObjectStatsRecorder() { return this.objectStatsRecorder; } @Override public void reset() { // nothing to reset } @Override public boolean isLegacyProductionModeEnabled() { return TCPropertiesImpl.getProperties() .getBoolean(TCPropertiesConsts.L2_ENABLE_LEGACY_PRODUCTION_MODE); } @Override public boolean isStarted() { return l2State.isStartState(); } @Override public boolean isActive() { return l2State.isActiveCoordinator(); } @Override public boolean isPassiveUninitialized() { return l2State.isPassiveUninitialized(); } @Override public boolean isPassiveStandby() { return l2State.isPassiveStandby(); } @Override public boolean isRecovering() { return l2State.isRecovering(); } @Override public long getStartTime() { return server.getStartTime(); } @Override public long getActivateTime() { return server.getActivateTime(); } @Override public boolean isGarbageCollectionEnabled() { return server.isGarbageCollectionEnabled(); } @Override public int getGarbageCollectionInterval() { return server.getGarbageCollectionInterval(); } @Override public void stop() { server.stop(); _sendNotification( "TCServer stopped", "Started", "java.lang.Boolean", Boolean.TRUE, Boolean.FALSE); } @Override public boolean isShutdownable() { return server.canShutdown(); } /** * This schedules the shutdown to occur one second after we return from this call because * otherwise JMX will be shutdown and we'll get all sorts of other errors trying to return from * this call. */ @Override public void shutdown() { if (!server.canShutdown()) { String msg = "Server cannot be shutdown because it is not fully started."; logger.error(msg); throw new RuntimeException(msg); } logger.warn("shutdown is invoked by MBean"); final Timer timer = new Timer("TCServerInfo shutdown timer"); final TimerTask task = new TimerTask() { @Override public void run() { server.shutdown(); } }; timer.schedule(task, 1000); } @Override public MBeanNotificationInfo[] getNotificationInfo() { return Arrays.asList(NOTIFICATION_INFO).toArray(EMPTY_NOTIFICATION_INFO); } @Override public void startBeanShell(int port) { server.startBeanShell(port); } @Override public String toString() { if (isStarted()) { return "starting, startTime(" + getStartTime() + ")"; } else if (isActive()) { return "active, activateTime(" + getActivateTime() + ")"; } else { return "stopped"; } } @Override public String getState() { return l2State.toString(); } @Override public String getVersion() { return productInfo.toShortString(); } @Override public String getMavenArtifactsVersion() { return productInfo.mavenArtifactsVersion(); } @Override public String getBuildID() { return buildID; } @Override public boolean isPatched() { return productInfo.isPatched(); } @Override public String getPatchLevel() { if (productInfo.isPatched()) { return productInfo.patchLevel(); } else { return ""; } } @Override public String getPatchVersion() { if (productInfo.isPatched()) { return productInfo.toLongPatchString(); } else { return ""; } } @Override public String getPatchBuildID() { if (productInfo.isPatched()) { return productInfo.patchBuildID(); } else { return ""; } } @Override public String getCopyright() { return productInfo.copyright(); } @Override public String getDescriptionOfCapabilities() { return server.getDescriptionOfCapabilities(); } @Override public L2Info[] getL2Info() { return server.infoForAllL2s(); } @Override public String getL2Identifier() { return server.getL2Identifier(); } @Override public ServerGroupInfo[] getServerGroupInfo() { return server.serverGroups(); } @Override public int getTSAListenPort() { return server.getTSAListenPort(); } @Override public int getTSAGroupPort() { return server.getTSAGroupPort(); } @Override public long getUsedMemory() { return manager.getMemoryUsage().getUsedMemory(); } @Override public long getMaxMemory() { return manager.getMemoryUsage().getMaxMemory(); } @Override public Map getStatistics() { HashMap<String, Object> map = new HashMap<String, Object>(); map.put(MEMORY_USED, Long.valueOf(getUsedMemory())); map.put(MEMORY_MAX, Long.valueOf(getMaxMemory())); return map; } @Override public byte[] takeCompressedThreadDump(long requestMillis) { return ThreadDumpUtil.getCompressedThreadDump(); } @Override public String getEnvironment() { return format(System.getProperties()); } @Override public String getTCProperties() { Properties props = TCPropertiesImpl.getProperties().addAllPropertiesTo(new Properties()); String keyPrefix = /* TCPropertiesImpl.SYSTEM_PROP_PREFIX */ null; return format(props, keyPrefix); } private String format(Properties properties) { return format(properties, null); } private String format(Properties properties, String keyPrefix) { StringBuffer sb = new StringBuffer(); Enumeration keys = properties.propertyNames(); ArrayList<String> l = new ArrayList<String>(); while (keys.hasMoreElements()) { Object o = keys.nextElement(); if (o instanceof String) { String key = (String) o; l.add(key); } } String[] props = l.toArray(new String[l.size()]); Arrays.sort(props); l.clear(); l.addAll(Arrays.asList(props)); int maxKeyLen = 0; for (String key : l) { maxKeyLen = Math.max(key.length(), maxKeyLen); } for (String key : l) { if (keyPrefix != null) { sb.append(keyPrefix); } sb.append(key); sb.append(":"); int spaceLen = maxKeyLen - key.length() + 1; for (int i = 0; i < spaceLen; i++) { sb.append(" "); } sb.append(properties.getProperty(key)); sb.append("\n"); } return sb.toString(); } @Override public String[] getProcessArguments() { String[] args = server.processArguments(); List<String> inputArgs = ManagementFactory.getRuntimeMXBean().getInputArguments(); if (args == null) { return inputArgs.toArray(new String[inputArgs.size()]); } else { List<String> l = new ArrayList<String>(); l.add(StringUtil.toString(args, " ", null, null)); l.addAll(inputArgs); return l.toArray(new String[l.size()]); } } @Override public boolean getRestartable() { return server.getRestartable(); } @Override public String getConfig() { return server.getConfig(); } @Override public String getHealthStatus() { // FIXME: the returned value should eventually contain a true representative status of L2 // server. // for now just return 'OK' to indicate that the process is up-and-running.. return "OK"; } @Override public void l2StateChanged(StateChangedEvent sce) { State state = sce.getCurrentState(); if (state.equals(StateManager.ACTIVE_COORDINATOR)) { server.updateActivateTime(); } debugPrintln( "***** msg=[" + stateChangeNotificationInfo.getMsg(state) + "] attrName=[" + stateChangeNotificationInfo.getAttributeName(state) + "] attrType=[" + stateChangeNotificationInfo.getAttributeType(state) + "] stateName=[" + state.getName() + "]"); _sendNotification( stateChangeNotificationInfo.getMsg(state), stateChangeNotificationInfo.getAttributeName(state), stateChangeNotificationInfo.getAttributeType(state), Boolean.FALSE, Boolean.TRUE); } private synchronized void _sendNotification( String msg, String attr, String type, Object oldVal, Object newVal) { sendNotification( new AttributeChangeNotification( this, nextSequenceNumber++, System.currentTimeMillis(), msg, attr, type, oldVal, newVal)); } private void debugPrintln(String s) { if (DEBUG) { System.err.println(s); } } @Override public boolean getRequestDebug() { return objectStatsRecorder.getRequestDebug(); } @Override public void setRequestDebug(boolean requestDebug) { objectStatsRecorder.setRequestDebug(requestDebug); } @Override public boolean getBroadcastDebug() { return objectStatsRecorder.getBroadcastDebug(); } @Override public void setBroadcastDebug(boolean broadcastDebug) { objectStatsRecorder.setBroadcastDebug(broadcastDebug); } @Override public boolean getCommitDebug() { return objectStatsRecorder.getCommitDebug(); } @Override public void setCommitDebug(boolean commitDebug) { objectStatsRecorder.setCommitDebug(commitDebug); } @Override public void gc() { ManagementFactory.getMemoryMXBean().gc(); } @Override public boolean isVerboseGC() { return ManagementFactory.getMemoryMXBean().isVerbose(); } @Override public void setVerboseGC(boolean verboseGC) { boolean oldValue = isVerboseGC(); ManagementFactory.getMemoryMXBean().setVerbose(verboseGC); _sendNotification("VerboseGC changed", "VerboseGC", "java.lang.Boolean", oldValue, verboseGC); } @Override public boolean isEnterprise() { return server.getClass().getSimpleName().equals("EnterpriseServerImpl"); } @Override public boolean isSecure() { return server.isSecure(); } @Override public String getSecurityServiceLocation() { return server.getSecurityServiceLocation(); } @Override public String getSecurityHostname() { server.getTSAListenPort(); return server.getSecurityHostname(); } @Override public String getIntraL2Username() { return server.getIntraL2Username(); } @Override public Integer getSecurityServiceTimeout() { return server.getSecurityServiceTimeout(); } @Override public void backup(final String name) throws IOException { server.backup(name); } @Override public String getRunningBackup() { return server.getRunningBackup(); } @Override public String getBackupStatus(final String name) throws IOException { return server.getBackupStatus(name); } @Override public String getBackupFailureReason(String name) throws IOException { return server.getBackupFailureReason(name); } @Override public Map<String, String> getBackupStatuses() throws IOException { return server.getBackupStatuses(); } @Override public String getResourceState() { return server.getResourceState(); } }
public class GlobalTransactionIDLowWaterMarkProvider implements GlobalTransactionManager, ServerTransactionListener { private static final TCLogger logger = TCLogging.getLogger(GlobalTransactionIDLowWaterMarkProvider.class); private static final State INITIAL = new State("INITAL"); private static final State STARTED = new State("STARTED"); private final ServerTransactionManager transactionManager; private final GlobalTransactionManager gtxm; private volatile GlobalTransactionManager lwmProvider; private State state = INITIAL; private final Set resentTxns = new HashSet(); private final GlobalTransactionManager NULL_GLOBAL_TXN_MGR = new GlobalTransactionManager() { public GlobalTransactionID getLowGlobalTransactionIDWatermark() { return GlobalTransactionID.NULL_ID; } }; public GlobalTransactionIDLowWaterMarkProvider( ServerTransactionManager transactionManager, GlobalTransactionManager gtxm) { this.transactionManager = transactionManager; this.gtxm = gtxm; this.lwmProvider = NULL_GLOBAL_TXN_MGR; } public void goToActiveMode() { transactionManager.addTransactionListener(this); } public GlobalTransactionID getLowGlobalTransactionIDWatermark() { return lwmProvider.getLowGlobalTransactionIDWatermark(); } public synchronized void addResentServerTransactionIDs(Collection stxIDs) { resentTxns.addAll(stxIDs); } public synchronized void clearAllTransactionsFor(ChannelID killedClient) { for (Iterator i = resentTxns.iterator(); i.hasNext(); ) { ServerTransactionID sid = (ServerTransactionID) i.next(); if (sid.getChannelID().equals(killedClient)) { i.remove(); } } switchLWMProviderIfReady(); } private void switchLWMProviderIfReady() { if (resentTxns.isEmpty() && state == STARTED) { logger.info( "Switching GlobalTransactionID Low Water mark provider since all resent transactions are applied"); this.lwmProvider = gtxm; this.transactionManager.removeTransactionListener(this); } } public void incomingTransactions(ChannelID cid, Set serverTxnIDs) { // NOP } public void transactionApplied(ServerTransactionID stxID) { // NOP } public synchronized void transactionCompleted(ServerTransactionID stxID) { resentTxns.remove(stxID); switchLWMProviderIfReady(); } public synchronized void transactionManagerStarted(Set cids) { state = STARTED; removeAllExceptFrom(cids); switchLWMProviderIfReady(); } private void removeAllExceptFrom(Set cids) { for (Iterator i = resentTxns.iterator(); i.hasNext(); ) { ServerTransactionID sid = (ServerTransactionID) i.next(); if (!cids.contains(sid.getChannelID())) { i.remove(); } } } }
/** * The whole intention of this class is to manage the workerThreads for each Listener * * @author Manoj G */ public class TCWorkerCommManager { private static final TCLogger logger = TCLogging.getLogger(TCWorkerCommManager.class); private static final TCLogger lossyLogger = new LossyTCLogger(logger, 10, LossyTCLoggerType.COUNT_BASED, false); private static final String WORKER_NAME_PREFIX = "TCWorkerComm # "; private final int totalWorkerComm; private final CoreNIOServices[] workerCommThreads; private final SetOnceFlag started = new SetOnceFlag(); private final SetOnceFlag stopped = new SetOnceFlag(); private final AtomicInteger nextWorkerCommId = new AtomicInteger(); TCWorkerCommManager(String name, int workerCommCount, SocketParams socketParams) { if (workerCommCount <= 0) { throw new IllegalArgumentException("invalid worker count: " + workerCommCount); } logger.info("Creating " + workerCommCount + " worker comm threads for " + name); this.totalWorkerComm = workerCommCount; this.workerCommThreads = new CoreNIOServices[workerCommCount]; for (int i = 0; i < this.workerCommThreads.length; i++) { this.workerCommThreads[i] = new CoreNIOServices(name + ":" + WORKER_NAME_PREFIX + i, this, socketParams); } } public CoreNIOServices getNextWorkerComm() { List<CoreNIOServices> leastWeightWorkerComms = getLeastWeightWorkerComms(); CoreNIOServices rv; Assert.eval(leastWeightWorkerComms.size() >= 1); if (leastWeightWorkerComms.size() == 1) { rv = leastWeightWorkerComms.get(0); } else { rv = leastWeightWorkerComms.get( nextWorkerCommId.getAndIncrement() % leastWeightWorkerComms.size()); } String message = "Selecting " + rv + " from " + Arrays.asList(this.workerCommThreads); if (logger.isDebugEnabled()) { logger.debug(message); } else { lossyLogger.info(message); } return rv; } private List<CoreNIOServices> getLeastWeightWorkerComms() { List<CoreNIOServices> selectedWorkerComms = new ArrayList<CoreNIOServices>(); int leastValue = Integer.MAX_VALUE; for (CoreNIOServices workerComm : workerCommThreads) { int presentValue = workerComm.getWeight(); if (presentValue < leastValue) { selectedWorkerComms.clear(); selectedWorkerComms.add(workerComm); leastValue = presentValue; } else if (presentValue == leastValue) { selectedWorkerComms.add(workerComm); } } return selectedWorkerComms; } public synchronized void start() { if (this.started.attemptSet()) { for (CoreNIOServices workerCommThread : this.workerCommThreads) { workerCommThread.start(); } } else { throw new IllegalStateException("already started"); } } public synchronized void stop() { if (!this.started.isSet()) { return; } if (this.stopped.attemptSet()) { for (int i = 0; i < this.totalWorkerComm; i++) { this.workerCommThreads[i].requestStop(); } } } protected CoreNIOServices getWorkerComm(int workerCommId) { return this.workerCommThreads[workerCommId]; } protected int getWeightForWorkerComm(int workerCommId) { return this.workerCommThreads[workerCommId].getWeight(); } protected long getTotalBytesReadByWorkerComm(int workerCommId) { return this.workerCommThreads[workerCommId].getTotalBytesRead(); } protected long getTotalBytesWrittenByWorkerComm(int workerCommId) { return this.workerCommThreads[workerCommId].getTotalBytesWritten(); } }
public class ConnectionHealthCheckerTest extends TCTestCase { CommunicationsManager serverComms; CommunicationsManager clientComms; TCMessageRouterImpl serverMessageRouter; TCMessageRouterImpl clientMessageRouter; NetworkListener serverLsnr; TCLogger logger = TCLogging.getLogger(ConnectionHealthCheckerImpl.class); protected void setUp(HealthCheckerConfig serverHCConf, HealthCheckerConfig clientHCConf) throws Exception { super.setUp(); NetworkStackHarnessFactory networkStackHarnessFactory = new PlainNetworkStackHarnessFactory(); logger.setLevel(LogLevelImpl.DEBUG); serverMessageRouter = new TCMessageRouterImpl(); clientMessageRouter = new TCMessageRouterImpl(); if (serverHCConf != null) { serverComms = new CommunicationsManagerImpl( "TestCommsMgr-Server", new NullMessageMonitor(), serverMessageRouter, networkStackHarnessFactory, new NullConnectionPolicy(), serverHCConf, Collections.EMPTY_MAP, Collections.EMPTY_MAP); } else { serverComms = new CommunicationsManagerImpl( "TestCommsMgr-Server", new NullMessageMonitor(), serverMessageRouter, networkStackHarnessFactory, new NullConnectionPolicy(), new DisabledHealthCheckerConfigImpl(), Collections.EMPTY_MAP, Collections.EMPTY_MAP); } if (clientHCConf != null) { clientComms = new CommunicationsManagerImpl( "TestCommsMgr-Client", new NullMessageMonitor(), clientMessageRouter, networkStackHarnessFactory, new NullConnectionPolicy(), clientHCConf, Collections.EMPTY_MAP, Collections.EMPTY_MAP); } else { clientComms = new CommunicationsManagerImpl( "TestCommsMgr-Client", new NullMessageMonitor(), clientMessageRouter, networkStackHarnessFactory, new NullConnectionPolicy(), new DisabledHealthCheckerConfigImpl(), Collections.EMPTY_MAP, Collections.EMPTY_MAP); } serverComms.addClassMapping(TCMessageType.PING_MESSAGE, PingMessage.class); ((CommunicationsManagerImpl) serverComms) .getMessageRouter() .routeMessageType( TCMessageType.PING_MESSAGE, new TCMessageSink() { @Override public void putMessage(TCMessage message) throws UnsupportedMessageTypeException { PingMessage pingMsg = (PingMessage) message; try { pingMsg.hydrate(); System.out.println("Server RECEIVE - PING seq no " + pingMsg.getSequence()); } catch (Exception e) { System.out.println("Server Exception during PingMessage hydrate:"); e.printStackTrace(); } PingMessage pingRplyMsg = pingMsg.createResponse(); pingRplyMsg.send(); } }); serverLsnr = serverComms.createListener( new NullSessionManager(), new TCSocketAddress(0), false, new DefaultConnectionIdFactory()); serverLsnr.start(new HashSet()); } ClientMessageChannel createClientMsgCh() { return createClientMsgCh(null); } ClientMessageChannel createClientMsgCh(CommunicationsManager clientCommsMgr) { CommunicationsManager commsMgr = (clientCommsMgr == null ? clientComms : clientCommsMgr); commsMgr.addClassMapping(TCMessageType.PING_MESSAGE, PingMessage.class); ((CommunicationsManagerImpl) commsMgr) .getMessageRouter() .routeMessageType( TCMessageType.PING_MESSAGE, new TCMessageSink() { @Override public void putMessage(TCMessage message) throws UnsupportedMessageTypeException { PingMessage pingMsg = (PingMessage) message; try { pingMsg.hydrate(); System.out.println(" Client RECEIVE - PING seq no " + pingMsg.getSequence()); } catch (Exception e) { System.out.println("Client Exception during PingMessage hydrate:"); e.printStackTrace(); } } }); ClientMessageChannel clientMsgCh = commsMgr.createClientChannel( new NullSessionManager(), 0, TCSocketAddress.LOOPBACK_IP, serverLsnr.getBindPort(), 1000, new ConnectionAddressProvider( new ConnectionInfo[] {new ConnectionInfo("localhost", serverLsnr.getBindPort())})); return clientMsgCh; } public long getMinSleepTimeToSendFirstProbe(HealthCheckerConfig config) { assertNotNull(config); /* Interval time is doubled to give grace period */ return config.getPingIdleTimeMillis() + (2 * config.getPingIntervalMillis()); } public long getMinSleepTimeToConirmDeath(HealthCheckerConfig config) { assertNotNull(config); /* Interval time is doubled to give grace period */ long exact_time = config.getPingIdleTimeMillis() + (config.getPingIntervalMillis() * config.getPingProbes()); long grace_time = config.getPingIntervalMillis(); return (exact_time + grace_time); } public void testL1ProbingL2() throws Exception { HealthCheckerConfig hcConfig = new HealthCheckerConfigImpl(4000, 2000, 1, "ClientCommsHC-Test01", false); this.setUp(null, hcConfig); ClientMessageChannel clientMsgCh = createClientMsgCh(); clientMsgCh.open(); // Verifications ConnectionHealthCheckerImpl connHC = (ConnectionHealthCheckerImpl) ((CommunicationsManagerImpl) clientComms).getConnHealthChecker(); assertNotNull(connHC); while (!connHC.isRunning() || (connHC.getTotalConnsUnderMonitor() <= 0)) { System.out.println("Yet to start the connection health cheker thread..."); ThreadUtil.reallySleep(1000); } SequenceGenerator sq = new SequenceGenerator(); for (int i = 1; i <= 5; i++) { PingMessage ping = (PingMessage) clientMsgCh.createMessage(TCMessageType.PING_MESSAGE); ping.initialize(sq); ping.send(); } ThreadUtil.reallySleep(getMinSleepTimeToSendFirstProbe(hcConfig)); System.out.println("Successfully sent " + connHC.getTotalProbesSentOnAllConns() + " Probes"); assertTrue(connHC.getTotalProbesSentOnAllConns() > 0); clientMsgCh.close(); } public void testL2ProbingL1AndClientClose() throws Exception { HealthCheckerConfig hcConfig = new HealthCheckerConfigImpl("ServerCommsHC-Test02"); this.setUp(hcConfig, new DisabledHealthCheckerConfigImpl()); ClientMessageChannel clientMsgCh = createClientMsgCh(); clientMsgCh.open(); // Verifications ConnectionHealthCheckerImpl connHC = (ConnectionHealthCheckerImpl) ((CommunicationsManagerImpl) serverComms).getConnHealthChecker(); assertNotNull(connHC); while (!connHC.isRunning() && (connHC.getTotalConnsUnderMonitor() != 1)) { System.out.println("Yet to start the connection health cheker thread..."); ThreadUtil.reallySleep(1000); } SequenceGenerator sq = new SequenceGenerator(); for (int i = 1; i <= 5; i++) { PingMessage ping = (PingMessage) clientMsgCh.createMessage(TCMessageType.PING_MESSAGE); ping.initialize(sq); ping.send(); } ThreadUtil.reallySleep(getMinSleepTimeToSendFirstProbe(hcConfig)); System.out.println("Successfully sent " + connHC.getTotalProbesSentOnAllConns() + " Probes"); assertTrue(connHC.getTotalProbesSentOnAllConns() > 0); clientMsgCh.close(); System.out.println("ClientMessasgeChannel Closed"); System.out.println("Sleeping for " + getMinSleepTimeToConirmDeath(hcConfig)); ThreadUtil.reallySleep(getMinSleepTimeToConirmDeath(hcConfig)); while (connHC.getTotalConnsUnderMonitor() != 0) { ThreadUtil.reallySleep(1000); System.out.println("Waiting for Client removal from Health Checker"); } System.out.println("Success"); } public void testL1L2ProbingBothsideAndClientClose() throws Exception { HealthCheckerConfig hcConfig = new HealthCheckerConfigImpl(4000, 2000, 5, "ServerCommsHC-Test03", false); HealthCheckerConfig hcConfig2 = new HealthCheckerConfigImpl(10000, 4000, 3, "ClientCommsHC-Test03", false); this.setUp(hcConfig, new DisabledHealthCheckerConfigImpl()); CommunicationsManager clientComms1 = new CommunicationsManagerImpl( "TestCommsMgr-Client1", new NullMessageMonitor(), new PlainNetworkStackHarnessFactory(true), new NullConnectionPolicy(), hcConfig2); CommunicationsManager clientComms2 = new CommunicationsManagerImpl( "TestCommsMgr-Client2", new NullMessageMonitor(), new PlainNetworkStackHarnessFactory(true), new NullConnectionPolicy(), hcConfig2); ClientMessageChannel clientMsgCh1 = createClientMsgCh(clientComms1); clientMsgCh1.open(); ClientMessageChannel clientMsgCh2 = createClientMsgCh(clientComms2); clientMsgCh2.open(); // Verifications ConnectionHealthCheckerImpl connHC = (ConnectionHealthCheckerImpl) ((CommunicationsManagerImpl) serverComms).getConnHealthChecker(); assertNotNull(connHC); while (!connHC.isRunning() && (connHC.getTotalConnsUnderMonitor() != 2)) { System.out.println("Yet to start the connection health cheker thread..."); ThreadUtil.reallySleep(1000); } SequenceGenerator sq = new SequenceGenerator(); for (int i = 1; i <= 5; i++) { PingMessage ping = (PingMessage) clientMsgCh1.createMessage(TCMessageType.PING_MESSAGE); ping.initialize(sq); ping.send(); ping = (PingMessage) clientMsgCh2.createMessage(TCMessageType.PING_MESSAGE); ping.initialize(sq); ping.send(); } ThreadUtil.reallySleep(getMinSleepTimeToSendFirstProbe(hcConfig)); System.out.println("Successfully sent " + connHC.getTotalProbesSentOnAllConns() + " Probes"); assertTrue(connHC.getTotalProbesSentOnAllConns() > 0); clientMsgCh1.close(); System.out.println("ClientMessasgeChannel 1 Closed"); ThreadUtil.reallySleep(getMinSleepTimeToSendFirstProbe(hcConfig)); assertEquals(1, connHC.getTotalConnsUnderMonitor()); } public void testL2ProbingL1AndClientUnResponsive() throws Exception { HealthCheckerConfig hcConfig = new HealthCheckerConfigImpl(5000, 2000, 2, "ServerCommsHC-Test04", false); this.setUp(hcConfig, null); ((CommunicationsManagerImpl) clientComms) .setConnHealthChecker(new ConnectionHealthCheckerDummyImpl()); ClientMessageChannel clientMsgCh = createClientMsgCh(); clientMsgCh.open(); // Verifications ConnectionHealthCheckerImpl connHC = (ConnectionHealthCheckerImpl) ((CommunicationsManagerImpl) serverComms).getConnHealthChecker(); assertNotNull(connHC); while (!connHC.isRunning() && (connHC.getTotalConnsUnderMonitor() <= 0)) { System.out.println("Yet to start the connection health cheker thread..."); ThreadUtil.reallySleep(1000); } SequenceGenerator sq = new SequenceGenerator(); for (int i = 1; i <= 5; i++) { PingMessage ping = (PingMessage) clientMsgCh.createMessage(TCMessageType.PING_MESSAGE); ping.initialize(sq); ping.send(); } System.out.println("Sleeping for " + getMinSleepTimeToConirmDeath(hcConfig)); ThreadUtil.reallySleep(getMinSleepTimeToConirmDeath(hcConfig)); assertEquals(0, connHC.getTotalConnsUnderMonitor()); } public void testL1ProbingL2AndServerUnResponsive() throws Exception { HealthCheckerConfig hcConfig = new HealthCheckerConfigImpl(5000, 2000, 2, "ClientCommsHC-Test05", false); this.setUp(null, hcConfig); ((CommunicationsManagerImpl) serverComms) .setConnHealthChecker(new ConnectionHealthCheckerDummyImpl()); ClientMessageChannel clientMsgCh = createClientMsgCh(); clientMsgCh.open(); // Verifications ConnectionHealthCheckerImpl connHC = (ConnectionHealthCheckerImpl) ((CommunicationsManagerImpl) clientComms).getConnHealthChecker(); assertNotNull(connHC); while (!connHC.isRunning() && (connHC.getTotalConnsUnderMonitor() <= 0)) { System.out.println("Yet to start the connection health cheker thread..."); ThreadUtil.reallySleep(1000); } SequenceGenerator sq = new SequenceGenerator(); for (int i = 1; i <= 5; i++) { PingMessage ping = (PingMessage) clientMsgCh.createMessage(TCMessageType.PING_MESSAGE); ping.initialize(sq); ping.send(); } System.out.println("Sleeping for " + getMinSleepTimeToConirmDeath(hcConfig)); ThreadUtil.reallySleep(getMinSleepTimeToConirmDeath(hcConfig)); assertEquals(0, connHC.getTotalConnsUnderMonitor()); } public void testL2L1WrongConfig() throws Exception { try { HealthCheckerConfig hcConfig = new HealthCheckerConfigImpl(30000, 40000, 3, "ServerCommsHC-Test06", false); this.setUp(hcConfig, null); } catch (AssertionError e) { // Expected. System.out.println("Got the Expected error"); } closeCommsMgr(); try { HealthCheckerConfig hcConfig = new HealthCheckerConfigImpl(30000, 0, 3, "ClientCommsHC-Test06", false); this.setUp(null, hcConfig); } catch (AssertionError e) { // Expected. System.out.println("Got the Expected error"); } } protected void closeCommsMgr() throws Exception { if (serverLsnr != null) serverLsnr.stop(1000); if (serverComms != null) serverComms.shutdown(); if (clientComms != null) clientComms.shutdown(); } @Override public void tearDown() throws Exception { super.tearDown(); logger.setLevel(LogLevelImpl.INFO); closeCommsMgr(); } }
public class TunnelingEventHandler extends AbstractEventHandler<JmxRemoteTunnelMessage> implements ClientHandshakeCallback { private static final TCLogger logger = TCLogging.getLogger(TunnelingEventHandler.class); private final MessageChannel channel; private final DSOMBeanConfig config; private TunnelingMessageConnection messageConnection; private boolean acceptOk; private final Object jmxReadyLock; private final SetOnceFlag localJmxServerReady; private boolean transportConnected; private boolean sentReadyMessage; private boolean stopAccept = false; private final UUID uuid; public TunnelingEventHandler(MessageChannel channel, DSOMBeanConfig config, UUID uuid) { this.channel = channel; this.config = config; acceptOk = false; jmxReadyLock = new Object(); localJmxServerReady = new SetOnceFlag(); transportConnected = false; sentReadyMessage = false; this.uuid = uuid; } public MessageChannel getMessageChannel() { return channel; } public UUID getUUID() { return uuid; } @Override public void handleEvent(JmxRemoteTunnelMessage messageEnvelope) { if (messageEnvelope.getCloseConnection()) { reset(); } else { final Message message = (Message) messageEnvelope.getTunneledMessage(); synchronized (this) { if (messageEnvelope.getInitConnection()) { if (messageConnection != null) { logger.warn( "Received a client connection initialization, resetting existing connection"); reset(); } messageConnection = new TunnelingMessageConnection(channel, true); acceptOk = true; notifyAll(); } else if (messageConnection == null) { logger.warn("Received unexpected data message, connection is not yet established"); } else { if (message != null) { messageConnection.incomingNetworkMessage(message); } else { logger.warn("Received tunneled message with no data, resetting connection"); reset(); } } } } } protected synchronized MessageConnection accept() throws IOException { while (!acceptOk && !stopAccept) { try { wait(); } catch (InterruptedException ie) { logger.warn("Interrupted while waiting for a new connection", ie); throw new IOException("Interrupted while waiting for new connection: " + ie.getMessage()); } } acceptOk = false; MessageConnection rv = messageConnection; if (rv == null) { // if we return null here it will cause an uncaught exception and trigger VM exit prematurely throw new IOException("no connection"); } return rv; } protected synchronized void stopAccept() { stopAccept = true; notifyAll(); } private synchronized void reset() { if (messageConnection != null) { messageConnection.close(); } messageConnection = null; acceptOk = false; synchronized (jmxReadyLock) { sentReadyMessage = false; } notifyAll(); } public void jmxIsReady() { synchronized (jmxReadyLock) { localJmxServerReady.set(); } sendJmxReadyMessageIfNecessary(); } public boolean isTunnelingReady() { synchronized (jmxReadyLock) { return localJmxServerReady.isSet() && transportConnected; } } /** * Once the local JMX server has successfully started (this happens in a background thread as DSO * is so early in the startup process that the system JMX server in 1.5+ can't be created inline * with other initialization) we send a 'ready' message to the L2 each time we connect to it. This * tells the L2 that they can connect to our local JMX server and see the beans we have in the DSO * client. */ private void sendJmxReadyMessageIfNecessary() { final boolean send; synchronized (jmxReadyLock) { send = isTunnelingReady() && !sentReadyMessage; if (send) { sentReadyMessage = true; } } // Doing this message send outside of the jmxReadyLock to avoid a // deadlock (CDV-132) if (send) { logger.info("Client JMX server ready; sending notification to L2 server"); L1JmxReady readyMessage = (L1JmxReady) channel.createMessage(TCMessageType.CLIENT_JMX_READY_MESSAGE); readyMessage.initialize(uuid, config.getTunneledDomains()); readyMessage.send(); } } @Override public void initializeHandshake(ClientHandshakeMessage handshakeMessage) { // Ignore } @Override public void pause() { reset(); synchronized (jmxReadyLock) { transportConnected = false; } } @Override public void unpause() { synchronized (jmxReadyLock) { // MNK-2553: Flip the transportConnected switch here in case we receive the transport // connected notification // late (i.e. after ClientHandshakeManager). transportConnected = true; } sendJmxReadyMessageIfNecessary(); } @Override public void shutdown(boolean fromShutdownHook) { // Ignore } }
public class ServerTransactionManagerImpl implements ServerTransactionManager, ServerTransactionManagerMBean, GlobalTransactionManager { private static final TCLogger logger = TCLogging.getLogger(ServerTransactionManager.class); private static final State PASSIVE_MODE = new State("PASSIVE-MODE"); private static final State ACTIVE_MODE = new State("ACTIVE-MODE"); // TODO::FIXME::Change this to concurrent hashmap with top level txn accounting private final Map transactionAccounts = Collections.synchronizedMap(new HashMap()); private final ClientStateManager stateManager; private final ObjectManager objectManager; private final ResentTransactionSequencer resentTxnSequencer; private final TransactionAcknowledgeAction action; private final LockManager lockManager; private final List rootEventListeners = new CopyOnWriteArrayList(); private final List txnEventListeners = new CopyOnWriteArrayList(); private final GlobalTransactionIDLowWaterMarkProvider lwmProvider; private final Counter transactionRateCounter; private final ChannelStats channelStats; private final ServerGlobalTransactionManager gtxm; private final ServerTransactionLogger txnLogger; private volatile State state = PASSIVE_MODE; public ServerTransactionManagerImpl( ServerGlobalTransactionManager gtxm, TransactionStore transactionStore, LockManager lockManager, ClientStateManager stateManager, ObjectManager objectManager, TransactionalObjectManager txnObjectManager, TransactionAcknowledgeAction action, Counter transactionRateCounter, ChannelStats channelStats, ServerTransactionManagerConfig config) { this.gtxm = gtxm; this.lockManager = lockManager; this.objectManager = objectManager; this.stateManager = stateManager; this.resentTxnSequencer = new ResentTransactionSequencer(this, gtxm, txnObjectManager); this.action = action; this.transactionRateCounter = transactionRateCounter; this.channelStats = channelStats; this.lwmProvider = new GlobalTransactionIDLowWaterMarkProvider(this, gtxm); this.txnLogger = new ServerTransactionLogger(logger, config); if (config.isLoggingEnabled()) { enableTransactionLogger(); } } public void enableTransactionLogger() { synchronized (txnLogger) { removeTransactionListener(txnLogger); addTransactionListener(txnLogger); } } public void disableTransactionLogger() { synchronized (txnLogger) { removeTransactionListener(txnLogger); } } public void dump() { StringBuffer buf = new StringBuffer("ServerTransactionManager\n"); buf.append("transactionAccounts: " + transactionAccounts); buf.append("\n/ServerTransactionManager"); System.err.println(buf.toString()); } /** * Shutdown clients are not cleared immediately. Only on completing of all txns this is processed. */ public void shutdownClient(final ChannelID waitee) { boolean callBackAdded = false; synchronized (transactionAccounts) { TransactionAccount deadClientTA = (TransactionAccount) transactionAccounts.get(waitee); if (deadClientTA != null) { deadClientTA.clientDead( new TransactionAccount.CallBackOnComplete() { public void onComplete(ChannelID dead) { synchronized (ServerTransactionManagerImpl.this.transactionAccounts) { transactionAccounts.remove(waitee); } stateManager.shutdownClient(waitee); lockManager.clearAllLocksFor(waitee); gtxm.shutdownClient(waitee); fireClientDisconnectedEvent(waitee); } }); callBackAdded = true; } TransactionAccount tas[] = (TransactionAccount[]) transactionAccounts .values() .toArray(new TransactionAccount[transactionAccounts.size()]); for (int i = 0; i < tas.length; i++) { TransactionAccount client = tas[i]; if (client == deadClientTA) continue; for (Iterator it = client.requestersWaitingFor(waitee).iterator(); it.hasNext(); ) { TransactionID reqID = (TransactionID) it.next(); acknowledgement(client.getClientID(), reqID, waitee); } } } if (!callBackAdded) { stateManager.shutdownClient(waitee); lockManager.clearAllLocksFor(waitee); gtxm.shutdownClient(waitee); fireClientDisconnectedEvent(waitee); } } public void start(Set cids) { synchronized (transactionAccounts) { int sizeB4 = transactionAccounts.size(); transactionAccounts.keySet().retainAll(cids); int sizeAfter = transactionAccounts.size(); if (sizeB4 != sizeAfter) { logger.warn("Cleaned up Transaction Accounts for : " + (sizeB4 - sizeAfter) + " clients"); } } // XXX:: The server could have crashed right after a client crash/disconnect before it had a // chance to remove // transactions from the DB. If we dont do this, then these will stick around for ever and cause // low-water mark to // remain the same for ever and ever. // For Network enabled Active/Passive, when a passive becomes active, this will be called and // the passive (now // active) will correct itself. gtxm.shutdownAllClientsExcept(cids); fireTransactionManagerStartedEvent(cids); } public void goToActiveMode() { state = ACTIVE_MODE; resentTxnSequencer.goToActiveMode(); lwmProvider.goToActiveMode(); } public GlobalTransactionID getLowGlobalTransactionIDWatermark() { return lwmProvider.getLowGlobalTransactionIDWatermark(); } public void addWaitingForAcknowledgement( ChannelID waiter, TransactionID txnID, ChannelID waitee) { TransactionAccount ci = getTransactionAccount(waiter); if (ci != null) { ci.addWaitee(waitee, txnID); } else { logger.warn( "Not adding to Wating for Ack since Waiter not found in the states map: " + waiter); } } // For testing public boolean isWaiting(ChannelID waiter, TransactionID txnID) { TransactionAccount c = getTransactionAccount(waiter); return c != null && c.hasWaitees(txnID); } private void acknowledge(ChannelID waiter, TransactionID txnID) { final ServerTransactionID serverTxnID = new ServerTransactionID(waiter, txnID); fireTransactionCompleteEvent(serverTxnID); if (isActive()) { action.acknowledgeTransaction(serverTxnID); } } public void acknowledgement(ChannelID waiter, TransactionID txnID, ChannelID waitee) { TransactionAccount transactionAccount = getTransactionAccount(waiter); if (transactionAccount == null) { // This can happen if an ack makes it into the system and the server crashed // leading to a removed state; logger.warn("Waiter not found in the states map: " + waiter); return; } if (transactionAccount.removeWaitee(waitee, txnID)) { acknowledge(waiter, txnID); } } public void apply( ServerTransaction txn, Map objects, BackReferences includeIDs, ObjectInstanceMonitor instanceMonitor) { final ServerTransactionID stxnID = txn.getServerTransactionID(); final ChannelID channelID = txn.getChannelID(); final TransactionID txnID = txn.getTransactionID(); final List changes = txn.getChanges(); GlobalTransactionID gtxID = txn.getGlobalTransactionID(); boolean active = isActive(); for (Iterator i = changes.iterator(); i.hasNext(); ) { DNA orgDNA = (DNA) i.next(); long version = orgDNA.getVersion(); if (version == DNA.NULL_VERSION) { Assert.assertFalse(gtxID.isNull()); version = gtxID.toLong(); } DNA change = new VersionizedDNAWrapper(orgDNA, version, true); ManagedObject mo = (ManagedObject) objects.get(change.getObjectID()); mo.apply(change, txnID, includeIDs, instanceMonitor, !active); if (active && !change.isDelta()) { // Only New objects reference are added here stateManager.addReference(txn.getChannelID(), mo.getID()); } } Map newRoots = txn.getNewRoots(); if (newRoots.size() > 0) { for (Iterator i = newRoots.entrySet().iterator(); i.hasNext(); ) { Entry entry = (Entry) i.next(); String rootName = (String) entry.getKey(); ObjectID newID = (ObjectID) entry.getValue(); objectManager.createRoot(rootName, newID); } } if (active) { channelStats.notifyTransaction(channelID); } transactionRateCounter.increment(); fireTransactionAppliedEvent(stxnID); } public void skipApplyAndCommit(ServerTransaction txn) { final ChannelID channelID = txn.getChannelID(); final TransactionID txnID = txn.getTransactionID(); TransactionAccount ci = getTransactionAccount(channelID); fireTransactionAppliedEvent(txn.getServerTransactionID()); if (ci.skipApplyAndCommit(txnID)) { acknowledge(channelID, txnID); } } public void commit( PersistenceTransactionProvider ptxp, Collection objects, Map newRoots, Collection appliedServerTransactionIDs, Set completedTransactionIDs) { PersistenceTransaction ptx = ptxp.newTransaction(); release(ptx, objects, newRoots); gtxm.commitAll(ptx, appliedServerTransactionIDs); gtxm.completeTransactions(ptx, completedTransactionIDs); ptx.commit(); committed(appliedServerTransactionIDs); } private void release(PersistenceTransaction ptx, Collection objects, Map newRoots) { // change done so now we can release the objects objectManager.releaseAll(ptx, objects); // NOTE: important to have released all objects in the TXN before // calling this event as the listeners tries to lookup for the object and blocks for (Iterator i = newRoots.entrySet().iterator(); i.hasNext(); ) { Map.Entry entry = (Entry) i.next(); fireRootCreatedEvent((String) entry.getKey(), (ObjectID) entry.getValue()); } } public void incomingTransactions( ChannelID cid, Set txnIDs, Collection txns, boolean relayed, Collection completedTxnIds) { final boolean active = isActive(); TransactionAccount ci = getOrCreateTransactionAccount(cid); ci.incommingTransactions(txnIDs); for (Iterator i = txns.iterator(); i.hasNext(); ) { final ServerTransaction txn = (ServerTransaction) i.next(); final ServerTransactionID stxnID = txn.getServerTransactionID(); final TransactionID txnID = stxnID.getClientTransactionID(); if (active && !relayed) { ci.relayTransactionComplete(txnID); } else if (!active) { gtxm.createGlobalTransactionDescIfNeeded(stxnID, txn.getGlobalTransactionID()); } } fireIncomingTransactionsEvent(cid, txnIDs); resentTxnSequencer.addTransactions(txns, completedTxnIds); } private boolean isActive() { return (state == ACTIVE_MODE); } public void transactionsRelayed(ChannelID channelID, Set serverTxnIDs) { TransactionAccount ci = getTransactionAccount(channelID); if (ci == null) { logger.warn("transactionsRelayed(): TransactionAccount not found for " + channelID); return; } for (Iterator i = serverTxnIDs.iterator(); i.hasNext(); ) { final ServerTransactionID txnId = (ServerTransactionID) i.next(); final TransactionID txnID = txnId.getClientTransactionID(); if (ci.relayTransactionComplete(txnID)) { acknowledge(channelID, txnID); } } } private void committed(Collection txnsIds) { for (Iterator i = txnsIds.iterator(); i.hasNext(); ) { final ServerTransactionID txnId = (ServerTransactionID) i.next(); final ChannelID waiter = txnId.getChannelID(); final TransactionID txnID = txnId.getClientTransactionID(); TransactionAccount ci = getTransactionAccount(waiter); if (ci != null && ci.applyCommitted(txnID)) { acknowledge(waiter, txnID); } } } public void broadcasted(ChannelID waiter, TransactionID txnID) { TransactionAccount ci = getTransactionAccount(waiter); if (ci != null && ci.broadcastCompleted(txnID)) { acknowledge(waiter, txnID); } } private TransactionAccount getOrCreateTransactionAccount(ChannelID clientID) { synchronized (transactionAccounts) { TransactionAccount ta = (TransactionAccount) transactionAccounts.get(clientID); if (state == ACTIVE_MODE) { if ((ta == null) || (ta instanceof PassiveTransactionAccount)) { Object old = transactionAccounts.put(clientID, (ta = new TransactionAccountImpl(clientID))); if (old != null) { logger.info("Transaction Account changed from : " + old + " to " + ta); } } } else { if ((ta == null) || (ta instanceof TransactionAccountImpl)) { Object old = transactionAccounts.put(clientID, (ta = new PassiveTransactionAccount(clientID))); if (old != null) { logger.info("Transaction Account changed from : " + old + " to " + ta); } } } return ta; } } private TransactionAccount getTransactionAccount(ChannelID clientID) { return (TransactionAccount) transactionAccounts.get(clientID); } public void addRootListener(ServerTransactionManagerEventListener listener) { if (listener == null) { throw new IllegalArgumentException("listener cannot be null"); } this.rootEventListeners.add(listener); } private void fireRootCreatedEvent(String rootName, ObjectID id) { for (Iterator iter = rootEventListeners.iterator(); iter.hasNext(); ) { try { ServerTransactionManagerEventListener listener = (ServerTransactionManagerEventListener) iter.next(); listener.rootCreated(rootName, id); } catch (Exception e) { if (logger.isDebugEnabled()) { logger.debug(e); } else { logger.warn("Exception in rootCreated event callback: " + e.getMessage()); } } } } public void addTransactionListener(ServerTransactionListener listener) { if (listener == null) { throw new IllegalArgumentException("listener cannot be null"); } this.txnEventListeners.add(listener); } public void removeTransactionListener(ServerTransactionListener listener) { if (listener == null) { throw new IllegalArgumentException("listener cannot be null"); } this.txnEventListeners.remove(listener); } public void callBackOnTxnsInSystemCompletion(TxnsInSystemCompletionLister l) { boolean callback = false; synchronized (transactionAccounts) { HashSet txnsInSystem = new HashSet(); for (Iterator i = transactionAccounts.entrySet().iterator(); i.hasNext(); ) { Entry entry = (Entry) i.next(); TransactionAccount client = (TransactionAccount) entry.getValue(); client.addAllPendingServerTransactionIDsTo(txnsInSystem); } if (txnsInSystem.isEmpty()) { callback = true; } else { addTransactionListener(new TxnsInSystemCompletionListenerCallback(l, txnsInSystem)); } } if (callback) { l.onCompletion(); } } private void fireIncomingTransactionsEvent(ChannelID cid, Set serverTxnIDs) { for (Iterator iter = txnEventListeners.iterator(); iter.hasNext(); ) { try { ServerTransactionListener listener = (ServerTransactionListener) iter.next(); listener.incomingTransactions(cid, serverTxnIDs); } catch (Exception e) { logger.error("Exception in Txn listener event callback: ", e); throw new AssertionError(e); } } } private void fireTransactionCompleteEvent(ServerTransactionID stxID) { for (Iterator iter = txnEventListeners.iterator(); iter.hasNext(); ) { try { ServerTransactionListener listener = (ServerTransactionListener) iter.next(); listener.transactionCompleted(stxID); } catch (Exception e) { logger.error("Exception in Txn listener event callback: ", e); throw new AssertionError(e); } } } private void fireTransactionAppliedEvent(ServerTransactionID stxID) { for (Iterator iter = txnEventListeners.iterator(); iter.hasNext(); ) { try { ServerTransactionListener listener = (ServerTransactionListener) iter.next(); listener.transactionApplied(stxID); } catch (Exception e) { logger.error("Exception in Txn listener event callback: ", e); throw new AssertionError(e); } } } private void fireTransactionManagerStartedEvent(Set cids) { for (Iterator iter = txnEventListeners.iterator(); iter.hasNext(); ) { try { ServerTransactionListener listener = (ServerTransactionListener) iter.next(); listener.transactionManagerStarted(cids); } catch (Exception e) { logger.error("Exception in Txn listener event callback: ", e); throw new AssertionError(e); } } } public void setResentTransactionIDs(ChannelID channelID, Collection transactionIDs) { if (transactionIDs.isEmpty()) return; Collection stxIDs = new ArrayList(); for (Iterator iter = transactionIDs.iterator(); iter.hasNext(); ) { TransactionID txn = (TransactionID) iter.next(); stxIDs.add(new ServerTransactionID(channelID, txn)); } fireAddResentTransactionIDsEvent(stxIDs); } private void fireAddResentTransactionIDsEvent(Collection stxIDs) { for (Iterator iter = txnEventListeners.iterator(); iter.hasNext(); ) { try { ServerTransactionListener listener = (ServerTransactionListener) iter.next(); listener.addResentServerTransactionIDs(stxIDs); } catch (Exception e) { logger.error("Exception in Txn listener event callback: ", e); throw new AssertionError(e); } } } private void fireClientDisconnectedEvent(ChannelID waitee) { for (Iterator iter = txnEventListeners.iterator(); iter.hasNext(); ) { try { ServerTransactionListener listener = (ServerTransactionListener) iter.next(); listener.clearAllTransactionsFor(waitee); } catch (Exception e) { logger.error("Exception in Txn listener event callback: ", e); throw new AssertionError(e); } } } private final class TxnsInSystemCompletionListenerCallback implements ServerTransactionListener { private final TxnsInSystemCompletionLister callback; private final HashSet txnsInSystem; public int count = 0; public TxnsInSystemCompletionListenerCallback( TxnsInSystemCompletionLister callback, HashSet txnsInSystem) { this.callback = callback; this.txnsInSystem = txnsInSystem; } public void addResentServerTransactionIDs(Collection stxIDs) { // NOP } public void clearAllTransactionsFor(ChannelID killedClient) { // NOP } public void incomingTransactions(ChannelID cid, Set serverTxnIDs) { // NOP } public void transactionApplied(ServerTransactionID stxID) { // NOP } public void transactionCompleted(ServerTransactionID stxID) { if (txnsInSystem.remove(stxID)) { if (txnsInSystem.isEmpty()) { ServerTransactionManagerImpl.this.removeTransactionListener(this); callback.onCompletion(); } } if (count++ % 100 == 0) { logger.warn( "TxnsInSystemCompletionLister :: Still waiting for completion of " + txnsInSystem.size() + " txns to call callback " + callback + " count = " + count); } } public void transactionManagerStarted(Set cids) { // NOP } } }
public class GarbageCollectHandler extends AbstractEventHandler { private static final TCLogger logger = TCLogging.getLogger(GarbageCollectHandler.class); private final Timer timer = new Timer("GarbageCollectHandler Timer"); private final boolean fullGCEnabled; private final long fullGCInterval; private final LifeCycleState gcState = new GCState(); private volatile boolean gcRunning = false; private GarbageCollector collector; private GarbageCollectionManager garbageCollectionManager; private Sink gcSink; public GarbageCollectHandler(final ObjectManagerConfig objectManagerConfig) { this.fullGCEnabled = objectManagerConfig.doGC(); this.fullGCInterval = objectManagerConfig.gcThreadSleepTime(); } @Override public void handleEvent(EventContext context) { timer.purge(); // Get rid of finished tasks if (context instanceof GarbageCollectContext) { GarbageCollectContext gcc = (GarbageCollectContext) context; if (gcc.getDelay() > 0) { final long delay = gcc.getDelay(); gcc.setDelay(0); scheduleDGC(gcc, delay); } else { gcRunning = true; collector.doGC(gcc.getType()); gcRunning = false; // We want to let inline gc clean stuff up (quickly) before another young/full gc happens garbageCollectionManager.scheduleInlineGarbageCollectionIfNecessary(); if (gcc instanceof PeriodicGarbageCollectContext) { // Rearm and requeue if it's a periodic gc. PeriodicGarbageCollectContext pgcc = (PeriodicGarbageCollectContext) gcc; pgcc.reset(); gcSink.add(pgcc); } } } else if (context instanceof InlineGCContext) { garbageCollectionManager.inlineCleanup(); } else { throw new AssertionError("Unknown context type: " + context.getClass().getName()); } } public void scheduleDGC(final GarbageCollectContext gcc, final long delay) { timer.schedule( new TimerTask() { @Override public void run() { gcSink.add(gcc); } }, delay); } @Override protected void initialize(ConfigurationContext context) { super.initialize(context); ServerConfigurationContext scc = (ServerConfigurationContext) context; collector = scc.getObjectManager().getGarbageCollector(); collector.setState(gcState); garbageCollectionManager = scc.getGarbageCollectionManager(); gcSink = scc.getStage(ServerConfigurationContext.GARBAGE_COLLECT_STAGE).getSink(); } private class GCState implements LifeCycleState { private volatile boolean stopRequested = false; @Override public void start() { if (fullGCEnabled) { gcSink.add(new PeriodicGarbageCollectContext(GCType.FULL_GC, fullGCInterval)); collector.setPeriodicEnabled(true); } } @Override public boolean isStopRequested() { return stopRequested; } @Override public boolean stopAndWait(long waitTime) { logger.info("Garbage collection is stopping, clearing out remaining contexts."); stopRequested = true; // Purge the sink of any scheduled gc's, this needs to be equivalent to stopping the garbage // collector thread. gcSink.clear(); long start = System.nanoTime(); while (gcRunning) { ThreadUtil.reallySleep(1000); if (TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start) > waitTime) { return false; } } return true; } } }
public class StateManagerImpl implements StateManager { private static final TCLogger logger = TCLogging.getLogger(StateManagerImpl.class); private final TCLogger consoleLogger; private final GroupManager groupManager; private final ElectionManager electionMgr; private final Sink stateChangeSink; private final WeightGeneratorFactory weightsFactory; private final CopyOnWriteArrayList<StateChangeListener> listeners = new CopyOnWriteArrayList<StateChangeListener>(); private final Object electionLock = new Object(); private final ClusterStatePersistor clusterStatePersistor; private NodeID activeNode = ServerID.NULL_ID; private volatile State state = START_STATE; private boolean electionInProgress = false; TerracottaOperatorEventLogger operatorEventLogger = TerracottaOperatorEventLogging.getEventLogger(); public StateManagerImpl( TCLogger consoleLogger, GroupManager groupManager, Sink stateChangeSink, StateManagerConfig stateManagerConfig, WeightGeneratorFactory weightFactory, final ClusterStatePersistor clusterStatePersistor) { this.consoleLogger = consoleLogger; this.groupManager = groupManager; this.stateChangeSink = stateChangeSink; this.weightsFactory = weightFactory; this.electionMgr = new ElectionManagerImpl(groupManager, stateManagerConfig); this.clusterStatePersistor = clusterStatePersistor; } @Override public State getCurrentState() { return this.state; } /* * XXX:: If ACTIVE went dead before any passive moved to STANDBY state, then the cluster is hung and there is no going * around it. If ACTIVE in persistent mode, it can come back and recover the cluster */ @Override public void startElection() { debugInfo("Starting election"); synchronized (electionLock) { if (electionInProgress) return; electionInProgress = true; } try { State initial = clusterStatePersistor.getInitialState(); // Went down as either PASSIVE_STANDBY or UNITIALIZED, either way we need to wait for the // active to zap, just skip // the election and wait for a zap. if (initial != null && !initial.equals(ACTIVE_COORDINATOR)) { info( "Skipping election and waiting for the active to zap since this this L2 did not go down as active."); } else if (state == START_STATE || state == PASSIVE_STANDBY) { runElection(); } else { info("Ignoring Election request since not in right state"); } } finally { synchronized (electionLock) { electionInProgress = false; } } } private void runElection() { NodeID myNodeID = getLocalNodeID(); NodeID winner = ServerID.NULL_ID; int count = 0; // Only new L2 if the DB was empty (no previous state) and the current state is START (as in // before any elections // concluded) boolean isNew = state == START_STATE && clusterStatePersistor.getInitialState() == null; while (getActiveNodeID().isNull()) { if (++count > 1) { logger.info( "Rerunning election since node " + winner + " never declared itself as ACTIVE !"); } debugInfo("Running election - isNew: " + isNew); winner = electionMgr.runElection(myNodeID, isNew, weightsFactory); if (winner == myNodeID) { debugInfo("Won Election, moving to active state. myNodeID/winner=" + myNodeID); moveToActiveState(); } else { electionMgr.reset(null); // Election is lost, but we wait for the active node to declare itself as winner. If this // doesn't happen in a // finite time we restart the election. This is to prevent some weird cases where two nodes // might end up // thinking the other one is the winner. // @see MNK-518 debugInfo("Lost election, waiting for winner to declare as active, winner=" + winner); waitUntilActiveNodeIDNotNull(electionMgr.getElectionTime()); } } } private synchronized void waitUntilActiveNodeIDNotNull(long timeout) { while (activeNode.isNull() && timeout > 0) { long start = System.currentTimeMillis(); try { wait(timeout); } catch (InterruptedException e) { logger.warn("Interrupted while waiting for ACTIVE to declare WON message ! ", e); break; } timeout = timeout - (System.currentTimeMillis() - start); } debugInfo( "Wait for other active to declare as active over. Declared? activeNodeId.isNull() = " + activeNode.isNull() + ", activeNode=" + activeNode); } // should be called from synchronized code private void setActiveNodeID(NodeID nodeID) { this.activeNode = nodeID; notifyAll(); } private NodeID getLocalNodeID() { return groupManager.getLocalNodeID(); } @Override public void registerForStateChangeEvents(StateChangeListener listener) { listeners.add(listener); } @Override public void fireStateChangedEvent(StateChangedEvent sce) { for (StateChangeListener listener : listeners) { listener.l2StateChanged(sce); } } private synchronized void moveToPassiveState(Enrollment winningEnrollment) { electionMgr.reset(winningEnrollment); if (state == START_STATE) { state = PASSIVE_UNINITIALIZED; info("Moved to " + state, true); fireStateChangedOperatorEvent(); stateChangeSink.add(new StateChangedEvent(START_STATE, state)); } else if (state == ACTIVE_COORDINATOR) { // TODO:: Support this later throw new AssertionError( "Cant move to " + PASSIVE_UNINITIALIZED + " from " + ACTIVE_COORDINATOR + " at least for now"); } else { debugInfo( "Move to passive state ignored - state=" + state + ", winningEnrollment: " + winningEnrollment); } } @Override public synchronized void moveToPassiveStandbyState() { if (state == ACTIVE_COORDINATOR) { // TODO:: Support this later throw new AssertionError( "Cant move to " + PASSIVE_STANDBY + " from " + ACTIVE_COORDINATOR + " at least for now"); } else if (state != PASSIVE_STANDBY) { stateChangeSink.add(new StateChangedEvent(state, PASSIVE_STANDBY)); state = PASSIVE_STANDBY; info("Moved to " + state, true); fireStateChangedOperatorEvent(); } else { info("Already in " + state); } } private synchronized void moveToActiveState() { if (state == START_STATE || state == PASSIVE_STANDBY) { // TODO :: If state == START_STATE publish cluster ID debugInfo("Moving to active state"); StateChangedEvent event = new StateChangedEvent(state, ACTIVE_COORDINATOR); state = ACTIVE_COORDINATOR; setActiveNodeID(getLocalNodeID()); info("Becoming " + state, true); fireStateChangedOperatorEvent(); electionMgr.declareWinner(this.activeNode); stateChangeSink.add(event); } else { throw new AssertionError("Cant move to " + ACTIVE_COORDINATOR + " from " + state); } } @Override public synchronized NodeID getActiveNodeID() { return activeNode; } @Override public boolean isActiveCoordinator() { return (state == ACTIVE_COORDINATOR); } public boolean isPassiveUnitialized() { return (state == PASSIVE_UNINITIALIZED); } @Override public void moveNodeToPassiveStandby(NodeID nodeID) { Assert.assertTrue(isActiveCoordinator()); logger.info("Requesting node " + nodeID + " to move to " + PASSIVE_STANDBY); GroupMessage msg = L2StateMessage.createMoveToPassiveStandbyMessage( EnrollmentFactory.createTrumpEnrollment(getLocalNodeID(), weightsFactory)); try { this.groupManager.sendTo(nodeID, msg); } catch (GroupException e) { logger.error(e); } } @Override public void handleClusterStateMessage(L2StateMessage clusterMsg) { debugInfo("Received cluster state message: " + clusterMsg); try { switch (clusterMsg.getType()) { case L2StateMessage.START_ELECTION: handleStartElectionRequest(clusterMsg); break; case L2StateMessage.ABORT_ELECTION: handleElectionAbort(clusterMsg); break; case L2StateMessage.ELECTION_RESULT: handleElectionResultMessage(clusterMsg); break; case L2StateMessage.ELECTION_WON: case L2StateMessage.ELECTION_WON_ALREADY: handleElectionWonMessage(clusterMsg); break; case L2StateMessage.MOVE_TO_PASSIVE_STANDBY: handleMoveToPassiveStandbyMessage(clusterMsg); break; default: throw new AssertionError("This message shouldn't have been routed here : " + clusterMsg); } } catch (GroupException ge) { logger.error("Zapping Node : Caught Exception while handling Message : " + clusterMsg, ge); groupManager.zapNode( clusterMsg.messageFrom(), L2HAZapNodeRequestProcessor.COMMUNICATION_ERROR, "Error handling Election Message " + L2HAZapNodeRequestProcessor.getErrorString(ge)); } } private void handleMoveToPassiveStandbyMessage(L2StateMessage clusterMsg) { moveToPassiveStandbyState(); } private synchronized void handleElectionWonMessage(L2StateMessage clusterMsg) { debugInfo("Received election_won or election_already_won msg: " + clusterMsg); Enrollment winningEnrollment = clusterMsg.getEnrollment(); if (state == ACTIVE_COORDINATOR) { // Can't get Election Won from another node : Split brain String error = state + " Received Election Won Msg : " + clusterMsg + ". A Terracotta server tried to join the mirror group as a second ACTIVE"; logger.error(error); if (clusterMsg.getType() == L2StateMessage.ELECTION_WON_ALREADY) { sendNGResponse(clusterMsg.messageFrom(), clusterMsg); } groupManager.zapNode( winningEnrollment.getNodeID(), L2HAZapNodeRequestProcessor.SPLIT_BRAIN, error); } else if (activeNode.isNull() || activeNode.equals(winningEnrollment.getNodeID()) || clusterMsg.getType() == L2StateMessage.ELECTION_WON) { // There is no active server for this node or the other node just detected a failure of ACTIVE // server and ran an // election and is sending the results. This can happen if this node for some reason is not // able to detect that // the active is down but the other node did. Go with the new active. setActiveNodeID(winningEnrollment.getNodeID()); moveToPassiveState(winningEnrollment); if (clusterMsg.getType() == L2StateMessage.ELECTION_WON_ALREADY) { sendOKResponse(clusterMsg.messageFrom(), clusterMsg); } } else { // This is done to solve DEV-1532. Node sent ELECTION_WON_ALREADY message but our ACTIVE is // intact. logger.warn( "Conflicting Election Won Msg : " + clusterMsg + " since I already have a ACTIVE Node : " + activeNode + ". Sending NG response"); // The reason we send a response for ELECTION_WON_ALREADY message is that if we don't agree we // don't want the // other server to send us cluster state messages. sendNGResponse(clusterMsg.messageFrom(), clusterMsg); } } private synchronized void handleElectionResultMessage(L2StateMessage msg) throws GroupException { if (activeNode.equals(msg.getEnrollment().getNodeID())) { Assert.assertFalse(ServerID.NULL_ID.equals(activeNode)); // This wouldn't normally happen, but we agree - so ack GroupMessage resultAgreed = L2StateMessage.createResultAgreedMessage(msg, msg.getEnrollment()); logger.info("Agreed with Election Result from " + msg.messageFrom() + " : " + resultAgreed); groupManager.sendTo(msg.messageFrom(), resultAgreed); } else if (state == ACTIVE_COORDINATOR || !activeNode.isNull() || (msg.getEnrollment().isANewCandidate() && state != START_STATE)) { // Condition 1 : // Obviously an issue. // Condition 2 : // This shouldn't happen normally, but is possible when there is some weird network error // where A sees B, // B sees A/C and C sees B and A is active and C is trying to run election // Force other node to rerun election so that we can abort // Condition 3 : // We don't want new L2s to win an election when there are old L2s in PASSIVE states. GroupMessage resultConflict = L2StateMessage.createResultConflictMessage( msg, EnrollmentFactory.createTrumpEnrollment(getLocalNodeID(), weightsFactory)); warn( "WARNING :: Active Node = " + activeNode + " , " + state + " received ELECTION_RESULT message from another node : " + msg + " : Forcing re-election " + resultConflict); groupManager.sendTo(msg.messageFrom(), resultConflict); } else { debugInfo("ElectionMgr handling election result msg: " + msg); electionMgr.handleElectionResultMessage(msg); } } private void handleElectionAbort(L2StateMessage clusterMsg) { if (state == ACTIVE_COORDINATOR) { // Cant get Abort back to ACTIVE, if so then there is a split brain String error = state + " Received Abort Election Msg : Possible split brain detected "; logger.error(error); groupManager.zapNode( clusterMsg.messageFrom(), L2HAZapNodeRequestProcessor.SPLIT_BRAIN, error); } else { debugInfo("ElectionMgr handling election abort"); electionMgr.handleElectionAbort(clusterMsg); } } private void handleStartElectionRequest(L2StateMessage msg) throws GroupException { if (state == ACTIVE_COORDINATOR) { // This is either a new L2 joining a cluster or a renegade L2. Force it to abort GroupMessage abortMsg = L2StateMessage.createAbortElectionMessage( msg, EnrollmentFactory.createTrumpEnrollment(getLocalNodeID(), weightsFactory)); info("Forcing Abort Election for " + msg + " with " + abortMsg); groupManager.sendTo(msg.messageFrom(), abortMsg); } else if (!electionMgr.handleStartElectionRequest(msg)) { // TODO::FIXME:: Commenting so that stage thread is not held up doing election. // startElectionIfNecessary(NodeID.NULL_ID); logger.warn("Not starting election as it was commented out"); } } // notify new node @Override public void publishActiveState(NodeID nodeID) throws GroupException { debugInfo("Publishing active state to nodeId: " + nodeID); Assert.assertTrue(isActiveCoordinator()); GroupMessage msg = L2StateMessage.createElectionWonAlreadyMessage( EnrollmentFactory.createTrumpEnrollment(getLocalNodeID(), weightsFactory)); L2StateMessage response = (L2StateMessage) groupManager.sendToAndWaitForResponse(nodeID, msg); validateResponse(nodeID, response); } private void validateResponse(NodeID nodeID, L2StateMessage response) throws GroupException { if (response == null || response.getType() != L2StateMessage.RESULT_AGREED) { String error = "Recd wrong response from : " + nodeID + " : msg = " + response + " while publishing Active State"; logger.error(error); // throwing this exception will initiate a zap elsewhere throw new GroupException(error); } } @Override public void startElectionIfNecessary(NodeID disconnectedNode) { Assert.assertFalse(disconnectedNode.equals(getLocalNodeID())); boolean elect = false; synchronized (this) { if (activeNode.equals(disconnectedNode)) { // ACTIVE Node is gone setActiveNodeID(ServerID.NULL_ID); } if (state != PASSIVE_UNINITIALIZED && state != ACTIVE_COORDINATOR && activeNode.isNull()) { elect = true; } } if (elect) { info("Starting Election to determine cluser wide ACTIVE L2"); startElection(); } else { debugInfo("Not starting election even though node left: " + disconnectedNode); } } private void sendOKResponse(NodeID fromNode, L2StateMessage msg) { try { groupManager.sendTo( fromNode, L2StateMessage.createResultAgreedMessage(msg, msg.getEnrollment())); } catch (GroupException e) { logger.error("Error handling message : " + msg, e); } } private void sendNGResponse(NodeID fromNode, L2StateMessage msg) { try { groupManager.sendTo( fromNode, L2StateMessage.createResultConflictMessage(msg, msg.getEnrollment())); } catch (GroupException e) { logger.error("Error handling message : " + msg, e); } } @Override public String toString() { return StateManagerImpl.class.getSimpleName() + ":" + this.state.toString(); } private void fireStateChangedOperatorEvent() { TSAManagementEventPayload tsaManagementEventPayload = new TSAManagementEventPayload("TSA.L2.STATE_CHANGE"); tsaManagementEventPayload.getAttributes().put("State", state.getName()); TerracottaRemoteManagement.getRemoteManagementInstance() .sendEvent(tsaManagementEventPayload.toManagementEvent()); operatorEventLogger.fireOperatorEvent( TerracottaOperatorEventFactory.createClusterNodeStateChangedEvent(state.getName())); } private void info(String message) { info(message, false); } private void info(String message, boolean console) { logger.info(message); if (console) { consoleLogger.info(message); } } private void warn(String message) { warn(message, false); } private void warn(String message, boolean console) { logger.warn(message); if (console) { consoleLogger.warn(message); } } private static void debugInfo(String message) { L2DebugLogging.log(logger, LogLevel.INFO, message, null); } }
class TCMemoryManagerJdk14 implements JVMMemoryManager { private static final TCLogger logger = TCLogging.getLogger(TCMemoryManagerJdk14.class); private final Runtime rt; public TCMemoryManagerJdk14() { rt = Runtime.getRuntime(); if (rt.maxMemory() == Long.MAX_VALUE) { logger.warn("Please specify Max memory using -Xmx flag for Memory manager to work properly"); } } public MemoryUsage getMemoryUsage() { return new Jdk14MemoryUsage(rt); } public MemoryUsage getOldGenUsage() { throw new UnsupportedOperationException(); } public boolean isMemoryPoolMonitoringSupported() { return false; } private static final class Jdk14MemoryUsage implements MemoryUsage { private final long max; private final long used; private final long free; private final long total; private final int usedPercentage; public Jdk14MemoryUsage(Runtime rt) { this.max = rt.maxMemory(); this.free = rt.freeMemory(); this.total = rt.totalMemory(); this.used = this.total - this.free; if (this.max == Long.MAX_VALUE) { this.usedPercentage = (int) (this.used * 100 / this.total); } else { this.usedPercentage = (int) (this.used * 100 / this.max); } } public String getDescription() { return "VM 1.4 Memory Usage"; } public long getFreeMemory() { return free; } public long getMaxMemory() { return max; } public long getUsedMemory() { return used; } public int getUsedPercentage() { return usedPercentage; } public String toString() { return "Jdk14MemoryUsage ( max = " + max + ", used = " + used + ", free = " + free + ", total = " + total + ", used % = " + usedPercentage + ")"; } // This is not supported in 1.4 public long getCollectionCount() { return -1; } } }
public class ElectionManagerImpl implements ElectionManager { private static final TCLogger logger = TCLogging.getLogger(ElectionManagerImpl.class); private static final State INIT = new State("Initial-State"); private static final State ELECTION_COMPLETE = new State("Election-Complete"); private static final State ELECTION_IN_PROGRESS = new State("Election-In-Progress"); private final GroupManager<L2StateMessage> groupManager; private final Map<NodeID, Enrollment> votes = new HashMap<>(); private State state = INIT; // XXX::NOTE:: These variables are not reset until next election private Enrollment myVote = null; private Enrollment winner; private final long electionTime; public ElectionManagerImpl(GroupManager groupManager, StateManagerConfig stateManagerConfig) { this.groupManager = groupManager; electionTime = stateManagerConfig.getElectionTimeInSecs() * 1000; } @Override public synchronized boolean handleStartElectionRequest(L2StateMessage msg) { Assert.assertEquals(L2StateMessage.START_ELECTION, msg.getType()); if (state == ELECTION_IN_PROGRESS && (myVote.isANewCandidate() || !msg.getEnrollment().isANewCandidate())) { // Another node is also joining in the election process, Cast its vote and notify my vote // Note : WE dont want to do this for new candidates when we are not new. Enrollment vote = msg.getEnrollment(); Enrollment old = votes.put(vote.getNodeID(), vote); boolean sendResponse = msg.inResponseTo().isNull(); if (old != null && !vote.equals(old)) { logger.warn( "Received duplicate vote : Replacing with new one : " + vote + " old one : " + old); sendResponse = true; } if (sendResponse) { // This is either not a response to this node initiating election or a duplicate vote. // Either case notify this // nodes vote L2StateMessage response = createElectionStartedMessage(msg, myVote); logger.info("Casted vote from " + msg + " My Response : " + response); try { groupManager.sendTo(msg.messageFrom(), response); } catch (GroupException e) { logger.error("Error sending Votes to : " + msg.messageFrom(), e); } } else { logger.info("Casted vote from " + msg); } return true; } else { logger.info("Ignoring Start Election Request : " + msg + " My state = " + state); return false; } } @Override public synchronized void handleElectionAbort(L2StateMessage msg) { Assert.assertEquals(L2StateMessage.ABORT_ELECTION, msg.getType()); debugInfo("Handling election abort"); if (state == ELECTION_IN_PROGRESS) { // An existing ACTIVE Node has forced election to quit Assert.assertNotNull(myVote); basicAbort(msg); } else { logger.warn("Ignoring Abort Election Request : " + msg + " My state = " + state); } } @Override public synchronized void handleElectionResultMessage(L2StateMessage msg) { Assert.assertEquals(L2StateMessage.ELECTION_RESULT, msg.getType()); debugInfo("Handling election result"); if (state == ELECTION_COMPLETE && !this.winner.equals(msg.getEnrollment())) { // conflict L2StateMessage resultConflict = L2StateMessage.createResultConflictMessage(msg, this.winner); logger.warn( "WARNING :: Election result conflict : Winner local = " + this.winner + " : remote winner = " + msg.getEnrollment()); try { groupManager.sendTo(msg.messageFrom(), resultConflict); } catch (GroupException e) { logger.error("Error sending Election result conflict message : " + resultConflict); } } else { // Agree to the result, abort the election if necessary if (state == ELECTION_IN_PROGRESS) { basicAbort(msg); } L2StateMessage resultAgreed = L2StateMessage.createResultAgreedMessage(msg, msg.getEnrollment()); logger.info("Agreed with Election Result from " + msg.messageFrom() + " : " + resultAgreed); try { groupManager.sendTo(msg.messageFrom(), resultAgreed); } catch (GroupException e) { logger.error("Error sending Election result agreed message : " + resultAgreed); } } } private void basicAbort(L2StateMessage msg) { reset(msg.getEnrollment()); logger.info("Aborted Election : Winner is : " + this.winner); } /** This method is called by the winner of the election to announce to the world */ @Override public synchronized void declareWinner(NodeID myNodeId) { Assert.assertEquals(winner.getNodeID(), myNodeId); L2StateMessage msg = createElectionWonMessage(this.winner); debugInfo("Announcing as winner: " + myNodeId); this.groupManager.sendAll(msg); logger.info("Declared as Winner: Winner is : " + this.winner); reset(winner); } @Override public synchronized void reset(Enrollment winningEnrollment) { this.winner = winningEnrollment; this.state = INIT; this.votes.clear(); this.myVote = null; notifyAll(); } @Override public NodeID runElection(NodeID myNodeId, boolean isNew, WeightGeneratorFactory weightsFactory) { NodeID winnerID = ServerID.NULL_ID; int count = 0; while (winnerID.isNull()) { if (count++ > 0) { logger.info("Requesting Re-election !!! count = " + count); } try { winnerID = doElection(myNodeId, isNew, weightsFactory); } catch (InterruptedException e) { logger.error("Interrupted during election : ", e); reset(null); } catch (GroupException e1) { logger.error("Error during election : ", e1); reset(null); } } return winnerID; } private synchronized void electionStarted(Enrollment e) { if (this.state == ELECTION_IN_PROGRESS) { throw new AssertionError("Election Already in Progress"); } this.state = ELECTION_IN_PROGRESS; this.myVote = e; this.winner = null; this.votes.clear(); this.votes.put(e.getNodeID(), e); // Cast my vote logger.info("Election Started : " + e); } private NodeID doElection(NodeID myNodeId, boolean isNew, WeightGeneratorFactory weightsFactory) throws GroupException, InterruptedException { // Step 1: publish to cluster NodeID, weight and election start Enrollment e = EnrollmentFactory.createEnrollment(myNodeId, isNew, weightsFactory); electionStarted(e); L2StateMessage msg = createElectionStartedMessage(e); debugInfo("Sending my election vote to all members"); groupManager.sendAll(msg); // Step 2: Wait for election completion waitTillElectionComplete(); // Step 3: Compute Winner Enrollment lWinner = computeResult(); if (lWinner != e) { logger.info("Election lost : Winner is : " + lWinner); Assert.assertNotNull(lWinner); return lWinner.getNodeID(); } // Step 4 : local host won the election, so notify world for acceptance msg = createElectionResultMessage(e); debugInfo("Won election, announcing to world and waiting for response..."); GroupResponse<L2StateMessage> responses = groupManager.sendAllAndWaitForResponse(msg); for (L2StateMessage response : responses.getResponses()) { Assert.assertEquals(msg.getMessageID(), response.inResponseTo()); if (response.getType() == L2StateMessage.RESULT_AGREED) { Assert.assertEquals(e, response.getEnrollment()); } else if (response.getType() == L2StateMessage.RESULT_CONFLICT) { logger.info( "Result Conflict: Local Result : " + e + " From : " + response.messageFrom() + " Result : " + response.getEnrollment()); return ServerID.NULL_ID; } else { throw new AssertionError( "Node : " + response.messageFrom() + " responded neither with RESULT_AGREED or RESULT_CONFLICT :" + response); } } // Step 5 : result agreed - I am the winner return myNodeId; } private synchronized Enrollment computeResult() { if (state == ELECTION_IN_PROGRESS) { state = ELECTION_COMPLETE; logger.info("Election Complete : " + votes.values() + " : " + state); winner = countVotes(); } return winner; } private Enrollment countVotes() { Enrollment computedWinner = null; for (Enrollment e : votes.values()) { if (computedWinner == null) { computedWinner = e; } else if (e.wins(computedWinner)) { computedWinner = e; } } Assert.assertNotNull(computedWinner); return computedWinner; } private synchronized void waitTillElectionComplete() throws InterruptedException { long diff = electionTime; debugInfo("Waiting till election complete, electionTime=" + electionTime); while (state == ELECTION_IN_PROGRESS && diff > 0) { long start = System.currentTimeMillis(); wait(diff); diff = diff - (System.currentTimeMillis() - start); } } private L2StateMessage createElectionStartedMessage(Enrollment e) { return L2StateMessage.createElectionStartedMessage(e); } private L2StateMessage createElectionWonMessage(Enrollment e) { return L2StateMessage.createElectionWonMessage(e); } private L2StateMessage createElectionResultMessage(Enrollment e) { return L2StateMessage.createElectionResultMessage(e); } private L2StateMessage createElectionStartedMessage(L2StateMessage msg, Enrollment e) { return L2StateMessage.createElectionStartedMessage(msg, e); } @Override public long getElectionTime() { return electionTime; } private static void debugInfo(String message) { L2DebugLogging.log(logger, LogLevel.INFO, message, null); } }
public class L1Management extends TerracottaManagement { private static final TCLogger logger = TCLogging.getLogger(L1Management.class); private final SetOnceFlag started; private final TunnelingEventHandler tunnelingHandler; private final Object mBeanServerLock; private MBeanServer mBeanServer; private final L1Info l1InfoBean; private final L1Dumper l1DumpBean; private JMXConnectorServer connServer; private volatile boolean stopped; public L1Management( final TunnelingEventHandler tunnelingHandler, final String rawConfigText, final TCClient client) { super(); started = new SetOnceFlag(); this.tunnelingHandler = tunnelingHandler; try { l1InfoBean = new L1Info(client, rawConfigText); l1DumpBean = new L1Dumper(client, l1InfoBean); } catch (NotCompliantMBeanException ncmbe) { throw new TCRuntimeException( "Unable to construct one of the L1 MBeans: this is a programming error in one of those beans", ncmbe); } mBeanServerLock = new Object(); } public synchronized void stop() throws IOException { stopped = true; if (connServer != null) { try { connServer.stop(); } finally { connServer = null; } } ManagementResources mr = new ManagementResources(); unregisterBeans(mr.getInternalMBeanDomain()); unregisterBeans(mr.getPublicMBeanDomain()); } private void unregisterBeans(String domain) { MBeanServer mbs = mBeanServer; if (mbs != null) { Set<ObjectName> queryNames; try { queryNames = mbs.queryNames(new ObjectName(domain + ":*,node=" + tunnelingHandler.getUUID()), null); } catch (MalformedObjectNameException e1) { throw new RuntimeException(e1); } for (ObjectName name : queryNames) { try { mbs.unregisterMBean(name); } catch (Exception e) { logger.error("error unregistering " + name, e); } } } } public synchronized void start(final boolean createDedicatedMBeanServer) { started.set(); Thread registrationThread = new Thread( new Runnable() { private static final int MAX_ATTEMPTS = 60 * 5; @Override public void run() { try { boolean registered = false; int attemptCounter = 0; while (!registered && attemptCounter++ < MAX_ATTEMPTS) { try { if (logger.isDebugEnabled()) { logger.debug( "Attempt #" + (attemptCounter + 1) + " to find the MBeanServer and register the L1 MBeans"); } attemptToRegister(createDedicatedMBeanServer); registered = true; if (logger.isDebugEnabled()) { logger.debug( "L1 MBeans registered with the MBeanServer successfully after " + (attemptCounter + 1) + " attempts"); } } catch (InstanceAlreadyExistsException e) { logger.error( "Exception while registering the L1 MBeans, they already seem to exist in the MBeanServer.", e); return; } catch (Exception e) { // Ignore and try again after 1 second, give the VM a chance to get started if (logger.isDebugEnabled()) { logger.debug("Caught exception while trying to register L1 MBeans", e); } try { Thread.sleep(1000); } catch (InterruptedException ie) { new Exception( "JMX registration thread interrupted, management beans will not be available", ie) .printStackTrace(); } } } if (registered) { tunnelingHandler.jmxIsReady(); } else { logger.error( "Aborted attempts to register management" + " beans after " + (MAX_ATTEMPTS / 60) + " min of trying."); } } finally { if (stopped) { try { L1Management.this.stop(); } catch (IOException e) { logger.error("Error stopping L1 management from registration thread"); } } } } }, "L1Management JMX registration"); registrationThread.setDaemon(true); registrationThread.start(); } @Override public Object findMBean(final ObjectName objectName, final Class mBeanInterface) throws IOException { if (objectName.equals(L1MBeanNames.L1INFO_PUBLIC)) return l1InfoBean; else { synchronized (mBeanServerLock) { if (mBeanServer != null) { return findMBean(objectName, mBeanInterface, mBeanServer); } } } return null; } public L1InfoMBean findL1InfoMBean() { return l1InfoBean; } private void attemptToRegister(final boolean createDedicatedMBeanServer) throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException, MalformedObjectNameException { synchronized (mBeanServerLock) { if (mBeanServer == null) { if (createDedicatedMBeanServer) { if (logger.isDebugEnabled()) { logger.debug("attemptToRegister(): Creating an MBeanServer since explicitly requested"); } mBeanServer = MBeanServerFactory.createMBeanServer(); } else { mBeanServer = getPlatformDefaultMBeanServer(); } addJMXConnectors(); } } registerMBeans(); } protected void registerMBeans() throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException, MalformedObjectNameException { registerMBean(l1DumpBean, MBeanNames.L1DUMPER_INTERNAL); registerMBean(l1InfoBean, L1MBeanNames.L1INFO_PUBLIC); } protected void registerMBean(Object bean, ObjectName name) throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException, MalformedObjectNameException { ObjectName modifiedName = TerracottaManagement.addNodeInfo(name, tunnelingHandler.getUUID()); mBeanServer.registerMBean(bean, modifiedName); } public MBeanServer getMBeanServer() { return mBeanServer; } private void addJMXConnectors() { // This will make the JobExecutor threads in remote JMX idle timeout after 5s (instead of 5 // minutes) try { Class c = Class.forName("com.sun.jmx.remote.opt.util.JobExecutor"); Method method = c.getMethod("setWaitingTime", Long.TYPE); method.setAccessible(true); method.invoke(null, 5000L); } catch (Exception e) { logger.warn("cannot adjust job executor timeout", e); } JMXServiceURL url = null; try { // LKC-2990 and LKC-3171: Remove the JMX generic optional logging java.util.logging.Logger jmxLogger = java.util.logging.Logger.getLogger("javax.management.remote.generic"); jmxLogger.setLevel(java.util.logging.Level.OFF); } catch (Throwable t) { logger.warn( "Unable to disable default logging in Sun's JMX package; when Terracotta clients go" + " up/down you may see stack traces printed to the log"); } try { final Map environment = new HashMap(); environment.put("jmx.remote.x.server.connection.timeout", Long.valueOf(Long.MAX_VALUE)); ProtocolProvider.addTerracottaJmxProvider(environment); environment.put(TunnelingMessageConnectionServer.TUNNELING_HANDLER, tunnelingHandler); environment.put(EnvHelp.SERVER_CONNECTION_TIMEOUT, String.valueOf(Long.MAX_VALUE)); url = new JMXServiceURL("terracotta", "localhost", 0); // Normally you should NOT do this in the client, but we have a modified version of // jmxremote_optional.jar that // uses a daemon thread to wait for connections so we don't hang the client connServer = JMXConnectorServerFactory.newJMXConnectorServer(url, environment, mBeanServer); connServer.start(); logger.info("Terracotta JMX connector available at[" + url + "]"); } catch (Exception e) { if (url != null) { logger.warn("Unable to start embedded JMX connector for url[" + url + "]", e); } else { logger.warn( "Unable to construct embedded JMX connector URL with params (terracotta, localhost, 0)"); } } } private MBeanServer getPlatformDefaultMBeanServer() { return ManagementFactory.getPlatformMBeanServer(); } }
public synchronized void start() { validateSecurityConfig(); final TCProperties tcProperties = TCPropertiesImpl.getProperties(); final int maxSize = tcProperties.getInt(TCPropertiesConsts.L1_SEDA_STAGE_SINK_CAPACITY); final SessionManager sessionManager = new SessionManagerImpl( new SessionManagerImpl.SequenceFactory() { @Override public Sequence newSequence() { return new SimpleSequence(); } }); this.threadGroup.addCallbackOnExitDefaultHandler( new CallbackOnExitHandler() { @Override public void callbackOnExit(CallbackOnExitState state) { cluster.fireNodeError(); } }); this.dumpHandler.registerForDump(new CallbackDumpAdapter(this.communicationStageManager)); final ReconnectConfig l1ReconnectConfig = getReconnectPropertiesFromServer(); final boolean useOOOLayer = l1ReconnectConfig.getReconnectEnabled(); final NetworkStackHarnessFactory networkStackHarnessFactory = getNetworkStackHarnessFactory(useOOOLayer, l1ReconnectConfig); this.counterManager = new CounterManagerImpl(); final MessageMonitor mm = MessageMonitorImpl.createMonitor(tcProperties, DSO_LOGGER); final TCMessageRouter messageRouter = new TCMessageRouterImpl(); this.communicationsManager = this.clientBuilder.createCommunicationsManager( mm, messageRouter, networkStackHarnessFactory, new NullConnectionPolicy(), this.connectionComponents.createConnectionInfoConfigItemByGroup().length, new HealthCheckerConfigClientImpl( tcProperties.getPropertiesFor(TCPropertiesConsts.L1_L2_HEALTH_CHECK_CATEGORY), "DSO Client"), getMessageTypeClassMapping(), ReconnectionRejectedHandlerL1.SINGLETON, securityManager, productId); DSO_LOGGER.debug("Created CommunicationsManager."); final ConnectionInfoConfig[] connectionInfoItems = this.connectionComponents.createConnectionInfoConfigItemByGroup(); final ConnectionInfo[] connectionInfo = connectionInfoItems[0].getConnectionInfos(); final String serverHost = connectionInfo[0].getHostname(); final int serverPort = connectionInfo[0].getPort(); clusterEventsStage = this.communicationStageManager.createStage( ClientConfigurationContext.CLUSTER_EVENTS_STAGE, ClusterInternalEventsContext.class, new ClusterInternalEventsHandler<ClusterInternalEventsContext>(cluster), 1, maxSize); final int socketConnectTimeout = tcProperties.getInt(TCPropertiesConsts.L1_SOCKET_CONNECT_TIMEOUT); if (socketConnectTimeout < 0) { throw new IllegalArgumentException("invalid socket time value: " + socketConnectTimeout); } this.channel = this.clientBuilder.createClientMessageChannel( this.communicationsManager, this.connectionComponents, sessionManager, MAX_CONNECT_TRIES, socketConnectTimeout, this); final ClientIDLoggerProvider cidLoggerProvider = new ClientIDLoggerProvider(this.channel); this.communicationStageManager.setLoggerProvider(cidLoggerProvider); DSO_LOGGER.debug("Created channel."); this.clientEntityManager = this.clientBuilder.createClientEntityManager(this.channel, this.communicationStageManager); RequestReceiveHandler receivingHandler = new RequestReceiveHandler(this.clientEntityManager); Stage<VoltronEntityResponse> entityResponseStage = this.communicationStageManager.createStage( ClientConfigurationContext.VOLTRON_ENTITY_RESPONSE_STAGE, VoltronEntityResponse.class, receivingHandler, 1, maxSize); Stage<Void> serverMessageStage = this.communicationStageManager.createStage( ClientConfigurationContext.SERVER_ENTITY_MESSAGE_STAGE, Void.class, new ServerMessageReceiveHandler<Void>(channel), 1, maxSize); TerracottaOperatorEventLogging.setNodeNameProvider(new ClientNameProvider(this.cluster)); final SampledRateCounterConfig sampledRateCounterConfig = new SampledRateCounterConfig(1, 300, true); this.counterManager.createCounter(sampledRateCounterConfig); this.counterManager.createCounter(sampledRateCounterConfig); // for SRA L1 Tx count final SampledCounterConfig sampledCounterConfig = new SampledCounterConfig(1, 300, true, 0L); this.counterManager.createCounter(sampledCounterConfig); this.threadGroup.addCallbackOnExitDefaultHandler( new CallbackDumpAdapter(this.clientEntityManager)); this.dumpHandler.registerForDump(new CallbackDumpAdapter(this.clientEntityManager)); final long timeOut = TCPropertiesImpl.getProperties().getLong(TCPropertiesConsts.LOGGING_LONG_GC_THRESHOLD); final LongGCLogger gcLogger = this.clientBuilder.createLongGCLogger(timeOut); this.tcMemManager.registerForMemoryEvents(gcLogger); // CDV-1181 warn if using CMS this.tcMemManager.checkGarbageCollectors(); this.threadIDManager = new ThreadIDManagerImpl(this.threadIDMap); // Setup the lock manager this.lockManager = this.clientBuilder.createLockManager( this.channel, new ClientIDLogger(this.channel, TCLogging.getLogger(ClientLockManager.class)), sessionManager, this.channel.getLockRequestMessageFactory(), this.threadIDManager, new ClientLockManagerConfigImpl( tcProperties.getPropertiesFor(TCPropertiesConsts.L1_LOCK_MANAGER_CATEGORY)), this.taskRunner); final CallbackDumpAdapter lockDumpAdapter = new CallbackDumpAdapter(this.lockManager); this.threadGroup.addCallbackOnExitDefaultHandler(lockDumpAdapter); this.dumpHandler.registerForDump(lockDumpAdapter); // Create the SEDA stages final Stage<Void> lockResponse = this.communicationStageManager.createStage( ClientConfigurationContext.LOCK_RESPONSE_STAGE, Void.class, new LockResponseHandler<Void>(sessionManager), 1, maxSize); final Stage<HydrateContext> hydrateStage = this.communicationStageManager.createStage( ClientConfigurationContext.HYDRATE_MESSAGE_STAGE, HydrateContext.class, new HydrateHandler(), 1, maxSize); // By design this stage needs to be single threaded. If it wasn't then cluster membership // messages could get // processed before the client handshake ack, and this client would get a faulty view of the // cluster at best, or // more likely an AssertionError final Stage<PauseContext> pauseStage = this.communicationStageManager.createStage( ClientConfigurationContext.CLIENT_COORDINATION_STAGE, PauseContext.class, new ClientCoordinationHandler<PauseContext>(), 1, maxSize); final Sink<PauseContext> pauseSink = pauseStage.getSink(); final Stage<Void> clusterMembershipEventStage = this.communicationStageManager.createStage( ClientConfigurationContext.CLUSTER_MEMBERSHIP_EVENT_STAGE, Void.class, new ClusterMembershipEventsHandler<Void>(cluster), 1, maxSize); final List<ClientHandshakeCallback> clientHandshakeCallbacks = new ArrayList<ClientHandshakeCallback>(); clientHandshakeCallbacks.add(this.lockManager); clientHandshakeCallbacks.add(this.clientEntityManager); final ProductInfo pInfo = ProductInfo.getInstance(); this.clientHandshakeManager = this.clientBuilder.createClientHandshakeManager( new ClientIDLogger(this.channel, TCLogging.getLogger(ClientHandshakeManagerImpl.class)), this.channel.getClientHandshakeMessageFactory(), pauseSink, sessionManager, cluster, this.uuid, this.name, pInfo.version(), Collections.unmodifiableCollection(clientHandshakeCallbacks)); ClientChannelEventController.connectChannelEventListener( channel, pauseSink, clientHandshakeManager); this.shutdownManager = new ClientShutdownManager(this, connectionComponents); final ClientConfigurationContext cc = new ClientConfigurationContext( this.communicationStageManager, this.lockManager, this.clientEntityManager, this.clientHandshakeManager); // DO NOT create any stages after this call this.communicationStageManager.startAll(cc, Collections.<PostInit>emptyList()); initChannelMessageRouter( messageRouter, hydrateStage.getSink(), lockResponse.getSink(), pauseSink, clusterMembershipEventStage.getSink(), entityResponseStage.getSink(), serverMessageStage.getSink()); new Thread( threadGroup, new Runnable() { public void run() { while (!clientStopped.isSet()) { try { openChannel(serverHost, serverPort); waitForHandshake(); connectionMade(); break; } catch (InterruptedException ie) { // We are in the process of letting the thread terminate so we don't handle this // in a special way. } } // don't reset interrupted, thread is done } }, "Connection Establisher - " + uuid) .start(); }
/** * The communication thread. Creates {@link Selector selector}, registers {@link SocketChannel} to * the selector and does other NIO operations. * * @author mgovinda */ class CoreNIOServices implements TCListenerEventListener, TCConnectionEventListener { private static final TCLogger logger = TCLogging.getLogger(CoreNIOServices.class); private final TCWorkerCommManager workerCommMgr; private final String commThreadName; private final SocketParams socketParams; private final CommThread readerComm; private final CommThread writerComm; private final SetOnceFlag stopRequested = new SetOnceFlag(); // maintains weight of all L1 Connections which is handled by this WorkerComm private final HashMap<TCConnection, Integer> managedConnectionsMap; private int clientWeights; private final List listeners = new ArrayList(); private String listenerString; private static enum COMM_THREAD_MODE { NIO_READER, NIO_WRITER } public CoreNIOServices( String commThreadName, TCWorkerCommManager workerCommManager, SocketParams socketParams) { this.commThreadName = commThreadName; this.workerCommMgr = workerCommManager; this.socketParams = socketParams; this.managedConnectionsMap = new HashMap<TCConnection, Integer>(); this.readerComm = new CommThread(COMM_THREAD_MODE.NIO_READER); this.writerComm = new CommThread(COMM_THREAD_MODE.NIO_WRITER); } public void start() { readerComm.start(); writerComm.start(); } public void requestStop() { if (stopRequested.attemptSet()) { readerComm.requestStop(); writerComm.requestStop(); } } public void cleanupChannel(SocketChannel channel, Runnable callback) { readerComm.cleanupChannel(channel, callback); writerComm.cleanupChannel(channel, callback); } @Override public void closeEvent(TCListenerEvent event) { listenerRemoved(event.getSource()); } public void registerListener(TCListenerImpl lsnr, ServerSocketChannel ssc) { requestAcceptInterest(lsnr, ssc); listenerAdded(lsnr); lsnr.addEventListener(this); } // listener was with readerComm only public void stopListener(ServerSocketChannel ssc, Runnable callback) { readerComm.stopListener(ssc, callback); } private synchronized void listenerRemoved(TCListener listener) { boolean removed = listeners.remove(listener); Assert.eval(removed); updateListenerString(); readerComm.updateThreadName(); writerComm.updateThreadName(); } private synchronized void listenerAdded(TCListener listener) { listeners.add(listener); updateListenerString(); readerComm.updateThreadName(); writerComm.updateThreadName(); } private void updateListenerString() { if (listeners.isEmpty()) { listenerString = ""; } StringBuffer buf = new StringBuffer(); buf.append(" (listen "); for (int i = 0, n = listeners.size(); i < n; i++) { TCListener listener = (TCListener) listeners.get(i); buf.append(listener.getBindAddress().getHostAddress()); buf.append(':'); buf.append(listener.getBindPort()); if (i < (n - 1)) { buf.append(','); } } buf.append(')'); listenerString = buf.toString(); } private synchronized String getListenerString() { return this.listenerString; } public long getTotalBytesRead() { return readerComm.getTotalBytesRead() + writerComm.getTotalBytesRead(); } public long getTotalBytesWritten() { return readerComm.getTotalBytesWritten() + writerComm.getTotalBytesWritten(); } public int getWeight() { synchronized (managedConnectionsMap) { return this.clientWeights; } } protected CommThread getReaderComm() { return this.readerComm; } protected CommThread getWriterComm() { return this.writerComm; } /** * Change thread ownership of a connection or upgrade weight. * * @param connection : connection which has to be transfered from the main selector thread to a * new worker comm thread that has the least weight. If the connection is already managed by a * comm thread, then just update connection's weight. * @param addWeightBy : upgrade weight of connection * @param channel : SocketChannel for the passed in connection */ public void addWeight( final TCConnectionImpl connection, final int addWeightBy, final SocketChannel channel) { synchronized (managedConnectionsMap) { // this connection is already handled by a WorkerComm if (this.managedConnectionsMap.containsKey(connection)) { this.clientWeights += addWeightBy; this.managedConnectionsMap.put( connection, this.managedConnectionsMap.get(connection) + addWeightBy); return; } } // MainComm Thread if (workerCommMgr == null) { return; } readerComm.unregister(channel); final CoreNIOServices workerComm = workerCommMgr.getNextWorkerComm(); connection.setCommWorker(workerComm); workerComm.addConnection(connection, addWeightBy); workerComm.requestReadWriteInterest(connection, channel); } private void addConnection(TCConnectionImpl connection, int initialWeight) { synchronized (managedConnectionsMap) { Assert.eval(!managedConnectionsMap.containsKey(connection)); managedConnectionsMap.put(connection, initialWeight); this.clientWeights += initialWeight; connection.addListener(this); } } @Override public void closeEvent(TCConnectionEvent event) { synchronized (managedConnectionsMap) { Assert.eval(managedConnectionsMap.containsKey(event.getSource())); int closedCientWeight = managedConnectionsMap.get(event.getSource()); this.clientWeights -= closedCientWeight; managedConnectionsMap.remove(event.getSource()); event.getSource().removeListener(this); } } @Override public void connectEvent(TCConnectionEvent event) { // } @Override public void endOfFileEvent(TCConnectionEvent event) { // } @Override public void errorEvent(TCConnectionErrorEvent errorEvent) { // } @Override public synchronized String toString() { return "[" + this.commThreadName + ", FD, wt:" + getWeight() + "]"; } void requestConnectInterest(TCConnectionImpl conn, SocketChannel sc) { readerComm.requestConnectInterest(conn, sc); } private void requestAcceptInterest(TCListenerImpl lsnr, ServerSocketChannel ssc) { readerComm.requestAcceptInterest(lsnr, ssc); } void requestReadInterest(TCChannelReader reader, ScatteringByteChannel channel) { readerComm.requestReadInterest(reader, channel); } void removeReadInterest(TCConnectionImpl conn, SelectableChannel channel) { readerComm.removeReadInterest(conn, channel); } void requestWriteInterest(TCChannelWriter writer, GatheringByteChannel channel) { writerComm.requestWriteInterest(writer, channel); } void removeWriteInterest(TCConnectionImpl conn, SelectableChannel channel) { writerComm.removeWriteInterest(conn, channel); } private void requestReadWriteInterest(TCConnectionImpl conn, SocketChannel sc) { readerComm.requestReadInterest(conn, sc); writerComm.requestWriteInterest(conn, sc); } protected class CommThread extends Thread { private final Selector selector; private final LinkedBlockingQueue selectorTasks; private final String name; private final AtomicLong bytesRead = new AtomicLong(0); private final AtomicLong bytesWritten = new AtomicLong(0); private final COMM_THREAD_MODE mode; public CommThread(final COMM_THREAD_MODE mode) { name = commThreadName + (mode == COMM_THREAD_MODE.NIO_READER ? "_R" : "_W"); setDaemon(true); setName(name); this.selector = createSelector(); this.selectorTasks = new LinkedBlockingQueue(); this.mode = mode; } private boolean isReader() { return (this.mode == COMM_THREAD_MODE.NIO_READER); } @Override public void run() { try { selectLoop(); } catch (Throwable t) { // if something goes wrong on selector level, we cannot recover logger.error("Unhandled exception from selectLoop", t); throw new RuntimeException(t); } finally { dispose(selector, selectorTasks); } } public void requestStop() { try { this.selector.wakeup(); } catch (Exception e) { logger.error("Exception trying to stop " + getName() + ": ", e); } } private void updateThreadName() { setName(name + getListenerString()); } private Selector createSelector() { Selector selector1 = null; final int tries = 3; boolean interrupted = false; try { for (int i = 0; i < tries; i++) { try { selector1 = Selector.open(); return selector1; } catch (IOException ioe) { throw new RuntimeException(ioe); } catch (NullPointerException npe) { if (i < tries && NIOWorkarounds.selectorOpenRace(npe)) { System.err.println( "Attempting to work around sun bug 6427854 (attempt " + (i + 1) + " of " + tries + ")"); try { Thread.sleep(new Random().nextInt(20) + 5); } catch (InterruptedException ie) { interrupted = true; } continue; } throw npe; } } } finally { Util.selfInterruptIfNeeded(interrupted); } return selector1; } void addSelectorTask(final Runnable task) { boolean isInterrupted = false; try { while (true) { try { this.selectorTasks.put(task); break; } catch (InterruptedException e) { logger.warn(e); isInterrupted = true; } } } finally { this.selector.wakeup(); Util.selfInterruptIfNeeded(isInterrupted); } } void unregister(final SelectableChannel channel) { if (Thread.currentThread() != this) { final CountDownLatch latch = new CountDownLatch(1); this.addSelectorTask( new Runnable() { @Override public void run() { CommThread.this.unregister(channel); latch.countDown(); } }); try { latch.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } } else { SelectionKey key = null; key = channel.keyFor(this.selector); if (key != null) { key.cancel(); key.attach(null); } } } void stopListener(final ServerSocketChannel ssc, final Runnable callback) { if (Thread.currentThread() != this) { Runnable task = new Runnable() { @Override public void run() { CommThread.this.stopListener(ssc, callback); } }; addSelectorTask(task); return; } try { cleanupChannel(ssc, null); } catch (Exception e) { logger.error(e); } finally { try { callback.run(); } catch (Exception e) { logger.error(e); } } } void cleanupChannel(final Channel ch, final Runnable callback) { if (null == ch) { // not expected logger.warn("null channel passed to cleanupChannel()", new Throwable()); return; } if (Thread.currentThread() != this) { if (logger.isDebugEnabled()) { logger.debug("queue'ing channel close operation"); } addSelectorTask( new Runnable() { @Override public void run() { CommThread.this.cleanupChannel(ch, callback); } }); return; } try { if (ch instanceof SelectableChannel) { SelectableChannel sc = (SelectableChannel) ch; try { SelectionKey sk = sc.keyFor(selector); if (sk != null) { sk.attach(null); sk.cancel(); } } catch (Exception e) { logger.warn("Exception trying to clear selection key", e); } } if (ch instanceof SocketChannel) { SocketChannel sc = (SocketChannel) ch; Socket s = sc.socket(); if (null != s) { synchronized (s) { if (s.isConnected()) { try { if (!s.isOutputShutdown()) { s.shutdownOutput(); } } catch (Exception e) { logger.warn("Exception trying to shutdown socket output: " + e.getMessage()); } try { if (!s.isClosed()) { s.close(); } } catch (Exception e) { logger.warn("Exception trying to close() socket: " + e.getMessage()); } } } } } else if (ch instanceof ServerSocketChannel) { ServerSocketChannel ssc = (ServerSocketChannel) ch; try { ssc.close(); } catch (Exception e) { logger.warn("Exception trying to close() server socket" + e.getMessage()); } } try { ch.close(); } catch (Exception e) { logger.warn("Exception trying to close channel", e); } } catch (Exception e) { // this is just a catch all to make sure that no exceptions will be thrown by this method, // please do not remove logger.error("Unhandled exception in cleanupChannel()", e); } finally { try { if (callback != null) { callback.run(); } } catch (Throwable t) { logger.error("Unhandled exception in cleanupChannel callback.", t); } } } private void dispose(Selector localSelector, LinkedBlockingQueue localSelectorTasks) { Assert.eval(Thread.currentThread() == this); if (localSelector != null) { for (Object element : localSelector.keys()) { try { SelectionKey key = (SelectionKey) element; cleanupChannel(key.channel(), null); } catch (Exception e) { logger.warn("Exception trying to close channel", e); } } try { localSelector.close(); } catch (Exception e) { if ((Os.isMac()) && (Os.isUnix()) && (e.getMessage().equals("Bad file descriptor"))) { // I can't find a specific bug about this, but I also can't seem to prevent the // exception on the Mac. // So just logging this as warning. logger.warn("Exception trying to close selector: " + e.getMessage()); } else { logger.error("Exception trying to close selector", e); } } } } private void selectLoop() throws IOException { Assert.eval(Thread.currentThread() == this); Selector localSelector = this.selector; LinkedBlockingQueue localSelectorTasks = this.selectorTasks; while (true) { final int numKeys; try { numKeys = localSelector.select(); } catch (IOException ioe) { if (NIOWorkarounds.linuxSelectWorkaround(ioe)) { logger.warn("working around Sun bug 4504001"); continue; } if (NIOWorkaroundsTemp.solarisSelectWorkaround(ioe)) { logger.warn("working around Solaris select IOException"); continue; } throw ioe; } catch (CancelledKeyException cke) { logger.warn("Cencelled Key " + cke); continue; } if (isStopRequested()) { if (logger.isDebugEnabled()) { logger.debug("Select loop terminating"); } return; } boolean isInterrupted = false; // run any pending selector tasks while (true) { Runnable task; while (true) { try { task = (Runnable) localSelectorTasks.poll(0, TimeUnit.MILLISECONDS); break; } catch (InterruptedException ie) { logger.error("Error getting task from task queue", ie); isInterrupted = true; } } if (null == task) { break; } try { task.run(); } catch (Exception e) { logger.error("error running selector task", e); } } Util.selfInterruptIfNeeded(isInterrupted); final Set selectedKeys = localSelector.selectedKeys(); if ((0 == numKeys) && (0 == selectedKeys.size())) { continue; } for (Iterator iter = selectedKeys.iterator(); iter.hasNext(); ) { SelectionKey key = (SelectionKey) iter.next(); iter.remove(); if (null == key) { logger.error("Selection key is null"); continue; } try { if (key.isAcceptable()) { doAccept(key); continue; } if (key.isConnectable()) { doConnect(key); continue; } if (isReader() && key.isValid() && key.isReadable()) { int read; TCChannelReader reader = (TCChannelReader) key.attachment(); do { read = reader.doRead(); this.bytesRead.addAndGet(read); } while ((read != 0) && key.isReadable()); } if (key.isValid() && !isReader() && key.isWritable()) { int written = ((TCChannelWriter) key.attachment()).doWrite(); this.bytesWritten.addAndGet(written); } TCConnection conn = (TCConnection) key.attachment(); if (conn != null && conn.isClosePending()) { conn.asynchClose(); } } catch (CancelledKeyException cke) { logger.info("selection key cancelled key@" + key.hashCode()); } catch (Exception e) { // DEV-9369. Do not reconnect on fatal errors. logger.info("Unhandled exception occured on connection layer", e); TCConnectionImpl conn = (TCConnectionImpl) key.attachment(); // TCConnectionManager will take care of closing and cleaning up resources conn.fireErrorEvent(new RuntimeException(e), null); } } // for } // while (true) } private void doAccept(final SelectionKey key) { SocketChannel sc = null; final TCListenerImpl lsnr = (TCListenerImpl) key.attachment(); try { final ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); sc = ssc.accept(); if (sc == null) { // non blocking channel accept can return null logger.warn("New connection accept didn't go through for " + ssc.socket()); return; } sc.configureBlocking(false); final TCConnectionImpl conn = lsnr.createConnection(sc, CoreNIOServices.this, socketParams); requestReadInterest(conn, sc); } catch (IOException ioe) { if (logger.isInfoEnabled()) { logger.info("IO Exception accepting new connection", ioe); } cleanupChannel(sc, null); } } private void doConnect(SelectionKey key) { SocketChannel sc = (SocketChannel) key.channel(); TCConnectionImpl conn = (TCConnectionImpl) key.attachment(); try { if (sc.finishConnect()) { sc.register(selector, SelectionKey.OP_READ, conn); conn.finishConnect(); } else { String errMsg = "finishConnect() returned false, but no exception thrown"; if (logger.isInfoEnabled()) { logger.info(errMsg); } conn.fireErrorEvent(new Exception(errMsg), null); } } catch (IOException ioe) { if (logger.isInfoEnabled()) { logger.info("IOException attempting to finish socket connection", ioe); } conn.fireErrorEvent(ioe, null); } } public long getTotalBytesRead() { return this.bytesRead.get(); } public long getTotalBytesWritten() { return this.bytesWritten.get(); } private void handleRequest(final InterestRequest req) { // ignore the request if we are stopped/stopping if (isStopRequested()) { return; } if (Thread.currentThread() == this) { modifyInterest(req); } else { final CommThread commTh = req.getCommNIOServiceThread(); Assert.assertNotNull(commTh); commTh.addSelectorTask( new Runnable() { @Override public void run() { commTh.handleRequest(req); } }); } } private boolean isStopRequested() { return stopRequested.isSet(); } private void modifyInterest(InterestRequest request) { Assert.eval(Thread.currentThread() == this); Selector localSelector = null; localSelector = selector; try { final int existingOps; SelectionKey key = request.channel.keyFor(localSelector); if (key != null) { if (!key.isValid()) { logger.warn( "Skipping modifyInterest - " + Constants.interestOpsToString(request.interestOps) + " on " + request.attachment); return; } existingOps = key.interestOps(); } else { existingOps = 0; } if (logger.isDebugEnabled()) { logger.debug(request); } if (request.add) { request.channel.register( localSelector, existingOps | request.interestOps, request.attachment); } else if (request.set) { request.channel.register(localSelector, request.interestOps, request.attachment); } else if (request.remove) { request.channel.register( localSelector, existingOps ^ request.interestOps, request.attachment); } else { throw new TCInternalError(); } } catch (ClosedChannelException cce) { logger.warn("Exception trying to process interest request: " + cce); } catch (CancelledKeyException cke) { logger.warn("Exception trying to process interest request: " + cke); } } void requestConnectInterest(TCConnectionImpl conn, SocketChannel sc) { handleRequest( InterestRequest.createSetInterestRequest(sc, conn, SelectionKey.OP_CONNECT, this)); } void requestReadInterest(TCChannelReader reader, ScatteringByteChannel channel) { Assert.eval(isReader()); handleRequest( InterestRequest.createAddInterestRequest( (SelectableChannel) channel, reader, SelectionKey.OP_READ, this)); } void requestWriteInterest(TCChannelWriter writer, GatheringByteChannel channel) { Assert.eval(!isReader()); handleRequest( InterestRequest.createAddInterestRequest( (SelectableChannel) channel, writer, SelectionKey.OP_WRITE, this)); } private void requestAcceptInterest(TCListenerImpl lsnr, ServerSocketChannel ssc) { Assert.eval(isReader()); handleRequest( InterestRequest.createSetInterestRequest(ssc, lsnr, SelectionKey.OP_ACCEPT, this)); } void removeWriteInterest(TCConnectionImpl conn, SelectableChannel channel) { Assert.eval(!isReader()); handleRequest( InterestRequest.createRemoveInterestRequest(channel, conn, SelectionKey.OP_WRITE, this)); } void removeReadInterest(TCConnectionImpl conn, SelectableChannel channel) { Assert.eval(isReader()); handleRequest( InterestRequest.createRemoveInterestRequest(channel, conn, SelectionKey.OP_READ, this)); } } private static class InterestRequest { final SelectableChannel channel; final Object attachment; final boolean set; final boolean add; final boolean remove; final int interestOps; final CommThread commNIOServiceThread; static InterestRequest createAddInterestRequest( SelectableChannel channel, Object attachment, int interestOps, CommThread nioServiceThread) { return new InterestRequest( channel, attachment, interestOps, false, true, false, nioServiceThread); } static InterestRequest createSetInterestRequest( SelectableChannel channel, Object attachment, int interestOps, CommThread nioServiceThread) { return new InterestRequest( channel, attachment, interestOps, true, false, false, nioServiceThread); } static InterestRequest createRemoveInterestRequest( SelectableChannel channel, Object attachment, int interestOps, CommThread nioServiceThread) { return new InterestRequest( channel, attachment, interestOps, false, false, true, nioServiceThread); } private InterestRequest( SelectableChannel channel, Object attachment, int interestOps, boolean set, boolean add, boolean remove, CommThread nioServiceThread) { Assert.eval(remove ^ set ^ add); Assert.eval(channel != null); this.channel = channel; this.attachment = attachment; this.set = set; this.add = add; this.remove = remove; this.interestOps = interestOps; this.commNIOServiceThread = nioServiceThread; } public CommThread getCommNIOServiceThread() { return commNIOServiceThread; } @Override public String toString() { StringBuffer buf = new StringBuffer(); buf.append("Interest modify request: ").append(channel.toString()).append("\n"); buf.append("Ops: ").append(Constants.interestOpsToString(interestOps)).append("\n"); buf.append("Set: ") .append(set) .append(", Remove: ") .append(remove) .append(", Add: ") .append(add) .append("\n"); buf.append("Attachment: "); if (attachment != null) { buf.append(attachment.toString()); } else { buf.append("null"); } buf.append("\n"); return buf.toString(); } } /** * A temporary class. These apis are available in the latest tim-api version. Since, TC 3.6 can't * use the newer tim-api version, having a copy of them here. */ private static class NIOWorkaroundsTemp { /** * Workaround for select() throwing IOException("Bad file number") in Solaris Sun bug 6994017 * looks related -- http://wesunsolve.net/bugid/id/6994017 */ private static boolean solarisSelectWorkaround(IOException ioe) { if (Os.isSolaris()) { String msg = ioe.getMessage(); if ((msg != null) && msg.contains("Bad file number")) { return true; } } return false; } } }
/** @author steve */ public class ClientTransactionManagerImpl implements ClientTransactionManager { private static final TCLogger logger = TCLogging.getLogger(ClientTransactionManagerImpl.class); private final ThreadLocal transaction = new ThreadLocal() { protected synchronized Object initialValue() { return new ThreadTransactionContext(); } }; private final ThreadLocal txnLogging = new ThreadLocal() { protected Object initialValue() { return new ThreadTransactionLoggingStack(); } }; private final ClientTransactionFactory txFactory; private final RemoteTransactionManager remoteTxManager; private final ClientObjectManager objectManager; private final ThreadLockManager lockManager; private final LiteralValues literalValues = new LiteralValues(); private final WaitListener waitListener = new WaitListener() { public void handleWaitEvent() { return; } }; private final ChannelIDProvider cidProvider; private final ClientTxMonitorMBean txMonitor; public ClientTransactionManagerImpl( ChannelIDProvider cidProvider, ClientObjectManager objectManager, ThreadLockManager lockManager, ClientTransactionFactory txFactory, RemoteTransactionManager remoteTxManager, RuntimeLogger runtimeLogger, final ClientTxMonitorMBean txMonitor) { this.cidProvider = cidProvider; this.txFactory = txFactory; this.remoteTxManager = remoteTxManager; this.objectManager = objectManager; this.objectManager.setTransactionManager(this); this.lockManager = lockManager; this.txMonitor = txMonitor; } public int queueLength(String lockName) { final LockID lockID = lockManager.lockIDFor(lockName); return lockManager.queueLength(lockID); } public int waitLength(String lockName) { final LockID lockID = lockManager.lockIDFor(lockName); return lockManager.waitLength(lockID); } public int localHeldCount(String lockName, int lockLevel) { final LockID lockID = lockManager.lockIDFor(lockName); return lockManager.localHeldCount(lockID, lockLevel); } public boolean isHeldByCurrentThread(String lockName, int lockLevel) { if (isTransactionLoggingDisabled()) { return true; } final LockID lockID = lockManager.lockIDFor(lockName); return lockManager.localHeldCount(lockID, lockLevel) > 0; } public boolean isLocked(String lockName, int lockLevel) { final LockID lockID = lockManager.lockIDFor(lockName); return lockManager.isLocked(lockID, lockLevel); } public void lock(String lockName, int lockLevel) { final LockID lockID = lockManager.lockIDFor(lockName); lockManager.lock(lockID, lockLevel); } public void unlock(String lockName) { final LockID lockID = lockManager.lockIDFor(lockName); if (lockID != null) { lockManager.unlock(lockID); getThreadTransactionContext().removeLock(lockID); } } public boolean tryBegin(String lockName, WaitInvocation timeout, int lockLevel) { logTryBegin0(lockName, lockLevel); if (isTransactionLoggingDisabled() || objectManager.isCreationInProgress()) { return true; } final TxnType txnType = getTxnTypeFromLockLevel(lockLevel); ClientTransaction currentTransaction = getTransactionOrNull(); if ((currentTransaction != null) && lockLevel == LockLevel.CONCURRENT) { // make formatter sane throw new AssertionError("Can't acquire concurrent locks in a nested lock context."); } final LockID lockID = lockManager.lockIDFor(lockName); boolean isLocked = lockManager.tryLock(lockID, timeout, lockLevel); if (!isLocked) { return isLocked; } pushTxContext(lockID, txnType); if (currentTransaction == null) { createTxAndInitContext(); } else { currentTransaction.setTransactionContext(this.peekContext()); } return isLocked; } public boolean begin(String lockName, int lockLevel) { logBegin0(lockName, lockLevel); if (isTransactionLoggingDisabled() || objectManager.isCreationInProgress()) { return false; } final TxnType txnType = getTxnTypeFromLockLevel(lockLevel); ClientTransaction currentTransaction = getTransactionOrNull(); final LockID lockID = lockManager.lockIDFor(lockName); pushTxContext(lockID, txnType); if (currentTransaction == null) { createTxAndInitContext(); } else { currentTransaction.setTransactionContext(this.peekContext()); } lockManager.lock(lockID, lockLevel); return true; } private TxnType getTxnTypeFromLockLevel(int lockLevel) { switch (lockLevel) { case LockLevel.READ: return TxnType.READ_ONLY; case LockLevel.CONCURRENT: return TxnType.CONCURRENT; case LockLevel.WRITE: return TxnType.NORMAL; case LockLevel.SYNCHRONOUS_WRITE: return TxnType.NORMAL; default: throw Assert.failure("don't know how to translate lock level " + lockLevel); } } public void wait(String lockName, WaitInvocation call, Object object) throws UnlockedSharedObjectException, InterruptedException { final ClientTransaction topTxn = getTransaction(); LockID lockID = lockManager.lockIDFor(lockName); if (lockID == null || lockID.isNull()) { lockID = topTxn.getLockID(); } commit(lockID, topTxn, true); try { lockManager.wait(lockID, call, object, waitListener); } finally { createTxAndInitContext(); } } public void notify(String lockName, boolean all, Object object) throws UnlockedSharedObjectException { final ClientTransaction currentTxn = getTransaction(); LockID lockID = lockManager.lockIDFor(lockName); if (lockID == null || lockID.isNull()) { lockID = currentTxn.getLockID(); } currentTxn.addNotify(lockManager.notify(lockID, all)); } private void logTryBegin0(String lockID, int type) { if (logger.isDebugEnabled()) { logger.debug("tryBegin(): lockID=" + (lockID == null ? "null" : lockID) + ", type = " + type); } } private void logBegin0(String lockID, int type) { if (logger.isDebugEnabled()) { logger.debug("begin(): lockID=" + (lockID == null ? "null" : lockID) + ", type = " + type); } } private ClientTransaction getTransactionOrNull() { ThreadTransactionContext tx = getThreadTransactionContext(); return tx.getCurrentTransaction(); } private ThreadTransactionContext getThreadTransactionContext() { return (ThreadTransactionContext) this.transaction.get(); } public ClientTransaction getTransaction() throws UnlockedSharedObjectException { return getTransaction(null); } private ClientTransaction getTransaction(Object context) throws UnlockedSharedObjectException { ClientTransaction tx = getTransactionOrNull(); if (tx == null) { String type = context == null ? null : context.getClass().getName(); String errorMsg = "Attempt to access a shared object outside the scope of a shared lock. " + "\nAll access to shared objects must be within the scope of one or more shared locks defined in your Terracotta configuration. " + "\nPlease alter the locks section of your Terracotta configuration so that this access is auto-locked or protected by a named lock."; String details = ""; if (type != null) { details += "Shared Object Type: " + type; } throw new UnlockedSharedObjectException( errorMsg, Thread.currentThread().getName(), cidProvider.getChannelID().toLong(), details); } return tx; } public void checkWriteAccess(Object context) { if (isTransactionLoggingDisabled()) { return; } // First check if we have any TXN context at all (else exception thrown) ClientTransaction txn = getTransaction(context); // make sure we're not in a read-only transaction // txn.readOnlyCheck(); if (txn.getTransactionType() == TxnType.READ_ONLY) { throw new ReadOnlyException( "Current transaction with read-only access attempted to modify a shared object. " + "\nPlease alter the locks section of your Terracotta configuration so that the methods involved in this transaction have read/write access.", Thread.currentThread().getName(), cidProvider.getChannelID().toLong()); } } /** * In order to support ReentrantLock, the TransactionContext that is going to be removed when * doing a commit may not always be at the top of a stack because an reentrant lock could issue a * lock within a synchronized block (although it is highly not recommended). Therefore, when a * commit is issued, we need to search back the stack from the top of the stack to find the * appropriate TransactionContext to be removed. Most likely, the TransactionContext to be removed * will be on the top of the stack. Therefore, the performance should be make must difference. * Only in some weird situations where reentrantLock is mixed with synchronized block will the * TransactionContext to be removed be found otherwise. */ public void commit(String lockName) throws UnlockedSharedObjectException { logCommit0(); if (isTransactionLoggingDisabled() || objectManager.isCreationInProgress()) { return; } // ClientTransaction tx = popTransaction(); ClientTransaction tx = getTransaction(); LockID lockID = lockManager.lockIDFor(lockName); if (lockID == null || lockID.isNull()) { lockID = tx.getLockID(); } boolean hasCommitted = commit(lockID, tx, false); popTransaction(lockManager.lockIDFor(lockName)); if (peekContext() != null) { if (hasCommitted) { createTxAndInitContext(); } else { // If the current transaction has not committed, we will reuse the current transaction // so that the current changes will have a chance to commit at the next commit point. tx.setTransactionContext(peekContext()); setTransaction(tx); } } } private void createTxAndInitContext() { ClientTransaction ctx = txFactory.newInstance(); ctx.setTransactionContext(peekContext()); setTransaction(ctx); } private ClientTransaction popTransaction() { ThreadTransactionContext ttc = getThreadTransactionContext(); return ttc.popCurrentTransaction(); } private ClientTransaction popTransaction(LockID lockID) { if (lockID == null || lockID.isNull()) { return popTransaction(); } ThreadTransactionContext ttc = getThreadTransactionContext(); return ttc.popCurrentTransaction(lockID); } private TransactionContext peekContext(LockID lockID) { ThreadTransactionContext ttc = getThreadTransactionContext(); return ttc.peekContext(lockID); } private TransactionContext peekContext() { ThreadTransactionContext ttc = getThreadTransactionContext(); return ttc.peekContext(); } private void pushTxContext(LockID lockID, TxnType txnType) { ThreadTransactionContext ttc = getThreadTransactionContext(); ttc.pushContext(lockID, txnType); } private void logCommit0() { if (logger.isDebugEnabled()) logger.debug("commit()"); } private boolean commit( LockID lockID, ClientTransaction currentTransaction, boolean isWaitContext) { try { return commitInternal(lockID, currentTransaction, isWaitContext); } catch (Throwable t) { remoteTxManager.stopProcessing(); Banner.errorBanner("Terracotta client shutting down due to error " + t); logger.error(t); if (t instanceof Error) { throw (Error) t; } if (t instanceof RuntimeException) { throw (RuntimeException) t; } throw new RuntimeException(t); } } private boolean commitInternal( LockID lockID, ClientTransaction currentTransaction, boolean isWaitContext) { Assert.assertNotNull("transaction", currentTransaction); try { disableTransactionLogging(); // If the current transactionContext is READ_ONLY, there is no need to commit. TransactionContext tc = peekContext(lockID); if (tc.getType().equals(TxnType.READ_ONLY)) { txMonitor.committedReadTransaction(); return false; } boolean hasPendingCreateObjects = objectManager.hasPendingCreateObjects(); if (hasPendingCreateObjects) { objectManager.addPendingCreateObjectsToTransaction(); } currentTransaction.setAlreadyCommitted(); if (currentTransaction.hasChangesOrNotifies() || hasPendingCreateObjects) { if (txMonitor.isEnabled()) { currentTransaction.updateMBean(txMonitor); } remoteTxManager.commit(currentTransaction); } return true; } finally { enableTransactionLogging(); // always try to unlock even if we are throwing an exception // if (!isWaitContext && !currentTransaction.isNull()) { // lockManager.unlock(currentTransaction.getLockID()); // } if (!isWaitContext && !currentTransaction.isNull()) { if (lockID != null && !lockID.isNull()) { lockManager.unlock(lockID); } else { throw new AssertionError("Trying to unlock with lockID = null!"); } } } } private void basicApply(Collection objectChanges, Map newRoots, boolean force) throws DNAException { List l = new LinkedList(); for (Iterator i = objectChanges.iterator(); i.hasNext(); ) { DNA dna = (DNA) i.next(); TCObject tcobj = null; Assert.assertTrue(dna.isDelta()); try { // This is a major hack to prevent distributed method calls // sent to apps that don't have the right classes from dying // This should be fixed in a better way some day :-) objectManager.getClassFor( Namespace.parseClassNameIfNecessary(dna.getTypeName()), dna.getDefiningLoaderDescription()); tcobj = objectManager.lookup(dna.getObjectID()); } catch (ClassNotFoundException cnfe) { logger.warn("Could not apply change because class not local:" + dna.getTypeName()); continue; } // Important to have a hard reference to the object while we apply // changes so that it doesn't get gc'd on us Object obj = tcobj == null ? null : tcobj.getPeerObject(); l.add(obj); if (obj != null) { try { tcobj.hydrate(dna, force); } catch (ClassNotFoundException cnfe) { logger.warn("Could not apply change because class not local:" + cnfe.getMessage()); throw new TCClassNotFoundException(cnfe); } } } for (Iterator i = newRoots.entrySet().iterator(); i.hasNext(); ) { Entry entry = (Entry) i.next(); String rootName = (String) entry.getKey(); ObjectID newRootID = (ObjectID) entry.getValue(); objectManager.replaceRootIDIfNecessary(rootName, newRootID); } } public void receivedAcknowledgement(SessionID sessionID, TransactionID transactionID) { this.remoteTxManager.receivedAcknowledgement(sessionID, transactionID); } public void receivedBatchAcknowledgement(TxnBatchID batchID) { this.remoteTxManager.receivedBatchAcknowledgement(batchID); } public void apply( TxnType txType, LockID[] lockIDs, Collection objectChanges, Set lookupObjectIDs, Map newRoots) { // beginNull(TxnType.NORMAL); try { disableTransactionLogging(); basicApply(objectChanges, newRoots, false); } finally { // removeTopTransaction(); enableTransactionLogging(); } } // private void removeTopTransaction() { // this.getThreadTransactionContext().popCurrentTransaction(); // } // // private void beginNull(TxnType txType) { // this.beginNull(LockID.NULL_ID, txType); // } // private void beginNull(LockID lockID, TxnType type) { // ClientTransaction current = getTransactionOrNull(); // this.pushTxContext(lockID, type); // if (current == null) { // current = txFactory.newNullInstance(lockID, type); // setTransaction(current); // } // } public void literalValueChanged(TCObject source, Object newValue, Object oldValue) { if (isTransactionLoggingDisabled()) { return; } try { disableTransactionLogging(); Object pojo = source.getPeerObject(); ClientTransaction tx = getTransaction(pojo); tx.literalValueChanged(source, newValue, oldValue); } finally { enableTransactionLogging(); } } public void fieldChanged( TCObject source, String classname, String fieldname, Object newValue, int index) { if (isTransactionLoggingDisabled()) { return; } try { disableTransactionLogging(); Object pojo = source.getPeerObject(); ClientTransaction tx = getTransaction(pojo); logFieldChanged0(source, classname, fieldname, newValue, tx); if (newValue != null && literalValues.isLiteralInstance(newValue)) { tx.fieldChanged(source, classname, fieldname, newValue, index); } else { if (newValue != null) { objectManager.checkPortabilityOfField(newValue, fieldname, pojo); } TCObject tco = objectManager.lookupOrCreate(newValue); tx.fieldChanged(source, classname, fieldname, tco.getObjectID(), index); // record the reference in this transaction -- This is to solve the race condition of // transactions // that reference objects newly "created" in other transactions that may not commit before // us if (newValue != null) { tx.createObject(tco); } } } finally { enableTransactionLogging(); } } public void arrayChanged(TCObject source, int startPos, Object array, int length) { if (isTransactionLoggingDisabled()) { return; } try { disableTransactionLogging(); Object pojo = source.getPeerObject(); ClientTransaction tx = getTransaction(pojo); if (!ClassUtils.isPrimitiveArray(array)) { Object[] objArray = (Object[]) array; for (int i = 0; i < length; i++) { Object element = objArray[i]; if (!literalValues.isLiteralInstance(element)) { if (element != null) objectManager.checkPortabilityOfField(element, String.valueOf(i), pojo); TCObject tco = objectManager.lookupOrCreate(element); objArray[i] = tco.getObjectID(); // record the reference in this transaction -- This is to solve the race condition of // transactions // that reference objects newly "created" in other transactions that may not commit // before us if (element != null) tx.createObject(tco); } } } tx.arrayChanged(source, startPos, array, length); } finally { enableTransactionLogging(); } } private void logFieldChanged0( TCObject source, String classname, String fieldname, Object newValue, ClientTransaction tx) { if (logger.isDebugEnabled()) logger.debug( "fieldChanged(source=" + source + ", classname=" + classname + ", fieldname=" + fieldname + ", newValue=" + newValue + ", tx=" + tx); } public void logicalInvoke(TCObject source, int method, String methodName, Object[] parameters) { if (isTransactionLoggingDisabled()) { return; } try { disableTransactionLogging(); Object pojo = source.getPeerObject(); ClientTransaction tx = getTransaction(pojo); for (int i = 0; i < parameters.length; i++) { Object p = parameters[i]; boolean isLiteral = literalValues.isLiteralInstance(p); if (!isLiteral) { if (p != null) { objectManager.checkPortabilityOfLogicalAction(parameters, i, methodName, pojo); } TCObject tco = objectManager.lookupOrCreate(p); parameters[i] = tco.getObjectID(); if (p != null) { // record the reference in this transaction -- This is to solve the race condition of // transactions // that reference objects newly "created" in other transactions that may not commit // before us tx.createObject(tco); } } } tx.logicalInvoke(source, method, parameters, methodName); } finally { enableTransactionLogging(); } } private void setTransaction(ClientTransaction tx) { getThreadTransactionContext().setCurrentTransaction(tx); } public void createObject(TCObject source) { getTransaction().createObject(source); } public void createRoot(String name, ObjectID rootID) { getTransaction().createRoot(name, rootID); } public void addReference(TCObject tco) { ClientTransaction txn = getTransactionOrNull(); if (txn != null) { txn.createObject(tco); } } public ChannelIDProvider getChannelIDProvider() { return cidProvider; } public void disableTransactionLogging() { ThreadTransactionLoggingStack txnStack = (ThreadTransactionLoggingStack) txnLogging.get(); txnStack.increament(); } public void enableTransactionLogging() { ThreadTransactionLoggingStack txnStack = (ThreadTransactionLoggingStack) txnLogging.get(); final int size = txnStack.decrement(); Assert.assertTrue("size=" + size, size >= 0); } public boolean isTransactionLoggingDisabled() { ThreadTransactionLoggingStack txnStack = (ThreadTransactionLoggingStack) txnLogging.get(); return (txnStack.get() > 0); } public static class ThreadTransactionLoggingStack { int callCount = 0; int increament() { return ++callCount; } int decrement() { return --callCount; } int get() { return callCount; } } public void addDmiDescriptor(DmiDescriptor dd) { getTransaction().addDmiDescritor(dd); } }
/** * Nothing particularly fancy, just a common place that defines the socket options we set for our * connections */ class SocketParams { private static final TCLogger logger = TCLogging.getLogger(SocketParams.class); private static final String PREFIX = "net.core"; private static final String RECV_BUFFER = "recv.buffer"; private static final String SEND_BUFFER = "send.buffer"; private static final String TCP_NO_DELAY = "tcpnodelay"; private static final String KEEP_ALIVE = "keepalive"; private final int recvBuffer; private final int sendBuffer; private final boolean tcpNoDelay; private final boolean keepAlive; SocketParams() { TCProperties props = TCPropertiesImpl.getProperties().getPropertiesFor(PREFIX); this.recvBuffer = props.getInt(RECV_BUFFER, -1); this.sendBuffer = props.getInt(SEND_BUFFER, -1); this.keepAlive = props.getBoolean(KEEP_ALIVE); this.tcpNoDelay = props.getBoolean(TCP_NO_DELAY); } void applySocketParams(Socket s) { if (sendBuffer > 0) { try { s.setSendBufferSize(sendBuffer); } catch (SocketException e) { logger.error("error setting sendBuffer to " + sendBuffer, e); } } if (recvBuffer > 0) { try { s.setReceiveBufferSize(recvBuffer); } catch (SocketException e) { logger.error("error setting recvBuffer to " + recvBuffer, e); } } try { s.setTcpNoDelay(tcpNoDelay); } catch (SocketException e) { logger.error("error setting TcpNoDelay to " + tcpNoDelay, e); } try { s.setKeepAlive(keepAlive); } catch (SocketException e) { logger.error("error setting KeepAlive to " + keepAlive, e); } } void applyServerSocketParams(ServerSocket s, boolean reuseAddress) { try { s.setReuseAddress(reuseAddress); } catch (SocketException e) { logger.error("error setting recvBuffer to " + recvBuffer, e); } if (recvBuffer > 0) { try { s.setReceiveBufferSize(recvBuffer); } catch (SocketException e) { logger.error("error setting recvBuffer to " + recvBuffer, e); } } } }
public class TerracottaEntityRef<T extends Entity, C> implements EntityRef<T, C> { private final TCLogger logger = TCLogging.getLogger(TerracottaEntityRef.class); private final ClientEntityManager entityManager; private final Class<T> type; private final long version; private final String name; private final EntityClientService<T, C, ? extends EntityMessage, ? extends EntityResponse> entityClientService; // Each instance fetched by this ref can be individually addressed by the server so it needs a // unique ID. private final AtomicLong nextClientInstanceID; public TerracottaEntityRef( ClientEntityManager entityManager, Class<T> type, long version, String name, EntityClientService<T, C, ? extends EntityMessage, ? extends EntityResponse> entityClientService, AtomicLong clientIds) { this.entityManager = entityManager; this.type = type; this.version = version; this.name = name; this.entityClientService = entityClientService; this.nextClientInstanceID = clientIds; } @Override public synchronized T fetchEntity() throws EntityNotFoundException, EntityVersionMismatchException { EntityClientEndpoint endpoint = null; try { ClientInstanceID clientInstanceID = new ClientInstanceID(this.nextClientInstanceID.getAndIncrement()); EntityDescriptor entityDescriptor = new EntityDescriptor(getEntityID(), clientInstanceID, this.version); endpoint = this.entityManager.fetchEntity( entityDescriptor, entityClientService.getMessageCodec(), null); } catch (EntityException e) { // In this case, we want to close the endpoint but still throw back the exception. // Note that we must externally only present the specific exception types we were expecting. // Thus, we need to check // that this is one of those supported types, asserting that there was an unexpected wire // inconsistency, otherwise. if (e instanceof EntityNotFoundException) { throw (EntityNotFoundException) e; } else if (e instanceof EntityVersionMismatchException) { throw (EntityVersionMismatchException) e; } else { throw Assert.failure("Unsupported exception type returned to fetch", e); } } catch (final Throwable t) { Util.printLogAndRethrowError(t, logger); } // Note that a failure to resolve the endpoint would have thrown so this can't be null. if (endpoint == null) { Assert.assertNotNull(endpoint); } return (T) entityClientService.create(endpoint); } @Override public String getName() { return name; } private EntityID getEntityID() { return new EntityID(type.getName(), name); } @Override public void create(C configuration) throws EntityNotProvidedException, EntityAlreadyExistsException, EntityVersionMismatchException { EntityID entityID = getEntityID(); try { this.entityManager .createEntity( entityID, this.version, entityClientService.serializeConfiguration(configuration)) .get(); } catch (EntityException e) { // Note that we must externally only present the specific exception types we were expecting. // Thus, we need to check // that this is one of those supported types, asserting that there was an unexpected wire // inconsistency, otherwise. e = ExceptionUtils.addLocalStackTraceToEntityException(e); if (e instanceof EntityNotProvidedException) { throw (EntityNotProvidedException) e; } else if (e instanceof EntityAlreadyExistsException) { throw (EntityAlreadyExistsException) e; } else if (e instanceof EntityVersionMismatchException) { throw (EntityVersionMismatchException) e; } else { // WARNING: Assert.failure returns an exception, instead of throwing one. throw Assert.failure("Unsupported exception type returned to create", e); } } catch (InterruptedException e) { // We don't expect an interruption here. throw new RuntimeException(e); } } @Override public C reconfigure(C configuration) throws EntityException { EntityID entityID = getEntityID(); try { return entityClientService.deserializeConfiguration( this.entityManager .reconfigureEntity( entityID, this.version, entityClientService.serializeConfiguration(configuration)) .get()); } catch (EntityException e) { throw ExceptionUtils.addLocalStackTraceToEntityException(e); } catch (InterruptedException e) { // We don't expect an interruption here. throw new RuntimeException(e); } } @Override public boolean destroy() throws EntityNotFoundException { try { return destroyEntity(); } catch (InterruptedException ie) { return false; } } private boolean destroyEntity() throws EntityNotFoundException, InterruptedException { EntityID entityID = getEntityID(); InvokeFuture<byte[]> future = this.entityManager.destroyEntity(entityID, this.version); boolean success = false; try { future.get(); success = true; } catch (EntityException e) { // Note that we must externally only present the specific exception types we were expecting. // Thus, we need to check // that this is one of those supported types, asserting that there was an unexpected wire // inconsistency, otherwise. if (e instanceof EntityNotFoundException) { throw (EntityNotFoundException) e; } else if (e instanceof EntityReferencedException) { success = false; } } return success; } }