process method

Future<TaskProcessOutcome> process(
  1. Envelope envelope, {
  2. int? deliveryAttempt,
  3. TaskStatus? existingStatus,
  4. TaskExecutionControl control = const TaskExecutionControl(),
})

Processes envelope and returns a transport-independent outcome.

deliveryAttempt overrides the envelope attempt for runtimes whose native retry counter is stored outside the message body. It is zero-based. existingStatus allows the caller to suppress a previously completed duplicate without giving the processor ownership of persistence.

Implementation

Future<TaskProcessOutcome> process(
  Envelope envelope, {
  int? deliveryAttempt,
  TaskStatus? existingStatus,
  TaskExecutionControl control = const TaskExecutionControl(),
}) async {
  if (deliveryAttempt != null && deliveryAttempt < 0) {
    throw ArgumentError.value(
      deliveryAttempt,
      'deliveryAttempt',
      'must be zero or greater',
    );
  }
  final effectiveEnvelope = deliveryAttempt == null
      ? envelope
      : envelope.copyWith(attempt: deliveryAttempt);

  if (existingStatus?.state.isTerminal ?? false) {
    return TaskProcessSkipped(
      envelope: effectiveEnvelope,
      existingStatus: existingStatus!,
    );
  }

  final handler = registry.resolve(effectiveEnvelope.name);
  if (handler == null) {
    return TaskProcessRejected(
      envelope: effectiveEnvelope,
      reason: TaskRejectionReason.unregisteredTask,
    );
  }

  final resolvedSigner = signer;
  if (resolvedSigner != null) {
    try {
      // Verify the wire body, not host-supplied delivery metadata. Native
      // retries retain the original signed body while advancing the counter.
      await resolvedSigner.verify(envelope);
    } on Object catch (error, stackTrace) {
      return TaskProcessRejected(
        envelope: effectiveEnvelope,
        reason: TaskRejectionReason.invalidSignature,
        error: error,
        stackTrace: stackTrace,
      );
    }
  }

  if (_isExpired(effectiveEnvelope)) {
    return TaskProcessCancelled(
      envelope: effectiveEnvelope,
      reason: TaskCancellationReason.expired,
    );
  }

  late final Map<String, Object?> decodedArgs;
  try {
    decodedArgs = _decodeArgs(effectiveEnvelope, handler);
  } on Object catch (error, stackTrace) {
    return TaskProcessRejected(
      envelope: effectiveEnvelope,
      reason: TaskRejectionReason.invalidPayload,
      error: error,
      stackTrace: stackTrace,
    );
  }

  final context = TaskContext(
    id: effectiveEnvelope.id,
    args: effectiveEnvelope.args,
    attempt: effectiveEnvelope.attempt,
    headers: effectiveEnvelope.headers,
    meta: effectiveEnvelope.meta,
    heartbeat: control.heartbeat,
    extendLease: control.extendLease,
    progress: control.progress,
    cancellation: control.cancellation,
    enqueuer: control.enqueuer,
    workflows: control.workflows,
    workflowEvents: control.workflowEvents,
  );

  try {
    control.cancellation.throwIfCancelled();
    final value = await invoke(
      context,
      () => _invokeHandler(
        handler,
        context,
        decodedArgs,
        hardTimeout: _resolveHardTimeLimit(
          effectiveEnvelope,
          handler.options,
        ),
      ),
    );
    return TaskProcessSuccess(envelope: effectiveEnvelope, value: value);
  } on TaskCancellationException catch (error) {
    return TaskProcessCancelled(
      envelope: effectiveEnvelope,
      reason: TaskCancellationReason.cancelled,
      error: error,
    );
  } on TaskRetryRequest catch (request, stackTrace) {
    return classifyRetry(
      effectiveEnvelope,
      handler,
      request,
      stackTrace,
    );
  } on Object catch (error, stackTrace) {
    await _notifyError(context, error, stackTrace);
    return classifyFailure(
      effectiveEnvelope,
      handler,
      error,
      stackTrace,
    );
  }
}