Commit ded9cead authored by Grégory Mantelet's avatar Grégory Mantelet
Browse files

[UWS] Fix recurrent ConcurrentModificationException during service overload.

This error occurred generally during the backup process while trying to
backup the job list of a specific user. If several of his jobs were running
and changing state during the backup process, this
ConcurrentModificationException was thrown. This generally happens when the same
user submits a lot of shorts jobs in the same time.

This exception was due to a non thread-safe usage of
UWSParameters.additionalParams. To fix this issue, instead of creating it as a
normal HashMap, it is now created as a ConcurrentHashMap.

The same modification has also been applied to UWSParameters.params. In addition
of the replacement of HashMap into ConcurrentHashMap, all `synchronized` blocks
have been removed....there should not be needed any more.
parent 1f4bc6b1
Loading
Loading
Loading
Loading
+198 −211
Original line number Original line Diff line number Diff line
@@ -16,7 +16,7 @@ package uws.job.parameters;
 * You should have received a copy of the GNU Lesser General Public License
 * You should have received a copy of the GNU Lesser General Public License
 * along with UWSLibrary.  If not, see <http://www.gnu.org/licenses/>.
 * along with UWSLibrary.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 * Copyright 2012-2017 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
 * Copyright 2012-2018 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
 *                       Astronomisches Rechen Institut (ARI)
 *                       Astronomisches Rechen Institut (ARI)
 */
 */


@@ -32,6 +32,7 @@ import java.util.List;
import java.util.Map;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;


import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequest;


@@ -116,7 +117,7 @@ import uws.service.request.UploadFile;
 * <p><i><u>note 2:</u> If several values have been submitted for the same UWS standard parameter, just the last occurrence is taken into account.</i></p>
 * <p><i><u>note 2:</u> If several values have been submitted for the same UWS standard parameter, just the last occurrence is taken into account.</i></p>
 *
 *
 * @author Gr&eacute;gory Mantelet (CDS;ARI)
 * @author Gr&eacute;gory Mantelet (CDS;ARI)
 * @version 4.2 (09/2017)
 * @version 4.4 (07/2018)
 */
 */
