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

org.zeromq.timer.ZTicket Maven / Gradle / Ivy

The newest version!
package org.zeromq.timer;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import zmq.util.Clock;
import zmq.util.Draft;
import zmq.util.Utils;
import zmq.util.function.Supplier;

/**
 * Manages set of tickets.
 * 

* Ticket timers are very fast in the case where * you use a lot of timers (thousands), and frequently remove and add them. * The main use case is expiry timers for servers that handle many clients, * and which reset the expiry timer for each message received from a client. * Whereas normal timers perform poorly as the number of clients grows, the * cost of ticket timers is constant, no matter the number of clients *

* Tickets can be added with a given delay. *
* When the delay of time expires after addition, handler method is executed with given arguments. *
* Ticket is NOT repetitive and will be executed once unless canceled. *

* This class is not thread-safe *

* This is a DRAFT class, and may change without notice. */ @Draft public final class ZTicket { /** * Opaque representation of a ticket. */ public static final class Ticket implements Comparable { private final ZTicket parent; private final TimerHandler handler; private final Object[] args; private long start; private long delay; private boolean alive = true; private Ticket(ZTicket parent, long now, long delay, TimerHandler handler, Object... args) { assert (args != null); this.parent = parent; this.start = now; this.delay = delay; this.handler = handler; this.args = args; } /** * Resets the ticket. */ public void reset() { if (alive) { parent.sort = true; start = parent.now(); } } /** * Cancels a ticket. * @return true if cancelled, false if already cancelled. */ public boolean cancel() { if (alive) { alive = false; parent.sort = true; return true; } return false; } /** * Changes the delay of the ticket. * @param delay the new delay of the ticket. */ public void setDelay(long delay) { if (alive) { parent.sort = true; this.delay = delay; } } @Override public int compareTo(Ticket other) { if (alive) { if (other.alive) { return Long.compare(start - other.start, other.delay - delay); } return -1; } return other.alive ? 1 : 0; } } private final List tickets; private final Supplier clock; private boolean sort; public ZTicket() { this(() -> TimeUnit.NANOSECONDS.toMillis(Clock.nowNS())); } ZTicket(Supplier clock) { this(clock, new ArrayList<>()); } ZTicket(Supplier clock, List tickets) { this.clock = clock; this.tickets = tickets; } private long now() { return clock.get(); } private void insert(Ticket ticket) { sort = tickets.add(ticket); } /** * Add ticket to the set. * @param delay the expiration delay in milliseconds. * @param handler the callback called at the expiration of the ticket. * @param args the optional arguments for the handler. * @return an opaque handle for further cancel and reset. */ public Ticket add(long delay, TimerHandler handler, Object... args) { if (handler == null) { return null; } Utils.checkArgument(delay > 0, "Delay of a ticket has to be strictly greater than 0"); final Ticket ticket = new Ticket(this, now(), delay, handler, args); insert(ticket); return ticket; } /** * Returns the time in millisecond until the next ticket. * @return the time in millisecond until the next ticket. */ public long timeout() { if (tickets.isEmpty()) { return -1; } sortIfNeeded(); // Tickets are sorted, so check first ticket Ticket first = tickets.get(0); long time = first.start - now() + first.delay; if (time > 0) { return time; } else { return 0; } } /** * Execute the tickets. * @return the number of tickets triggered. */ public int execute() { int executed = 0; final long now = now(); sortIfNeeded(); Set cancelled = new HashSet<>(); for (Ticket ticket : this.tickets) { if (now - ticket.start < ticket.delay) { // tickets are ordered, not meeting the condition means the next ones do not as well break; } if (!ticket.alive) { // Dead ticket, let's continue cancelled.add(ticket); continue; } ticket.alive = false; cancelled.add(ticket); ticket.handler.time(ticket.args); ++executed; } for (int idx = tickets.size(); idx-- > 0; ) { Ticket ticket = tickets.get(idx); if (ticket.alive) { break; } cancelled.add(ticket); } this.tickets.removeAll(cancelled); cancelled.clear(); return executed; } private void sortIfNeeded() { if (sort) { sort = false; Collections.sort(tickets); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy