/**
   * Sends a message.
   *
   * <p>Current thread is NOT blocked by this method call. But no two response-actions (onReceive or
   * onFail on any request) or response protocols will be executed at same time, so you can write
   * not thread-safe code inside them.
   *
   * <p>
   *
   * <p>This gets being very similar to automaton programming :)
   *
   * @param address receiver of message
   * @param message mail entry
   * @param type way of sending a message: TCP, single UPD...
   * @param timeout timeout in milliseconds
   * @param receiveListener an action to invoke when got an answer.
   * @param failListener an action to invoke when timeout exceeded.
   * @param <ReplyType> response message type
   */
  public <ReplyType extends ResponseMessage> void send(
      InetSocketAddress address,
      RequestMessage<ReplyType> message,
      DispatchType type,
      int timeout,
      ReceiveListener<ReplyType> receiveListener,
      FailListener failListener) {
    BlockingQueue<ResponseMessage> responseContainer = submit(address, message, type);

    // TODO: make in single thread
    scheduledExecutor.schedule(
        () -> {
          //noinspection unchecked
          ReplyType response = (ReplyType) responseContainer.poll();
          if (response != null) receiveListener.onReceive(address, response);
          else failListener.onFail(address);
        },
        timeout,
        TimeUnit.MILLISECONDS);
    // TODO: clear responseWaiters map
  }
 @Override
 public void close() throws IOException {
   executor.shutdownNow();
   scheduledExecutor.shutdownNow();
 }
 /**
  * Sends message to itself in specified delay.
  *
  * <p>Used to schedule some tasks and execute them sequentially with other response-actions.
  * Executed action must be specified as response protocol.
  *
  * @param message reminder message
  * @param delay when to send a mention
  */
 public void remind(ReminderMessage message, int delay) {
   Runnable remind =
       () -> send(null, message, DispatchType.LOOPBACK, 10000, (addr, response) -> {}, addr -> {});
   scheduledExecutor.schedule(remind, delay, TimeUnit.MILLISECONDS);
 }