int decRefCnt() {
   int refcnt = refcntUpdater.decrementAndGet(this);
   if (refcnt < 0) {
     throw new RuntimeException("refcnt:" + refcnt);
   }
   return refcnt;
 }
    void subscribeNext() {
      Observable<? extends T> t;
      synchronized (guard) {
        t = queue.peek();
        if (t == null || active >= maxConcurrency) {
          return;
        }
        active++;
        queue.poll();
      }

      MergeItemSubscriber itemSub = new MergeItemSubscriber(SOURCE_INDEX.getAndIncrement(this));
      subscribers.add(itemSub);

      csub.add(itemSub);

      WIP.incrementAndGet(this);

      t.unsafeSubscribe(itemSub);

      request(1);
    }
 int incRefCnt() {
   return refcntUpdater.incrementAndGet(this);
 }
 boolean casLen(int cmp, int val) {
   return lenUpdater.compareAndSet(this, cmp, val);
 }
  static class Node {
    static ConcurrentSoftQueue<Node> pool = new ConcurrentSoftQueue<Node>();

    static Node alloc() {
      final int threshold = 2;
      int tryCnt = 0;
      while (true) {
        tryCnt++;
        Node n = pool.poll();
        if (n == null) {
          return new Node();
        } else {
          if (n.getRefCnt() > 0) {
            pool.add(n);
            if (tryCnt <= threshold) continue;
            else return new Node();
          } else {
            if (n.next != null) n.next.decRefCnt();
            n.init();
            return n;
          }
        }
      }
    }

    static void free(Node n) {
      pool.add(n);
    }

    private static void nodeCopy(
        double[] srcKeys,
        Object[] srcVals,
        int srcBegin,
        double[] targetKeys,
        Object[] targetVals,
        int targetBegin,
        int copyLen) {
      System.arraycopy(srcKeys, srcBegin, targetKeys, targetBegin, copyLen);
      System.arraycopy(srcVals, srcBegin, targetVals, targetBegin, copyLen);
    }

    static Node safeNext(Node b) {
      while (true) {
        Node n = b.next;
        if (n == null) return null;
        n.incRefCnt();
        if (n == b.next) return n;
        else release(n);
      }
    }

    static void release(Node n) {
      n.decRefCnt();
    }

    volatile int refcnt;
    volatile int length;
    final double[] keys;
    final Object[] vals;
    volatile Node next;

    Node() {
      this(CHUNK_SIZE);
    }

    Node(int capacity) {
      keys = new double[capacity];
      vals = new Object[capacity];
      refcnt = 1;
      next = null;
    }

    void init() {
      incRefCnt();
      length = 0;
      next = null;
      clearEntry();
    }

    public void clearEntry() {
      Arrays.fill(vals, null);
    }

    public String toString() {
      String str =
          "Node(refcnt:" + refcnt + ", isMarked:" + isMarked() + ", length:" + length + ")[";
      for (int i = 0; i < len(); i++) {
        str += keys[i] + " ";
      }
      str += "]";
      return str;
    }

    void _print() {
      System.out.print("Node(refcnt:" + refcnt + "," + length + "/" + keys.length + ")[");
      for (int i = 0; i < length; i++) {
        // System.out.print(keys[i]+":"+vals[i]+" ");
        System.out.print(keys[i] + " ");
      }
      System.out.println("]");
    }

    static final AtomicReferenceFieldUpdater<Node, Node> nextUpdater =
        AtomicReferenceFieldUpdater.newUpdater(Node.class, Node.class, "next");
    static final AtomicIntegerFieldUpdater<Node> lenUpdater =
        AtomicIntegerFieldUpdater.newUpdater(Node.class, "length");
    static final AtomicIntegerFieldUpdater<Node> refcntUpdater =
        AtomicIntegerFieldUpdater.newUpdater(Node.class, "refcnt");

    boolean casNext(Node cmp, Node val) {
      boolean success = nextUpdater.compareAndSet(this, cmp, val);
      if (!success) {
        System.err.println("setting next failed");
      }
      return success;
    }

    boolean casLen(int cmp, int val) {
      return lenUpdater.compareAndSet(this, cmp, val);
    }

    int incRefCnt() {
      return refcntUpdater.incrementAndGet(this);
    }

    int decRefCnt() {
      int refcnt = refcntUpdater.decrementAndGet(this);
      if (refcnt < 0) {
        throw new RuntimeException("refcnt:" + refcnt);
      }
      return refcnt;
    }

    int getRefCnt() {
      return refcnt;
    }

    boolean isFull() {
      return len() == keys.length;
    }

    int len() {
      int len = length;
      return (len >= 0) ? len : -len;
    }

    boolean mark() {
      int len = length;
      if (len < 0) return false;
      return casLen(len, -len);
    }

    boolean isMarked() {
      return length < 0;
    }

    double first() {
      assert len() != 0;
      return keys[0];
    }

    double last() {
      assert len() != 0;
      return keys[len() - 1];
    }

    boolean contains(double key) {
      return Arrays.binarySearch(keys, 0, len(), key) >= 0;
    }

    int findKeyIndex(double key) {
      return Arrays.binarySearch(keys, 0, len(), key);
    }

    Object get(double key) {
      if (len() == 0) return null;
      int pos;
      if (key == keys[0]) pos = 0;
      else pos = Arrays.binarySearch(keys, 0, len(), key);
      if (pos < 0) return null;
      return vals[pos];
    }

    Object replace(double key, Object expect, Object value) {
      synchronized (this) {
        if (isMarked()) return Retry;

        int len = len();
        int pos = Arrays.binarySearch(keys, 0, len, key);
        if (pos < 0) return null;
        Object old = vals[pos];
        if (expect == null) {
          vals[pos] = value;
          return old;
        } else if (expect.equals(old)) {
          vals[pos] = value;
          return old;
        } else {
          return null;
        }
      }
    }

    // no concurrent read/write access is assumed
    Object put(double key, Object value, ConcurrentDoubleOrderedListMap orderedMap) {
      int len = len();
      int pos = Arrays.binarySearch(keys, 0, len, key);
      if (pos >= 0) {
        Object old = vals[pos];
        vals[pos] = value;
        return old;
      } else {
        pos = -(pos + 1);
        putReally(pos, key, value, orderedMap);
        return null;
      }
    }

    private boolean emptySlotInNextTwo() {
      if (next == null) return false;
      if (!next.isFull()) return true;
      if (next.next == null) return false;
      if (!next.next.isFull()) return true;
      return false;
    }

    // no concurrent read/write access is assumed
    private void putReally(
        int pos, double key, Object value, ConcurrentDoubleOrderedListMap orderedMap) {
      int len = len();
      if (len + 1 <= keys.length) { // inserted in the current node
        nodeCopy(keys, vals, pos, keys, vals, pos + 1, len - pos);
        keys[pos] = key;
        vals[pos] = value;
        length = len + 1;
        if (pos == 0) {
          orderedMap.skipListMap.put(keys[0], this);
          if (len != 0) orderedMap.skipListMap.remove(keys[1], this);
        }
      } else if (emptySlotInNextTwo()) {
        if (pos == len) {
          next.put(key, value, orderedMap);
          return;
        }
        next.put(keys[len - 1], vals[len - 1], orderedMap);
        nodeCopy(keys, vals, pos, keys, vals, pos + 1, len - pos - 1);
        keys[pos] = key;
        vals[pos] = value;
        if (pos == 0) {
          orderedMap.skipListMap.remove(keys[1], this);
          orderedMap.skipListMap.put(keys[0], this);
        }
      } else { // current node is full, so requires a new node
        Node n = Node.alloc();
        double[] nkeys = n.keys;
        Object[] nvals = n.vals;
        int l1 = len / 2, l2 = len - l1;
        if (next == null && pos == len) { // this is the last node, simply add to the new node.
          nkeys[0] = key;
          nvals[0] = value;
          n.length = 1;
          orderedMap.skipListMap.put(nkeys[0], n);
        } else if (pos < l1) { // key,value is stored in the current node
          length = l1 + 1;
          n.length = l2;
          nodeCopy(keys, vals, l1, nkeys, nvals, 0, l2);

          nodeCopy(keys, vals, pos, keys, vals, pos + 1, l1 - pos);
          keys[pos] = key;
          vals[pos] = value;
          if (pos == 0) {
            orderedMap.skipListMap.remove(keys[1]);
            orderedMap.skipListMap.put(keys[0], this);
          }
          orderedMap.skipListMap.put(nkeys[0], n);
        } else { // key,value is stored in the new node
          length = l1;
          n.length = l2 + 1;
          int newpos = pos - l1;

          nodeCopy(keys, vals, l1, nkeys, nvals, 0, newpos);
          nkeys[newpos] = key;
          nvals[newpos] = value;
          nodeCopy(keys, vals, pos, nkeys, nvals, newpos + 1, l2 - newpos);

          orderedMap.skipListMap.put(nkeys[0], n);
        }
        n.next = this.next;
        this.next = n;
      }
    }

    // concurrent read/write access is allowed
    boolean appendNewAtomic(double key, Object value, ConcurrentDoubleOrderedListMap orderedMap) {
      synchronized (this) {
        if (isMarked()) return false;
        if (next != null) return false;

        Node n = Node.alloc();
        n.put(key, value, orderedMap);
        // assert n.len()==1;
        n.next = null;
        boolean success = casNext(null, n);
        assert success;
        return true;
      }
    }
    // concurrent read/write access is allowed
    Object putAtomic(
        double key,
        Object value,
        Node b,
        boolean onlyIfAbsent,
        ConcurrentDoubleOrderedListMap orderedMap) {
      synchronized (b) {
        if (b.isMarked()) return Retry;
        synchronized (this) {
          if (isMarked()) return Retry;

          int len = len();
          int pos = Arrays.binarySearch(keys, 0, len, key);
          if (pos >= 0) {
            Object old = vals[pos];
            if (onlyIfAbsent) {
              if (old == null) {
                vals[pos] = value;
                return null;
              } else {
                return old;
              }
            }
            vals[pos] = value;
            return old;
          }
          pos = -(pos + 1);
          putAtomicReally(b, pos, key, value, orderedMap);
          return null;
        }
      }
    }

    // only used by putAtomic and PutAtomicIfAbsent. Inside synchronized(b) and synchronized(this).
    private void putAtomicReally(
        Node b, int pos, double key, Object value, ConcurrentDoubleOrderedListMap orderedMap) {
      int len = len();
      if (len + 1 <= keys.length) {
        if (pos == len) { // in-place append in the current node
          keys[pos] = key;
          vals[pos] = value;
          length = len + 1;
          if (pos == 0) {
            orderedMap.skipListMap.put(keys[0], this);
          }
        } else { // copied to a new node, replacing the current node
          mark();
          Node n = Node.alloc();
          n.next = this.next;
          if (next != null) next.incRefCnt();
          double[] nkeys = n.keys;
          Object[] nvals = n.vals;
          n.length = len + 1;
          nodeCopy(keys, vals, 0, nkeys, nvals, 0, pos);
          nkeys[pos] = key;
          nvals[pos] = value;
          nodeCopy(keys, vals, pos, nkeys, nvals, pos + 1, len - pos);

          orderedMap.skipListMap.put(nkeys[0], n);
          b.casNext(this, n); // should always succeed.
          if (pos == 0) {
            orderedMap.skipListMap.remove(keys[0], this);
          }
          release(this);
          free(this);
        }
      } else { // requires 2 new nodes, to replace the current node
        mark();
        Node n1 = Node.alloc();
        double[] n1keys = n1.keys;
        Object[] n1vals = n1.vals;
        Node n2 = Node.alloc();
        double[] n2keys = n2.keys;
        Object[] n2vals = n2.vals;
        int l1 = len / 2, l2 = len - l1;
        if (pos < l1) { // key, value stored in n1
          n1.length = l1 + 1;
          n2.length = l2;

          nodeCopy(keys, vals, 0, n1keys, n1vals, 0, pos);
          n1keys[pos] = key;
          n1vals[pos] = value;
          nodeCopy(keys, vals, pos, n1keys, n1vals, pos + 1, l1 - pos);
          nodeCopy(keys, vals, l1, n2keys, n2vals, 0, l2);

          n1.next = n2;
          n2.next = this.next;
          if (next != null) next.incRefCnt();

          orderedMap.skipListMap.put(n1keys[0], n1);
          orderedMap.skipListMap.put(n2keys[0], n2);
          b.casNext(this, n1); // should always succeed.
          if (pos == 0) {
            orderedMap.skipListMap.remove(keys[0], this);
          }
          release(this);
          free(this);
        } else { // key,value is stored in n2
          n1.length = l1;
          n2.length = l2 + 1;
          int newpos = pos - l1;

          nodeCopy(keys, vals, 0, n1keys, n1vals, 0, l1);

          nodeCopy(keys, vals, l1, n2keys, n2vals, 0, newpos);
          n2keys[newpos] = key;
          n2vals[newpos] = value;
          nodeCopy(keys, vals, pos, n2keys, n2vals, newpos + 1, l2 - newpos);

          n1.next = n2;
          n2.next = this.next;
          if (next != null) next.incRefCnt();

          orderedMap.skipListMap.put(n1keys[0], n1);
          orderedMap.skipListMap.put(n2keys[0], n2);
          b.casNext(this, n1); // should always succeed.
          release(this);
          free(this);
        }
      }
    }
  }
  static final class SourceSubscriber<T> extends Subscriber<Observable<? extends T>> {
    final NotificationLite<T> nl = NotificationLite.instance();
    final int maxConcurrency;
    final Subscriber<T> s;
    final CompositeSubscription csub;
    final Object guard;

    volatile int wip;

    @SuppressWarnings("rawtypes")
    static final AtomicIntegerFieldUpdater<SourceSubscriber> WIP =
        AtomicIntegerFieldUpdater.newUpdater(SourceSubscriber.class, "wip");

    volatile int sourceIndex;

    @SuppressWarnings("rawtypes")
    static final AtomicIntegerFieldUpdater<SourceSubscriber> SOURCE_INDEX =
        AtomicIntegerFieldUpdater.newUpdater(SourceSubscriber.class, "sourceIndex");

    /** Guarded by guard. */
    int active;
    /** Guarded by guard. */
    final Queue<Observable<? extends T>> queue;

    /** Indicates the emitting phase. Guarded by this. */
    boolean emitting;
    /** Counts the missed emitting calls. Guarded by this. */
    int missedEmitting;
    /** The last buffer index in the round-robin drain scheme. Accessed while emitting == true. */
    int lastIndex;

    /** Guarded by itself. */
    final List<MergeItemSubscriber> subscribers;

    volatile long requested;

    @SuppressWarnings("rawtypes")
    static final AtomicLongFieldUpdater<SourceSubscriber> REQUESTED =
        AtomicLongFieldUpdater.newUpdater(SourceSubscriber.class, "requested");

    public SourceSubscriber(int maxConcurrency, Subscriber<T> s, CompositeSubscription csub) {
      super(s);
      this.maxConcurrency = maxConcurrency;
      this.s = s;
      this.csub = csub;
      this.guard = new Object();
      this.queue = new ArrayDeque<Observable<? extends T>>(maxConcurrency);
      this.subscribers = Collections.synchronizedList(new ArrayList<MergeItemSubscriber>());
      this.wip = 1;
    }

    @Override
    public void onStart() {
      request(maxConcurrency);
    }

    @Override
    public void onNext(Observable<? extends T> t) {
      synchronized (guard) {
        queue.add(t);
      }
      subscribeNext();
    }

    void subscribeNext() {
      Observable<? extends T> t;
      synchronized (guard) {
        t = queue.peek();
        if (t == null || active >= maxConcurrency) {
          return;
        }
        active++;
        queue.poll();
      }

      MergeItemSubscriber itemSub = new MergeItemSubscriber(SOURCE_INDEX.getAndIncrement(this));
      subscribers.add(itemSub);

      csub.add(itemSub);

      WIP.incrementAndGet(this);

      t.unsafeSubscribe(itemSub);

      request(1);
    }

    @Override
    public void onError(Throwable e) {
      Object[] active;
      synchronized (subscribers) {
        active = subscribers.toArray();
        subscribers.clear();
      }

      try {
        s.onError(e);

        unsubscribe();
      } finally {
        for (Object o : active) {
          @SuppressWarnings("unchecked")
          MergeItemSubscriber a = (MergeItemSubscriber) o;
          a.release();
        }
      }
    }

    @Override
    public void onCompleted() {
      WIP.decrementAndGet(this);
      drain();
    }

    protected void downstreamRequest(long n) {
      for (; ; ) {
        long r = requested;
        long u;
        if (r != Long.MAX_VALUE && n == Long.MAX_VALUE) {
          u = Long.MAX_VALUE;
        } else if (r + n < 0) {
          u = Long.MAX_VALUE;
        } else {
          u = r + n;
        }
        if (REQUESTED.compareAndSet(this, r, u)) {
          break;
        }
      }
      drain();
    }

    protected void drain() {
      synchronized (this) {
        if (emitting) {
          missedEmitting++;
          return;
        }
        emitting = true;
        missedEmitting = 0;
      }
      final List<SourceSubscriber<T>.MergeItemSubscriber> subs = subscribers;
      final Subscriber<T> child = s;
      Object[] active = new Object[subs.size()];
      do {
        long r;

        outer:
        while ((r = requested) > 0) {
          int idx = lastIndex;
          synchronized (subs) {
            if (subs.size() == active.length) {
              active = subs.toArray(active);
            } else {
              active = subs.toArray();
            }
          }

          int resumeIndex = 0;
          int j = 0;
          for (Object o : active) {
            @SuppressWarnings("unchecked")
            MergeItemSubscriber e = (MergeItemSubscriber) o;
            if (e.index == idx) {
              resumeIndex = j;
              break;
            }
            j++;
          }
          int sumConsumed = 0;
          for (int i = 0; i < active.length; i++) {
            j = (i + resumeIndex) % active.length;

            @SuppressWarnings("unchecked")
            final MergeItemSubscriber e = (MergeItemSubscriber) active[j];
            final RxRingBuffer b = e.buffer;
            lastIndex = e.index;

            if (!e.once && b.peek() == null) {
              subs.remove(e);

              synchronized (guard) {
                this.active--;
              }
              csub.remove(e);

              e.release();

              subscribeNext();

              WIP.decrementAndGet(this);

              continue outer;
            }

            int consumed = 0;
            Object v;
            while (r > 0 && (v = b.poll()) != null) {
              nl.accept(child, v);
              if (child.isUnsubscribed()) {
                return;
              }
              r--;
              consumed++;
            }
            if (consumed > 0) {
              sumConsumed += consumed;
              REQUESTED.addAndGet(this, -consumed);
              e.requestMore(consumed);
            }
            if (r == 0) {
              break outer;
            }
          }
          if (sumConsumed == 0) {
            break;
          }
        }

        if (active.length == 0) {
          if (wip == 0) {
            child.onCompleted();
            return;
          }
        }
        synchronized (this) {
          if (missedEmitting == 0) {
            emitting = false;
            break;
          }
          missedEmitting = 0;
        }
      } while (true);
    }

    final class MergeItemSubscriber extends Subscriber<T> {
      volatile boolean once = true;
      final int index;
      final RxRingBuffer buffer;

      public MergeItemSubscriber(int index) {
        buffer = RxRingBuffer.getSpmcInstance();
        this.index = index;
      }

      @Override
      public void onStart() {
        request(RxRingBuffer.SIZE);
      }

      @Override
      public void onNext(T t) {
        try {
          buffer.onNext(t);
        } catch (MissingBackpressureException ex) {
          onError(ex);
          return;
        }

        drain();
      }

      @Override
      public void onError(Throwable e) {
        SourceSubscriber.this.onError(e);
      }

      @Override
      public void onCompleted() {
        if (once) {
          once = false;
          drain();
        }
      }
      /** Request more from upstream. */
      void requestMore(long n) {
        request(n);
      }

      void release() {
        // NO-OP for now
        buffer.release();
      }
    }
  }
    protected void drain() {
      synchronized (this) {
        if (emitting) {
          missedEmitting++;
          return;
        }
        emitting = true;
        missedEmitting = 0;
      }
      final List<SourceSubscriber<T>.MergeItemSubscriber> subs = subscribers;
      final Subscriber<T> child = s;
      Object[] active = new Object[subs.size()];
      do {
        long r;

        outer:
        while ((r = requested) > 0) {
          int idx = lastIndex;
          synchronized (subs) {
            if (subs.size() == active.length) {
              active = subs.toArray(active);
            } else {
              active = subs.toArray();
            }
          }

          int resumeIndex = 0;
          int j = 0;
          for (Object o : active) {
            @SuppressWarnings("unchecked")
            MergeItemSubscriber e = (MergeItemSubscriber) o;
            if (e.index == idx) {
              resumeIndex = j;
              break;
            }
            j++;
          }
          int sumConsumed = 0;
          for (int i = 0; i < active.length; i++) {
            j = (i + resumeIndex) % active.length;

            @SuppressWarnings("unchecked")
            final MergeItemSubscriber e = (MergeItemSubscriber) active[j];
            final RxRingBuffer b = e.buffer;
            lastIndex = e.index;

            if (!e.once && b.peek() == null) {
              subs.remove(e);

              synchronized (guard) {
                this.active--;
              }
              csub.remove(e);

              e.release();

              subscribeNext();

              WIP.decrementAndGet(this);

              continue outer;
            }

            int consumed = 0;
            Object v;
            while (r > 0 && (v = b.poll()) != null) {
              nl.accept(child, v);
              if (child.isUnsubscribed()) {
                return;
              }
              r--;
              consumed++;
            }
            if (consumed > 0) {
              sumConsumed += consumed;
              REQUESTED.addAndGet(this, -consumed);
              e.requestMore(consumed);
            }
            if (r == 0) {
              break outer;
            }
          }
          if (sumConsumed == 0) {
            break;
          }
        }

        if (active.length == 0) {
          if (wip == 0) {
            child.onCompleted();
            return;
          }
        }
        synchronized (this) {
          if (missedEmitting == 0) {
            emitting = false;
            break;
          }
          missedEmitting = 0;
        }
      } while (true);
    }
 @Override
 public void onCompleted() {
   WIP.decrementAndGet(this);
   drain();
 }