code: func (e *engineImpl) enqueueInvocations(c context.Context, job *Job, req []task.Request) ([]*Invocation, error) {
  	assertInTransaction(c)
  
  	// Create N new Invocation entities in Starting state.
  	invs, err := e.allocateInvocations(c, job, req)
  	if err != nil {
  		return nil, err
  	}
  
  	// Enqueue a task that eventually calls 'launchInvocationTask' for each new
  	// invocation.
  	invIDs := make([]int64, len(invs))
  	for i, inv := range invs {
  		invIDs[i] = inv.ID
  	}
  	if err := e.kickLaunchInvocationsBatchTask(c, job.JobID, invIDs); err != nil {
  		cleanupUnreferencedInvocations(c, invs)
  		return nil, err
  	}
  
  	// Make the job know that there are invocations pending. This will make them
  	// show up in UI and API after the current transaction lands. If it doesn't
  	// land, new invocations will remain hanging as garbage, not referenced by
  	// anything.
  	job.ActiveInvocations = append(job.ActiveInvocations, invIDs...)
  	return invs, nil
  }
docs: // enqueueInvocations allocated a bunch of Invocation entities, adds them to
  // ActiveInvocations list of the job and enqueues a tq task that kicks off their
  // execution.
  //
  // Must be called within a Job transaction, but creates Invocation entities
  // outside the transaction (since they are in different entity groups). If the
  // transaction fails, these entities may keep hanging unreferenced by anything
  // as garbage. This is fine, since they are not discoverable by any queries.