<commit_before>package ibis.ipl.impl.registry.central.server;

import ibis.ipl.impl.IbisIdentifier;
import ibis.ipl.impl.Location;
import ibis.ipl.impl.registry.Connection;
import ibis.ipl.impl.registry.central.Election;
import ibis.ipl.impl.registry.central.ElectionSet;
import ibis.ipl.impl.registry.central.Event;
import ibis.ipl.impl.registry.central.EventList;
import ibis.ipl.impl.registry.central.ListMemberSet;
import ibis.ipl.impl.registry.central.Member;
import ibis.ipl.impl.registry.central.MemberSet;
import ibis.ipl.impl.registry.central.Protocol;
import ibis.ipl.impl.registry.central.TreeMemberSet;
import ibis.ipl.impl.registry.statistics.Statistics;
import ibis.smartsockets.virtual.VirtualSocketFactory;
import ibis.util.ThreadPool;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Map;

import org.apache.log4j.Logger;

final class Pool implements Runnable {

	public static final int BOOTSTRAP_LIST_SIZE = 25;

	// 10 seconds connect timeout
	private static final int CONNECT_TIMEOUT = 10000;

	// minimum time between two "ping" attempts if maybeDead() was
	// called by the user
	private static final long RECENTLY_SEEN_THRESHOLD = 1000;

	private static final Logger logger = Logger.getLogger(Pool.class);

	private final VirtualSocketFactory socketFactory;

	// list of all joins, leaves, elections, etc.
	private final EventList events;

	private final boolean peerBootstrap;

	// private final boolean gossip;
	//
	// private final boolean tree;

	private final long heartbeatInterval;

	private int currentEventTime;

	private int minEventTime;

	private final ElectionSet elections;

	private final MemberSet members;

	private final OndemandEventPusher pusher;

	private final String name;

	private final String ibisImplementationIdentifier;

	private final boolean closedWorld;

	// size of this pool (if closed world)
	private final int fixedSize;

	private final boolean printEvents;

	private final boolean printErrors;

	// statistics are only kept on the request of the user

	private final Statistics statistics;

	// simple statistics which are always kept,
	// so the server can print them if so requested
	private final int[] eventStats;

	private final Map<String, Integer> sequencers;

	private int nextID;

	private boolean ended = false;

	private boolean closed = false;
	
	private Event closeEvent = null;

	private boolean terminated = false;

	private Event terminateEvent = null;

	Pool(String name, VirtualSocketFactory socketFactory,
			boolean peerBootstrap, long heartbeatInterval,
			long eventPushInterval, boolean gossip, long gossipInterval,
			boolean adaptGossipInterval, boolean tree, boolean closedWorld,
			int poolSize, boolean keepStatistics, long statisticsInterval,
			String ibisImplementationIdentifier, boolean printEvents,
			boolean printErrors) {
		this.name = name;
		this.socketFactory = socketFactory;
		this.peerBootstrap = peerBootstrap;
		this.heartbeatInterval = heartbeatInterval;
		// this.gossip = gossip;
		// this.tree = tree;
		this.closedWorld = closedWorld;
		this.fixedSize = poolSize;
		this.ibisImplementationIdentifier = ibisImplementationIdentifier;
		this.printEvents = printEvents;
		this.printErrors = printErrors;

		if (keepStatistics) {
			statistics = new Statistics(Protocol.OPCODE_NAMES);
			statistics.setID("server", name);
			statistics.startWriting(statisticsInterval);
		} else {
			;
			statistics = null;
		}

		currentEventTime = 0;
		minEventTime = 0;
		nextID = 0;
		sequencers = new HashMap<String, Integer>();

		events = new EventList();
		eventStats = new int[Event.NR_OF_TYPES];
		elections = new ElectionSet();

		if (gossip) {
			members = new ListMemberSet();
			new IterativeEventPusher(this, eventPushInterval, false, false);
			new RandomEventPusher(this, gossipInterval, adaptGossipInterval);
		} else if (tree) {
			members = new TreeMemberSet();
			// on new event send to children in tree
			// FIXME: hack? also check for needed updates every second?
			new IterativeEventPusher(this, 1000, true, true);

			// once in a while forward to everyone
			new IterativeEventPusher(this, eventPushInterval, false, false);
		} else { // central
			members = new ListMemberSet();
			new IterativeEventPusher(this, eventPushInterval, true, false);
		}

		pusher = new OndemandEventPusher(this);

		ThreadPool.createNew(this, "pool pinger thread");

	}

