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

com.jetbrains.pluginverifier.repository.cache.ResourceCache.kt Maven / Gradle / Ivy

/*
 * Copyright 2000-2020 JetBrains s.r.o. and other contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 */

package com.jetbrains.pluginverifier.repository.cache

import com.jetbrains.plugin.structure.base.utils.closeOnException
import com.jetbrains.pluginverifier.repository.cleanup.SizeEvictionPolicy
import com.jetbrains.pluginverifier.repository.cleanup.SizeWeight
import com.jetbrains.pluginverifier.repository.provider.ResourceProvider
import com.jetbrains.pluginverifier.repository.resources.EvictionPolicy
import com.jetbrains.pluginverifier.repository.resources.ResourceRepositoryImpl
import com.jetbrains.pluginverifier.repository.resources.ResourceRepositoryResult
import com.jetbrains.pluginverifier.repository.resources.ResourceWeight
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.io.Closeable
import java.time.Clock

/**
 * Resource cache is intended to cache any resources which
 * fetching may be expensive.
 *
 * Initially, the cache is empty.
 * The resources are fetched and cached on demand: if a resource
 * requested by a [key] [K] is not available in the cache,
 * the [resourceProvider] provides the corresponding resource. It works
 * concurrently, meaning that in case several threads request a resource
 * by the same key, only one of them will actually provide the resource,
 * while others will wait for the first to complete.
 *
 * The resources are [returned] [getResourceCacheEntry] wrapped in [ResourceCacheEntry]
 * that protect them from eviction from the cache while the resources
 * are used by requesting threads. Only once all the [cache entries] [ResourceCacheEntry]
 * of a resource by a specific [key] [K] get [closed] [ResourceCacheEntry.close],
 * the resource _may be_ [disposed] [disposer]. Note that it may not happen immediately
 * since the same resource may be requested once again shortly.
 *
 * While there are available "slots" in the cache, the resources are not disposed.
 * All the unreleased resources will be [disposed] [disposer] once the cache is [closed] [close].
 */
class ResourceCache>(
  /**
   * [ResourceProvider] that provides the
   * requested resources by [keys] [K].
   */
  resourceProvider: ResourceProvider,
  /**
   * The disposer used to close the resources.
   *
   * The resources are closed either when the [cleanup] [SizeEvictionPolicy] procedure
   * determines to evict the corresponding resources,
   * or when the resources are removed from the [resourceRepository].
   * On [close], all the resources are removed and closed.
   */
  disposer: (R) -> Unit,
  /**
   * [EvictionPolicy] that manages eviction of resources
   * held in this cache.
   */
  evictionPolicy: EvictionPolicy,
  /**
   * Initial weight of the resources held in this cache.
   */
  initialWeight: W,
  /**
   * Weigher of the resources held in this cache.
   */
  weigher: (R) -> W,
  /**
   * The cache name that can be used for logging and debugging purposes
   */
  private val presentableName: String
) : Closeable {

  companion object {
    private val LOG: Logger = LoggerFactory.getLogger(ResourceCache::class.java)
  }

  /**
   * Resource [repository] [com.jetbrains.pluginverifier.repository.resources.ResourceRepository]
   * of the allocated [resources] [R].
   *
   * Initially, the repository is empty, meaning that there
   * are no resources opened.
   *
   * When the repository is full and a new resource is requested,
   * unused resources are [disposed] [disposer].
   */
  private val resourceRepository = ResourceRepositoryImpl(
    evictionPolicy,
    Clock.systemUTC(),
    resourceProvider,
    initialWeight,
    weigher,
    disposer,
    presentableName
  )

  /**
   * A flag indicating whether _this_ cache is already closed.
   * It is protected by the synchronized blocks.
   */
  private var isClosed = false

  /**
   * Enqueues for closing all resources.
   * The resources that have no locks registered at this
   * moment will be closed immediately, while the locked resources
   * will be closed once they become released by their holders.
   *
   * The resources being requested at the time of [close] invocation will
   * be released and closed at the [getResourceCacheEntry].
   * Thus, no new resources can be allocated after the [close] is invoked.
   */
  @Synchronized
  override fun close() {
    LOG.debug("Closing the $presentableName")
    if (!isClosed) {
      isClosed = true
      resourceRepository.removeAll()
    }
  }

  /**
   * Provides the [ResourceCacheEntry] that contains
   * the [resource] [ResourceCacheEntry.resource].
   *
   * Possible results of this method invocation
   * are represented as instances of the [ResourceCacheEntryResult].
   * If the [Found] [ResourceCacheEntryResult.Found] is returned,
   * the corresponding [ResourceCacheEntry] must be
   * [closed] [ResourceCacheEntry.close] after being used.
   */
  @Throws(InterruptedException::class)
  fun getResourceCacheEntry(key: K): ResourceCacheEntryResult {
    /**
     * Cancel the fetching if _this_ resource cache is already closed.
     */
    synchronized(this) {
      if (isClosed) {
        throw InterruptedException()
      }
    }
    val repositoryResult = resourceRepository.get(key)
    val lockedResource = with(repositoryResult) {
      when (this) {
        is ResourceRepositoryResult.Found -> lockedResource
        is ResourceRepositoryResult.Failed<*, *> -> return ResourceCacheEntryResult.Failed(reason, error)
        is ResourceRepositoryResult.NotFound<*, *> -> return ResourceCacheEntryResult.NotFound(reason)
      }
    }
    /**
     * If _this_ cache was closed after the [key]
     * had been requested, release the lock and register
     * the [key] for deletion: it will be either
     * removed immediately, or just after the last
     * holder releases the lock.
     */
    synchronized(this) {
      if (isClosed) {
        lockedResource.release()
        resourceRepository.remove(key)
        throw InterruptedException()
      }
      return lockedResource.closeOnException {
        ResourceCacheEntryResult.Found(ResourceCacheEntry(lockedResource))
      }
    }
  }

  /**
   * Explicitly remove the resource from this cache and invoke the associated disposer.
   */
  fun remove(key: K) {
    resourceRepository.remove(key)
  }

}

fun  createSizeLimitedResourceCache(
  cacheSize: Int,
  resourceProvider: ResourceProvider,
  disposer: (R) -> Unit,
  presentableName: String
): ResourceCache = ResourceCache(
  resourceProvider,
  disposer,
  SizeEvictionPolicy(cacheSize),
  SizeWeight(0),
  { SizeWeight(1) },
  presentableName
)




© 2015 - 2025 Weber Informatics LLC | Privacy Policy