public <T> T retryIfInterrupted(final @NotNull FileChannelIdempotentOperation<T> operation) throws IOException {
    boolean interruptedStatusWasCleared = false;
    try {
      for (int attempt = 0; ; attempt++) {
        final FileChannel channelLocalCopy = channel;
        if (channelLocalCopy == null && attempt == 0) {
          //we haven't tried yet -> just reject
          throw new ClosedChannelException();
        }
        try {
          if (channelLocalCopy == null && attempt >= 0) {
            //we have tried already, and failed, so now we can't just reject, since previous unsuccessful
            // attempts may corrupt the data -> throw CCException (which will be caught and re-tried)
            throw new ClosedChannelException();
          }
          return operation.execute(channelLocalCopy);
        }
        catch (ClosedChannelException e) {
          totalRetriedAttempts.incrementAndGet();

          if (attempt >= MAX_RETRIES) {
            IOException ioe = new IOException(
              "Channel[" + path + "][@" + System.identityHashCode(channelLocalCopy) + "] " +
              "is interrupted/closed in the middle of operation " + MAX_RETRIES + " times in the row: surrender");
            ioe.addSuppressed(e);
            throw ioe;
          }
          //TODO RC: this catches _all_ close causes, not only thread interruptions!
          //         (for the latter only ClosedByInterruptException should be caught)
          //         ...Actually, catching all ClosedXXXExceptions make sense for the primary
          //         purpose of the class -- avoid corrupted IO-ops by making them 'atomic'
          //         (kind-of) i.e. all-or-nothing. From the PoV of keeping data structures
          //         in a consistent state -- async .close() is the same kind of danger as
          //         Thread.interrupt(): both could interrupt IO-op mid-flight giving corrupted
          //         result or leaving data structure in a corrupted state. Hence it is pretty
          //         reasonable to work around both issues same way.
          //         There are 2 issues here, though:
          //         1) That behavior is inconsistent with that class javadocs (and even class
          //         name) promises -- docs promise the class deals with Thread.interrupt(),
          //         while really it does more than that, which could come as surprise for
          //         somebody naive enough to trust the docs.
          //         2) Class currently handles async .close() in an inconsistent way: inconsistent
          //         with how .interrupt() is handled, and internally-inconsistent. I.e. async .close()
          //         could make Retryer 'closed' forever -- or could be silently ignored, depending
          //         on exact timing of the calls.
          //         This is not how .interrupt() is handled: interrupt()-ed IO-op is repeated until
          //         succeed, but Thread.interrupted status is remembered, and restored after op is
          //         succeed -- so interrupt() is not 'swallowed' silently, it is just 'postponed'
          //         until op currently in-flight finishes.
          //         But for async .close() it is different: if 'closed' status observed _before_
          //         IOps started -> ChannelClosedException is thrown, and Retryer remains closed
          //         forever, all following IOps will fail with the same exception. But if closed
          //         status observed in the middle of an operation -> channel is reopened, and
          //         closed status just silently disappears. This is hardly the 'least surprise'
          //         behavior, so must be fixed: either we handle async .close() same ways as
          //         .interrupt() -- i.e. postpone actual closing until current IOps finishes,
          //         but restore the 'closed' status afterwards -- or we explicitly state in the
          //         docs that 'closed' state is reversible, and remove 'if (!isOpen())' branch
          //         from the method start.
          //         (See @Ignored test ResilientFileChannel_MultiThreaded_Test.onceClosed_FileChannelInterruptsRetryer_RemainsClosedForever
          //         which fails now)


          boolean logStackTrace = LOG_STACKTRACE_IF_RETRY_CHAIN_LONGER > 0
                                  && (attempt % LOG_STACKTRACE_IF_RETRY_CHAIN_LONGER == (LOG_STACKTRACE_IF_RETRY_CHAIN_LONGER - 1));
          if (logStackTrace) {
            LOG.warn("Channel[" + path + "][@" + System.identityHashCode(channelLocalCopy) + "] is closed during " + operation
                     + " " + LOG_STACKTRACE_IF_RETRY_CHAIN_LONGER + " times in a row -- suspicious, log stacktrace", e);
          }
          else {
            LOG.warn("Channel[" + path + "][@" + System.identityHashCode(channelLocalCopy) + "] is closed during " + operation
                     + " => trying to reopen it again. Reason: " + e);
          }
          if (Thread.currentThread().isInterrupted()) {
            Thread.interrupted();//must clear interrupted status, otherwise newly opened channel throws ClosedByInterrupt again
            interruptedStatusWasCleared = true;
          }
          //TODO RC: should we remember 'close' status also, and apply it after the loop?

          reopenChannel();
        }
      }
    }
    finally {
      if (interruptedStatusWasCleared) {
        Thread.currentThread().interrupt();
      }
    }
  }
public <T> T retryIfInterrupted(final @NotNull FileChannelIdempotentOperation<T> operation)