Skip to content
Snippets Groups Projects
Commit d9041712 authored by gmantele's avatar gmantele
Browse files

[TAP & UWS] 2 MAJOR BUGS FIXED (these bugs were affecting performances).

1) [TAP & UWS] ]MAJOR BUG FIX: The abortion of an SQL query is now correctly
implemented. Before this fix, 2 mistakes prevented this clean abortion:
  a/ The thread was not cancelled because the SQL query execution was
blocking the thread. Then the thread could not treat the interruption though
it was flagged as interrupted.
  b/ The function UWSJob.isStopped() considered the job as stopped because
the interrupted flag was set, even though the thread was still processing
(and the database too). Because of that it returned true and the job phase
was ABORTED though the thread was still running.
  NOW:
  a/ TAPJob calls the function Statement.cancel() (if supported) in order
to cancel the SQL query execution properly inside the database.
  b/ The function UWSJob.isStopped() does not test any more the interrupted flag
and returns true only if the thread is really stopped.
  IN BRIEF: It is now sure that a job in the phase ABORTED is really stopped
(that's to say: thread stopped AND DB query execution stopped).

2) [TAP] BUG FIX: When the writing of a result is abnormaly interrupted for any
reason, the file which was being written is deleted.
parent 2b505506
Branches
No related tags found
No related merge requests found
......@@ -104,7 +104,7 @@ import adql.query.ADQLQuery;
* </p>
*
* @author Gr&eacute;gory Mantelet (CDS;ARI)
* @version 2.0 (04/2015)
* @version 2.1 (11/2015)
*/
public class ADQLExecutor {
......@@ -224,9 +224,20 @@ public class ADQLExecutor {
try{
return start();
}catch(IOException ioe){
if (thread.isInterrupted())
return report;
else
throw new UWSException(ioe);
}catch(TAPException te){
if (thread.isInterrupted())
return report;
else
throw new UWSException(te.getHttpErrorCode(), te);
}catch(UWSException ue){
if (thread.isInterrupted())
return report;
else
throw ue;
}
}
......@@ -249,6 +260,17 @@ public class ADQLExecutor {
dbConn = service.getFactory().getConnection(jobID);
}
/**
* Cancel the current SQL query execution or result set fetching if any is currently running.
* If no such process is on going, this function has no effect.
*
* @since 2.1
*/
public final void cancelQuery(){
if (dbConn != null && progression == ExecutionProgression.EXECUTING_ADQL)
dbConn.cancel(true);
}
/**
* <p>Start the synchronous processing of the ADQL query.</p>
*
......@@ -625,12 +647,14 @@ public class ADQLExecutor {
}
// CASE ASYNCHRONOUS:
else{
boolean completed = false;
long start = -1, end = -1;
Result result = null;
JobThread jobThread = (JobThread)thread;
try{
// Create a UWS Result object to store the result
// (the result will be stored in a file and this object is the association between the job and the result file):
JobThread jobThread = (JobThread)thread;
Result result = jobThread.createResult();
result = jobThread.createResult();
// Set the MIME type of the result format in the result description:
result.setMimeType(formatter.getMimeType());
......@@ -646,10 +670,25 @@ public class ADQLExecutor {
// Add the result description and link in the job description:
jobThread.publishResult(result);
completed = true;
logger.logTAP(LogLevel.INFO, report, "RESULT_WRITTEN", "Result formatted (in " + formatter.getMimeType() + " ; " + (report.nbRows < 0 ? "?" : report.nbRows) + " rows ; " + ((report.resultingColumns == null) ? "?" : report.resultingColumns.length) + " columns) in " + ((start <= 0 || end <= 0) ? "?" : (end - start)) + "ms!", null);
}catch(IOException ioe){
// Propagate the exception:
throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, ioe, "Impossible to write in the file into the result of the job " + report.jobID + " must be written!");
}finally{
if (!completed){
// Delete the result file (it is either incomplete or incorrect ;
// it is then not reliable and is anyway not associated with the job and so could not be later deleted when the job will be):
if (result != null){
try{
service.getFileManager().deleteResult(result, jobThread.getJob());
}catch(IOException ioe){
logger.logTAP(LogLevel.ERROR, report, "WRITING_RESULT", "The result writting has failed and the produced partial result must be deleted, but this deletion also failed! (job: " + report.jobID + ")", ioe);
}
}
}
}
}
}
......
......@@ -45,7 +45,7 @@ import uws.service.log.UWSLog.LogLevel;
* </p>
*
* @author Gr&eacute;gory Mantelet (CDS;ARI)
* @version 2.0 (04/2015)
* @version 2.1 (11/2015)
*/
public class TAPJob extends UWSJob {
private static final long serialVersionUID = 1L;
......@@ -341,6 +341,31 @@ public class TAPJob extends UWSJob {
}
}
/** @since 2.1 */
@Override
protected void stop(){
if (!isStopped()){
synchronized(thread){
stopping = true;
// Interrupts the thread:
thread.interrupt();
// Cancel the query execution if any currently running:
((AsyncThread)thread).executor.cancelQuery();
// Wait a little for its end:
if (waitForStop > 0){
try{
thread.join(waitForStop);
}catch(InterruptedException ie){
getLogger().logJob(LogLevel.WARNING, this, "END", "Unexpected InterruptedException while waiting for the end of the execution of the job \"" + jobId + "\" (thread ID: " + thread.getId() + ")!", ie);
}
}
}
}
}
/**
* This exception is thrown by a job execution when no database connection are available anymore.
*
......
......@@ -74,7 +74,7 @@ import adql.translator.PostgreSQLTranslator;
* </p>
*
* @author Gr&eacute;gory Mantelet (ARI)
* @version 2.0 (04/2015)
* @version 2.1 (11/2015)
* @since 2.0
*/
public final class ConfigurableTAPFactory extends AbstractTAPFactory {
......@@ -281,6 +281,9 @@ public final class ConfigurableTAPFactory extends AbstractTAPFactory {
@Override
public void freeConnection(DBConnection conn){
try{
// Cancel any possible query that could be running:
conn.cancel(false);
// Close the connection (if a connection pool is used, the connection is not really closed but is freed and kept in the pool for further usage):
((JDBCConnection)conn).getInnerConnection().close();
}catch(SQLException se){
service.getLogger().error("Can not close properly the connection \"" + conn.getID() + "\"!", se);
......
......@@ -22,6 +22,7 @@ package tap.data;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.NoSuchElementException;
......@@ -42,11 +43,16 @@ import adql.translator.JDBCTranslator;
* </i></p>
*
* @author Gr&eacute;gory Mantelet (ARI)
* @version 2.1 (10/2015)
* @version 2.1 (11/2015)
* @since 2.0
*/
public class ResultSetTableIterator implements TableIterator {
/** Statement associated with the ResultSet/Dataset to read.
* <i>MAY be NULL</i>
* @since 2.1 */
private final Statement stmt;
/** ResultSet/Dataset to read. */
private final ResultSet data;
......@@ -90,10 +96,41 @@ public class ResultSetTableIterator implements TableIterator {
* @throws DataReadException If the given ResultSet is closed or if the metadata (columns count and types) can not be fetched.
*
* @see #convertType(int, String, String)
* @see #ResultSetTableIterator(ResultSet, JDBCTranslator, String, DBColumn[])
* @see #ResultSetTableIterator(Statement, ResultSet, JDBCTranslator, String, DBColumn[])
*/
public ResultSetTableIterator(final ResultSet dataSet) throws NullPointerException, DataReadException{
this(dataSet, null, null, null);
this(null, dataSet, null, null, null);
}
/**
* <p>Build a TableIterator able to read rows and columns of the given ResultSet.</p>
*
* <p>
* In order to provide the metadata through {@link #getMetadata()}, this constructor is trying to guess the datatype
* from the DBMS column datatype (using {@link #convertType(int, String, String)}).
* </p>
*
* <h3>Type guessing</h3>
*
* <p>
* In order to guess a TAP type from a DBMS type, this constructor will call {@link #convertType(int, String, String)}
* which deals with the most common standard datatypes known in Postgres, SQLite, MySQL, Oracle and JavaDB/Derby.
* This conversion is therefore not as precise as the one expected by a translator. That's why it is recommended
* to use one of the constructor having a {@link JDBCTranslator} in parameter.
* </p>
*
* @param dataSet Dataset over which this iterator must iterate.
*
* @throws NullPointerException If NULL is given in parameter.
* @throws DataReadException If the given ResultSet is closed or if the metadata (columns count and types) can not be fetched.
*
* @see #convertType(int, String, String)
* @see #ResultSetTableIterator(Statement, ResultSet, JDBCTranslator, String, DBColumn[])
*
* @since 2.1
*/
public ResultSetTableIterator(final Statement stmt, final ResultSet dataSet) throws NullPointerException, DataReadException{
this(stmt, dataSet, null, null, null);
}
/**
......@@ -127,10 +164,49 @@ public class ResultSetTableIterator implements TableIterator {
* @throws DataReadException If the given ResultSet is closed or if the metadata (columns count and types) can not be fetched.
*
* @see #convertType(int, String, String)
* @see ResultSetTableIterator#ResultSetTableIterator(ResultSet, JDBCTranslator, String, DBColumn[])
* @see #ResultSetTableIterator(Statement, ResultSet, JDBCTranslator, String, DBColumn[])
*/
public ResultSetTableIterator(final ResultSet dataSet, final String dbms) throws NullPointerException, DataReadException{
this(dataSet, null, dbms, null);
this(null, dataSet, null, dbms, null);
}
/**
* <p>Build a TableIterator able to read rows and columns of the given ResultSet.</p>
*
* <p>
* In order to provide the metadata through {@link #getMetadata()}, this constructor is trying to guess the datatype
* from the DBMS column datatype (using {@link #convertType(int, String, String)}).
* </p>
*
* <h3>Type guessing</h3>
*
* <p>
* In order to guess a TAP type from a DBMS type, this constructor will call {@link #convertType(int, String, String)}
* which deals with the most common standard datatypes known in Postgres, SQLite, MySQL, Oracle and JavaDB/Derby.
* This conversion is therefore not as precise as the one expected by a translator. That's why it is recommended
* to use one of the constructor having a {@link JDBCTranslator} in parameter.
* </p>
*
* <p><i><b>Important</b>:
* The second parameter of this constructor is given as second parameter of {@link #convertType(int, String, String)}.
* <b>This parameter is really used ONLY when the DBMS is SQLite ("sqlite").</b>
* Indeed, SQLite has so many datatype restrictions that it is absolutely needed to know it is the DBMS from which the
* ResultSet is coming. Without this information, type guessing will be unpredictable!
* </i></p>
*
* @param dataSet Dataset over which this iterator must iterate.
* @param dbms Lower-case string which indicates from which DBMS the given ResultSet is coming. <i>note: MAY be NULL.</i>
*
* @throws NullPointerException If NULL is given in parameter.
* @throws DataReadException If the given ResultSet is closed or if the metadata (columns count and types) can not be fetched.
*
* @see #convertType(int, String, String)
* @see #ResultSetTableIterator(Statement, ResultSet, JDBCTranslator, String, DBColumn[])
*
* @since 2.1
*/
public ResultSetTableIterator(final Statement stmt, final ResultSet dataSet, final String dbms) throws NullPointerException, DataReadException{
this(stmt, dataSet, null, dbms, null);
}
/**
......@@ -159,10 +235,44 @@ public class ResultSetTableIterator implements TableIterator {
* @throws DataReadException If the given ResultSet is closed or if the metadata (columns count and types) can not be fetched.
*
* @see #convertType(int, String, String)
* @see ResultSetTableIterator#ResultSetTableIterator(ResultSet, JDBCTranslator, String, DBColumn[])
* @see #ResultSetTableIterator(Statement, ResultSet, JDBCTranslator, String, DBColumn[])
*/
public ResultSetTableIterator(final ResultSet dataSet, final JDBCTranslator translator) throws NullPointerException, DataReadException{
this(dataSet, translator, null, null);
this(null, dataSet, translator, null, null);
}
/**
* <p>Build a TableIterator able to read rows and columns of the given ResultSet.</p>
*
* <p>
* In order to provide the metadata through {@link #getMetadata()}, this constructor is trying to guess the datatype
* from the DBMS column datatype (using {@link #convertType(int, String, String)}).
* </p>
*
* <h3>Type guessing</h3>
*
* <p>
* In order to guess a TAP type from a DBMS type, this constructor will call {@link #convertType(int, String, String)}
* which will ask to the given translator ({@link JDBCTranslator#convertTypeFromDB(int, String, String, String[])})
* if not NULL. However if no translator is provided, this function will proceed to a default conversion
* using the most common standard datatypes known in Postgres, SQLite, MySQL, Oracle and JavaDB/Derby.
* This conversion is therefore not as precise as the one expected by the translator.
* </p>
*
* @param dataSet Dataset over which this iterator must iterate.
* @param translator The {@link JDBCTranslator} used to transform the ADQL query into SQL query. This translator is also able to convert
* JDBC types and to parse geometrical values. <i>note: MAY be NULL</i>
*
* @throws NullPointerException If NULL is given in parameter.
* @throws DataReadException If the given ResultSet is closed or if the metadata (columns count and types) can not be fetched.
*
* @see #convertType(int, String, String)
* @see #ResultSetTableIterator(Statement, ResultSet, JDBCTranslator, String, DBColumn[])
*
* @since 2.1
*/
public ResultSetTableIterator(final Statement stmt, final ResultSet dataSet, final JDBCTranslator translator) throws NullPointerException, DataReadException{
this(stmt, dataSet, translator, null, null);
}
/**
......@@ -199,10 +309,52 @@ public class ResultSetTableIterator implements TableIterator {
* @throws DataReadException If the given ResultSet is closed or if the metadata (columns count and types) can not be fetched.
*
* @see #convertType(int, String, String)
* @see ResultSetTableIterator#ResultSetTableIterator(ResultSet, JDBCTranslator, String, DBColumn[])
* @see #ResultSetTableIterator(Statement, ResultSet, JDBCTranslator, String, DBColumn[])
*/
public ResultSetTableIterator(final ResultSet dataSet, final JDBCTranslator translator, final String dbms) throws NullPointerException, DataReadException{
this(dataSet, translator, dbms, null);
this(null, dataSet, translator, dbms, null);
}
/**
* <p>Build a TableIterator able to read rows and columns of the given ResultSet.</p>
*
* <p>
* In order to provide the metadata through {@link #getMetadata()}, this constructor is trying to guess the datatype
* from the DBMS column datatype (using {@link #convertType(int, String, String)}).
* </p>
*
* <h3>Type guessing</h3>
*
* <p>
* In order to guess a TAP type from a DBMS type, this constructor will call {@link #convertType(int, String, String)}
* which will ask to the given translator ({@link JDBCTranslator#convertTypeFromDB(int, String, String, String[])})
* if not NULL. However if no translator is provided, this function will proceed to a default conversion
* using the most common standard datatypes known in Postgres, SQLite, MySQL, Oracle and JavaDB/Derby.
* This conversion is therefore not as precise as the one expected by the translator.
* </p>
*
* <p><i><b>Important</b>:
* The third parameter of this constructor is given as second parameter of {@link #convertType(int, String, String)}.
* <b>This parameter is really used ONLY when the translator conversion failed and when the DBMS is SQLite ("sqlite").</b>
* Indeed, SQLite has so many datatype restrictions that it is absolutely needed to know it is the DBMS from which the
* ResultSet is coming. Without this information, type guessing will be unpredictable!
* </i></p>
*
* @param dataSet Dataset over which this iterator must iterate.
* @param translator The {@link JDBCTranslator} used to transform the ADQL query into SQL query. This translator is also able to convert
* JDBC types and to parse geometrical values. <i>note: MAY be NULL</i>
* @param dbms Lower-case string which indicates from which DBMS the given ResultSet is coming. <i>note: MAY be NULL.</i>
*
* @throws NullPointerException If NULL is given in parameter.
* @throws DataReadException If the given ResultSet is closed or if the metadata (columns count and types) can not be fetched.
*
* @see #convertType(int, String, String)
* @see #ResultSetTableIterator(Statement, ResultSet, JDBCTranslator, String, DBColumn[])
*
* @since 2.1
*/
public ResultSetTableIterator(final Statement stmt, final ResultSet dataSet, final JDBCTranslator translator, final String dbms) throws NullPointerException, DataReadException{
this(stmt, dataSet, translator, dbms, null);
}
/**
......@@ -256,12 +408,74 @@ public class ResultSetTableIterator implements TableIterator {
* @throws DataReadException If the metadata (columns count and types) can not be fetched.
*
* @see #convertType(int, String, String)
* @see #ResultSetTableIterator(Statement, ResultSet, JDBCTranslator, String, DBColumn[])
*/
public ResultSetTableIterator(final ResultSet dataSet, final JDBCTranslator translator, final String dbms, final DBColumn[] resultMeta) throws NullPointerException, DataReadException{
this(null, dataSet, translator, dbms, resultMeta);
}
/**
* <p>Build a TableIterator able to read rows and columns of the given ResultSet.</p>
*
* <p>
* In order to provide the metadata through {@link #getMetadata()}, this constructor is reading first the given metadata (if any),
* and then, try to guess the datatype from the DBMS column datatype (using {@link #convertType(int, String, String)}).
* </p>
*
* <h3>Provided metadata</h3>
*
* <p>The third parameter of this constructor aims to provide the metadata expected for each column of the ResultSet.</p>
*
* <p>
* For that, it is expected that all these metadata are {@link TAPColumn} objects. Indeed, simple {@link DBColumn}
* instances do not have the type information. If just {@link DBColumn}s are provided, the ADQL name it provides will be kept
* but the type will be guessed from the type provide by the ResultSetMetadata.
* </p>
*
* <p><i>Note:
* If this parameter is incomplete (array length less than the column count returned by the ResultSet or some array items are NULL),
* column metadata will be associated in the same order as the ResultSet columns. Missing metadata will be built from the
* {@link ResultSetMetaData} and so the types will be guessed.
* </i></p>
*
* <h3>Type guessing</h3>
*
* <p>
* In order to guess a TAP type from a DBMS type, this constructor will call {@link #convertType(int, String, String)}
* which will ask to the given translator ({@link JDBCTranslator#convertTypeFromDB(int, String, String, String[])})
* if not NULL. However if no translator is provided, this function will proceed to a default conversion
* using the most common standard datatypes known in Postgres, SQLite, MySQL, Oracle and JavaDB/Derby.
* This conversion is therefore not as precise as the one expected by the translator.
* </p>
*
* <p><i><b>Important</b>:
* The third parameter of this constructor is given as second parameter of {@link #convertType(int, String, String)}.
* <b>This parameter is really used ONLY when the translator conversion failed and when the DBMS is SQLite ("sqlite").</b>
* Indeed, SQLite has so many datatype restrictions that it is absolutely needed to know it is the DBMS from which the
* ResultSet is coming. Without this information, type guessing will be unpredictable!
* </i></p>
*
* @param dataSet Dataset over which this iterator must iterate.
* @param translator The {@link JDBCTranslator} used to transform the ADQL query into SQL query. This translator is also able to convert
* JDBC types and to parse geometrical values. <i>note: MAY be NULL</i>
* @param dbms Lower-case string which indicates from which DBMS the given ResultSet is coming. <i>note: MAY be NULL.</i>
* @param resultMeta List of expected columns. <i>note: these metadata are expected to be really {@link TAPColumn} objects ; MAY be NULL.</i>
*
* @throws NullPointerException If NULL is given in parameter.
* @throws DataReadException If the metadata (columns count and types) can not be fetched.
*
* @see #convertType(int, String, String)
*
* @since 2.1
*/
public ResultSetTableIterator(final Statement stmt, final ResultSet dataSet, final JDBCTranslator translator, final String dbms, final DBColumn[] resultMeta) throws NullPointerException, DataReadException{
// A dataset MUST BE provided:
if (dataSet == null)
throw new NullPointerException("Missing ResultSet object over which to iterate!");
// Set the associated statement:
this.stmt = stmt;
// Keep a reference to the ResultSet:
data = dataSet;
......@@ -296,10 +510,17 @@ public class ResultSetTableIterator implements TableIterator {
@Override
public void close() throws DataReadException{
boolean rsClosed = false;
try{
data.close();
rsClosed = true;
if (stmt != null)
stmt.close();
}catch(SQLException se){
if (!rsClosed)
throw new DataReadException("Can not close the iterated ResultSet!", se);
else
throw new DataReadException("ResultSet successfully closed but impossible to closed the associated Statement!", se);
}
}
......
......@@ -42,7 +42,7 @@ import adql.query.ADQLQuery;
* </p>
*
* @author Gr&eacute;gory Mantelet (CDS;ARI)
* @version 2.0 (03/2015)
* @version 2.1 (11/2015)
*/
public interface DBConnection {
......@@ -268,4 +268,31 @@ public interface DBConnection {
*/
public void setFetchSize(final int size);
/**
* <p>Stop the execution of the current query.</p>
*
* <p>
* If asked. a rollback of the current transaction can also be performed
* after the cancellation (if successful) of the query.
* </p>
*
* <p>
* This function should <b>never</b> return any kind of exception. This is particularly important
* in the following cases:
* </p>
* <ul>
* <li>this function is not implemented</li>
* <li>the database driver or another API used to interact with a "database" does not support the cancel operation</li>
* <li>no query is currently running</li>
* <li>a rollback is not possible or failed</li>
* </ul>
* <p>However, if an exception occurs it may be directly logged at least as a WARNING.</p>
*
* @param rollback <code>true</code> to cancel the statement AND rollback the current connection transaction,
* <code>false</code> to just cancel the statement.
*
* @since 2.1
*/
public void cancel(final boolean rollback);
}
This diff is collapsed.
......@@ -36,7 +36,7 @@ import uk.ac.starlink.table.StoragePolicy;
* Format any given query (table) result into FITS.
*
* @author Gr&eacute;gory Mantelet (ARI)
* @version 2.0 (04/2015)
* @version 2.1 (11/2015)
* @since 2.0
*/
public class FITSFormat implements OutputFormat {
......@@ -84,15 +84,21 @@ public class FITSFormat implements OutputFormat {
ColumnInfo[] colInfos = VOTableFormat.toColumnInfos(result, execReport, thread);
// Turns the result set into a table:
LimitedStarTable table = new LimitedStarTable(result, colInfos, execReport.parameters.getMaxRec());
LimitedStarTable table = new LimitedStarTable(result, colInfos, execReport.parameters.getMaxRec(), thread);
// Copy the table on disk (or in memory if the table is short):
StarTable copyTable = StoragePolicy.PREFER_DISK.copyTable(table);
if (thread.isInterrupted())
throw new InterruptedException();
/* Format the table in FITS (2 passes are needed for that, hence the copy on disk),
* and write it in the given output stream: */
new FitsTableWriter().writeStarTable(copyTable, output);
if (thread.isInterrupted())
throw new InterruptedException();
execReport.nbRows = table.getNbReadRows();
output.flush();
......
......@@ -83,7 +83,7 @@ import adql.db.DBType.DBDatatype;
* </p>
*
* @author Gr&eacute;gory Mantelet (CDS;ARI)
* @version 2.1 (07/2015)
* @version 2.1 (11/2015)
*/
public class VOTableFormat implements OutputFormat {
......@@ -330,7 +330,7 @@ public class VOTableFormat implements OutputFormat {
ColumnInfo[] colInfos = toColumnInfos(queryResult, execReport, thread);
/* Turns the result set into a table. */
LimitedStarTable table = new LimitedStarTable(queryResult, colInfos, execReport.parameters.getMaxRec());
LimitedStarTable table = new LimitedStarTable(queryResult, colInfos, execReport.parameters.getMaxRec(), thread);
/* Prepares the object that will do the serialization work. */
VOSerializer voser = VOSerializer.makeSerializer(votFormat, votVersion, table);
......@@ -347,6 +347,9 @@ public class VOTableFormat implements OutputFormat {
execReport.nbRows = table.getNbReadRows();
out.flush();
if (thread.isInterrupted())
throw new InterruptedException();
/* Check for overflow and write INFO if required. */
if (table.lastSequenceOverflowed()){
out.write("<INFO name=\"QUERY_STATUS\" value=\"OVERFLOW\"/>");
......@@ -588,7 +591,7 @@ public class VOTableFormat implements OutputFormat {
* </p>
*
* @author Gr&eacute;gory Mantelet (CDS;ARI)
* @version 2.0 (10/2014)
* @version 2.1 (11/2015)
* @since 2.0
*/
public static class LimitedStarTable extends AbstractStarTable {
......@@ -602,6 +605,10 @@ public class VOTableFormat implements OutputFormat {
/** Iterator over the data to read using this special {@link StarTable} */
private final TableIterator tableIt;
/** Thread covering this execution. If it is interrupted, the writing must stop as soon as possible.
* @since 2.1 */
private final Thread threadToWatch;
/** Limit on the number of rows to read. Over this limit, an "overflow" event occurs and {@link #overflow} is set to TRUE. */
private final long maxrec;
......@@ -620,9 +627,11 @@ public class VOTableFormat implements OutputFormat {
* @param tableIt Data on which to iterate using this special {@link StarTable}.
* @param colInfos Information about all columns.
* @param maxrec Limit on the number of rows to read. <i>(if negative, there will be no limit)</i>
* @param thread Parent thread. When an interruption is detected the writing must stop as soon as possible.
*/
LimitedStarTable(final TableIterator tableIt, final ColumnInfo[] colInfos, final long maxrec){
LimitedStarTable(final TableIterator tableIt, final ColumnInfo[] colInfos, final long maxrec, final Thread thread){
this.tableIt = tableIt;
this.threadToWatch = thread;
nbCol = colInfos.length;
columnInfos = colInfos;
this.maxrec = maxrec;
......@@ -675,7 +684,7 @@ public class VOTableFormat implements OutputFormat {
public boolean next() throws IOException{
irow++;
try{
if (maxrec < 0 || irow < maxrec){
if (!threadToWatch.isInterrupted() && (maxrec < 0 || irow < maxrec)){
boolean hasNext = tableIt.nextRow();
if (hasNext){
for(int i = 0; i < nbCol && tableIt.hasNextCol(); i++)
......
......@@ -1320,7 +1320,7 @@ public class UWSJob extends SerializableUWSObject {
// Set the end time:
setEndTime(new Date());
}else if (thread == null || (thread != null && !thread.isAlive()))
}else if ((thread == null || (thread != null && !thread.isAlive())) && phase.getPhase() != ExecutionPhase.ABORTED)
throw new UWSException(UWSException.BAD_REQUEST, UWSExceptionFactory.incorrectPhaseTransition(getJobId(), phase.getPhase(), ExecutionPhase.ABORTED));
}else
getLogger().logJob(LogLevel.WARNING, this, "ABORT", "Abortion of the job \"" + getJobId() + "\" asked but not yet effective (after having waited " + waitForStop + "ms)!", null);
......@@ -1391,12 +1391,20 @@ public class UWSJob extends SerializableUWSObject {
}
/**
* Tells whether the thread is different from <i>null</i>, is not alive, is interrupted or is finished (see {@link JobThread#isFinished()}).
* <p>Tells whether the thread is different from <i>null</i>, is not alive or is finished (see {@link JobThread#isFinished()}).</p>
*
* <p><i><b>Important note:</b>
* Having the interrupted flag set to <code>true</code> is not enough to consider the job as stopped.
* So, if the job has been interrupted but is still running, it should mean that the {@link JobThread#jobWork()} does not
* check the interrupted flag of the thread often enough or not at the right moments. In such case, the job can not be
* considered as stopped/aborted - so the phase stays {@link ExecutionPhase#EXECUTING EXECUTING} - until the thread is "unblocked"
* and the interruption is detected.
* </i></p>
*
* @return <i>true</i> if the thread is not still running, <i>false</i> otherwise.
*/
protected final boolean isStopped(){
return thread == null || !thread.isAlive() || thread.isInterrupted() || thread.isFinished();
return thread == null || !thread.isAlive() || thread.isFinished();
}
/**
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment