// 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 }
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() }