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

io.vlingo.lattice.grid.spaces.SpaceActor Maven / Gradle / Ivy

Go to download

Tooling for reactive Domain-Driven Design projects that are highly concurrent. Includes compute grid, actor caching, spaces, cross-node cluster messaging, CQRS, and Event Sourcing support.

There is a newer version: 1.7.5
Show newest version
// Copyright © 2012-2020 VLINGO LABS. All rights reserved.
//
// This Source Code Form is subject to the terms of the
// Mozilla Public License, v. 2.0. If a copy of the MPL
// was not distributed with this file, You can obtain
// one at https://mozilla.org/MPL/2.0/.

package io.vlingo.lattice.grid.spaces;

import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;

import io.vlingo.actors.Actor;
import io.vlingo.common.Cancellable;
import io.vlingo.common.Completes;
import io.vlingo.common.Scheduled;

public class SpaceActor extends Actor implements Space, Scheduled> {
  private static final long Brief = 5;
  private static final long Rounding = 100;

  private final Set> expirableItems;
  private final Set expirableQueries;
  private final Duration defaultScanInterval;
  private final Map, Map>> registry;
  private final ScheduledQueryRunnerEvictor scheduledQueryRunnerEvictor;
  private final ScheduledSweeper scheduledSweeper;
  private final Scheduled> scheduled;

  @SuppressWarnings("unchecked")
  public SpaceActor(final Duration defaultScanInterval) {
    this.defaultScanInterval = defaultScanInterval;
    this.expirableItems = new TreeSet<>();
    this.expirableQueries = new TreeSet<>();
    this.registry = new HashMap<>();
    this.scheduled = selfAs(Scheduled.class);
    this.scheduledQueryRunnerEvictor = new ScheduledQueryRunnerEvictor();
    this.scheduledSweeper = new ScheduledSweeper();
  }

  @Override
  public  Completes itemFor(final Class protocol, final Class type, final Object... parameters) {
    // Fail; not implemented. See SpaceItemFactoryRelay#itemFor.
    return completes().with(null);
  }

  @Override
  public  Completes> put(final Key key, final Item item) {
    manage(key, item);

    return completes().with(KeyItem.of(key, item.object, item.lease));
  }

  @Override
  public  Completes>> get(final Key key, final Period until) {
    final ExpirableItem item = item(key, true);

    if (item == null) {
      periodicQuery(key, true, until);

      return completes();
    } else {
      return completes().with(Optional.of(KeyItem.of(key, item.object, item.lease)));
    }
  }

  @Override
  public  Completes>> take(final Key key, final Period until) {
    final ExpirableItem item = item(key, false);

    if (item == null) {
      periodicQuery(key, false, until);

      return completes();
    } else {
      return completes().with(Optional.of(KeyItem.of(key, item.object, item.lease)));
    }
  }

  @Override
  public void intervalSignal(final Scheduled> scheduled, final ScheduledScanner scanner) {
    scanner.scan();
  }

  private  ExpirableItem expiringItem(final Key key, final Item item) {
    final Instant expiration = item.lease.toFutureInstant();
    return new ExpirableItem<>(key, item.object, expiration, item.lease);
  }

  private  ExpirableQuery expiringQuery(final Key key, final boolean retainItem, final Period period) {
    final Instant expiration = period.toFutureInstant();
    return new ExpirableQuery(key, retainItem, expiration, period, completesEventually());
  }

  private  ExpirableItem item(final Key key, final boolean retain) {
    final Map> itemMap = itemMap(key);

    if (retain) {
      return itemMap.get(key);
    } else {
      return itemMap.remove(key);
    }
  }

  @SuppressWarnings("unchecked")
  private  Map> itemMap(final Key key) {
    final Class keyClass = (Class) key.getClass();

    Map> itemMap = getItemMap(keyClass);

    if (itemMap == null) {
      itemMap = new HashMap<>();
      putItemMap(keyClass, itemMap);
    }

    return itemMap;
  }

  @SuppressWarnings("rawtypes")
  private Map getItemMap(final Class keyClass) {
    return registry.get(keyClass);
  }

  @SuppressWarnings({ "unchecked", "rawtypes" })
  private  void putItemMap(final Class keyClass, Map itemMap) {
    registry.put(keyClass, itemMap);
  }

  @SuppressWarnings({ "unchecked", "rawtypes" })
  private  void manage(final Key key, final Item item) {
    final ExpirableItem expiringItem = expiringItem(key, item);

    final Map> itemMap = itemMap(expiringItem.key);

    itemMap.put(expiringItem.key, expiringItem);

    if (!expiringItem.isMaximumExpiration()) {
      expirableItems.add(expiringItem);

      scheduledSweeper.scheduleBy((Item) item);
    }
  }

