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

org.opentcs.strategies.basic.scheduling.ReservationPool Maven / Gradle / Ivy

There is a newer version: 6.2.0
Show newest version
/**
 * Copyright (c) The openTCS Authors.
 *
 * This program is free software and subject to the MIT license. (For details,
 * see the licensing information (LICENSE.txt) you should have received with
 * this copy of the software.)
 */
package org.opentcs.strategies.basic.scheduling;

import static java.util.Objects.requireNonNull;

import jakarta.annotation.Nonnull;
import jakarta.inject.Inject;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.Set;
import java.util.stream.Collectors;
import org.opentcs.components.kernel.Scheduler;
import org.opentcs.data.model.TCSResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 */
public class ReservationPool {

  /**
   * This class's Logger.
   */
  private static final Logger LOG = LoggerFactory.getLogger(ReservationPool.class);
  /**
   * All claims.
   */
  private final Map>>> claimsByClient
      = new HashMap<>();
  /**
   * ReservationEntry instances for each TCSResource.
   */
  private final Map, ReservationEntry> reservations = new HashMap<>();

  /**
   * Creates a new instance.
   */
  @Inject
  public ReservationPool() {
  }

  /**
   * Returns a reservation entry for the given resource.
   *
   * @param resource The resource for which to return the reservation entry.
   * @return The reservation entry for the given resource.
   */
  @Nonnull
  public ReservationEntry getReservationEntry(TCSResource resource) {
    requireNonNull(resource, "resource");

    ReservationEntry entry = reservations.get(resource);
    if (entry == null) {
      entry = new ReservationEntry(resource);
      reservations.put(resource, entry);
    }
    return entry;
  }

  /**
   * Returns the sequence of resource sets claimed by the given client.
   *
   * @param client The client.
   * @return The sequence of resource sets claimed by the given client.
   */
  @Nonnull
  public List>> getClaim(
      @Nonnull
      Scheduler.Client client
  ) {
    requireNonNull(client, "client");

    return claimsByClient.getOrDefault(client, new ArrayDeque<>()).stream()
        .map(resourceSet -> Set.copyOf(resourceSet))
        .collect(Collectors.toList());
  }

  /**
   * Sets the sequence of claimed resource sets for the given client.
   *
   * @param client The client.
   * @param resources The sequence of claimed resources.
   */
  public void setClaim(
      @Nonnull
      Scheduler.Client client,
      @Nonnull
      List>> resources
  ) {
    requireNonNull(client, "client");
    requireNonNull(resources, "resources");

    claimsByClient.put(client, new ArrayDeque<>(resources));
  }

  /**
   * Removes the given resource set from the head of the sequence of claimed resource sets for the
   * given client.
   *
   * @param client The client.
   * @param resources The resource set to be removed from the head of the client's claim sequence.
   * @throws IllegalArgumentException If the given resource set is not the head of the client's
   * claim sequence.
   */
  public void unclaim(
      @Nonnull
      Scheduler.Client client,
      @Nonnull
      Set> resources
  )
      throws IllegalArgumentException {
    requireNonNull(client, "client");
    requireNonNull(resources, "resources");

    if (!claimsByClient.containsKey(client) || claimsByClient.get(client).isEmpty()) {
      return;
    }

    if (!isNextInClaim(client, resources)) {
      throw new IllegalArgumentException(
          String.format(
              "Resources to unclaim and head of claimed resource don't match: %s != %s",
              resources,
              claimsByClient.get(client).peek()
          )
      );
    }

    claimsByClient.get(client).remove();
  }

  /**
   * Checks whether the given resource set is at the head of the given client's claim sequence.
   *
   * @param client The client.
   * @param resources The resources to be checked.
   * @return true if, and only if, the given resource set is at the head of the given
   * client's claim sequence.
   */
  public boolean isNextInClaim(
      @Nonnull
      Scheduler.Client client,
      @Nonnull
      Set> resources
  ) {
    requireNonNull(client, "client");
    requireNonNull(resources, "resources");

    if (!claimsByClient.containsKey(client) || claimsByClient.get(client).isEmpty()) {
      return false;
    }

    if (!Objects.equals(resources, claimsByClient.get(client).peek())) {
      return false;
    }

    return true;
  }

