-
Notifications
You must be signed in to change notification settings - Fork 501
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
Fix deadlock in txsub.System.Tick()
and tx_bad_seq
errors
#815
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,9 +15,10 @@ import ( | |
// Its methods tie together the various pieces used to reliably submit transactions | ||
// to a stellar-core instance. | ||
type System struct { | ||
initializer sync.Once | ||
tickInProgress bool | ||
initializer sync.Once | ||
|
||
tickMutex sync.Mutex | ||
tickInProgress bool | ||
|
||
Pending OpenSubmissionList | ||
Results ResultProvider | ||
|
@@ -73,12 +74,20 @@ func (sys *System) Submit(ctx context.Context, env string) (result <-chan Result | |
// check the configured result provider for an existing result | ||
r := sys.Results.ResultByHash(ctx, info.Hash) | ||
|
||
if r.Err != ErrNoResults { | ||
if r.Err == nil { | ||
sys.Log.Ctx(ctx).WithField("hash", info.Hash).Info("Found submission result in a DB") | ||
sys.finish(ctx, response, r) | ||
return | ||
} | ||
|
||
if r.Err != ErrNoResults { | ||
sys.Log.Ctx(ctx).WithField("hash", info.Hash).Info("Error getting submission result from a DB") | ||
sys.finish(ctx, response, r) | ||
return | ||
} | ||
|
||
// From now: r.Err == ErrNoResults | ||
|
||
curSeq, err := sys.Sequences.Get([]string{info.SourceAddress}) | ||
if err != nil { | ||
sys.finish(ctx, response, Result{Err: err, EnvelopeXDR: env}) | ||
|
@@ -170,25 +179,40 @@ func (sys *System) submitOnce(ctx context.Context, env string) SubmissionResult | |
return sr | ||
} | ||
|
||
// setTickInProgress sets `tickInProgress` to `true` if it's not | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like we set There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch! #824 |
||
// `false`. Returns `true` if `tickInProgress` has been switched | ||
// to `true` inside this method and `Tick()` should continue. | ||
func (sys *System) setTickInProgress(ctx context.Context) bool { | ||
sys.tickMutex.Lock() | ||
defer sys.tickMutex.Unlock() | ||
|
||
if sys.tickInProgress { | ||
logger := log.Ctx(ctx) | ||
logger.Info("ticking in progress") | ||
return false | ||
} | ||
|
||
sys.tickInProgress = true | ||
return true | ||
} | ||
|
||
func (sys *System) unsetTickInProgress() { | ||
sys.tickMutex.Lock() | ||
defer sys.tickMutex.Unlock() | ||
sys.tickInProgress = false | ||
} | ||
|
||
// Tick triggers the system to update itself with any new data available. | ||
func (sys *System) Tick(ctx context.Context) { | ||
sys.Init() | ||
logger := log.Ctx(ctx) | ||
|
||
// Make sure Tick is not run concurrently | ||
sys.tickMutex.Lock() | ||
if sys.tickInProgress { | ||
logger.Debug("ticking in progress") | ||
if !sys.setTickInProgress(ctx) { | ||
return | ||
} | ||
sys.tickInProgress = true | ||
sys.tickMutex.Unlock() | ||
|
||
defer func() { | ||
sys.tickMutex.Lock() | ||
sys.tickInProgress = false | ||
sys.tickMutex.Unlock() | ||
}() | ||
defer sys.unsetTickInProgress() | ||
|
||
logger. | ||
WithField("queued", sys.SubmissionQueue.String()). | ||
|
@@ -199,6 +223,7 @@ func (sys *System) Tick(ctx context.Context) { | |
curSeq, err := sys.Sequences.Get(addys) | ||
if err != nil { | ||
logger.WithStack(err).Error(err) | ||
return | ||
} else { | ||
sys.SubmissionQueue.Update(curSeq) | ||
} | ||
|
@@ -229,6 +254,7 @@ func (sys *System) Tick(ctx context.Context) { | |
stillOpen, err := sys.Pending.Clean(ctx, sys.SubmissionTimeout) | ||
if err != nil { | ||
logger.WithStack(err).Error(err) | ||
return | ||
} | ||
|
||
sys.Metrics.OpenSubmissionsGauge.Update(int64(stillOpen)) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,7 @@ import ( | |
"github.com/sirupsen/logrus" | ||
"github.com/spf13/cobra" | ||
"github.com/spf13/viper" | ||
"github.com/stellar/go/network" | ||
"github.com/stellar/go/services/horizon/internal" | ||
"github.com/stellar/go/support/log" | ||
"github.com/throttled/throttled" | ||
|
@@ -180,7 +181,7 @@ func init() { | |
|
||
rootCmd.PersistentFlags().String( | ||
"network-passphrase", | ||
"", | ||
network.TestNetworkPassphrase, | ||
"Override the network passphrase", | ||
) | ||
|
||
|
@@ -239,6 +240,10 @@ func initConfig() { | |
stdLog.Fatal("Invalid config: stellar-core-url is blank. Please specify --stellar-core-url on the command line or set the STELLAR_CORE_URL environment variable.") | ||
} | ||
|
||
if viper.GetString("network-passphrase") == "" { | ||
stdLog.Fatal("Invalid config: network-passphrase is blank. Please specify --network-passphrase on the command line or set the NETWORK_PASSPHRASE environment variable.") | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hrm, is it a breaking change to make this required? It seems like it might be, and it'd be better to leave There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, set default value to testnet passphrase but in the next minor we need to make this empty by default. Ex. if you run ingestion node without web server and when stellar-core is not running it should be explicitly set by Horizon admin (#817). |
||
ll, err := logrus.ParseLevel(viper.GetString("log-level")) | ||
|
||
if err != nil { | ||
|
@@ -299,6 +304,7 @@ func initConfig() { | |
LogLevel: ll, | ||
LogFile: lf, | ||
MaxPathLength: uint(viper.GetInt("max-path-length")), | ||
NetworkPassphrase: viper.GetString("network-passphrase"), | ||
SentryDSN: viper.GetString("sentry-dsn"), | ||
LogglyToken: viper.GetString("loggly-token"), | ||
LogglyTag: viper.GetString("loggly-tag"), | ||
|
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.
Nothing wrong with the logic here but what is the reason that we have an Err field in r as oppose to have ResultByHash return r and err directly?
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.
It's also used as an object returned by channel.