Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Result of tx processing returned as QueuedTxResult #537

Merged
merged 1 commit into from
Feb 2, 2018

Conversation

dshulyak
Copy link
Contributor

@dshulyak dshulyak commented Jan 6, 2018

Currently, it is quite easy to introduce concurrency issues while working
with transaction object. For example, race issue will exist every time
while a transaction is processed in a separate goroutine and caller will
try to check for an error before an event to the Done channel is sent.

Current change removes all the data that is updated on transaction and leaves
it with ID, Args, and Context (which is not used at the moment).

Blocked by: #530

@dshulyak dshulyak force-pushed the tximutable branch 3 times, most recently from 2c91b13 to d75f6f0 Compare January 6, 2018 11:20
@divan
Copy link
Contributor

divan commented Jan 9, 2018

Thanks for PR, it's really nice idea to split QueueTx into two types. One thing I can't figure out at the moment — what's the purpose of having Tx QueuedTx field in the QueuedTxResult type?

Looks like every call of WaitForTransaction(tx, c) has an access to the original QueuedTx variable anyway. And I don't see any real usage of that Tx field.

Also, I struggle with a newWaitForTransaction() signature: it accepts QueuedTx and channel of QueuedTxResults as parameters, but those two things should be logically connected. Package API should not allow calling WaitForTransaction() with Tx1 and channel of results for Tx2, for example, right?. I cannot quickly see how to redesign it though.

With current status-react-status-go API it looks like we use QueueTransaction() and WaitForTransaction() together, so maybe it makes sense to simply merge QueueTransaction() and WaitTransaction()?

cc @adambabik

@dshulyak
Copy link
Contributor Author

dshulyak commented Jan 9, 2018

@divan no reason, I was pretty sure that it was removed. When I added it I wanted to get rid from tx passed to WaitTransaction, but then I noticed that I can't.

I can add Result field to a tx, and then it won't be possible to mix tx1 with tx2 result channel.
For some reason i dislike such channel on objects, but it still will be safe to work with transactions

type QueuedTx struct {
	ID      QueuedTxID
	Context context.Context
	Args    SendTxArgs
	Result chan QueuedTxResult 
}

@divan
Copy link
Contributor

divan commented Jan 9, 2018

@dshulyak that makes sense now. Yeah, I guess the issue here is that the channel used only once for getting a single result, and it feels weird because we used to use channels for a stream of data and not just single value. But I think that's fine.

@divan
Copy link
Contributor

divan commented Jan 18, 2018

@dshulyak can you please check conflicts with develop?

@dshulyak
Copy link
Contributor Author

rebased, ready for review

Copy link
Contributor

@themue themue left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we're inside of package transactions I'm asking myself if it's a good opportunity to drop the prefix Tx from TxQueue and TxQueueManager?

tx := common.CreateTransaction(ctx, args)

if err := m.txQueueManager.QueueTransaction(tx); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

err := now can be err = .

@dshulyak
Copy link
Contributor Author

As we're inside of package transactions I'm asking myself if it's a good opportunity to drop the prefix Tx from TxQueue and TxQueueManager?

this is good idea. i am planning to create a pr where i will remove QueuedTxID. I will also include mentioned cleanup

Copy link
Contributor

@mandrigin mandrigin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few notes, looks good otherwise.

@@ -206,7 +204,7 @@ type TxQueueManager interface {
QueueTransaction(tx *QueuedTx) error

// WaitForTransactions blocks until transaction is completed, discarded or timed out.
WaitForTransaction(tx *QueuedTx) error
WaitForTransaction(tx *QueuedTx) RawCompleteTransactionResult
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't Result part assume that transaction is completed? I'd renamed it to RawTransactionResult if that makes sense.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would even change it to TransactionResult.

// discard notifications with empty tx
if queuedTx == nil {
func NotifyOnReturn(queuedTx *common.QueuedTx, err error) {
// we don't want to notify a user if tx wassent successfully
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: was sent

txQueue: queue.New(),
addrLock: &AddrLocker{},
notify: true,
sendCompletionTimeout: DefaultTxSendCompletionTimeout * time.Second,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would move * time.Second to the const declaration instead.

select {
case rst := <-tx.Result:
return rst
case <-time.After(m.sendCompletionTimeout):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to rename it to completionTimeout?

}

return tx.Hash.Hex(), nil
// handle empty hash
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it a TODO?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think i forgot to remove, hash cant be empty if err is nil now

ErrorMessage: queuedTx.Err.Error(),
ErrorCode: strconv.Itoa(sendTransactionErrorCode(queuedTx.Err)),
ErrorMessage: err.Error(),
ErrorCode: strconv.Itoa(sendTransactionErrorCode(err)),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't ErrorCode be an integer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because it is expected on consumer side? i can make this conversion when marshaling though

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But on the consumer side, it can be an integer and it should be ok. Here, it will just convert 1 to "1". I don't think it will make any difference to status-react.

@@ -206,7 +204,7 @@ type TxQueueManager interface {
QueueTransaction(tx *QueuedTx) error

// WaitForTransactions blocks until transaction is completed, discarded or timed out.
WaitForTransaction(tx *QueuedTx) error
WaitForTransaction(tx *QueuedTx) RawCompleteTransactionResult
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would even change it to TransactionResult.

if m.notify {
NotifyOnEnqueue(tx)
}
return err
return nil
}

func (m *Manager) txDone(tx *common.QueuedTx, hash gethcommon.Hash, err error) {
m.txQueue.Done(tx.ID, hash, err) //nolint: errcheck
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should handle this error because why not?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missed this at first, the only error that can be reported is that tx is not found
i think there is only 3-4 cases when this error can be reported:

  • tx discarded but then completed
  • tx timed out but then completed, or vice versa

and maybe

  • discarded and then timed out

i think in all of them it is safe just to exit without notification, will add tomorrow

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would at least log a warning message as it's pretty unusual situation. It will help to debug if there will be any issues with the app.

Context: ctx,
Args: args,
Done: make(chan struct{}),
Result: make(chan RawCompleteTransactionResult, 1),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally, I am not a fan of using a buffered channel to pass the result because it can be read only once. I assume it's used instead of mutex and getters/setters. On the other hand, if it works fine now and is not something that limits us then let's leave it.

@adambabik
Copy link
Contributor

And one more thing @dshulyak, let's verify that transactions/... unit tests do not report any race conditions when running with -race flag. It was a problem before.

@dshulyak
Copy link
Contributor Author

thanks for review, i will address comments today

@adambabik as for race conditions, i fixed them in the previous PR, so this change just adds additional guarantees that they won't be added again

go test -race ./geth/transactions/...
ok github.com/status-im/status-go/geth/transactions 1.938s
? github.com/status-im/status-go/geth/transactions/fake [no test files]
ok github.com/status-im/status-go/geth/transactions/queue 1.964s

@dshulyak dshulyak force-pushed the tximutable branch 2 times, most recently from 11cbd95 to afa68ca Compare January 30, 2018 14:49
@dshulyak
Copy link
Contributor Author

@adambabik @mandrigin @themue can we get this in? i will address the last thing in next PR #538

Copy link
Contributor

@adambabik adambabik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are pretty close!

@@ -61,17 +59,17 @@ type ReturnSendTransactionEvent struct {
Args common.SendTxArgs `json:"args"`
MessageID string `json:"message_id"`
ErrorMessage string `json:"error_message"`
ErrorCode string `json:"error_code"`
ErrorCode int `json:"error_code,string"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see now that ErrorCode was defined as a string in a struct. This is a nice solution!

@@ -133,9 +133,6 @@ func (q *TxQueue) Reset() {
// Enqueue enqueues incoming transaction
func (q *TxQueue) Enqueue(tx *common.QueuedTx) error {
log.Info(fmt.Sprintf("enqueue transaction: %s", tx.ID))
if (tx.Hash != gethcommon.Hash{} || tx.Err != nil) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I remember when it can happen that a transaction is queued multiple times. It's possible from the interface. We should check if the given transaction is not in the q.transactions already.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you meant transaction managers interface, right? not one that is used by react, i don't see how react can set ID for queued transaction

case rst := <-tx.Result:
return rst
case <-time.After(m.completionTimeout):
m.txDone(tx, gethcommon.Hash{}, queue.ErrQueuedTxTimedOut)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if ErrQueuedTxTimedOut should be in queue package. queue package does not know anything about timing out.

@@ -244,7 +250,7 @@ func (m *Manager) DiscardTransaction(id common.QueuedTxID) error {
}
err = m.txQueue.Done(id, gethcommon.Hash{}, queue.ErrQueuedTxDiscarded)
if m.notify {
NotifyOnReturn(tx)
NotifyOnReturn(tx, queue.ErrQueuedTxDiscarded)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, maybe ErrQueuedTxDiscarded should be defined in Manager? queue also has no notion of discarding.

@@ -197,10 +197,9 @@ func (s *TxQueueTestSuite) TestAccountMismatch() {
To: common.ToAddress(TestConfig.Account2.Address),
})

err := txQueueManager.QueueTransaction(tx)
s.NoError(err)
txQueueManager.QueueTransaction(tx)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In some places, the error is checked

s.NoError(txQueueManager.QueueTransaction(tx))

and in here it's not. It should be consistent. Btw. why does linter not complain that error is not checked?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe linter doesn't complain in tests

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. We would need to add "Test": true to .gometalinter.json to have test files checked.

if m.notify {
NotifyOnEnqueue(tx)
}
return err
return nil
}

func (m *Manager) txDone(tx *common.QueuedTx, hash gethcommon.Hash, err error) {
m.txQueue.Done(tx.ID, hash, err) //nolint: errcheck
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would at least log a warning message as it's pretty unusual situation. It will help to debug if there will be any issues with the app.

@dshulyak dshulyak force-pushed the tximutable branch 4 times, most recently from f086496 to 2dae2eb Compare January 31, 2018 10:59
Copy link
Contributor

@adambabik adambabik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything looks good except for one place where a deadlock is possible.

return ErrQueuedTxAlreadyProcessed
q.mu.RLock()
if _, ok := q.transactions[tx.ID]; ok {
return ErrQueuedTxExist
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deadlock possibility as q.mu.RUnlock() may never be executed. It also suggests there is no unit test for this case.

@@ -75,34 +77,44 @@ func (m *Manager) QueueTransaction(tx *common.QueuedTx) error {
to = tx.Args.To.Hex()
}
log.Info("queue a new transaction", "id", tx.ID, "from", tx.Args.From.Hex(), "to", to)
err := m.txQueue.Enqueue(tx)
if err := m.txQueue.Enqueue(tx); err == queue.ErrQueuedTxExist {
log.Warn("Transaction was already enqueud", tx.ID)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: s/enqueud/enqueued

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any particular reason why we don't want to notify about this error? It may be beneficial for status-react as this also indicates a bug in the UI. UI should prevent from submitting the same transaction twice.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i thought that warning will be enough to catch this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, lets leave things how they were but with warning

Copy link
Contributor

@divan divan Jan 31, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm opinionated, but warning is a least useful log level :) Why not print it as an error, if we are sure that txqueue client (status-react I mean) should guarantee there is no double enqueueing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually status react cant cause this error, it just sends from, to and amount. and we generate a random uid for that

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would prefer just to ignore this error, as it doesn't lead to any real problem. but i will just pass it up the stack

Copy link
Contributor

@divan divan Jan 31, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whatever makes more sense. The logic is that the decision "this condition is fine/is erroneous" should be codified. Warning level is literally asking a human to decide if it's fine or not. And humans often don't know the answer :)

@dshulyak dshulyak force-pushed the tximutable branch 3 times, most recently from c139d5d to 2ff58d1 Compare January 31, 2018 12:59
Currently it is quite easy to introduce concurrency issues while working
with transaction object. For example, race issue will exist every time
while transaction is processed in a separate goroutine and caller will
try to check for an error before event to Done channel is sent.

This change removes all the data that is updated on transaction and leaves
it with ID, Args and Context (which is not used at the moment).

Signed-off-by: Dmitry Shulyak <[email protected]>
@dshulyak
Copy link
Contributor Author

dshulyak commented Jan 31, 2018

@adambabik @mandrigin folks, any reason not to merge this change?

Copy link
Contributor

@adambabik adambabik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Contributor

@mandrigin mandrigin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@dshulyak dshulyak merged commit 653da5b into status-im:develop Feb 2, 2018
canercidam added a commit to canercidam/status-go that referenced this pull request Feb 2, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants