Example #1
0
// New initializes and returns a transaction for passed storage engine. The options
// are used to change the behavior of the transaction itself.
func New(engine storage.Engine, options Options) (*Transaction, error) {
	var (
		id  uint64
		err error
	)

	// Start time of the transaction.
	startTime := time.Now().UTC()

	// Increment the transaction ID.
	if id, err = txid(engine); err != nil {
		logrus.Errorf("transactor: could not create transaction: %s", err)
		return nil, ErrNoID
	}

	if options.ReceiveWait == 0 {
		options.ReceiveWait = DefaultOptions.ReceiveWait
	}

	if options.BufferSize == 0 {
		options.BufferSize = DefaultOptions.BufferSize
	}

	tx := Transaction{
		ID:        id,
		StartTime: startTime,
		Engine:    engine,
		options:   options,
		pipes:     make(map[string]*Pipeline),
		stream:    make(chan *origins.Fact, options.BufferSize),
		done:      make(chan struct{}),
		errch:     make(chan error),
		pipewg:    &sync.WaitGroup{},
		mainwg:    &sync.WaitGroup{},

		entity: &origins.Ident{
			Name: fmt.Sprint(id),
		},
	}

	tx.mainwg.Add(1)

	// Run transaction in the background.
	go tx.run()

	// Write facts about the transaction including the identity
	// and start time.
	tx.Write(&origins.Fact{
		Domain: origins.TransactionsDomain,
		Entity: tx.entity,
		Attribute: &origins.Ident{
			Domain: origins.AttrsDomain,
			Name:   "ident",
		},
		Value: &origins.Ident{
			Name: tx.entity.Name,
		},
	})

	tx.Write(&origins.Fact{
		Domain: origins.TransactionsDomain,
		Entity: tx.entity,
		Attribute: &origins.Ident{
			Name: "startTime",
		},
		Value: &origins.Ident{
			Name: chrono.FormatNano(tx.StartTime),
		},
	})

	return &tx, nil
}
Example #2
0
func (tx *Transaction) run() {
	// Start the receiver. This blocks until the stream ends or is interrupted.
	err := tx.receive()

	if err == nil {
		// Collect the domains affected across transacted facts and generates
		// facts about them.
		var (
			domain string
			fact   *origins.Fact
		)

		identAttr := &origins.Ident{
			Domain: origins.AttrsDomain,
			Name:   "ident",
		}

		for _, domain = range tx.domains {
			// Transact the identity attribute.
			fact = &origins.Fact{
				Domain: origins.DomainsDomain,
				Entity: &origins.Ident{
					Name: domain,
				},
				Attribute: identAttr,
				Value: &origins.Ident{
					Name: domain,
				},
			}

			if err = tx.route(fact, false); err != nil {
				break
			}

			// Transact a fact that declares the domain has been seen by this
			// transaction.
			fact = &origins.Fact{
				Domain: origins.DomainsDomain,
				Entity: &origins.Ident{
					Name: domain,
				},
				Attribute: &origins.Ident{
					Name: "ping",
				},
				Value: tx.entity,
			}

			if err = tx.route(fact, false); err != nil {
				break
			}
		}
	}

	// Set the error.
	tx.Error = err

	// Mark the end time of processing.
	tx.EndTime = time.Now().UTC()

	// Transact the end time.
	tx.route(&origins.Fact{
		Domain: origins.TransactionsDomain,
		Entity: tx.entity,
		Attribute: &origins.Ident{
			Name: "endTime",
		},
		Value: &origins.Ident{
			Name: chrono.FormatNano(tx.EndTime),
		},
	}, false)

	// Error during the transaction that caused the abort.
	if tx.Error != nil {
		tx.route(&origins.Fact{
			Domain: origins.TransactionsDomain,
			Entity: tx.entity,
			Attribute: &origins.Ident{
				Name: "error",
			},
			Value: &origins.Ident{
				Name: fmt.Sprint(tx.Error),
			},
		}, false)
	}

	// Signal the transaction is closed so no more external facts are received.
	close(tx.done)

	// Wait for the pipelines to finish there work.
	tx.pipewg.Wait()

	// Complete the transaction by committing or aborting.
	tx.complete()

	// Signal the main goroutine is done.
	tx.mainwg.Done()
}