<commit_before>package edu.harvard.econcs.turkserver.server;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicReference;

import org.apache.commons.configuration.Configuration;
import org.cometd.bayeux.server.ServerSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.andrewmao.misc.ConcurrentBooleanCounter;

import com.google.common.collect.ImmutableMap;
import com.google.inject.Inject;
import com.google.inject.name.Named;

import edu.harvard.econcs.turkserver.api.Configurator;
import edu.harvard.econcs.turkserver.api.HITWorker;
import edu.harvard.econcs.turkserver.config.TSConfig;

/**
 * Default lobby implementation where users that are ready are grouped into 
 * @author mao
 *
 */
public class ReadyStateLobby implements Lobby {
	
	protected final Logger logger = LoggerFactory.getLogger(getClass().getSimpleName());

	final boolean defaultStatus;
	final Configurator configurator;	
	
	private final ConcurrentBooleanCounter<HITWorkerImpl> lobbyStatus;		
	final AtomicReference<String> serverMessage;
	
	private LobbyListener lobbyListener;
	
	@Inject
	public ReadyStateLobby(
			@Named(TSConfig.EXP_CONFIGURATOR) Configurator configurator,
			Configuration conf
			) {
		
		this.configurator = configurator;
		this.defaultStatus = conf.getBoolean(TSConfig.SERVER_LOBBY_DEFAULT);
		
		lobbyStatus = new ConcurrentBooleanCounter<HITWorkerImpl>();								
		serverMessage = new AtomicReference<String>("");
		
	}

	@Override
	public void setListener(LobbyListener listener) {
		lobbyListener = listener;
	}

	@Override
	public void setMessage(String text) {
		serverMessage.set(text);
		
		/* publish the message to lobby
		 * It's fine to broadcast here as it is a rare occurrence
		 */
		broadcastLobbyStatus();		
	}

	@Override
	public void userJoined(HITWorkerImpl hitw) {
		/*
		 * NOTE: Seemed possible to remove synchronization from this method
		 * Giving it a shot with lobby unit tests
		 */
		
		// Put default lobby status in here
		lobbyStatus.put(hitw, defaultStatus);
		
		if( defaultStatus ) {
			// Create debug experiments if we have enough players, default to ready			
			logger.info("lobby has {} people, {} ready", lobbyStatus.size(), lobbyStatus.getTrueCount());			
			tryExperimentStart();			
		}

		// TODO remove this and let users send their own ready message
		broadcastLobbyStatus();
	}
	
	private synchronized boolean tryExperimentStart() {
		// Don't try anything if not enough people in lobby
		int expSize = configurator.groupSize(); 
		if( lobbyStatus.getTrueCount() < expSize ) return false;
		
		// Generate the list of experiment clients					
		
		HITWorkerGroupImpl expClients = new HITWorkerGroupImpl();

		// Count up exactly expSize people for the new experiment
		int counter = 0;
		for( Map.Entry<HITWorkerImpl, Boolean> e : lobbyStatus.entrySet() ) {						
			if( e.getValue() == true ) {							
				expClients.add(e.getKey());
				counter++;
			}

			// Don't put more than the required number of people, even if more are ready
			if(counter == expSize) break;
		}				
				
//		System.out.println("Current lobby before sending: " + lobbyStatus);
//		System.out.println("Lobby sending out a group: " + expClients);
		
		lobbyListener.createNewExperiment(expClients);
		
		/*
		 * NOTE: They must be in experiment first before we can safely remove from lobby
		 * 
		 * Avoids race condition where workers removed from lobby but not yet in experiment
		 * may get placed in the lobby a second time, causing big problems
		 */
		for( HITWorker id : expClients.getHITWorkers() )	
			lobbyStatus.remove((HITWorkerImpl) id);
		
//		System.out.println("Current lobby after sending: " + lobbyStatus);
		return true;
	}

	@Override
	public boolean updateStatus(HITWorkerImpl hitw, Map<String, Object> data) {
		
		boolean newStatus = defaultStatus || Boolean.parseBoolean(data.get("ready").toString());
		
		/*
		 * Ignore lobby updates for people not in lobby
		 * MONUMENT FOR MASSIVE DOUBLE-JOIN MYSTERY BUG
		 */
		Boolean oldStatus = lobbyStatus.replace(hitw, newStatus);
		
		/*
		 * Do nothing if user was already removed from lobby
		 * or there was no change to the status
		 */
		if( oldStatus == null || oldStatus == newStatus ) {
			broadcastLobbyStatus(); // TODO remove this once lobby updates are fixed
			return false;
		}
		
		tryExperimentStart();
		
		if( !defaultStatus && lobbyStatus.size() < configurator.groupSize() ) {				
			// Make sure everyone's ready is disabled
			for( HITWorkerImpl id : lobbyStatus.keySet() ) {
				lobbyStatus.replace(id, false);
			}
		}
		
		// Notify everyone who is remaining in the lobby
		broadcastLobbyStatus();
		return true;
	}

	@Override
	public boolean userQuit(HITWorkerImpl worker) {		
		// Was this dude in the lobby? If so remove him from the lobby and notify lobby ppl
		if( worker == null ) return false;
		
		synchronized(this) {
			 if( lobbyStatus.remove(worker) == null ) return false;
		}
		
		logger.info(String.format("%s (%s) removed from lobby",
				worker.getHitId(), worker.getUsername()));	<commit_msg>updated reset after someone leaves in lobby<commit_after>package edu.harvard.econcs.turkserver.server;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicReference;

import org.apache.commons.configuration.Configuration;
import org.cometd.bayeux.server.ServerSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.andrewmao.misc.ConcurrentBooleanCounter;

import com.google.common.collect.ImmutableMap;
import com.google.inject.Inject;
import com.google.inject.name.Named;

import edu.harvard.econcs.turkserver.api.Configurator;
import edu.harvard.econcs.turkserver.api.HITWorker;
import edu.harvard.econcs.turkserver.config.TSConfig;

/**
 * Default lobby implementation where users that are ready are grouped into 
 * @author mao
 *
 */
public class ReadyStateLobby implements Lobby {
	
	protected final Logger logger = LoggerFactory.getLogger(getClass().getSimpleName());

	final boolean defaultStatus;
	final Configurator configurator;	
	
	private final ConcurrentBooleanCounter<HITWorkerImpl> lobbyStatus;		
	final AtomicReference<String> serverMessage;
	
	private LobbyListener lobbyListener;
	
	@Inject
	public ReadyStateLobby(
			@Named(TSConfig.EXP_CONFIGURATOR) Configurator configurator,
			Configuration conf
			) {
		
		this.configurator = configurator;
		this.defaultStatus = conf.getBoolean(TSConfig.SERVER_LOBBY_DEFAULT);
		
		lobbyStatus = new ConcurrentBooleanCounter<HITWorkerImpl>();								
		serverMessage = new AtomicReference<String>("");
		
	}

	@Override
	public void setListener(LobbyListener listener) {
		lobbyListener = listener;
	}

	@Override
	public void setMessage(String text) {
		serverMessage.set(text);
		
		/* publish the message to lobby
		 * It's fine to broadcast here as it is a rare occurrence
		 */
		broadcastLobbyStatus();		
	}

	@Override
	public void userJoined(HITWorkerImpl hitw) {
		/*
		 * NOTE: Seemed possible to remove synchronization from this method
		 * Giving it a shot with lobby unit tests
		 */
		
		// Put default lobby status in here
		lobbyStatus.put(hitw, defaultStatus);
		
		if( defaultStatus ) {
			// Create debug experiments if we have enough players, default to ready			
			logger.info("lobby has {} people, {} ready", lobbyStatus.size(), lobbyStatus.getTrueCount());			
			tryExperimentStart();			
		}

		// TODO remove this and let users send their own ready message
		broadcastLobbyStatus();
	}
	
	private synchronized boolean tryExperimentStart() {
		// Don't try anything if not enough people in lobby
		int expSize = configurator.groupSize(); 
		if( lobbyStatus.getTrueCount() < expSize ) return false;
		
		// Generate the list of experiment clients					
		
		HITWorkerGroupImpl expClients = new HITWorkerGroupImpl();

		// Count up exactly expSize people for the new experiment
		int counter = 0;
		for( Map.Entry<HITWorkerImpl, Boolean> e : lobbyStatus.entrySet() ) {						
			if( e.getValue() == true ) {							
				expClients.add(e.getKey());
				counter++;
			}

			// Don't put more than the required number of people, even if more are ready
			if(counter == expSize) break;
		}				
				
//		System.out.println("Current lobby before sending: " + lobbyStatus);
//		System.out.println("Lobby sending out a group: " + expClients);
		
		lobbyListener.createNewExperiment(expClients);
		
		/*
		 * NOTE: They must be in experiment first before we can safely remove from lobby
		 * 
		 * Avoids race condition where workers removed from lobby but not yet in experiment
		 * may get placed in the lobby a second time, causing big problems
		 */
		for( HITWorker id : expClients.getHITWorkers() )	
			lobbyStatus.remove((HITWorkerImpl) id);
		
//		System.out.println("Current lobby after sending: " + lobbyStatus);
		return true;
	}

	@Override
	public boolean updateStatus(HITWorkerImpl hitw, Map<String, Object> data) {
		
		boolean newStatus = defaultStatus || Boolean.parseBoolean(data.get("ready").toString());
		
		/*
		 * Ignore lobby updates for people not in lobby
		 * MONUMENT FOR MASSIVE DOUBLE-JOIN MYSTERY BUG
		 */
		Boolean oldStatus = lobbyStatus.replace(hitw, newStatus);
		
		/*
		 * Do nothing if user was already removed from lobby
		 * or there was no change to the status
		 */
		if( oldStatus == null || oldStatus == newStatus ) {
			broadcastLobbyStatus(); // TODO remove this once lobby updates are fixed
			return false;
		}
		
		tryExperimentStart();
		
		checkLobbyState();
		
		// Notify everyone who is remaining in the lobby
		broadcastLobbyStatus();
		return true;
	}

	void checkLobbyState() {
		if( !defaultStatus && lobbyStatus.size() < configurator.groupSize() ) {				
			// Make sure everyone's ready is disabled
			for( HITWorkerImpl id : lobbyStatus.keySet() ) {
				lobbyStatus.replace(id, false);
			}
		}
	}

	@Override
	public boolean userQuit(HITWorkerImpl worker) {		
		// Was this dude in the lobby? If so remove him from the lobby and notify lobby ppl
		if( worker == null ) return false;
		
		synchronized(this) {
			 if( lobbyStatus.remove(worker) == null ) return false;
		}
		
		logger.info(String.format("%s (%s) removed from lobby",
				worker.getHitId(), worker.getUsername()));	
		
		checkLobbyState();
{'fraction_non_alphanumeric': 0.09541143787719131, 'fraction_numerical': 0.0005747676980553693, 'mean_word_length': 3.7736625514403292, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 15, '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': '5939842', 'n_tokens_mistral': 3523, 'n_tokens_neox': 3014, 'n_words': 910}