public class UWSParameters implements Iterable<Entry<String, Object>> {
public class UWSParameters implements Iterable<Entry<String, Object>> {


@@ -138,7 +139,7 @@ public class UWSParameters implements Iterable<Entry<String,Object>> {
	/**
	/**
	 * List of all extracted parameters.
	 * List of all extracted parameters.
	 */
	 */
	protected final Map<String,Object> params = new HashMap<String,Object>(10);
	protected final Map<String, Object> params = new ConcurrentHashMap<String, Object>(10);


	/**
	/**
	 * <p>List of all UWS additional parameters.</p>
	 * <p>List of all UWS additional parameters.</p>
@@ -258,7 +259,8 @@ public class UWSParameters implements Iterable<Entry<String,Object>> {
		try{
		try{
			if (request.getAttribute(UWS.REQ_ATTRIBUTE_PARAMETERS) != null)
			if (request.getAttribute(UWS.REQ_ATTRIBUTE_PARAMETERS) != null)
				return (Map<String, Object>)request.getAttribute(UWS.REQ_ATTRIBUTE_PARAMETERS);
				return (Map<String, Object>)request.getAttribute(UWS.REQ_ATTRIBUTE_PARAMETERS);
		}catch(Exception e){} // 2 possible exceptions: ClassCastException and NullPointerException
		}catch(Exception e){
		} // 2 possible exceptions: ClassCastException and NullPointerException


		/* If there is no such attribute or if it is not of the good type,
		/* If there is no such attribute or if it is not of the good type,
		 * extract only application/x-www-form-urlencoded parameters: */
		 * extract only application/x-www-form-urlencoded parameters: */
@@ -392,12 +394,11 @@ public class UWSParameters implements Iterable<Entry<String,Object>> {
	/**
	/**
	 * <p>Set the default value to all missing parameters (ONLY for those who have a controller -&gt; see {@link InputParamController}).</p>
	 * <p>Set the default value to all missing parameters (ONLY for those who have a controller -&gt; see {@link InputParamController}).</p>
	 *
	 *
	 * <p><i><u>note:</u> This method is thread safe (synchronized on the list of parameters) !</i></p>
	 * <p><i><u>note:</u> This method is thread safe!</i></p>
	 */
	 */
	public final void init(){
	public final void init(){
		Iterator<Entry<String, InputParamController>> itControllers = getControllers();
		Iterator<Entry<String, InputParamController>> itControllers = getControllers();
		if (itControllers != null){
		if (itControllers != null){
			synchronized(params){
			Entry<String, InputParamController> entry;
			Entry<String, InputParamController> entry;
			while(itControllers.hasNext()){
			while(itControllers.hasNext()){
				entry = itControllers.next();
				entry = itControllers.next();
@@ -406,13 +407,12 @@ public class UWSParameters implements Iterable<Entry<String,Object>> {
			}
			}
		}
		}
	}
	}
	}


	/**
	/**
	 * <p>Updates this list of UWS parameters with the given ones. No check is done on the given parameters
	 * <p>Updates this list of UWS parameters with the given ones. No check is done on the given parameters
	 * (since they come from an instance of {@link UWSParameters}, they are supposed to be correct)</p>
	 * (since they come from an instance of {@link UWSParameters}, they are supposed to be correct)</p>
	 *
	 *
	 * <p><i><u>note:</u> This method is thread safe (synchronized on the list of parameters) !</i></p>
	 * <p><i><u>note:</u> This method is thread safe!</i></p>
	 *
	 *
	 * @param newParams	The parameters to update.
	 * @param newParams	The parameters to update.
	 *
	 *
@@ -422,7 +422,6 @@ public class UWSParameters implements Iterable<Entry<String,Object>> {
	 */
	 */
	public String[] update(final UWSParameters newParams) throws UWSException{
	public String[] update(final UWSParameters newParams) throws UWSException{
		if (newParams != null && !newParams.params.isEmpty()){
		if (newParams != null && !newParams.params.isEmpty()){
			synchronized(params){
			additionalParams = null;
			additionalParams = null;
			files = null;
			files = null;
			String[] updated = new String[newParams.params.size()];
			String[] updated = new String[newParams.params.size()];
@@ -445,7 +444,8 @@ public class UWSParameters implements Iterable<Entry<String,Object>> {
						if (oldValue != null && oldValue instanceof UploadFile){
						if (oldValue != null && oldValue instanceof UploadFile){
							try{
							try{
								((UploadFile)oldValue).deleteFile();
								((UploadFile)oldValue).deleteFile();
								}catch(IOException ioe){}
							}catch(IOException ioe){
							}
						}
						}
						// Perform the replacement:
						// Perform the replacement:
						params.put(entry.getKey(), entry.getValue());
						params.put(entry.getKey(), entry.getValue());
@@ -457,7 +457,6 @@ public class UWSParameters implements Iterable<Entry<String,Object>> {
				updated[i++] = entry.getKey();
				updated[i++] = entry.getKey();
			}
			}
			return updated;
			return updated;
			}
		}else
		}else
			return new String[0];
			return new String[0];
	}
	}
@@ -497,7 +496,6 @@ public class UWSParameters implements Iterable<Entry<String,Object>> {
	public final Iterator<UploadFile> getFiles(){
	public final Iterator<UploadFile> getFiles(){
		if (files == null){
		if (files == null){
			files = new ArrayList<UploadFile>(3);
			files = new ArrayList<UploadFile>(3);
			synchronized(params){
			for(Object v : params.values()){
			for(Object v : params.values()){
				if (v == null)
				if (v == null)
					continue;
					continue;
@@ -511,7 +509,6 @@ public class UWSParameters implements Iterable<Entry<String,Object>> {
				}
				}
			}
			}
		}
		}
		}
		return files.iterator();
		return files.iterator();
	}
	}


@@ -519,7 +516,7 @@ public class UWSParameters implements Iterable<Entry<String,Object>> {
	 * <p>Sets the given value to the specified parameter.
	 * <p>Sets the given value to the specified parameter.
	 * But if the given value is <i>null</i>, the specified parameter is merely removed.</p>
	 * But if the given value is <i>null</i>, the specified parameter is merely removed.</p>
	 *
	 *
	 * <p><i><u>note 1:</u> This method is thread safe (synchronized on the list of parameters) !</i></p>
	 * <p><i><u>note 1:</u> This method is thread safe!</i></p>
	 * <p><i><u>note 2:</u> The case of the parameter name MUST BE correct EXCEPT FOR the standard UWS parameters (i.e. runId, executionDuration, destructionTime).</i></p>
	 * <p><i><u>note 2:</u> The case of the parameter name MUST BE correct EXCEPT FOR the standard UWS parameters (i.e. runId, executionDuration, destructionTime).</i></p>
	 * <p><i><u>note 3:</u> If the specified parameter is a read-only UWS parameter (i.e. jobId, startTime, endTime, results, errorSummary, quote), this function does nothing and merely returns NULL.</i></p>
	 * <p><i><u>note 3:</u> If the specified parameter is a read-only UWS parameter (i.e. jobId, startTime, endTime, results, errorSummary, quote), this function does nothing and merely returns NULL.</i></p>
	 * <p><i><u>note 4:</u> A value equals to NULL, means that the specified parameter must be removed.</i></p>
	 * <p><i><u>note 4:</u> A value equals to NULL, means that the specified parameter must be removed.</i></p>
@@ -547,7 +544,6 @@ public class UWSParameters implements Iterable<Entry<String,Object>> {
		if (normalizedName == null)
		if (normalizedName == null)
			return null;
			return null;


		synchronized(params){
		additionalParams = null;
		additionalParams = null;
		files = null;
		files = null;


@@ -580,14 +576,14 @@ public class UWSParameters implements Iterable<Entry<String,Object>> {
			if (oldValue != null && oldValue instanceof UploadFile){
			if (oldValue != null && oldValue instanceof UploadFile){
				try{
				try{
					((UploadFile)oldValue).deleteFile();
					((UploadFile)oldValue).deleteFile();
					}catch(IOException ioe){}
				}catch(IOException ioe){
				}
			}
			}


			// Set the new value:
			// Set the new value:
			return params.put(normalizedName, value);
			return params.put(normalizedName, value);
		}
		}
	}
	}
	}


	/**
	/**
	 * Removes the value of the given input parameter name.
	 * Removes the value of the given input parameter name.
@@ -606,7 +602,6 @@ public class UWSParameters implements Iterable<Entry<String,Object>> {
		if (normalizedName == null)
		if (normalizedName == null)
			return null;
			return null;


		synchronized(params){
		additionalParams = null;
		additionalParams = null;
		files = null;
		files = null;
		// Remove the file:
		// Remove the file:
@@ -615,12 +610,12 @@ public class UWSParameters implements Iterable<Entry<String,Object>> {
		if (removed != null && removed instanceof UploadFile){
		if (removed != null && removed instanceof UploadFile){
			try{
			try{
				((UploadFile)removed).deleteFile();
				((UploadFile)removed).deleteFile();
				}catch(IOException ioe){}
			}catch(IOException ioe){
			}
		}
		}
		// Return the value of the removed parameter:
		// Return the value of the removed parameter:
		return removed;
		return removed;
	}
	}
	}


	/**
	/**
	 * Normalizes the given name.
	 * Normalizes the given name.
@@ -669,13 +664,11 @@ public class UWSParameters implements Iterable<Entry<String,Object>> {
	 * @return	The given phase.
	 * @return	The given phase.
	 */
	 */
	public final String getInputPhase(){
	public final String getInputPhase(){
		synchronized(params){
		if (hasInputPhase())
		if (hasInputPhase())
			return params.remove(UWSJob.PARAM_PHASE).toString();
			return params.remove(UWSJob.PARAM_PHASE).toString();
		else
		else
			return null;
			return null;
	}
	}
	}


	/**
	/**
	 * Gets the value of the parameter {@link UWSJob#PARAM_RUN_ID runId}.
	 * Gets the value of the parameter {@link UWSJob#PARAM_RUN_ID runId}.
@@ -697,9 +690,7 @@ public class UWSParameters implements Iterable<Entry<String,Object>> {
			else if (value instanceof String){
			else if (value instanceof String){
				try{
				try{
					Long duration = Long.parseLong((String)value);
					Long duration = Long.parseLong((String)value);
					synchronized(params){
					params.put(UWSJob.PARAM_EXECUTION_DURATION, duration);
					params.put(UWSJob.PARAM_EXECUTION_DURATION, duration);
					}
					return duration;
					return duration;
				}catch(NumberFormatException nfe){
				}catch(NumberFormatException nfe){
					;
					;
@@ -721,9 +712,7 @@ public class UWSParameters implements Iterable<Entry<String,Object>> {
			else if (value instanceof String){
			else if (value instanceof String){
				try{
				try{
					Date destruction = ISO8601Format.parseToDate((String)value);
					Date destruction = ISO8601Format.parseToDate((String)value);
					synchronized(params){
					params.put(UWSJob.PARAM_DESTRUCTION_TIME, destruction);
					params.put(UWSJob.PARAM_DESTRUCTION_TIME, destruction);
					}
					return destruction;
					return destruction;
				}catch(ParseException pe){
				}catch(ParseException pe){
					;
					;
@@ -743,8 +732,7 @@ public class UWSParameters implements Iterable<Entry<String,Object>> {
	 */
	 */
	public final Map<String, Object> getAdditionalParameters(){
	public final Map<String, Object> getAdditionalParameters(){
		if (additionalParams == null){
		if (additionalParams == null){
			synchronized(params){
			additionalParams = new ConcurrentHashMap<String, Object>(params.size());
				additionalParams = new HashMap<String,Object>(params.size());
			for(Entry<String, Object> entry : params.entrySet()){
			for(Entry<String, Object> entry : params.entrySet()){
				boolean uwsParam = false;
				boolean uwsParam = false;
				for(int i = 0; !uwsParam && i < UWS_RW_PARAMETERS.length; i++)
				for(int i = 0; !uwsParam && i < UWS_RW_PARAMETERS.length; i++)
@@ -755,7 +743,6 @@ public class UWSParameters implements Iterable<Entry<String,Object>> {
					additionalParams.put(entry.getKey(), entry.getValue());
					additionalParams.put(entry.getKey(), entry.getValue());
			}
			}
		}
		}
		}
		return additionalParams;
		return additionalParams;
	}
	}