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

spice.util.WorkCache.scala Maven / Gradle / Ivy

package spice.util

import rapid.{Fiber, Task}

import java.util.concurrent.ConcurrentHashMap

/**
 * WorkCache effectively operates on a Key to guarantee that two jobs for the same Key are not concurrently processed
 * and additional checks on the Key will wait for the same result.
 *
 * @tparam Key the key tied to the work
 * @tparam Result the result of the work
 */
trait WorkCache[Key, Result] {
  private val map = new ConcurrentHashMap[Key, Fiber[Result]]

  /**
   * Check for a persisted result. Work that is completed by `work` should persist to be able to be retrieved by this
   * call.
   *
   * @param key the Key to look up
   * @return Some(result) if the work has already been done for this key, None otherwise to say that the work should be
   *         done.
   */
  protected def persisted(key: Key): Task[Option[Result]]

  /**
   * Execute the work to generate the Result for this Key. The contract of this method is to persist the result so that
   * it may be retrieved by the `persisted` method.
   *
   * @param key the Key to do work for
   * @return the Result of the work
   */
  protected def work(key: Key): Task[WorkResult[Result]]

  /**
   * Retrieve the Result for this Key doing work if necessary and retrieving from persisted state if available.
   */
  def apply(key: Key): Task[Result] = Task(Option(map.get(key))).flatMap {
    case Some(f) => f
    case None => persisted(key).flatMap {
      case Some(result) => Task.pure(result)
      case None => {
        val completable = Task.completable[Result]
        map.put(key, completable.start())
        work(key)
          .flatMap {
            case WorkResult.FinalResult(result) =>
              completable.success(result)
              map.remove(key)
              Task.pure(result)
            case WorkResult.ProgressiveResult(result, complete) =>
              complete
                .map { result =>
                  completable.success(result)
                  map.remove(key)
                }
                .handleError(throwable => Task(errorHandler(key, throwable).start()))
                .start()
              Task.pure(result)
          }
          .handleError(errorHandler(key, _))
      }
    }
  }

  protected def errorHandler(key: Key, throwable: Throwable): Task[Result] = Task {
    scribe.error(s"Error while processing $key ", throwable)
    throw throwable
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy