All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.intellij.util.io.OpenChannelsCache Maven / Gradle / Ivy

There is a newer version: 2.1.0-Beta1-1.0.25
Show newest version
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.util.io;

import com.intellij.openapi.util.io.FileUtilRt;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;

import java.io.Closeable;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.*;
import java.util.*;

@ApiStatus.Internal
final class OpenChannelsCache { // TODO: Will it make sense to have a background thread, that flushes the cache by timeout?
  private final int myCacheSizeLimit;
  @NotNull
  private final Map myCache;

  private final Object myLock = new Object();

  OpenChannelsCache(final int cacheSizeLimit) {
    myCacheSizeLimit = cacheSizeLimit;
    myCache = new LinkedHashMap<>(cacheSizeLimit, 0.5f, true);
  }

  @FunctionalInterface
  interface ChannelProcessor {
    T process(@NotNull FileChannel channel) throws IOException;
  }

  /**
   * Parameter {@param processor} should be idempotent because sometimes calculation might be restarted
   * when file channel was closed by thread interruption
   */
   T useChannel(@NotNull Path path,
                   @NotNull ChannelProcessor processor,
                   boolean read) throws IOException {
    ChannelDescriptor descriptor;
    synchronized (myLock) {
      descriptor = myCache.get(path);
      if (descriptor == null) {
        releaseOverCachedChannels();
        descriptor = new ChannelDescriptor(path, read);
        myCache.put(path, descriptor);
      }
      if (!read && descriptor.isReadOnly()) {
        if (descriptor.isLocked()) {
          descriptor = new ChannelDescriptor(path, false);
        }
        else {
          // re-open as write
          closeChannel(path);
          descriptor = new ChannelDescriptor(path, false);
          myCache.put(path, descriptor);
        }
      }
      descriptor.lock();
    }

    try {
      return processor.process(descriptor.getChannel());
    } finally {
      synchronized (myLock) {
        descriptor.unlock();
      }
    }
  }

  void closeChannel(Path path) throws IOException {
    synchronized (myLock) {
      final ChannelDescriptor descriptor = myCache.remove(path);

      if (descriptor != null) {
        assert !descriptor.isLocked();
        descriptor.close();
      }
    }
  }

  private void releaseOverCachedChannels() throws IOException {
    int dropCount = myCache.size() - myCacheSizeLimit;

    if (dropCount >= 0) {
      List keysToDrop = new ArrayList<>();
      for (Map.Entry entry : myCache.entrySet()) {
        if (dropCount < 0) break;
        if (!entry.getValue().isLocked()) {
          dropCount--;
          keysToDrop.add(entry.getKey());
        }
      }

      for (Path file : keysToDrop) {
        closeChannel(file);
      }
    }
  }

  static final class ChannelDescriptor implements Closeable {
    private int myLockCount = 0;
    private final @NotNull UnInterruptibleFileChannel myChannel;
    private final boolean myReadOnly;

    private static final OpenOption[] MODIFIABLE_OPTS = {StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE};
    private static final OpenOption[] READ_ONLY_OPTS = {StandardOpenOption.READ};

    ChannelDescriptor(@NotNull Path file, boolean readOnly) throws IOException {
      myReadOnly = readOnly;
      myChannel = Objects.requireNonNull(FileUtilRt.doIOOperation(lastAttempt -> {
        try {
          return new UnInterruptibleFileChannel(file, readOnly ? READ_ONLY_OPTS : MODIFIABLE_OPTS);
        }
        catch (NoSuchFileException ex) {
          Path parent = file.getParent();
          if (!readOnly) {
            if (!Files.exists(parent)) {
              Files.createDirectories(parent);
            }
            if (!lastAttempt) return null;
          }
          throw ex;
        }
      }));
    }

    boolean isReadOnly() {
      return myReadOnly;
    }

    void lock() {
      myLockCount++;
    }

    void unlock() {
      myLockCount--;
    }

    boolean isLocked() {
      return myLockCount != 0;
    }

    @NotNull UnInterruptibleFileChannel getChannel() {
      return myChannel;
    }

    @Override
    public void close() throws IOException {
      myChannel.close();
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy