Example #1
0
func (attempt *attempt) Finish(data map[string]interface{}) error {
	return attempt.do(func() error {
		if attempt.status != coordinate.Failed && !attempt.isPending() {
			return coordinate.ErrNotPending
		}
		attempt.finish(coordinate.Finished, data)

		// Does the work unit data include an "output" key
		// that we understand?
		if attempt.workUnit.activeAttempt != attempt {
			return nil
		}
		if data == nil {
			data = attempt.data
		}
		if data == nil {
			data = attempt.workUnit.data
		}
		var newUnits map[string]coordinate.AddWorkUnitItem
		var nextWorkSpec *workSpec
		output, ok := data["output"]
		if ok {
			newUnits = coordinate.ExtractWorkUnitOutput(output, attempt.Coordinate().clock.Now())
		}
		if newUnits != nil {
			then := attempt.workUnit.workSpec.meta.NextWorkSpecName
			if then != "" {
				nextWorkSpec, ok = attempt.workUnit.workSpec.namespace.workSpecs[then]
				nextWorkSpec.addWorkUnits(newUnits)
			}
		}

		return nil
	})
}
Example #2
0
func (a *attempt) Finish(data map[string]interface{}) error {
	// Mark the attempt finished, then create any new work units
	// declared in an "output" key.
	//
	// These do not have to happen atomically.  So first, just mark
	// the attempt as done.
	err := withTx(a, false, func(tx *sql.Tx) error {
		return a.complete(tx, data, "finished")
	})
	if err != nil {
		return err
	}

	// A fast path: if we have a data dictionary and there is
	// no "output", stop.
	if data != nil {
		if _, present := data["output"]; !present {
			return nil
		}
	}

	// Otherwise we maybe have "output".  Do one query to the
	// database that gets back the work unit data (if we need it)
	// and the matching next work spec.  A join could fail, which
	// would result in nothing coming back, which would be okay.
	// This also depends on this attempt still being the active
	// attempt, which again, we can check in the query.
	params := queryParams{}
	outputs := []string{
		"next.id",
		"next.name",
	}
	tables := []string{
		workUnitTable,
		workSpecTable,
		workSpecTable + " next",
	}
	conditions := []string{
		isWorkUnit(&params, a.unit.id),
		workUnitHasAttempt(&params, a.id),
		workUnitInThisSpec,
		workSpecNextWorkSpec + "=next.name",
		workSpecNamespace + "=next.namespace_id",
	}
	if data == nil {
		// We need both the most recent attempt data and
		// the original unit data
		outputs = append(outputs, workUnitData, attemptData)
		tables = append(tables, attemptTable)
		conditions = append(conditions, attemptThisWorkUnit)
	}
	query := buildSelect(outputs, tables, conditions)
	spec := workSpec{namespace: a.unit.spec.namespace}
	err = withTx(a, true, func(tx *sql.Tx) (err error) {
		row := tx.QueryRow(query, params...)
		if data == nil {
			var unitData, attemptData []byte
			err = row.Scan(&spec.id, &spec.name, &unitData, &attemptData)
			if err == nil {
				if attemptData != nil {
					data, err = bytesToMap(attemptData)
				} else if unitData != nil {
					data, err = bytesToMap(unitData)
				} else {
					data = map[string]interface{}{}
				}
			}
		} else {
			err = row.Scan(&spec.id, &spec.name)
		}
		return
	})

	// Now, either that query failed, or we have both work unit
	// data and a next work spec.
	if err == sql.ErrNoRows {
		// As a reminder:
		// * a isn't the active attempt; or
		// * spec["then"] points nowhere
		// In any case, no outputs and we're done
		return nil
	}
	if err != nil {
		return err
	}

	units := coordinate.ExtractWorkUnitOutput(data["output"], a.Coordinate().clock.Now())
	if units == nil {
		return nil // nothing to do
	}
	for name, item := range units {
		var dataBytes []byte
		dataBytes, err = mapToBytes(item.Data)
		if err != nil {
			return err
		}
		_, err = spec.addWorkUnit(name, dataBytes, item.Meta)
		if err != nil {
			return err
		}
	}

	return nil
}