/** {@inheritDoc} */
  @Override
  public void addAttributeListener(GridTaskSessionAttributeListener lsnr, boolean rewind) {
    A.notNull(lsnr, "lsnr");

    Map<Object, Object> attrs = null;

    List<GridTaskSessionAttributeListener> lsnrs;

    synchronized (mux) {
      lsnrs = new ArrayList<GridTaskSessionAttributeListener>(this.lsnrs.size());

      lsnrs.addAll(this.lsnrs);

      lsnrs.add(lsnr);

      lsnrs = Collections.unmodifiableList(lsnrs);

      this.lsnrs = lsnrs;

      if (rewind) attrs = new HashMap<Object, Object>(this.attrs);
    }

    if (rewind) {
      for (Map.Entry<Object, Object> entry : attrs.entrySet()) {
        for (GridTaskSessionAttributeListener l : lsnrs) {
          l.onAttributeSet(entry.getKey(), entry.getValue());
        }
      }
    }
  }
  /** {@inheritDoc} */
  @Override
  public boolean waitForAttributes(Map<?, ?> attrs, long timeout) throws InterruptedException {
    A.notNull(attrs, "attrs");

    if (attrs.isEmpty()) {
      return true;
    }

    if (timeout == 0) timeout = Long.MAX_VALUE;

    long now = System.currentTimeMillis();

    // Prevent overflow.
    long end = now + timeout < 0 ? Long.MAX_VALUE : now + timeout;

    // Don't wait longer than session timeout.
    if (end > endTime) end = endTime;

    synchronized (mux) {
      boolean isFound = false;

      while (!closed
          && !(isFound = this.attrs.entrySet().containsAll(attrs.entrySet()))
          && now < end) {
        mux.wait(end - now);

        now = System.currentTimeMillis();
      }

      if (closed) throw new InterruptedException("Session was closed: " + this);

      return isFound;
    }
  }
  /** @param attrs Attributes to set. */
  public void setInternal(Map<?, ?> attrs) {
    A.notNull(attrs, "attrs");

    if (attrs.isEmpty()) return;

    List<GridTaskSessionAttributeListener> lsnrs;

    synchronized (mux) {
      this.attrs.putAll(attrs);

      lsnrs = this.lsnrs;

      mux.notifyAll();
    }

    for (Map.Entry<?, ?> entry : attrs.entrySet())
      for (GridTaskSessionAttributeListener lsnr : lsnrs)
        lsnr.onAttributeSet(entry.getKey(), entry.getValue());
  }
  /** {@inheritDoc} */
  @Override
  public GridFuture<?> addData(Map<K, V> entries) throws IllegalStateException {
    A.notNull(entries, "entries");

    return addData(entries.entrySet());
  }