	private static void print(String message) {
		System.err.printf("%tT Central Registry: %s\n", System
				.currentTimeMillis(), message);
	}

	synchronized int getEventTime() {
		return currentEventTime;
	}

	synchronized int getMinEventTime() {
		return minEventTime;
	}

	synchronized Event addEvent(int type, String description,
			IbisIdentifier ibis, IbisIdentifier... ibisses) {
		Event event = new Event(currentEventTime, type, description, ibis,
				ibisses);
		logger.debug("adding new event: " + event);
		events.add(event);
		eventStats[type]++;

		currentEventTime++;
		notifyAll();

		return event;
	}

	synchronized void waitForEventTime(int time, long timeout) {
		long deadline = System.currentTimeMillis() + timeout;

		if (timeout == 0) {
			deadline = Long.MAX_VALUE;
		}

		while (getEventTime() < time) {
			if (hasEnded()) {
				return;
			}

			long currentTime = System.currentTimeMillis();

			if (currentTime >= deadline) {
				return;
			}

			try {
				wait(deadline - currentTime);
			} catch (InterruptedException e) {
				// IGNORE
			}
		}
	}

	synchronized int getSize() {
		return members.size();
	}

	int getFixedSize() {
		return fixedSize;
	}

	public boolean isClosedWorld() {
		return closedWorld;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see ibis.ipl.impl.registry.central.SuperPool#ended()
	 */
	synchronized boolean hasEnded() {
		return ended;
	}

	synchronized boolean isClosed() {
		return closed;
	}

	synchronized void end() {
		ended = true;
		pusher.enqueue(null);
		if (statistics != null) {
			statistics.write();
			statistics.end();
		}
	}

	public void saveStatistics() {
		if (statistics != null) {
			statistics.write();
		}
	}

	String getName() {
		return name;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see ibis.ipl.impl.registry.central.SuperPool#join(byte[], byte[],
	 *      ibis.ipl.impl.Location)
	 */
	synchronized Member join(byte[] implementationData, byte[] clientAddress,
			Location location, String ibisImplementationIdentifier)
			throws IOException {
		if (hasEnded()) {
			throw new IOException("Pool already ended");
		}

		if (isClosed()) {
			throw new IOException("Pool already closed");
		}

		if (!ibisImplementationIdentifier
				.equals(this.ibisImplementationIdentifier)) {
			throw new IOException("Ibis implementation "
					+ ibisImplementationIdentifier
					+ " does not match pool's Ibis implementation: "
					+ this.ibisImplementationIdentifier);
		}
		logger.debug("ibis version: " + ibisImplementationIdentifier);

		String id = Integer.toString(nextID);
		nextID++;

		IbisIdentifier identifier = new IbisIdentifier(id, implementationData,
				clientAddress, location, name);

		Event event = addEvent(Event.JOIN, null, identifier);

		Member member = new Member(identifier, event);
		member.setCurrentTime(getMinEventTime());
		member.updateTime();

		members.add(member);

		if (logger.isDebugEnabled()) {
			logger.debug("members now: " + members);
		}

		if (statistics != null) {
			statistics.newPoolSize(members.size());
		}

		if (printEvents) {
			print(identifier + " joined pool \"" + name + "\" now "
					+ members.size() + " members");
		}

		if (closedWorld && nextID >= fixedSize) {
			close();
		}

		return member;
	}

	private synchronized void close() {
		if (closed) {
			return;
		}
		closed = true;
		closeEvent = addEvent(Event.POOL_CLOSED, null, null, new IbisIdentifier[0]);
		if (printEvents) {
			print("pool \"" + name + "\" now closed");
		}
	}

	void writeBootstrapList(DataOutputStream out) throws IOException {
		if (!peerBootstrap) {
			// send a list containing 0 members. It is not used anyway
			out.writeInt(0);
			return;
		}

		Member[] peers = getRandomMembers(BOOTSTRAP_LIST_SIZE);

		out.writeInt(peers.length);
		for (Member member : peers) {
			member.getIbis().writeTo(out);
		}

	}

	public void writeState(DataOutputStream out, int joinTime)
			throws IOException {

		ByteArrayOutputStream arrayOut = new ByteArrayOutputStream();
		DataOutputStream dataOut = new DataOutputStream(arrayOut);

		// create byte array of data
		synchronized (this) {
			dataOut.writeInt(currentEventTime);

			members.writeTo(dataOut);
			elections.writeTo(dataOut);

			Event[] signals = events
					.getSignalEvents(joinTime, currentEventTime);
			dataOut.writeInt(signals.length);
			for (Event event : signals) {
				event.writeTo(dataOut);
			}

			dataOut.writeBoolean(closed);
			if (closed) {
				closeEvent.writeTo(dataOut);
			}
			dataOut.writeBoolean(terminated);
			if (terminated) {
				terminateEvent.writeTo(dataOut);
			}
		}

		dataOut.flush();
		byte[] bytes = arrayOut.toByteArray();

		out.writeInt(bytes.length);
		out.write(bytes);

		logger.debug("pool state size = " + bytes.length);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see ibis.ipl.impl.registry.central.SuperPool#leave(ibis.ipl.impl.IbisIdentifier)
	 */
	synchronized void leave(IbisIdentifier identifier) throws Exception {
		if (members.remove(identifier) == null) {
			// May happen if it was declared dead before. So, no exception.
			// --Ceriel
			// logger.error("unknown ibis " + identifier + " tried to leave");
			// throw new Exception("ibis unknown: " + identifier);
			logger.debug("unknown ibis " + identifier + " tried to leave");
			return;
		}
		if (printEvents) {
			print(identifier + " left pool \"" + name + "\" now "
					+ members.size() + " members");
		}

		addEvent(Event.LEAVE, null, identifier);

		if (statistics != null) {
			statistics.newPoolSize(members.size());
		}

		Election[] deadElections = elections.getElectionsWonBy(identifier);

		for (Election election : deadElections) {
			addEvent(Event.UN_ELECT, election.getName(), election.getWinner());
			if (statistics != null) {
				statistics.electionEvent();
			}
		}

		if (members.size() == 0) {
			end();
			if (printEvents) {
				print("pool \"" + name + "\" ended");
			} else {
				logger.info("Central Registry: " + "pool \"" + name
						+ "\" ended");
			}
			notifyAll();

		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see ibis.ipl.impl.registry.central.SuperPool#dead(ibis.ipl.impl.IbisIdentifier)
	 */
	synchronized void dead(IbisIdentifier identifier, Exception exception) {
		Member member = members.remove(identifier);
		if (member == null) {
			// member removed already
			return;
		}

		if (printEvents) {
			if (printErrors) {
				print(identifier + " died in pool \"" + name + "\" now "
						+ members.size() + " members, caused by:");
				exception.printStackTrace(System.err);
			} else {
				print(identifier + " died in pool \"" + name + "\" now "
						+ members.size() + " members");
			}
		}

		addEvent(Event.DIED, null, identifier);

		if (statistics != null) {
			statistics.newPoolSize(members.size());
		}

		Election[] deadElections = elections.getElectionsWonBy(identifier);

		for (Election election : deadElections) {
			addEvent(Event.UN_ELECT, election.getName(), election.getWinner());
			if (statistics != null) {
				statistics.electionEvent();
			}

			elections.remove(election.getName());
		}

		if (members.size() == 0) {
			end();
			if (printEvents) {
				print("pool " + name + " ended");
			} else {
				logger.info("Central Registry: " + "pool \"" + name
						+ "\" ended");
			}
			notifyAll();
		}

		pusher.enqueue(member);
	}

	synchronized Event[] getEvents(int startTime) {
		return events.getList(startTime);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see ibis.ipl.impl.registry.central.SuperPool#elect(java.lang.String,
	 *      ibis.ipl.impl.IbisIdentifier)
	 */
	synchronized IbisIdentifier elect(String electionName,
			IbisIdentifier candidate) {
		Election election = elections.get(electionName);

		if (election == null) {
			// Do the election now. The caller WINS! :)

			Event event = addEvent(Event.ELECT, electionName, candidate);
			if (statistics != null) {
				statistics.electionEvent();
			}

			election = new Election(event);

			elections.put(election);

			if (printEvents) {
				print(candidate + " won election \"" + electionName
						+ "\" in pool \"" + name + "\"");
			}

		}

		return election.getWinner();
	}<commit_msg>check if ibis is a member before allowing him to win election<commit_after>package ibis.ipl.impl.registry.central.server;

import ibis.ipl.impl.IbisIdentifier;
import ibis.ipl.impl.Location;
import ibis.ipl.impl.registry.Connection;
import ibis.ipl.impl.registry.central.Election;
import ibis.ipl.impl.registry.central.ElectionSet;
import ibis.ipl.impl.registry.central.Event;
import ibis.ipl.impl.registry.central.EventList;
import ibis.ipl.impl.registry.central.ListMemberSet;
import ibis.ipl.impl.registry.central.Member;
import ibis.ipl.impl.registry.central.MemberSet;
import ibis.ipl.impl.registry.central.Protocol;
import ibis.ipl.impl.registry.central.TreeMemberSet;
import ibis.ipl.impl.registry.statistics.Statistics;
import ibis.smartsockets.virtual.VirtualSocketFactory;
import ibis.util.ThreadPool;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Map;

import org.apache.log4j.Logger;

final class Pool implements Runnable {

	public static final int BOOTSTRAP_LIST_SIZE = 25;

	// 10 seconds connect timeout
	private static final int CONNECT_TIMEOUT = 10000;

	// minimum time between two "ping" attempts if maybeDead() was
	// called by the user
	private static final long RECENTLY_SEEN_THRESHOLD = 1000;

	private static final Logger logger = Logger.getLogger(Pool.class);

	private final VirtualSocketFactory socketFactory;

	// list of all joins, leaves, elections, etc.
	private final EventList events;

	private final boolean peerBootstrap;

	// private final boolean gossip;
	//
	// private final boolean tree;

	private final long heartbeatInterval;

	private int currentEventTime;

	private int minEventTime;

	private final ElectionSet elections;

	private final MemberSet members;

	private final OndemandEventPusher pusher;

	private final String name;

	private final String ibisImplementationIdentifier;

	private final boolean closedWorld;

	// size of this pool (if closed world)
	private final int fixedSize;

	private final boolean printEvents;

	private final boolean printErrors;

	// statistics are only kept on the request of the user

	private final Statistics statistics;

	// simple statistics which are always kept,
	// so the server can print them if so requested
	private final int[] eventStats;

	private final Map<String, Integer> sequencers;

	private int nextID;

	private boolean ended = false;

	private boolean closed = false;

	private Event closeEvent = null;

	private boolean terminated = false;

	private Event terminateEvent = null;

	Pool(String name, VirtualSocketFactory socketFactory,
			boolean peerBootstrap, long heartbeatInterval,
			long eventPushInterval, boolean gossip, long gossipInterval,
			boolean adaptGossipInterval, boolean tree, boolean closedWorld,
			int poolSize, boolean keepStatistics, long statisticsInterval,
			String ibisImplementationIdentifier, boolean printEvents,
			boolean printErrors) {
		this.name = name;
		this.socketFactory = socketFactory;
		this.peerBootstrap = peerBootstrap;
		this.heartbeatInterval = heartbeatInterval;
		// this.gossip = gossip;
		// this.tree = tree;
		this.closedWorld = closedWorld;
		this.fixedSize = poolSize;
		this.ibisImplementationIdentifier = ibisImplementationIdentifier;
		this.printEvents = printEvents;
		this.printErrors = printErrors;

		if (keepStatistics) {
			statistics = new Statistics(Protocol.OPCODE_NAMES);
			statistics.setID("server", name);
			statistics.startWriting(statisticsInterval);
		} else {
			;
			statistics = null;
		}

		currentEventTime = 0;
		minEventTime = 0;
		nextID = 0;
		sequencers = new HashMap<String, Integer>();

		events = new EventList();
		eventStats = new int[Event.NR_OF_TYPES];
		elections = new ElectionSet();

		if (gossip) {
			members = new ListMemberSet();
			new IterativeEventPusher(this, eventPushInterval, false, false);
			new RandomEventPusher(this, gossipInterval, adaptGossipInterval);
		} else if (tree) {
			members = new TreeMemberSet();
			// on new event send to children in tree
			// FIXME: hack? also check for needed updates every second?
			new IterativeEventPusher(this, 1000, true, true);

			// once in a while forward to everyone
			new IterativeEventPusher(this, eventPushInterval, false, false);
		} else { // central
			members = new ListMemberSet();
			new IterativeEventPusher(this, eventPushInterval, true, false);
		}

		pusher = new OndemandEventPusher(this);

		ThreadPool.createNew(this, "pool pinger thread");

	}

	private static void print(String message) {
		System.err.printf("%tT Central Registry: %s\n", System
				.currentTimeMillis(), message);
	}

	synchronized int getEventTime() {
		return currentEventTime;
	}

	synchronized int getMinEventTime() {
		return minEventTime;
	}

	synchronized Event addEvent(int type, String description,
			IbisIdentifier ibis, IbisIdentifier... ibisses) {
		Event event = new Event(currentEventTime, type, description, ibis,
				ibisses);
		logger.debug("adding new event: " + event);
		events.add(event);
		eventStats[type]++;

		currentEventTime++;
		notifyAll();

		return event;
	}

	synchronized void waitForEventTime(int time, long timeout) {
		long deadline = System.currentTimeMillis() + timeout;

		if (timeout == 0) {
			deadline = Long.MAX_VALUE;
		}

		while (getEventTime() < time) {
			if (hasEnded()) {
				return;
			}

			long currentTime = System.currentTimeMillis();

			if (currentTime >= deadline) {
				return;
			}

			try {
				wait(deadline - currentTime);
			} catch (InterruptedException e) {
				// IGNORE
			}
		}
	}

	synchronized int getSize() {
		return members.size();
	}

	int getFixedSize() {
		return fixedSize;
	}

	public boolean isClosedWorld() {
		return closedWorld;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see ibis.ipl.impl.registry.central.SuperPool#ended()
	 */
	synchronized boolean hasEnded() {
		return ended;
	}

	synchronized boolean isClosed() {
		return closed;
	}

	synchronized void end() {
		ended = true;
		pusher.enqueue(null);
		if (statistics != null) {
			statistics.write();
			statistics.end();
		}
	}

	public void saveStatistics() {
		if (statistics != null) {
			statistics.write();
		}
	}

	String getName() {
		return name;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see ibis.ipl.impl.registry.central.SuperPool#join(byte[], byte[],
	 *      ibis.ipl.impl.Location)
	 */
	synchronized Member join(byte[] implementationData, byte[] clientAddress,
			Location location, String ibisImplementationIdentifier)
			throws IOException {
		if (hasEnded()) {
			throw new IOException("Pool already ended");
		}

		if (isClosed()) {
			throw new IOException("Pool already closed");
		}

		if (!ibisImplementationIdentifier
				.equals(this.ibisImplementationIdentifier)) {
			throw new IOException("Ibis implementation "
					+ ibisImplementationIdentifier
					+ " does not match pool's Ibis implementation: "
					+ this.ibisImplementationIdentifier);
		}
		logger.debug("ibis version: " + ibisImplementationIdentifier);

		String id = Integer.toString(nextID);
		nextID++;

		IbisIdentifier identifier = new IbisIdentifier(id, implementationData,
				clientAddress, location, name);

		Event event = addEvent(Event.JOIN, null, identifier);

		Member member = new Member(identifier, event);
		member.setCurrentTime(getMinEventTime());
		member.updateTime();

		members.add(member);

		if (logger.isDebugEnabled()) {
			logger.debug("members now: " + members);
		}

		if (statistics != null) {
			statistics.newPoolSize(members.size());
		}

		if (printEvents) {
			print(identifier + " joined pool \"" + name + "\" now "
					+ members.size() + " members");
		}

		if (closedWorld && nextID >= fixedSize) {
			close();
		}

		return member;
	}

	private synchronized void close() {
		if (closed) {
			return;
		}
		closed = true;
		closeEvent = addEvent(Event.POOL_CLOSED, null, null,
				new IbisIdentifier[0]);
		if (printEvents) {
			print("pool \"" + name + "\" now closed");
		}
	}

	void writeBootstrapList(DataOutputStream out) throws IOException {
		if (!peerBootstrap) {
			// send a list containing 0 members. It is not used anyway
			out.writeInt(0);
			return;
		}

		Member[] peers = getRandomMembers(BOOTSTRAP_LIST_SIZE);

		out.writeInt(peers.length);
		for (Member member : peers) {
			member.getIbis().writeTo(out);
		}

	}

	public void writeState(DataOutputStream out, int joinTime)
			throws IOException {

		ByteArrayOutputStream arrayOut = new ByteArrayOutputStream();
		DataOutputStream dataOut = new DataOutputStream(arrayOut);

		// create byte array of data
		synchronized (this) {
			dataOut.writeInt(currentEventTime);

			members.writeTo(dataOut);
			elections.writeTo(dataOut);

			Event[] signals = events
					.getSignalEvents(joinTime, currentEventTime);
			dataOut.writeInt(signals.length);
			for (Event event : signals) {
				event.writeTo(dataOut);
			}

			dataOut.writeBoolean(closed);
			if (closed) {
				closeEvent.writeTo(dataOut);
			}
			dataOut.writeBoolean(terminated);
			if (terminated) {
				terminateEvent.writeTo(dataOut);
			}
		}

		dataOut.flush();
		byte[] bytes = arrayOut.toByteArray();

		out.writeInt(bytes.length);
		out.write(bytes);

		logger.debug("pool state size = " + bytes.length);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see ibis.ipl.impl.registry.central.SuperPool#leave(ibis.ipl.impl.IbisIdentifier)
	 */
	synchronized void leave(IbisIdentifier identifier) throws Exception {
		if (members.remove(identifier) == null) {
			// May happen if it was declared dead before. So, no exception.
			// --Ceriel
			// logger.error("unknown ibis " + identifier + " tried to leave");
			// throw new Exception("ibis unknown: " + identifier);
			logger.debug("unknown ibis " + identifier + " tried to leave");
			return;
		}
		if (printEvents) {
			print(identifier + " left pool \"" + name + "\" now "
					+ members.size() + " members");
		}

		addEvent(Event.LEAVE, null, identifier);

		if (statistics != null) {
			statistics.newPoolSize(members.size());
		}

		Election[] deadElections = elections.getElectionsWonBy(identifier);

		for (Election election : deadElections) {
			addEvent(Event.UN_ELECT, election.getName(), election.getWinner());
			if (statistics != null) {
				statistics.electionEvent();
			}
		}

		if (members.size() == 0) {
			end();
			if (printEvents) {
				print("pool \"" + name + "\" ended");
			} else {
				logger.info("Central Registry: " + "pool \"" + name
						+ "\" ended");
			}
			notifyAll();

		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see ibis.ipl.impl.registry.central.SuperPool#dead(ibis.ipl.impl.IbisIdentifier)
	 */
	synchronized void dead(IbisIdentifier identifier, Exception exception) {
		Member member = members.remove(identifier);
		if (member == null) {
			// member removed already
			return;
		}

		if (printEvents) {
			if (printErrors) {
				print(identifier + " died in pool \"" + name + "\" now "
						+ members.size() + " members, caused by:");
				exception.printStackTrace(System.err);
			} else {
				print(identifier + " died in pool \"" + name + "\" now "
						+ members.size() + " members");
			}
		}

		addEvent(Event.DIED, null, identifier);

		if (statistics != null) {
			statistics.newPoolSize(members.size());
		}

		Election[] deadElections = elections.getElectionsWonBy(identifier);

		for (Election election : deadElections) {
			addEvent(Event.UN_ELECT, election.getName(), election.getWinner());
			if (statistics != null) {
				statistics.electionEvent();
			}

			elections.remove(election.getName());
		}

		if (members.size() == 0) {
			end();
			if (printEvents) {
				print("pool " + name + " ended");
			} else {
				logger.info("Central Registry: " + "pool \"" + name
						+ "\" ended");
			}
			notifyAll();
		}

		pusher.enqueue(member);
	}

	synchronized Event[] getEvents(int startTime) {
		return events.getList(startTime);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see ibis.ipl.impl.registry.central.SuperPool#elect(java.lang.String,
	 *      ibis.ipl.impl.IbisIdentifier)
	 */
	synchronized IbisIdentifier elect(String electionName,
			IbisIdentifier candidate) throws IOException {
		Election election = elections.get(electionName);

		if (election == null) {
			if (!members.contains(candidate)) {
				throw new IOException(candidate + " tries to win election "
						+ electionName + ", but is not a member of the pool");
			}

			// Do the election now. The caller WINS! :)

			Event event = addEvent(Event.ELECT, electionName, candidate);
			if (statistics != null) {
				statistics.electionEvent();
			}

			election = new Election(event);

			elections.put(election);

			if (printEvents) {
				print(candidate + " won election \"" + electionName
						+ "\" in pool \"" + name + "\"");
			}

		}

		return election.getWinner();
	}
{'fraction_non_alphanumeric': 0.11951392166116093, 'fraction_numerical': 0.0021236432279377066, 'mean_word_length': 3.7557508883486066, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6955114', 'n_tokens_mistral': 9122, 'n_tokens_neox': 7761, 'n_words': 1933}