in src/adu_workflow/src/agent_workflow.c [867:1050]
void ADUC_Workflow_WorkCompletionCallback(const void* workCompletionToken, ADUC_Result result, bool isAsync)
{
// We own these objects, so no issue making them non-const.
ADUC_MethodCall_Data* methodCallData = (ADUC_MethodCall_Data*)workCompletionToken;
ADUC_WorkflowData* workflowData = methodCallData->WorkflowData;
// NOLINTNEXTLINE(misc-redundant-expression)
if (AducResultCodeIndicatesInProgress(result.ResultCode))
{
Log_Error("WorkComplete received InProgress result code - should not happen!");
goto done;
}
// Need to avoid deadlock b/c main thread typically takes lock higher in the callstack above
// TransistionWorkflow and processing DeploymentInProgress state is synchronous
if (isAsync)
{
s_workflow_lock();
}
ADUCITF_WorkflowStep currentWorkflowStep = workflow_get_current_workflowstep(workflowData->WorkflowHandle);
const ADUC_WorkflowHandlerMapEntry* entry = GetWorkflowHandlerMapEntryForAction(currentWorkflowStep);
if (entry == NULL)
{
Log_Error("Invalid UpdateAction %u -- ignoring", currentWorkflowStep);
goto done;
}
if (ADUC_WorkflowData_GetCurrentAction(workflowData) == ADUCITF_UpdateAction_Cancel)
{
Log_Error("workflow data current action should not be Cancel.");
goto done;
}
Log_Info(
"Action '%s' complete. Result: %d (%s), %d (0x%x)",
ADUCITF_WorkflowStepToString(entry->WorkflowStep),
result.ResultCode,
result.ResultCode == 0 ? "failed" : "succeeded",
result.ExtendedResultCode,
result.ExtendedResultCode);
entry->OperationCompleteFunc(methodCallData, result);
if (IsAducResultCodeSuccess(result.ResultCode))
{
// Operation succeeded -- go to next state.
const ADUCITF_State nextUpdateStateOnSuccess = entry->NextStateOnSuccess;
Log_Info(
"WorkCompletionCallback: %s succeeded. Going to state %s",
ADUCITF_WorkflowStepToString(entry->WorkflowStep),
ADUCITF_StateToString(nextUpdateStateOnSuccess));
ADUC_Workflow_SetUpdateState(workflowData, nextUpdateStateOnSuccess);
// Transitioning to idle (or failed) state frees and nulls-out the WorkflowHandle as a side-effect of
// setting the update state.
if (ADUC_WorkflowData_GetLastReportedState(workflowData) != ADUCITF_State_Idle)
{
// Operation is now complete. Clear both inprogress and cancel requested.
workflow_clear_inprogress_and_cancelrequested(workflowData->WorkflowHandle);
//
// We are now ready to transition to the next step of the workflow.
//
ADUC_Workflow_AutoTransitionWorkflow(workflowData, true);
goto done;
}
}
else
{
// Operation (e.g. Download) failed or was cancelled - both are considered AducResult failure codes.
if (workflow_get_operation_cancel_requested(workflowData->WorkflowHandle))
{
ADUC_WorkflowCancellationType cancellationType =
workflow_get_cancellation_type(workflowData->WorkflowHandle);
const char* cancellationTypeStr = ADUC_Workflow_CancellationTypeToString(cancellationType);
Log_Warn("Handling cancel completion, cancellation type '%s'.", cancellationTypeStr);
if (cancellationType == ADUC_WorkflowCancellationType_Replacement
|| cancellationType == ADUC_WorkflowCancellationType_Retry
|| cancellationType == ADUC_WorkflowCancellationType_ComponentChanged)
{
Log_Info("Starting process of deployment for '%s'", cancellationTypeStr);
// Note: Must NOT call linux platform layer Idle method to reset cancellation request to false in the
// platform layer because that would destroy and NULL out the WorkflowHandle in the workflowData.
if (cancellationType == ADUC_WorkflowCancellationType_Replacement)
{
// Cleanup the download sandbox for the current workflowId
// since it will not be transitioning to Idle state (where
// sandbox cleanup is normally done)
char* workflowId = ADUC_WorkflowData_GetWorkflowId(workflowData); // see workflow_free_string below
char* workFolder = ADUC_WorkflowData_GetWorkFolder(workflowData); // see workflow_free_string below
if (workflowId != NULL && workFolder != NULL)
{
Log_Info("Cleanup sandbox before replacement workflow");
const ADUC_UpdateActionCallbacks* updateActionCallbacks =
&(workflowData->UpdateActionCallbacks);
updateActionCallbacks->SandboxDestroyCallback(
updateActionCallbacks->PlatformLayerHandle, workflowId, workFolder);
}
workflow_free_string(workflowId);
workflow_free_string(workFolder);
// Reset workflow state to process deployment and transfer
// the deferred workflow to current.
workflow_update_for_replacement(workflowData->WorkflowHandle);
}
else
{
// it's a retry. Reset workflow state to reprocess deployment.
workflow_update_for_retry(workflowData->WorkflowHandle);
}
ADUC_WorkflowData_SetLastReportedState(ADUCITF_State_Idle, workflowData);
// ProcessDeployment's OperationFunc called by TransitionWorkflow is synchronous so it kicks off the
// download worker thread after reporting DeploymentInProgress ACK for the replacement/retry, so we
// return instead of goto done to avoid the redundant ADUC_Workflow_AutoTransitionWorkflow call.
ADUC_Workflow_TransitionWorkflow(workflowData);
goto done;
}
if (cancellationType != ADUC_WorkflowCancellationType_Normal)
{
Log_Error("Invalid cancellation Type '%s' when cancel requested.", cancellationTypeStr);
goto done;
}
// Operation cancelled.
//
// We are now at the completion of the operation that was cancelled via a Cancel update action
// and will just return to Idle state.
//
// Ignore the result of the operation, which most likely is cancelled, e.g. ADUC_Result_Failure_Cancelled.
Log_Warn("Operation cancelled - returning to Idle state");
result.ResultCode = ADUC_Result_Failure_Cancelled;
result.ExtendedResultCode = 0;
ADUC_Workflow_SetUpdateStateWithResult(workflowData, ADUCITF_State_Idle, result);
}
else
{
// Operation failed.
const ADUCITF_State nextUpdateStateOnFailure = entry->NextStateOnFailure;
Log_Info(
"WorkCompletionCallback: %s failed. Going to state %s",
ADUCITF_WorkflowStepToString(entry->WorkflowStep),
ADUCITF_StateToString(nextUpdateStateOnFailure));
// Reset so that a Retry/Replacement avoids cancel and instead properly starts processing.
workflow_set_operation_in_progress(workflowData->WorkflowHandle, false);
ADUC_Workflow_SetUpdateState(workflowData, nextUpdateStateOnFailure);
ADUC_Workflow_AutoTransitionWorkflow(workflowData, false);
}
}
done:
// lifetime of methodCallData now ends as the operation work has completed.
free(methodCallData);
if (isAsync)
{
s_workflow_unlock();
}
}