  private void periodicQuery(final Key key, final boolean retain, final Period until) {
    final ExpirableQuery query = expiringQuery(key, retain, until);

    expirableQueries.add(query);

    scheduledQueryRunnerEvictor.scheduleBy(query);
  }

  //================================
  // ScheduledQueryRunnerEvictor
  //================================

  private class ScheduledQueryRunnerEvictor implements ScheduledScanner {
    private Optional cancellable;
    private Duration currentDuration;

    ScheduledQueryRunnerEvictor() {
      this.cancellable = Optional.empty();
      this.currentDuration = Duration.ofMillis(Long.MAX_VALUE);
    }

    @Override
    public void scan() {
      final Instant now = Instant.now();

      final List confirmedExpirables = new ArrayList<>();

      for (final ExpirableQuery expirableQuery : expirableQueries) {
        final ExpirableItem item = item(expirableQuery.key, expirableQuery.retainItem);

        if (item != null) {
          expirableQuery.completes.with(Optional.of(KeyItem.of(item.key, item.object, item.lease)));
          confirmedExpirables.add(expirableQuery);
        } else if (item == null) {
          if (now.isAfter(expirableQuery.expiresOn)) {
            confirmedExpirables.add(expirableQuery);
            expirableQuery.completes.with(Optional.empty());
          }
        }
      }

      for (final ExpirableQuery expirableQuery : confirmedExpirables) {
        expirableQueries.remove(expirableQuery);
      }

      final Iterator iterator = expirableQueries.iterator();

      if (iterator.hasNext()) {
        final long millis = (iterator.next().expiresOn.getEpochSecond() - Instant.now().getEpochSecond()) * 1_000;
        final Duration minQueryDuration = Duration.ofMillis(millis < 0 ? Rounding : millis);
        currentDuration = min(minQueryDuration, defaultScanInterval);
      } else {
        currentDuration = defaultScanInterval;
      }

      schedule();
    }

    @Override
    public void scheduleBy(final ScheduledScannable scannable) {
      final ExpirableQuery query = scannable.scannable();
      final long rounded = query.period.toMilliseconds() + Rounding;

      if (rounded < currentDuration.toMillis()) {
        currentDuration = min(query.period.duration, defaultScanInterval);
      }

      schedule();
    }

    private Duration min(final Duration duration1, final Duration duration2) {
      return duration1.toMillis() < duration2.toMillis() ? duration1 : duration2;
    }

    private void schedule() {
      cancellable.ifPresent(canceller -> canceller.cancel());

      cancellable = Optional.of(scheduler().scheduleOnce(scheduled, this, Duration.ZERO, currentDuration));
    }
  }

  //================================
  // ScheduledSweeper
  //================================

  @SuppressWarnings("rawtypes")
  private class ScheduledSweeper implements ScheduledScanner {
    private Optional cancellable;
    private Duration currentDuration;

    ScheduledSweeper() {
      this.cancellable = Optional.empty();
      this.currentDuration = Duration.ofMillis(Long.MAX_VALUE);
    }

    @Override
    public void scan() {
      final Instant now = Instant.now();

      final List> confirmedExpirables = new ArrayList<>();

      for (final ExpirableItem expirableItem : expirableItems) {
        if (now.isAfter(expirableItem.expiresOn)) {
          if (itemMap(expirableItem.key).remove(expirableItem.key) != null) {
            confirmedExpirables.add(expirableItem);
          }
        }
      }

      for (final ExpirableItem expirableItem : confirmedExpirables) {
        expirableItems.remove(expirableItem);
      }

      final Iterator> iterator = expirableItems.iterator();

      if (iterator.hasNext()) {
        final long millis = iterator.next().expiresOn.toEpochMilli() - Instant.now().toEpochMilli();
        currentDuration = Duration.ofMillis(millis < 0 ? Brief : millis);
      } else {
        currentDuration = defaultScanInterval;
      }

      schedule();
    }

    @Override
    public void scheduleBy(final ScheduledScannable scannable) {
      final Item item = scannable.scannable();
      final long rounded = item.lease.duration.toMillis() + Rounding;

      if (rounded < currentDuration.toMillis()) {
        currentDuration = item.lease.duration;

        schedule();
      }
    }

    private void schedule() {
      cancellable.ifPresent(canceller -> canceller.cancel());

      cancellable = Optional.of(scheduler().scheduleOnce(scheduled, this, Duration.ZERO, currentDuration));
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy