/**
 * 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();
  }
}
Example #4
0
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
  }
}
Example #8
0
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);
  }
}
Example #11
0
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;
    }
  }
}
Example #16
0
/** @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;
  }
}