  /**
   * Returns all resources allocated by the given client.
   *
   * @param client The client for which to return all allocated resources.
   * @return All resources allocated by the given client.
   */
  @Nonnull
  public Set> allocatedResources(
      @Nonnull
      Scheduler.Client client
  ) {
    requireNonNull(client, "client");

    return reservations.entrySet().stream()
        .filter(entry -> entry.getValue().isAllocatedBy(client))
        .map(entry -> entry.getKey())
        .collect(Collectors.toSet());
  }

  /**
   * Checks if all resources in the given set of resources are be available for the given client.
   *
   * @param resources The set of resources to be checked.
   * @param client The client for which to check.
   * @return true if, and only if, all resources in the given set
   * are available for the given client.
   */
  public boolean resourcesAvailableForUser(
      @Nonnull
      Set> resources,
      @Nonnull
      Scheduler.Client client
  ) {
    requireNonNull(resources, "resources");
    requireNonNull(client, "client");

    for (TCSResource curResource : resources) {
      // Check if the resource is available.
      ReservationEntry entry = getReservationEntry(curResource);
      if (!entry.isFree() && !entry.isAllocatedBy(client)) {
        LOG.debug(
            "{}: Resource {} unavailable, reserved by {}",
            client.getId(),
            curResource.getName(),
            entry.getClient().getId()
        );
        return false;
      }
    }
    return true;
  }

  public void free(
      @Nonnull
      Scheduler.Client client,
      @Nonnull
      Set> resources
  ) {
    requireNonNull(client, "client");
    requireNonNull(resources, "resources");

    LOG.debug("{}: Releasing resources: {}", client.getId(), resources);
    for (TCSResource curResource : getFreeableResources(resources, client)) {
      getReservationEntry(curResource).free();
    }
  }

  public void freeAll(
      @Nonnull
      Scheduler.Client client
  ) {
    requireNonNull(client, "client");

    reservations.values().stream()
        .filter(reservationEntry -> reservationEntry.isAllocatedBy(client))
        .forEach(reservationEntry -> reservationEntry.freeCompletely());
  }

  @Nonnull
  public Map>> getAllocations() {
    final Map>> result = new HashMap<>();
    for (Map.Entry, ReservationEntry> curEntry : reservations.entrySet()) {
      final TCSResource curResource = curEntry.getKey();
      final Scheduler.Client curUser = curEntry.getValue().getClient();
      if (curUser != null) {
        Set> userResources = result.get(curUser.getId());
        if (userResources == null) {
          userResources = new HashSet<>();
        }
        userResources.add(curResource);
        result.put(curUser.getId(), userResources);
      }
    }
    return result;
  }

  public void clear() {
    claimsByClient.clear();
    reservations.clear();
  }

  /**
   * Returns a set of resources that is a subset of the given set of resources and is reserved/could
   * be released by the given client.
   *
   * @param resources The set of resources to be filtered for resources that could be released.
   * @param client The client that should be able to release the returned resources.
   * @return A set of resources that is a subset of the given set of resources and is reserved/could
   * be released by the given client.
   */
  @Nonnull
  private Set> getFreeableResources(
      @Nonnull
      Set> resources,
      @Nonnull
      Scheduler.Client client
  ) {
    // Make sure we're freeing only resources that are allocated by us.
    final Set> freeableResources = new HashSet<>();
    for (TCSResource curRes : resources) {
      ReservationEntry entry = getReservationEntry(curRes);
      if (!entry.isAllocatedBy(client)) {
        LOG.warn("{}: Freed resource not reserved: {}, entry: {}", client.getId(), curRes, entry);
      }
      else {
        freeableResources.add(curRes);
      }
    }
    return freeableResources;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy