5555import org .apache .cloudstack .framework .config .ConfigKey ;
5656import org .apache .cloudstack .framework .config .Configurable ;
5757import org .apache .cloudstack .framework .jobs .AsyncJob ;
58+ import org .apache .cloudstack .framework .jobs .AsyncJobDataHelper ;
5859import org .apache .cloudstack .framework .jobs .AsyncJobDispatcher ;
5960import org .apache .cloudstack .framework .jobs .AsyncJobExecutionContext ;
6061import org .apache .cloudstack .framework .jobs .AsyncJobManager ;
@@ -412,21 +413,7 @@ public List<Long> doInTransaction(final TransactionStatus status) {
412413 if (logger .isDebugEnabled ()) {
413414 logger .debug ("Update db status for job-" + jobId );
414415 }
415- job .setStatus (jobStatus );
416- job .setResultCode (resultCode );
417-
418- if (resultObject != null ) {
419- job .updateResultWithEncryptionIfNeeded (resultObject );
420- } else {
421- job .updateResultWithEncryptionIfNeeded (null );
422- }
423-
424- final Date currentGMTTime = DateUtil .currentGMTTime ();
425- job .setLastUpdated (currentGMTTime );
426- job .setCompleteMsid (getMsid ());
427- job .setRemoved (currentGMTTime );
428- job .setExecutingMsid (null );
429- _jobDao .update (jobId , job );
416+ recordCompletion (job , jobStatus , resultCode , resultObject );
430417
431418 if (logger .isDebugEnabled ()) {
432419 logger .debug ("Wake up jobs joined with job-" + jobId + " and disjoin all subjobs created from job- " + jobId );
@@ -457,6 +444,24 @@ public List<Long> doInTransaction(final TransactionStatus status) {
457444 _messageBus .publish (null , AsyncJob .Topics .JOB_STATE , PublishScope .GLOBAL , jobId );
458445 }
459446
447+ void recordCompletion (final AsyncJobVO job , final Status jobStatus , final int resultCode , final String resultObject ) {
448+ job .setStatus (jobStatus );
449+ job .setResultCode (resultCode );
450+ if (jobStatus != JobInfo .Status .CANCELLED ) {
451+ // a cancel requested after this job's last read did not take: it ended on its own
452+ job .setCancelRequested (false );
453+ job .setCancelDelivered (false );
454+ }
455+ job .updateResultWithEncryptionIfNeeded (resultObject );
456+
457+ final Date currentGMTTime = DateUtil .currentGMTTime ();
458+ job .setLastUpdated (currentGMTTime );
459+ job .setCompleteMsid (getMsid ());
460+ job .setRemoved (currentGMTTime );
461+ job .setExecutingMsid (null );
462+ _jobDao .update (job .getId (), job );
463+ }
464+
460465 private String convertHumanReadableJson (String resultObj ) {
461466
462467 if (resultObj != null && resultObj .contains ("/" ) && resultObj .contains ("{" )){
@@ -1607,31 +1612,35 @@ static String getJobCancelReasonFromResult(final AsyncJobVO job) {
16071612 }
16081613 }
16091614
1610- private void requestJobCancel (final AsyncJobVO job , final String cancelledBy ) {
1611- job .setCancelRequested (true );
1612- job .setCancelDelivered (false );
1613- job .setResultCode (ApiErrorCode .INTERNAL_ERROR .getHttpCode ());
1614- job .updateResultWithEncryptionIfNeeded (cancelledResult (cancelledBy ));
1615- job .setLastUpdated (DateUtil .currentGMTTime ());
1616- _jobDao .update (job .getId (), job );
1615+ private boolean requestJobCancel (final AsyncJobVO job , final String cancelledBy ) {
1616+ final String result = AsyncJobDataHelper .encryptResultIfNeeded (job .getCmd (), cancelledResult (cancelledBy ));
1617+ return _jobDao .requestCancel (job .getId (), ApiErrorCode .INTERNAL_ERROR .getHttpCode (), result );
16171618 }
16181619
16191620 private void withdrawJobCancel (final long jobId ) {
1620- final AsyncJobVO job = _jobDao .findById (jobId );
1621- if (job == null || !job .isCancelRequested () || job .getStatus () != JobInfo .Status .IN_PROGRESS ) {
1621+ if (!_jobDao .withdrawCancel (jobId )) {
16221622 return ;
16231623 }
1624- job .setCancelRequested (false );
1625- job .setCancelDelivered (false );
1626- job .setResultCode (0 );
1627- job .updateResultWithEncryptionIfNeeded (null );
1628- job .setLastUpdated (DateUtil .currentGMTTime ());
1629- _jobDao .update (jobId , job );
16301624 for (final JobCancellationHandler handler : getJobCancellationHandlers ()) {
16311625 handler .jobCancelWithdrawn (jobId );
16321626 }
16331627 }
16341628
1629+ private CloudRuntimeException refusedByState (final long jobId , final AsyncJobVO job ) {
1630+ if (job == null ) {
1631+ return refused ("Cannot cancel, job-" + jobId + " no longer exists." );
1632+ }
1633+ if (job .isCancelRequested () && job .getStatus () == JobInfo .Status .IN_PROGRESS ) {
1634+ final String earlier = getJobCancelReasonFromResult (job );
1635+ return refused ("Cannot cancel job-" + jobId + ", it is already being cancelled" + (earlier != null ? " (" + earlier + ")" : "" ) + "." );
1636+ }
1637+ if (job .getStatus () != null && job .getStatus ().done ()) {
1638+ _queueMgr .purgeAsyncJobQueueItemId (jobId );
1639+ return refused ("Cannot cancel, job-" + jobId + " is not running. Current job status is " + job .getStatus () + "." );
1640+ }
1641+ return null ;
1642+ }
1643+
16351644 @ Override
16361645 public boolean hasOnlyQueuedWork (final long jobId ) {
16371646 final List <AsyncJobVO > children = _jobDao .listChildJobs (jobId );
@@ -1731,9 +1740,9 @@ public void cancelAsyncJob(final long jobId, final String reason) {
17311740 CallContext .current ().setEventDetails ("Job id: " + job .getUuid () + ". " + cancelledBy );
17321741 }
17331742
1734- if ( job . isCancelRequested () && job . getStatus () == JobInfo . Status . IN_PROGRESS ) {
1735- final String earlier = getJobCancelReasonFromResult ( job );
1736- throw refused ( "Cannot cancel job-" + jobId + ", it is already being cancelled" + ( earlier != null ? " (" + earlier + ")" : "" ) + "." ) ;
1743+ final CloudRuntimeException stateRefusal = refusedByState ( jobId , job );
1744+ if ( stateRefusal != null ) {
1745+ throw stateRefusal ;
17371746 }
17381747
17391748 boolean onlyQueuedWork = false ;
@@ -1759,11 +1768,6 @@ public void cancelAsyncJob(final long jobId, final String reason) {
17591768 }
17601769 }
17611770
1762- if (job .getStatus () != null && job .getStatus ().done ()) {
1763- _queueMgr .purgeAsyncJobQueueItemId (jobId );
1764- throw refused ("Cannot cancel, job-" + jobId + " is not running. Current job status is " + job .getStatus () + "." );
1765- }
1766-
17671771 // ask before changing state: a job whose work cannot be stopped is refused, not recorded as cancelled
17681772 for (final JobCancellationHandler handler : getJobCancellationHandlers ()) {
17691773 final String refusal = handler .checkJobCannotBeCancelled (jobId );
@@ -1773,7 +1777,11 @@ public void cancelAsyncJob(final long jobId, final String reason) {
17731777 }
17741778
17751779 logger .info ("Cancellation of job-{} requested: {}" , jobId , cancelledBy );
1776- requestJobCancel (job , cancelledBy );
1780+ if (!requestJobCancel (job , cancelledBy )) {
1781+ // the job completed, or another cancel got in, since it was read: its result is left as it is
1782+ final CloudRuntimeException lateRefusal = refusedByState (jobId , _jobDao .findByIdIncludingRemoved (jobId ));
1783+ throw lateRefusal != null ? lateRefusal : refused ("Cannot cancel job-" + jobId + ", its state changed meanwhile." );
1784+ }
17771785 // the flag stops a queued work job from starting; one taken by a server before the flag is work under way that cannot be stopped
17781786 if (onlyQueuedWork && !hasOnlyQueuedWork (jobId )) {
17791787 withdrawJobCancel (jobId );
@@ -1795,6 +1803,11 @@ public void cancelAsyncJob(final long jobId, final String reason) {
17951803 throw new CloudRuntimeException (errMessage , t );
17961804 }
17971805
1806+ final AsyncJobVO current = _jobDao .findByIdIncludingRemoved (jobId );
1807+ if (current != null && current .getStatus () != null && current .getStatus ().done () && current .getStatus () != JobInfo .Status .CANCELLED ) {
1808+ throw refused ("Cannot cancel job-" + jobId + ", it completed meanwhile. Current job status is " + current .getStatus () + "." );
1809+ }
1810+
17981811 final Long executingMsid = job .getExecutingMsid ();
17991812 if (executingMsid == null || executingMsid .equals (getMsid ())) {
18001813 finalizeCancelledJob (jobId );
0 commit comments