-
Notifications
You must be signed in to change notification settings - Fork 248
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
Conversation
2c91b13
to
d75f6f0
Compare
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 Looks like every call of Also, I struggle with a new With current cc @adambabik |
@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.
|
@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. |
@dshulyak can you please check conflicts with develop? |
rebased, ready for review |
06d0ec1
to
81647fb
Compare
There was a problem hiding this 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
?
geth/api/backend.go
Outdated
tx := common.CreateTransaction(ctx, args) | ||
|
||
if err := m.txQueueManager.QueueTransaction(tx); err != nil { |
There was a problem hiding this comment.
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 =
.
this is good idea. i am planning to create a pr where i will remove QueuedTxID. I will also include mentioned cleanup |
There was a problem hiding this 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.
geth/common/types.go
Outdated
@@ -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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
.
geth/transactions/notifications.go
Outdated
// 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: was sent
geth/transactions/txqueue_manager.go
Outdated
txQueue: queue.New(), | ||
addrLock: &AddrLocker{}, | ||
notify: true, | ||
sendCompletionTimeout: DefaultTxSendCompletionTimeout * time.Second, |
There was a problem hiding this comment.
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.
geth/transactions/txqueue_manager.go
Outdated
select { | ||
case rst := <-tx.Result: | ||
return rst | ||
case <-time.After(m.sendCompletionTimeout): |
There was a problem hiding this comment.
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
?
geth/transactions/txqueue_manager.go
Outdated
} | ||
|
||
return tx.Hash.Hex(), nil | ||
// handle empty hash |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it a TODO?
There was a problem hiding this comment.
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
geth/transactions/notifications.go
Outdated
ErrorMessage: queuedTx.Err.Error(), | ||
ErrorCode: strconv.Itoa(sendTransactionErrorCode(queuedTx.Err)), | ||
ErrorMessage: err.Error(), | ||
ErrorCode: strconv.Itoa(sendTransactionErrorCode(err)), |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
geth/common/types.go
Outdated
@@ -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 |
There was a problem hiding this comment.
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
.
geth/transactions/txqueue_manager.go
Outdated
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
geth/common/utils.go
Outdated
Context: ctx, | ||
Args: args, | ||
Done: make(chan struct{}), | ||
Result: make(chan RawCompleteTransactionResult, 1), |
There was a problem hiding this comment.
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.
And one more thing @dshulyak, let's verify that |
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
|
11cbd95
to
afa68ca
Compare
@adambabik @mandrigin @themue can we get this in? i will address the last thing in next PR #538 |
There was a problem hiding this 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"` |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
geth/transactions/txqueue_manager.go
Outdated
case rst := <-tx.Result: | ||
return rst | ||
case <-time.After(m.completionTimeout): | ||
m.txDone(tx, gethcommon.Hash{}, queue.ErrQueuedTxTimedOut) |
There was a problem hiding this comment.
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.
geth/transactions/txqueue_manager.go
Outdated
@@ -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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
geth/transactions/txqueue_manager.go
Outdated
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 |
There was a problem hiding this comment.
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.
f086496
to
2dae2eb
Compare
There was a problem hiding this 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.
geth/transactions/queue/queue.go
Outdated
return ErrQueuedTxAlreadyProcessed | ||
q.mu.RLock() | ||
if _, ok := q.transactions[tx.ID]; ok { | ||
return ErrQueuedTxExist |
There was a problem hiding this comment.
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.
geth/transactions/txqueue_manager.go
Outdated
@@ -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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo: s/enqueud/enqueued
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 :)
c139d5d
to
2ff58d1
Compare
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]>
@adambabik @mandrigin folks, any reason not to merge this change? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
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