From 2d85bb384b9a8dd7196707caaaa735353a48555c Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Thu, 10 Oct 2024 16:09:08 +0200 Subject: [PATCH] feat: handle too many client connection error --- cmd/serve.go | 5 + internal/README.md | 257 ++++++++++-------- .../v1/controllers_accounts_add_metadata.go | 8 +- internal/api/v1/controllers_accounts_count.go | 3 + .../controllers_accounts_delete_metadata.go | 9 +- internal/api/v1/controllers_accounts_list.go | 3 + internal/api/v1/controllers_accounts_read.go | 3 + .../api/v1/controllers_balances_aggregates.go | 9 +- internal/api/v1/controllers_balances_list.go | 9 +- internal/api/v1/controllers_info.go | 9 +- internal/api/v1/controllers_logs_list.go | 11 +- internal/api/v1/controllers_stats.go | 9 +- .../controllers_transactions_add_metadata.go | 3 + .../api/v1/controllers_transactions_count.go | 9 +- .../api/v1/controllers_transactions_create.go | 5 + ...ontrollers_transactions_delete_metadata.go | 3 + .../api/v1/controllers_transactions_list.go | 9 +- .../api/v1/controllers_transactions_read.go | 3 + .../api/v1/controllers_transactions_revert.go | 3 + .../v2/controllers_accounts_add_metadata.go | 8 +- internal/api/v2/controllers_accounts_count.go | 3 + .../controllers_accounts_delete_metadata.go | 9 +- internal/api/v2/controllers_accounts_list.go | 3 + internal/api/v2/controllers_accounts_read.go | 3 + internal/api/v2/controllers_balances.go | 3 + internal/api/v2/controllers_ledgers_create.go | 3 + .../v2/controllers_ledgers_delete_metadata.go | 9 +- internal/api/v2/controllers_ledgers_info.go | 9 +- internal/api/v2/controllers_ledgers_list.go | 3 + internal/api/v2/controllers_ledgers_read.go | 3 + .../v2/controllers_ledgers_update_metadata.go | 8 +- internal/api/v2/controllers_logs_export.go | 9 +- internal/api/v2/controllers_logs_import.go | 3 + internal/api/v2/controllers_logs_list.go | 3 + internal/api/v2/controllers_stats.go | 9 +- .../controllers_transactions_add_metadata.go | 3 + .../api/v2/controllers_transactions_count.go | 3 + .../api/v2/controllers_transactions_create.go | 3 + ...ontrollers_transactions_delete_metadata.go | 3 + .../api/v2/controllers_transactions_list.go | 3 + .../api/v2/controllers_transactions_read.go | 3 + .../api/v2/controllers_transactions_revert.go | 3 + internal/api/v2/controllers_volumes.go | 3 + .../ledger/controller_with_events.go | 3 +- ...ontroller_with_too_many_client_handling.go | 97 +++++++ ...too_many_client_handling_generated_test.go | 50 ++++ ...ller_with_too_many_client_handling_test.go | 79 ++++++ internal/controller/ledger/errors.go | 1 + internal/controller/ledger/log_process.go | 4 - internal/controller/system/controller.go | 23 +- internal/controller/system/module.go | 9 + 51 files changed, 614 insertions(+), 134 deletions(-) create mode 100644 internal/controller/ledger/controller_with_too_many_client_handling.go create mode 100644 internal/controller/ledger/controller_with_too_many_client_handling_generated_test.go create mode 100644 internal/controller/ledger/controller_with_too_many_client_handling_test.go diff --git a/cmd/serve.go b/cmd/serve.go index e0340b9eb..dacecc798 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -5,6 +5,7 @@ import ( "errors" "github.com/formancehq/ledger/internal/storage/driver" "net/http" + "time" "github.com/formancehq/ledger/internal/bus" otelpyroscope "github.com/grafana/otel-profiling-go" @@ -88,6 +89,10 @@ func NewServeCommand() *cobra.Command { NSCacheConfiguration: ledgercontroller.CacheConfiguration{ MaxCount: serveConfiguration.numscriptCacheMaxCount, }, + DatabaseRetryConfiguration: systemcontroller.DatabaseRetryConfiguration{ + MaxRetry: 10, + Delay: time.Millisecond*100, + }, }), bus.NewFxModule(), ballast.Module(serveConfiguration.ballastSize), diff --git a/internal/README.md b/internal/README.md index eaa32e484..71e425d6d 100644 --- a/internal/README.md +++ b/internal/README.md @@ -21,6 +21,7 @@ import "github.com/formancehq/ledger/internal" - [func \(c \*Configuration\) SetDefaults\(\)](<#Configuration.SetDefaults>) - [func \(c \*Configuration\) Validate\(\) error](<#Configuration.Validate>) - [type CreatedTransaction](<#CreatedTransaction>) + - [func \(p CreatedTransaction\) GetMemento\(\) any](<#CreatedTransaction.GetMemento>) - [func \(p CreatedTransaction\) Type\(\) LogType](<#CreatedTransaction.Type>) - [type DeletedMetadata](<#DeletedMetadata>) - [func \(s DeletedMetadata\) Type\(\) LogType](<#DeletedMetadata.Type>) @@ -32,6 +33,7 @@ import "github.com/formancehq/ledger/internal" - [func \(e ErrInvalidLedgerName\) Error\(\) string](<#ErrInvalidLedgerName.Error>) - [func \(e ErrInvalidLedgerName\) Is\(err error\) bool](<#ErrInvalidLedgerName.Is>) - [type FeatureSet](<#FeatureSet>) + - [func \(f FeatureSet\) String\(\) string](<#FeatureSet.String>) - [func \(f FeatureSet\) With\(feature, value string\) FeatureSet](<#FeatureSet.With>) - [type Ledger](<#Ledger>) - [func MustNewWithDefault\(name string\) Ledger](<#MustNewWithDefault>) @@ -54,6 +56,7 @@ import "github.com/formancehq/ledger/internal" - [func \(l LogType\) String\(\) string](<#LogType.String>) - [func \(lt \*LogType\) UnmarshalJSON\(data \[\]byte\) error](<#LogType.UnmarshalJSON>) - [func \(lt LogType\) Value\(\) \(driver.Value, error\)](<#LogType.Value>) +- [type Memento](<#Memento>) - [type Move](<#Move>) - [type Moves](<#Moves>) - [func \(m Moves\) ComputePostCommitEffectiveVolumes\(\) PostCommitVolumes](<#Moves.ComputePostCommitEffectiveVolumes>) @@ -70,6 +73,7 @@ import "github.com/formancehq/ledger/internal" - [func \(p Postings\) Reverse\(\) Postings](<#Postings.Reverse>) - [func \(p Postings\) Validate\(\) \(int, error\)](<#Postings.Validate>) - [type RevertedTransaction](<#RevertedTransaction>) + - [func \(r RevertedTransaction\) GetMemento\(\) any](<#RevertedTransaction.GetMemento>) - [func \(r RevertedTransaction\) Type\(\) LogType](<#RevertedTransaction.Type>) - [type SavedMetadata](<#SavedMetadata>) - [func \(s SavedMetadata\) Type\(\) LogType](<#SavedMetadata.Type>) @@ -112,7 +116,7 @@ import "github.com/formancehq/ledger/internal" ## Constants -Current set of features: | Name | Default value | Possible configuration | Description | |\-|\-|\-|\-| | ACCOUNT\_METADATA\_HISTORY | + ```go const ( @@ -200,7 +204,7 @@ var Zero = big.NewInt(0) ``` -## func [ComputeIdempotencyHash]() +## func ComputeIdempotencyHash ```go func ComputeIdempotencyHash(inputs any) string @@ -209,7 +213,7 @@ func ComputeIdempotencyHash(inputs any) string -## type [Account]() +## type Account @@ -219,16 +223,16 @@ type Account struct { Address string `json:"address" bun:"address"` Metadata metadata.Metadata `json:"metadata" bun:"metadata,type:jsonb"` - FirstUsage time.Time `json:"-" bun:"first_usage"` - InsertionDate time.Time `json:"_" bun:"insertion_date"` - UpdatedAt time.Time `json:"-" bun:"updated_at"` - Volumes VolumesByAssets `json:"volumes,omitempty" bun:"pcv,scanonly"` - EffectiveVolumes VolumesByAssets `json:"effectiveVolumes,omitempty" bun:"pcev,scanonly"` + FirstUsage time.Time `json:"-" bun:"first_usage,nullzero"` + InsertionDate time.Time `json:"_" bun:"insertion_date,nullzero"` + UpdatedAt time.Time `json:"-" bun:"updated_at,nullzero"` + Volumes VolumesByAssets `json:"volumes,omitempty" bun:"volumes,scanonly"` + EffectiveVolumes VolumesByAssets `json:"effectiveVolumes,omitempty" bun:"effective_volumes,scanonly"` } ``` -## type [AccountMetadata]() +## type AccountMetadata @@ -237,7 +241,7 @@ type AccountMetadata map[string]metadata.Metadata ``` -## type [AccountsVolumes]() +## type AccountsVolumes @@ -253,7 +257,7 @@ type AccountsVolumes struct { ``` -## type [BalancesByAssets]() +## type BalancesByAssets @@ -262,7 +266,7 @@ type BalancesByAssets map[string]*big.Int ``` -## type [BalancesByAssetsByAccounts]() +## type BalancesByAssetsByAccounts @@ -271,7 +275,7 @@ type BalancesByAssetsByAccounts map[string]BalancesByAssets ``` -## type [Configuration]() +## type Configuration @@ -284,7 +288,7 @@ type Configuration struct { ``` -### func [NewDefaultConfiguration]() +### func NewDefaultConfiguration ```go func NewDefaultConfiguration() Configuration @@ -293,7 +297,7 @@ func NewDefaultConfiguration() Configuration -### func \(\*Configuration\) [SetDefaults]() +### func \(\*Configuration\) SetDefaults ```go func (c *Configuration) SetDefaults() @@ -302,7 +306,7 @@ func (c *Configuration) SetDefaults() -### func \(\*Configuration\) [Validate]() +### func \(\*Configuration\) Validate ```go func (c *Configuration) Validate() error @@ -311,7 +315,7 @@ func (c *Configuration) Validate() error -## type [CreatedTransaction]() +## type CreatedTransaction @@ -322,8 +326,17 @@ type CreatedTransaction struct { } ``` + +### func \(CreatedTransaction\) GetMemento + +```go +func (p CreatedTransaction) GetMemento() any +``` + + + -### func \(CreatedTransaction\) [Type]() +### func \(CreatedTransaction\) Type ```go func (p CreatedTransaction) Type() LogType @@ -332,7 +345,7 @@ func (p CreatedTransaction) Type() LogType -## type [DeletedMetadata]() +## type DeletedMetadata @@ -345,7 +358,7 @@ type DeletedMetadata struct { ``` -### func \(DeletedMetadata\) [Type]() +### func \(DeletedMetadata\) Type ```go func (s DeletedMetadata) Type() LogType @@ -354,7 +367,7 @@ func (s DeletedMetadata) Type() LogType -### func \(\*DeletedMetadata\) [UnmarshalJSON]() +### func \(\*DeletedMetadata\) UnmarshalJSON ```go func (s *DeletedMetadata) UnmarshalJSON(data []byte) error @@ -363,7 +376,7 @@ func (s *DeletedMetadata) UnmarshalJSON(data []byte) error -## type [ErrInvalidBucketName]() +## type ErrInvalidBucketName @@ -374,7 +387,7 @@ type ErrInvalidBucketName struct { ``` -### func \(ErrInvalidBucketName\) [Error]() +### func \(ErrInvalidBucketName\) Error ```go func (e ErrInvalidBucketName) Error() string @@ -383,7 +396,7 @@ func (e ErrInvalidBucketName) Error() string -### func \(ErrInvalidBucketName\) [Is]() +### func \(ErrInvalidBucketName\) Is ```go func (e ErrInvalidBucketName) Is(err error) bool @@ -392,7 +405,7 @@ func (e ErrInvalidBucketName) Is(err error) bool -## type [ErrInvalidLedgerName]() +## type ErrInvalidLedgerName @@ -403,7 +416,7 @@ type ErrInvalidLedgerName struct { ``` -### func \(ErrInvalidLedgerName\) [Error]() +### func \(ErrInvalidLedgerName\) Error ```go func (e ErrInvalidLedgerName) Error() string @@ -412,7 +425,7 @@ func (e ErrInvalidLedgerName) Error() string -### func \(ErrInvalidLedgerName\) [Is]() +### func \(ErrInvalidLedgerName\) Is ```go func (e ErrInvalidLedgerName) Is(err error) bool @@ -421,7 +434,7 @@ func (e ErrInvalidLedgerName) Is(err error) bool -## type [FeatureSet]() +## type FeatureSet @@ -429,8 +442,17 @@ func (e ErrInvalidLedgerName) Is(err error) bool type FeatureSet map[string]string ``` + +### func \(FeatureSet\) String + +```go +func (f FeatureSet) String() string +``` + + + -### func \(FeatureSet\) [With]() +### func \(FeatureSet\) With ```go func (f FeatureSet) With(feature, value string) FeatureSet @@ -439,7 +461,7 @@ func (f FeatureSet) With(feature, value string) FeatureSet -## type [Ledger]() +## type Ledger @@ -450,12 +472,12 @@ type Ledger struct { Configuration ID int `json:"id" bun:"id,type:int,scanonly"` Name string `json:"name" bun:"name,type:varchar(255),pk"` - AddedAt time.Time `json:"addedAt" bun:"addedat,type:timestamp"` + AddedAt time.Time `json:"addedAt" bun:"added_at,type:timestamp,nullzero"` } ``` -### func [MustNewWithDefault]() +### func MustNewWithDefault ```go func MustNewWithDefault(name string) Ledger @@ -464,7 +486,7 @@ func MustNewWithDefault(name string) Ledger -### func [New]() +### func New ```go func New(name string, configuration Configuration) (*Ledger, error) @@ -473,7 +495,7 @@ func New(name string, configuration Configuration) (*Ledger, error) -### func [NewWithDefaults]() +### func NewWithDefaults ```go func NewWithDefaults(name string) (*Ledger, error) @@ -482,7 +504,7 @@ func NewWithDefaults(name string) (*Ledger, error) -### func \(Ledger\) [HasFeature]() +### func \(Ledger\) HasFeature ```go func (l Ledger) HasFeature(feature, value string) bool @@ -491,7 +513,7 @@ func (l Ledger) HasFeature(feature, value string) bool -### func \(Ledger\) [WithMetadata]() +### func \(Ledger\) WithMetadata ```go func (l Ledger) WithMetadata(m metadata.Metadata) Ledger @@ -500,7 +522,7 @@ func (l Ledger) WithMetadata(m metadata.Metadata) Ledger -## type [Log]() +## type Log Log represents atomic actions made on the ledger. @@ -510,18 +532,18 @@ type Log struct { Type LogType `json:"type" bun:"type,type:log_type"` Data LogPayload `json:"data" bun:"data,type:jsonb"` - Date time.Time `json:"date" bun:"date,type:timestamptz"` + Date time.Time `json:"date" bun:"date,type:timestamptz,nullzero"` IdempotencyKey string `json:"idempotencyKey" bun:"idempotency_key,type:varchar(256),unique,nullzero"` // IdempotencyHash is a signature used when using IdempotencyKey. // It allows to check if the usage of IdempotencyKey match inputs given on the first idempotency key usage. IdempotencyHash string `json:"idempotencyHash" bun:"idempotency_hash,unique,nullzero"` ID int `json:"id" bun:"id,unique,type:numeric"` - Hash []byte `json:"hash" bun:"hash,type:bytea"` + Hash []byte `json:"hash" bun:"hash,type:bytea,scanonly"` } ``` -### func [NewLog]() +### func NewLog ```go func NewLog(payload LogPayload) Log @@ -530,7 +552,7 @@ func NewLog(payload LogPayload) Log -### func \(Log\) [ChainLog]() +### func \(Log\) ChainLog ```go func (l Log) ChainLog(previous *Log) Log @@ -539,7 +561,7 @@ func (l Log) ChainLog(previous *Log) Log -### func \(\*Log\) [ComputeHash]() +### func \(\*Log\) ComputeHash ```go func (l *Log) ComputeHash(previous *Log) @@ -548,7 +570,7 @@ func (l *Log) ComputeHash(previous *Log) -### func \(\*Log\) [UnmarshalJSON]() +### func \(\*Log\) UnmarshalJSON ```go func (l *Log) UnmarshalJSON(data []byte) error @@ -557,7 +579,7 @@ func (l *Log) UnmarshalJSON(data []byte) error -### func \(Log\) [WithIdempotencyKey]() +### func \(Log\) WithIdempotencyKey ```go func (l Log) WithIdempotencyKey(key string) Log @@ -566,7 +588,7 @@ func (l Log) WithIdempotencyKey(key string) Log -## type [LogPayload]() +## type LogPayload @@ -577,7 +599,7 @@ type LogPayload interface { ``` -### func [HydrateLog]() +### func HydrateLog ```go func HydrateLog(_type LogType, data []byte) (LogPayload, error) @@ -586,7 +608,7 @@ func HydrateLog(_type LogType, data []byte) (LogPayload, error) -## type [LogType]() +## type LogType @@ -606,7 +628,7 @@ const ( ``` -### func [LogTypeFromString]() +### func LogTypeFromString ```go func LogTypeFromString(logType string) LogType @@ -615,7 +637,7 @@ func LogTypeFromString(logType string) LogType -### func \(LogType\) [MarshalJSON]() +### func \(LogType\) MarshalJSON ```go func (lt LogType) MarshalJSON() ([]byte, error) @@ -624,7 +646,7 @@ func (lt LogType) MarshalJSON() ([]byte, error) -### func \(\*LogType\) [Scan]() +### func \(\*LogType\) Scan ```go func (lt *LogType) Scan(src interface{}) error @@ -633,7 +655,7 @@ func (lt *LogType) Scan(src interface{}) error -### func \(LogType\) [String]() +### func \(LogType\) String ```go func (l LogType) String() string @@ -642,7 +664,7 @@ func (l LogType) String() string -### func \(\*LogType\) [UnmarshalJSON]() +### func \(\*LogType\) UnmarshalJSON ```go func (lt *LogType) UnmarshalJSON(data []byte) error @@ -651,7 +673,7 @@ func (lt *LogType) UnmarshalJSON(data []byte) error -### func \(LogType\) [Value]() +### func \(LogType\) Value ```go func (lt LogType) Value() (driver.Value, error) @@ -659,8 +681,19 @@ func (lt LogType) Value() (driver.Value, error) + +## type Memento + + + +```go +type Memento interface { + GetMemento() any +} +``` + -## type [Move]() +## type Move @@ -668,7 +701,6 @@ func (lt LogType) Value() (driver.Value, error) type Move struct { bun.BaseModel `bun:"table:moves"` - Ledger string `bun:"ledger,type:varchar"` TransactionID int `bun:"transactions_id,type:bigint"` IsSource bool `bun:"is_source,type:bool"` Account string `bun:"accounts_address,type:varchar"` @@ -682,7 +714,7 @@ type Move struct { ``` -## type [Moves]() +## type Moves @@ -691,7 +723,7 @@ type Moves []*Move ``` -### func \(Moves\) [ComputePostCommitEffectiveVolumes]() +### func \(Moves\) ComputePostCommitEffectiveVolumes ```go func (m Moves) ComputePostCommitEffectiveVolumes() PostCommitVolumes @@ -700,7 +732,7 @@ func (m Moves) ComputePostCommitEffectiveVolumes() PostCommitVolumes -## type [PostCommitVolumes]() +## type PostCommitVolumes @@ -709,7 +741,7 @@ type PostCommitVolumes map[string]VolumesByAssets ``` -### func \(PostCommitVolumes\) [AddInput]() +### func \(PostCommitVolumes\) AddInput ```go func (a PostCommitVolumes) AddInput(account, asset string, input *big.Int) @@ -718,7 +750,7 @@ func (a PostCommitVolumes) AddInput(account, asset string, input *big.Int) -### func \(PostCommitVolumes\) [AddOutput]() +### func \(PostCommitVolumes\) AddOutput ```go func (a PostCommitVolumes) AddOutput(account, asset string, output *big.Int) @@ -727,7 +759,7 @@ func (a PostCommitVolumes) AddOutput(account, asset string, output *big.Int) -### func \(PostCommitVolumes\) [Copy]() +### func \(PostCommitVolumes\) Copy ```go func (a PostCommitVolumes) Copy() PostCommitVolumes @@ -736,7 +768,7 @@ func (a PostCommitVolumes) Copy() PostCommitVolumes -### func \(PostCommitVolumes\) [Merge]() +### func \(PostCommitVolumes\) Merge ```go func (a PostCommitVolumes) Merge(volumes PostCommitVolumes) PostCommitVolumes @@ -745,7 +777,7 @@ func (a PostCommitVolumes) Merge(volumes PostCommitVolumes) PostCommitVolumes -## type [Posting]() +## type Posting @@ -759,7 +791,7 @@ type Posting struct { ``` -### func [NewPosting]() +### func NewPosting ```go func NewPosting(source string, destination string, asset string, amount *big.Int) Posting @@ -768,7 +800,7 @@ func NewPosting(source string, destination string, asset string, amount *big.Int -### func \(Posting\) [GetDestination]() +### func \(Posting\) GetDestination ```go func (p Posting) GetDestination() string @@ -777,7 +809,7 @@ func (p Posting) GetDestination() string -### func \(Posting\) [GetSource]() +### func \(Posting\) GetSource ```go func (p Posting) GetSource() string @@ -786,7 +818,7 @@ func (p Posting) GetSource() string -## type [Postings]() +## type Postings @@ -795,7 +827,7 @@ type Postings []Posting ``` -### func \(Postings\) [Reverse]() +### func \(Postings\) Reverse ```go func (p Postings) Reverse() Postings @@ -804,7 +836,7 @@ func (p Postings) Reverse() Postings -### func \(Postings\) [Validate]() +### func \(Postings\) Validate ```go func (p Postings) Validate() (int, error) @@ -813,7 +845,7 @@ func (p Postings) Validate() (int, error) -## type [RevertedTransaction]() +## type RevertedTransaction @@ -824,8 +856,17 @@ type RevertedTransaction struct { } ``` + +### func \(RevertedTransaction\) GetMemento + +```go +func (r RevertedTransaction) GetMemento() any +``` + + + -### func \(RevertedTransaction\) [Type]() +### func \(RevertedTransaction\) Type ```go func (r RevertedTransaction) Type() LogType @@ -834,7 +875,7 @@ func (r RevertedTransaction) Type() LogType -## type [SavedMetadata]() +## type SavedMetadata @@ -847,7 +888,7 @@ type SavedMetadata struct { ``` -### func \(SavedMetadata\) [Type]() +### func \(SavedMetadata\) Type ```go func (s SavedMetadata) Type() LogType @@ -856,7 +897,7 @@ func (s SavedMetadata) Type() LogType -### func \(\*SavedMetadata\) [UnmarshalJSON]() +### func \(\*SavedMetadata\) UnmarshalJSON ```go func (s *SavedMetadata) UnmarshalJSON(data []byte) error @@ -865,7 +906,7 @@ func (s *SavedMetadata) UnmarshalJSON(data []byte) error -## type [Transaction]() +## type Transaction @@ -886,7 +927,7 @@ type Transaction struct { ``` -### func [NewTransaction]() +### func NewTransaction ```go func NewTransaction() Transaction @@ -895,7 +936,7 @@ func NewTransaction() Transaction -### func \(Transaction\) [InvolvedAccountAndAssets]() +### func \(Transaction\) InvolvedAccountAndAssets ```go func (tx Transaction) InvolvedAccountAndAssets() map[string][]string @@ -904,7 +945,7 @@ func (tx Transaction) InvolvedAccountAndAssets() map[string][]string -### func \(Transaction\) [InvolvedAccounts]() +### func \(Transaction\) InvolvedAccounts ```go func (tx Transaction) InvolvedAccounts() []string @@ -913,7 +954,7 @@ func (tx Transaction) InvolvedAccounts() []string -### func \(Transaction\) [IsReverted]() +### func \(Transaction\) IsReverted ```go func (tx Transaction) IsReverted() bool @@ -922,7 +963,7 @@ func (tx Transaction) IsReverted() bool -### func \(Transaction\) [JSONSchemaExtend]() +### func \(Transaction\) JSONSchemaExtend ```go func (Transaction) JSONSchemaExtend(schema *jsonschema.Schema) @@ -931,7 +972,7 @@ func (Transaction) JSONSchemaExtend(schema *jsonschema.Schema) -### func \(Transaction\) [MarshalJSON]() +### func \(Transaction\) MarshalJSON ```go func (tx Transaction) MarshalJSON() ([]byte, error) @@ -940,7 +981,7 @@ func (tx Transaction) MarshalJSON() ([]byte, error) -### func \(Transaction\) [Reverse]() +### func \(Transaction\) Reverse ```go func (tx Transaction) Reverse() Transaction @@ -949,7 +990,7 @@ func (tx Transaction) Reverse() Transaction -### func \(Transaction\) [VolumeUpdates]() +### func \(Transaction\) VolumeUpdates ```go func (tx Transaction) VolumeUpdates() []AccountsVolumes @@ -958,7 +999,7 @@ func (tx Transaction) VolumeUpdates() []AccountsVolumes -### func \(Transaction\) [WithInsertedAt]() +### func \(Transaction\) WithInsertedAt ```go func (tx Transaction) WithInsertedAt(date time.Time) Transaction @@ -967,7 +1008,7 @@ func (tx Transaction) WithInsertedAt(date time.Time) Transaction -### func \(Transaction\) [WithMetadata]() +### func \(Transaction\) WithMetadata ```go func (tx Transaction) WithMetadata(m metadata.Metadata) Transaction @@ -976,7 +1017,7 @@ func (tx Transaction) WithMetadata(m metadata.Metadata) Transaction -### func \(Transaction\) [WithPostCommitEffectiveVolumes]() +### func \(Transaction\) WithPostCommitEffectiveVolumes ```go func (tx Transaction) WithPostCommitEffectiveVolumes(volumes PostCommitVolumes) Transaction @@ -985,7 +1026,7 @@ func (tx Transaction) WithPostCommitEffectiveVolumes(volumes PostCommitVolumes) -### func \(Transaction\) [WithPostings]() +### func \(Transaction\) WithPostings ```go func (tx Transaction) WithPostings(postings ...Posting) Transaction @@ -994,7 +1035,7 @@ func (tx Transaction) WithPostings(postings ...Posting) Transaction -### func \(Transaction\) [WithReference]() +### func \(Transaction\) WithReference ```go func (tx Transaction) WithReference(ref string) Transaction @@ -1003,7 +1044,7 @@ func (tx Transaction) WithReference(ref string) Transaction -### func \(Transaction\) [WithRevertedAt]() +### func \(Transaction\) WithRevertedAt ```go func (tx Transaction) WithRevertedAt(timestamp time.Time) Transaction @@ -1012,7 +1053,7 @@ func (tx Transaction) WithRevertedAt(timestamp time.Time) Transaction -### func \(Transaction\) [WithTimestamp]() +### func \(Transaction\) WithTimestamp ```go func (tx Transaction) WithTimestamp(ts time.Time) Transaction @@ -1021,7 +1062,7 @@ func (tx Transaction) WithTimestamp(ts time.Time) Transaction -## type [TransactionData]() +## type TransactionData @@ -1036,7 +1077,7 @@ type TransactionData struct { ``` -### func [NewTransactionData]() +### func NewTransactionData ```go func NewTransactionData() TransactionData @@ -1045,7 +1086,7 @@ func NewTransactionData() TransactionData -### func \(TransactionData\) [WithPostings]() +### func \(TransactionData\) WithPostings ```go func (data TransactionData) WithPostings(postings ...Posting) TransactionData @@ -1054,7 +1095,7 @@ func (data TransactionData) WithPostings(postings ...Posting) TransactionData -## type [Transactions]() +## type Transactions @@ -1065,7 +1106,7 @@ type Transactions struct { ``` -## type [Volumes]() +## type Volumes @@ -1077,7 +1118,7 @@ type Volumes struct { ``` -### func [NewEmptyVolumes]() +### func NewEmptyVolumes ```go func NewEmptyVolumes() Volumes @@ -1086,7 +1127,7 @@ func NewEmptyVolumes() Volumes -### func [NewVolumesInt64]() +### func NewVolumesInt64 ```go func NewVolumesInt64(input, output int64) Volumes @@ -1095,7 +1136,7 @@ func NewVolumesInt64(input, output int64) Volumes -### func \(Volumes\) [Balance]() +### func \(Volumes\) Balance ```go func (v Volumes) Balance() *big.Int @@ -1104,7 +1145,7 @@ func (v Volumes) Balance() *big.Int -### func \(Volumes\) [Copy]() +### func \(Volumes\) Copy ```go func (v Volumes) Copy() Volumes @@ -1113,7 +1154,7 @@ func (v Volumes) Copy() Volumes -### func \(Volumes\) [JSONSchemaExtend]() +### func \(Volumes\) JSONSchemaExtend ```go func (Volumes) JSONSchemaExtend(schema *jsonschema.Schema) @@ -1122,7 +1163,7 @@ func (Volumes) JSONSchemaExtend(schema *jsonschema.Schema) -### func \(Volumes\) [MarshalJSON]() +### func \(Volumes\) MarshalJSON ```go func (v Volumes) MarshalJSON() ([]byte, error) @@ -1131,7 +1172,7 @@ func (v Volumes) MarshalJSON() ([]byte, error) -### func \(Volumes\) [WithInput]() +### func \(Volumes\) WithInput ```go func (v Volumes) WithInput(input *big.Int) Volumes @@ -1140,7 +1181,7 @@ func (v Volumes) WithInput(input *big.Int) Volumes -### func \(Volumes\) [WithOutput]() +### func \(Volumes\) WithOutput ```go func (v Volumes) WithOutput(output *big.Int) Volumes @@ -1149,7 +1190,7 @@ func (v Volumes) WithOutput(output *big.Int) Volumes -## type [VolumesByAssets]() +## type VolumesByAssets @@ -1158,7 +1199,7 @@ type VolumesByAssets map[string]Volumes ``` -### func \(VolumesByAssets\) [Balances]() +### func \(VolumesByAssets\) Balances ```go func (v VolumesByAssets) Balances() BalancesByAssets @@ -1167,7 +1208,7 @@ func (v VolumesByAssets) Balances() BalancesByAssets -## type [VolumesWithBalance]() +## type VolumesWithBalance @@ -1180,7 +1221,7 @@ type VolumesWithBalance struct { ``` -## type [VolumesWithBalanceByAssetByAccount]() +## type VolumesWithBalanceByAssetByAccount @@ -1193,7 +1234,7 @@ type VolumesWithBalanceByAssetByAccount struct { ``` -## type [VolumesWithBalanceByAssets]() +## type VolumesWithBalanceByAssets diff --git a/internal/api/v1/controllers_accounts_add_metadata.go b/internal/api/v1/controllers_accounts_add_metadata.go index a582b322b..50a3687c3 100644 --- a/internal/api/v1/controllers_accounts_add_metadata.go +++ b/internal/api/v1/controllers_accounts_add_metadata.go @@ -2,6 +2,7 @@ package v1 import ( "encoding/json" + "github.com/formancehq/go-libs/platform/postgres" "github.com/formancehq/ledger/internal/controller/ledger" "github.com/formancehq/ledger/pkg/accounts" "net/http" @@ -38,7 +39,12 @@ func addAccountMetadata(w http.ResponseWriter, r *http.Request) { Metadata: m, })) if err != nil { - api.InternalServerError(w, r, err) + switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) + default: + api.InternalServerError(w, r, err) + } return } diff --git a/internal/api/v1/controllers_accounts_count.go b/internal/api/v1/controllers_accounts_count.go index 38b2c0df4..991b00fc1 100644 --- a/internal/api/v1/controllers_accounts_count.go +++ b/internal/api/v1/controllers_accounts_count.go @@ -2,6 +2,7 @@ package v1 import ( "fmt" + "github.com/formancehq/go-libs/platform/postgres" "net/http" "errors" @@ -34,6 +35,8 @@ func countAccounts(w http.ResponseWriter, r *http.Request) { count, err := l.CountAccounts(r.Context(), *query) if err != nil { switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) case errors.Is(err, ledgercontroller.ErrInvalidQuery{}) || errors.Is(err, ledgercontroller.ErrMissingFeature{}): api.BadRequest(w, ErrValidation, err) default: diff --git a/internal/api/v1/controllers_accounts_delete_metadata.go b/internal/api/v1/controllers_accounts_delete_metadata.go index 5de701536..9f7fccc06 100644 --- a/internal/api/v1/controllers_accounts_delete_metadata.go +++ b/internal/api/v1/controllers_accounts_delete_metadata.go @@ -1,6 +1,8 @@ package v1 import ( + "errors" + "github.com/formancehq/go-libs/platform/postgres" "github.com/formancehq/ledger/internal/controller/ledger" "net/http" "net/url" @@ -25,7 +27,12 @@ func deleteAccountMetadata(w http.ResponseWriter, r *http.Request) { Key: chi.URLParam(r, "key"), }), ); err != nil { - api.InternalServerError(w, r, err) + switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) + default: + api.InternalServerError(w, r, err) + } return } diff --git a/internal/api/v1/controllers_accounts_list.go b/internal/api/v1/controllers_accounts_list.go index 730b37fb9..495986e70 100644 --- a/internal/api/v1/controllers_accounts_list.go +++ b/internal/api/v1/controllers_accounts_list.go @@ -1,6 +1,7 @@ package v1 import ( + "github.com/formancehq/go-libs/platform/postgres" "net/http" "errors" @@ -33,6 +34,8 @@ func listAccounts(w http.ResponseWriter, r *http.Request) { cursor, err := l.ListAccounts(r.Context(), *query) if err != nil { switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) case errors.Is(err, ledgercontroller.ErrMissingFeature{}): api.BadRequest(w, ErrValidation, err) default: diff --git a/internal/api/v1/controllers_accounts_read.go b/internal/api/v1/controllers_accounts_read.go index 3fb64f39e..644bd51c0 100644 --- a/internal/api/v1/controllers_accounts_read.go +++ b/internal/api/v1/controllers_accounts_read.go @@ -1,6 +1,7 @@ package v1 import ( + "errors" "net/http" "net/url" @@ -28,6 +29,8 @@ func getAccount(w http.ResponseWriter, r *http.Request) { acc, err := l.GetAccount(r.Context(), query) if err != nil { switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) case postgres.IsNotFoundError(err): acc = &ledger.Account{ Address: address, diff --git a/internal/api/v1/controllers_balances_aggregates.go b/internal/api/v1/controllers_balances_aggregates.go index 5c99dbbc5..ce451b5cf 100644 --- a/internal/api/v1/controllers_balances_aggregates.go +++ b/internal/api/v1/controllers_balances_aggregates.go @@ -1,6 +1,8 @@ package v1 import ( + "errors" + "github.com/formancehq/go-libs/platform/postgres" "net/http" "github.com/formancehq/go-libs/api" @@ -37,7 +39,12 @@ func getBalancesAggregated(w http.ResponseWriter, r *http.Request) { balances, err := common.LedgerFromContext(r.Context()).GetAggregatedBalances(r.Context(), query) if err != nil { - api.InternalServerError(w, r, err) + switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) + default: + api.InternalServerError(w, r, err) + } return } diff --git a/internal/api/v1/controllers_balances_list.go b/internal/api/v1/controllers_balances_list.go index 20c671f50..8597b75f9 100644 --- a/internal/api/v1/controllers_balances_list.go +++ b/internal/api/v1/controllers_balances_list.go @@ -1,6 +1,8 @@ package v1 import ( + "errors" + "github.com/formancehq/go-libs/platform/postgres" "math/big" "net/http" @@ -32,7 +34,12 @@ func getBalances(w http.ResponseWriter, r *http.Request) { cursor, err := l.ListAccounts(r.Context(), q.WithExpandVolumes()) if err != nil { - api.InternalServerError(w, r, err) + switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) + default: + api.InternalServerError(w, r, err) + } return } diff --git a/internal/api/v1/controllers_info.go b/internal/api/v1/controllers_info.go index 64681ec11..8d6efef73 100644 --- a/internal/api/v1/controllers_info.go +++ b/internal/api/v1/controllers_info.go @@ -1,6 +1,8 @@ package v1 import ( + "errors" + "github.com/formancehq/go-libs/platform/postgres" "net/http" "github.com/go-chi/chi/v5" @@ -29,7 +31,12 @@ func getLedgerInfo(w http.ResponseWriter, r *http.Request) { } res.Storage.Migrations, err = ledger.GetMigrationsInfo(r.Context()) if err != nil { - api.InternalServerError(w, r, err) + switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) + default: + api.InternalServerError(w, r, err) + } return } diff --git a/internal/api/v1/controllers_logs_list.go b/internal/api/v1/controllers_logs_list.go index eb75346e0..12e344e21 100644 --- a/internal/api/v1/controllers_logs_list.go +++ b/internal/api/v1/controllers_logs_list.go @@ -1,7 +1,9 @@ package v1 import ( + "errors" "fmt" + "github.com/formancehq/go-libs/platform/postgres" "net/http" "github.com/formancehq/go-libs/api" @@ -67,13 +69,18 @@ func getLogs(w http.ResponseWriter, r *http.Request) { query = ledgercontroller.NewListLogsQuery(ledgercontroller.PaginatedQueryOptions[any]{ QueryBuilder: qb, - PageSize: uint64(pageSize), + PageSize: pageSize, }) } cursor, err := l.ListLogs(r.Context(), query) if err != nil { - api.InternalServerError(w, r, err) + switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) + default: + api.InternalServerError(w, r, err) + } return } diff --git a/internal/api/v1/controllers_stats.go b/internal/api/v1/controllers_stats.go index 843101796..162cbe832 100644 --- a/internal/api/v1/controllers_stats.go +++ b/internal/api/v1/controllers_stats.go @@ -1,6 +1,8 @@ package v1 import ( + "errors" + "github.com/formancehq/go-libs/platform/postgres" "net/http" "github.com/formancehq/go-libs/api" @@ -12,7 +14,12 @@ func getStats(w http.ResponseWriter, r *http.Request) { stats, err := l.GetStats(r.Context()) if err != nil { - api.InternalServerError(w, r, err) + switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) + default: + api.InternalServerError(w, r, err) + } return } diff --git a/internal/api/v1/controllers_transactions_add_metadata.go b/internal/api/v1/controllers_transactions_add_metadata.go index 1a3a3fe4e..46aee088f 100644 --- a/internal/api/v1/controllers_transactions_add_metadata.go +++ b/internal/api/v1/controllers_transactions_add_metadata.go @@ -2,6 +2,7 @@ package v1 import ( "encoding/json" + "github.com/formancehq/go-libs/platform/postgres" "net/http" "strconv" @@ -34,6 +35,8 @@ func addTransactionMetadata(w http.ResponseWriter, r *http.Request) { Metadata: m, })); err != nil { switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) case errors.Is(err, ledgercontroller.ErrNotFound): api.NotFound(w, err) default: diff --git a/internal/api/v1/controllers_transactions_count.go b/internal/api/v1/controllers_transactions_count.go index e34f7ab77..2740b1c84 100644 --- a/internal/api/v1/controllers_transactions_count.go +++ b/internal/api/v1/controllers_transactions_count.go @@ -1,7 +1,9 @@ package v1 import ( + "errors" "fmt" + "github.com/formancehq/go-libs/platform/postgres" "net/http" "github.com/formancehq/go-libs/api" @@ -25,7 +27,12 @@ func countTransactions(w http.ResponseWriter, r *http.Request) { count, err := common.LedgerFromContext(r.Context()). CountTransactions(r.Context(), ledgercontroller.NewListTransactionsQuery(*options)) if err != nil { - api.InternalServerError(w, r, err) + switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) + default: + api.InternalServerError(w, r, err) + } return } diff --git a/internal/api/v1/controllers_transactions_create.go b/internal/api/v1/controllers_transactions_create.go index e80038aa8..95e6c5974 100644 --- a/internal/api/v1/controllers_transactions_create.go +++ b/internal/api/v1/controllers_transactions_create.go @@ -3,6 +3,7 @@ package v1 import ( "encoding/json" "fmt" + "github.com/formancehq/go-libs/platform/postgres" "math/big" "net/http" @@ -86,6 +87,8 @@ func createTransaction(w http.ResponseWriter, r *http.Request) { res, err := l.CreateTransaction(r.Context(), getCommandParameters(r, common.TxToScriptData(txData, false))) if err != nil { switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) case errors.Is(err, &ledgercontroller.ErrInsufficientFunds{}): api.BadRequest(w, ErrInsufficientFund, err) case errors.Is(err, &ledgercontroller.ErrInvalidVars{}) || errors.Is(err, ledgercontroller.ErrCompilationFailed{}): @@ -122,6 +125,8 @@ func createTransaction(w http.ResponseWriter, r *http.Request) { res, err := l.CreateTransaction(r.Context(), getCommandParameters(r, runScript)) if err != nil { switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) case errors.Is(err, &ledgercontroller.ErrInsufficientFunds{}): api.BadRequest(w, ErrInsufficientFund, err) case errors.Is(err, &ledgercontroller.ErrInvalidVars{}) || diff --git a/internal/api/v1/controllers_transactions_delete_metadata.go b/internal/api/v1/controllers_transactions_delete_metadata.go index 261529bfd..cb89a528d 100644 --- a/internal/api/v1/controllers_transactions_delete_metadata.go +++ b/internal/api/v1/controllers_transactions_delete_metadata.go @@ -1,6 +1,7 @@ package v1 import ( + "github.com/formancehq/go-libs/platform/postgres" "net/http" "strconv" @@ -28,6 +29,8 @@ func deleteTransactionMetadata(w http.ResponseWriter, r *http.Request) { Key: metadataKey, })); err != nil { switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) case errors.Is(err, ledgercontroller.ErrNotFound): api.NotFound(w, err) default: diff --git a/internal/api/v1/controllers_transactions_list.go b/internal/api/v1/controllers_transactions_list.go index 40e26f2b5..818720a7e 100644 --- a/internal/api/v1/controllers_transactions_list.go +++ b/internal/api/v1/controllers_transactions_list.go @@ -1,6 +1,8 @@ package v1 import ( + "errors" + "github.com/formancehq/go-libs/platform/postgres" "net/http" "github.com/formancehq/go-libs/api" @@ -31,7 +33,12 @@ func listTransactions(w http.ResponseWriter, r *http.Request) { cursor, err := l.ListTransactions(r.Context(), *query) if err != nil { - api.InternalServerError(w, r, err) + switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) + default: + api.InternalServerError(w, r, err) + } return } diff --git a/internal/api/v1/controllers_transactions_read.go b/internal/api/v1/controllers_transactions_read.go index 7bc7a6403..807e9bcd9 100644 --- a/internal/api/v1/controllers_transactions_read.go +++ b/internal/api/v1/controllers_transactions_read.go @@ -1,6 +1,7 @@ package v1 import ( + "errors" "net/http" "strconv" @@ -32,6 +33,8 @@ func readTransaction(w http.ResponseWriter, r *http.Request) { tx, err := l.GetTransaction(r.Context(), query) if err != nil { switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) case postgres.IsNotFoundError(err): api.NotFound(w, err) default: diff --git a/internal/api/v1/controllers_transactions_revert.go b/internal/api/v1/controllers_transactions_revert.go index fec50f5f1..4150856ba 100644 --- a/internal/api/v1/controllers_transactions_revert.go +++ b/internal/api/v1/controllers_transactions_revert.go @@ -1,6 +1,7 @@ package v1 import ( + "github.com/formancehq/go-libs/platform/postgres" "net/http" "strconv" @@ -31,6 +32,8 @@ func revertTransaction(w http.ResponseWriter, r *http.Request) { ) if err != nil { switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) case errors.Is(err, &ledgercontroller.ErrInsufficientFunds{}): api.BadRequest(w, ErrInsufficientFund, err) case errors.Is(err, ledgercontroller.ErrAlreadyReverted{}): diff --git a/internal/api/v2/controllers_accounts_add_metadata.go b/internal/api/v2/controllers_accounts_add_metadata.go index e0a2a3150..0d05b3ae5 100644 --- a/internal/api/v2/controllers_accounts_add_metadata.go +++ b/internal/api/v2/controllers_accounts_add_metadata.go @@ -2,6 +2,7 @@ package v2 import ( "encoding/json" + "github.com/formancehq/go-libs/platform/postgres" "github.com/formancehq/ledger/internal/controller/ledger" "net/http" "net/url" @@ -33,7 +34,12 @@ func addAccountMetadata(w http.ResponseWriter, r *http.Request) { Metadata: m, })) if err != nil { - api.InternalServerError(w, r, err) + switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) + default: + api.InternalServerError(w, r, err) + } return } diff --git a/internal/api/v2/controllers_accounts_count.go b/internal/api/v2/controllers_accounts_count.go index 55c34d6af..3404e664c 100644 --- a/internal/api/v2/controllers_accounts_count.go +++ b/internal/api/v2/controllers_accounts_count.go @@ -2,6 +2,7 @@ package v2 import ( "fmt" + "github.com/formancehq/go-libs/platform/postgres" "net/http" "errors" @@ -22,6 +23,8 @@ func countAccounts(w http.ResponseWriter, r *http.Request) { count, err := l.CountAccounts(r.Context(), ledgercontroller.NewListAccountsQuery(*options)) if err != nil { switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) case errors.Is(err, ledgercontroller.ErrInvalidQuery{}) || errors.Is(err, ledgercontroller.ErrMissingFeature{}): api.BadRequest(w, ErrValidation, err) default: diff --git a/internal/api/v2/controllers_accounts_delete_metadata.go b/internal/api/v2/controllers_accounts_delete_metadata.go index 0d8d65300..c0971bb21 100644 --- a/internal/api/v2/controllers_accounts_delete_metadata.go +++ b/internal/api/v2/controllers_accounts_delete_metadata.go @@ -1,6 +1,8 @@ package v2 import ( + "errors" + "github.com/formancehq/go-libs/platform/postgres" "github.com/formancehq/ledger/internal/controller/ledger" "net/http" "net/url" @@ -26,7 +28,12 @@ func deleteAccountMetadata(w http.ResponseWriter, r *http.Request) { Key: chi.URLParam(r, "key"), }), ); err != nil { - api.InternalServerError(w, r, err) + switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) + default: + api.InternalServerError(w, r, err) + } return } diff --git a/internal/api/v2/controllers_accounts_list.go b/internal/api/v2/controllers_accounts_list.go index 5c9578c00..7e3c40733 100644 --- a/internal/api/v2/controllers_accounts_list.go +++ b/internal/api/v2/controllers_accounts_list.go @@ -1,6 +1,7 @@ package v2 import ( + "github.com/formancehq/go-libs/platform/postgres" "net/http" "errors" @@ -29,6 +30,8 @@ func listAccounts(w http.ResponseWriter, r *http.Request) { cursor, err := l.ListAccounts(r.Context(), *query) if err != nil { switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) case errors.Is(err, ledgercontroller.ErrInvalidQuery{}) || errors.Is(err, ledgercontroller.ErrMissingFeature{}): api.BadRequest(w, ErrValidation, err) default: diff --git a/internal/api/v2/controllers_accounts_read.go b/internal/api/v2/controllers_accounts_read.go index 22259ab0e..a8bc58b72 100644 --- a/internal/api/v2/controllers_accounts_read.go +++ b/internal/api/v2/controllers_accounts_read.go @@ -1,6 +1,7 @@ package v2 import ( + "errors" "net/http" "net/url" @@ -37,6 +38,8 @@ func readAccount(w http.ResponseWriter, r *http.Request) { acc, err := l.GetAccount(r.Context(), query) if err != nil { switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) case postgres.IsNotFoundError(err): api.NotFound(w, err) default: diff --git a/internal/api/v2/controllers_balances.go b/internal/api/v2/controllers_balances.go index 450f75c2a..740e2820a 100644 --- a/internal/api/v2/controllers_balances.go +++ b/internal/api/v2/controllers_balances.go @@ -1,6 +1,7 @@ package v2 import ( + "github.com/formancehq/go-libs/platform/postgres" "net/http" "errors" @@ -29,6 +30,8 @@ func readBalancesAggregated(w http.ResponseWriter, r *http.Request) { *pitFilter, queryBuilder, api.QueryParamBool(r, "use_insertion_date") || api.QueryParamBool(r, "useInsertionDate"))) if err != nil { switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) case errors.Is(err, ledgercontroller.ErrInvalidQuery{}) || errors.Is(err, ledgercontroller.ErrMissingFeature{}): api.BadRequest(w, ErrValidation, err) default: diff --git a/internal/api/v2/controllers_ledgers_create.go b/internal/api/v2/controllers_ledgers_create.go index f6c6cc44b..47ea6842b 100644 --- a/internal/api/v2/controllers_ledgers_create.go +++ b/internal/api/v2/controllers_ledgers_create.go @@ -2,6 +2,7 @@ package v2 import ( "encoding/json" + "github.com/formancehq/go-libs/platform/postgres" "io" "net/http" @@ -32,6 +33,8 @@ func createLedger(systemController system.Controller) http.HandlerFunc { if err := systemController.CreateLedger(r.Context(), chi.URLParam(r, "ledger"), configuration); err != nil { switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) case errors.Is(err, system.ErrLedgerAlreadyExists): api.BadRequest(w, ErrValidation, err) case errors.Is(err, ledger.ErrInvalidLedgerName{}) || diff --git a/internal/api/v2/controllers_ledgers_delete_metadata.go b/internal/api/v2/controllers_ledgers_delete_metadata.go index 02b449477..1bbbae69a 100644 --- a/internal/api/v2/controllers_ledgers_delete_metadata.go +++ b/internal/api/v2/controllers_ledgers_delete_metadata.go @@ -1,6 +1,8 @@ package v2 import ( + "errors" + "github.com/formancehq/go-libs/platform/postgres" "net/http" "github.com/formancehq/ledger/internal/controller/system" @@ -12,7 +14,12 @@ import ( func deleteLedgerMetadata(b system.Controller) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if err := b.DeleteLedgerMetadata(r.Context(), chi.URLParam(r, "ledger"), chi.URLParam(r, "key")); err != nil { - api.InternalServerError(w, r, err) + switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) + default: + api.InternalServerError(w, r, err) + } return } diff --git a/internal/api/v2/controllers_ledgers_info.go b/internal/api/v2/controllers_ledgers_info.go index cacf30786..1cac87d11 100644 --- a/internal/api/v2/controllers_ledgers_info.go +++ b/internal/api/v2/controllers_ledgers_info.go @@ -1,6 +1,8 @@ package v2 import ( + "errors" + "github.com/formancehq/go-libs/platform/postgres" "net/http" "github.com/go-chi/chi/v5" @@ -29,7 +31,12 @@ func getLedgerInfo(w http.ResponseWriter, r *http.Request) { } res.Storage.Migrations, err = ledger.GetMigrationsInfo(r.Context()) if err != nil { - api.InternalServerError(w, r, err) + switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) + default: + api.InternalServerError(w, r, err) + } return } diff --git a/internal/api/v2/controllers_ledgers_list.go b/internal/api/v2/controllers_ledgers_list.go index 10bc18f9e..843b53450 100644 --- a/internal/api/v2/controllers_ledgers_list.go +++ b/internal/api/v2/controllers_ledgers_list.go @@ -1,6 +1,7 @@ package v2 import ( + "github.com/formancehq/go-libs/platform/postgres" "net/http" "errors" @@ -30,6 +31,8 @@ func listLedgers(b system.Controller) http.HandlerFunc { ledgers, err := b.ListLedgers(r.Context(), *query) if err != nil { switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) case errors.Is(err, ledgercontroller.ErrInvalidQuery{}) || errors.Is(err, ledgercontroller.ErrMissingFeature{}): api.BadRequest(w, ErrValidation, err) default: diff --git a/internal/api/v2/controllers_ledgers_read.go b/internal/api/v2/controllers_ledgers_read.go index 6523a3389..fcd317e31 100644 --- a/internal/api/v2/controllers_ledgers_read.go +++ b/internal/api/v2/controllers_ledgers_read.go @@ -1,6 +1,7 @@ package v2 import ( + "errors" "net/http" "github.com/formancehq/ledger/internal/controller/system" @@ -16,6 +17,8 @@ func readLedger(b system.Controller) http.HandlerFunc { ledger, err := b.GetLedger(r.Context(), chi.URLParam(r, "ledger")) if err != nil { switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) case postgres.IsNotFoundError(err): api.NotFound(w, err) default: diff --git a/internal/api/v2/controllers_ledgers_update_metadata.go b/internal/api/v2/controllers_ledgers_update_metadata.go index 4b1d510e4..80b595bc3 100644 --- a/internal/api/v2/controllers_ledgers_update_metadata.go +++ b/internal/api/v2/controllers_ledgers_update_metadata.go @@ -2,6 +2,7 @@ package v2 import ( "encoding/json" + "github.com/formancehq/go-libs/platform/postgres" "net/http" "errors" @@ -21,7 +22,12 @@ func updateLedgerMetadata(systemController systemcontroller.Controller) http.Han } if err := systemController.UpdateLedgerMetadata(r.Context(), chi.URLParam(r, "ledger"), m); err != nil { - api.InternalServerError(w, r, err) + switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) + default: + api.InternalServerError(w, r, err) + } return } diff --git a/internal/api/v2/controllers_logs_export.go b/internal/api/v2/controllers_logs_export.go index 6b70b6bea..d5ba02061 100644 --- a/internal/api/v2/controllers_logs_export.go +++ b/internal/api/v2/controllers_logs_export.go @@ -3,6 +3,8 @@ package v2 import ( "context" "encoding/json" + "errors" + "github.com/formancehq/go-libs/platform/postgres" "net/http" ledgercontroller "github.com/formancehq/ledger/internal/controller/ledger" @@ -18,7 +20,12 @@ func exportLogs(w http.ResponseWriter, r *http.Request) { if err := common.LedgerFromContext(r.Context()).Export(r.Context(), ledgercontroller.ExportWriterFn(func(ctx context.Context, log ledger.Log) error { return enc.Encode(log) })); err != nil { - api.InternalServerError(w, r, err) + switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) + default: + api.InternalServerError(w, r, err) + } return } } diff --git a/internal/api/v2/controllers_logs_import.go b/internal/api/v2/controllers_logs_import.go index a12a5124d..0437f7f04 100644 --- a/internal/api/v2/controllers_logs_import.go +++ b/internal/api/v2/controllers_logs_import.go @@ -2,6 +2,7 @@ package v2 import ( "encoding/json" + "github.com/formancehq/go-libs/platform/postgres" "io" "net/http" @@ -23,6 +24,8 @@ func importLogs(w http.ResponseWriter, r *http.Request) { dec := json.NewDecoder(r.Body) handleError := func(err error) { switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) case errors.Is(err, ledgercontroller.ErrImport{}): api.BadRequest(w, "IMPORT", err) default: diff --git a/internal/api/v2/controllers_logs_list.go b/internal/api/v2/controllers_logs_list.go index 3596b99d5..21e65b298 100644 --- a/internal/api/v2/controllers_logs_list.go +++ b/internal/api/v2/controllers_logs_list.go @@ -2,6 +2,7 @@ package v2 import ( "fmt" + "github.com/formancehq/go-libs/platform/postgres" "net/http" "errors" @@ -47,6 +48,8 @@ func listLogs(w http.ResponseWriter, r *http.Request) { cursor, err := l.ListLogs(r.Context(), query) if err != nil { switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) case errors.Is(err, ledgercontroller.ErrInvalidQuery{}): api.BadRequest(w, ErrValidation, err) default: diff --git a/internal/api/v2/controllers_stats.go b/internal/api/v2/controllers_stats.go index f6a4648c6..055a8d52b 100644 --- a/internal/api/v2/controllers_stats.go +++ b/internal/api/v2/controllers_stats.go @@ -1,6 +1,8 @@ package v2 import ( + "errors" + "github.com/formancehq/go-libs/platform/postgres" "net/http" "github.com/formancehq/go-libs/api" @@ -12,7 +14,12 @@ func readStats(w http.ResponseWriter, r *http.Request) { stats, err := l.GetStats(r.Context()) if err != nil { - api.InternalServerError(w, r, err) + switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) + default: + api.InternalServerError(w, r, err) + } return } diff --git a/internal/api/v2/controllers_transactions_add_metadata.go b/internal/api/v2/controllers_transactions_add_metadata.go index 181695bac..e05ceeb5b 100644 --- a/internal/api/v2/controllers_transactions_add_metadata.go +++ b/internal/api/v2/controllers_transactions_add_metadata.go @@ -2,6 +2,7 @@ package v2 import ( "encoding/json" + "github.com/formancehq/go-libs/platform/postgres" "net/http" "strconv" @@ -34,6 +35,8 @@ func addTransactionMetadata(w http.ResponseWriter, r *http.Request) { Metadata: m, })); err != nil { switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) case errors.Is(err, ledgercontroller.ErrNotFound): api.NotFound(w, err) default: diff --git a/internal/api/v2/controllers_transactions_count.go b/internal/api/v2/controllers_transactions_count.go index 31face73c..5ea259507 100644 --- a/internal/api/v2/controllers_transactions_count.go +++ b/internal/api/v2/controllers_transactions_count.go @@ -2,6 +2,7 @@ package v2 import ( "fmt" + "github.com/formancehq/go-libs/platform/postgres" "net/http" "errors" @@ -22,6 +23,8 @@ func countTransactions(w http.ResponseWriter, r *http.Request) { CountTransactions(r.Context(), ledgercontroller.NewListTransactionsQuery(*options)) if err != nil { switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) case errors.Is(err, ledgercontroller.ErrInvalidQuery{}) || errors.Is(err, ledgercontroller.ErrMissingFeature{}): api.BadRequest(w, ErrValidation, err) default: diff --git a/internal/api/v2/controllers_transactions_create.go b/internal/api/v2/controllers_transactions_create.go index 20cbc1d5e..0ab65b95e 100644 --- a/internal/api/v2/controllers_transactions_create.go +++ b/internal/api/v2/controllers_transactions_create.go @@ -2,6 +2,7 @@ package v2 import ( "encoding/json" + "github.com/formancehq/go-libs/platform/postgres" "net/http" ledgercontroller "github.com/formancehq/ledger/internal/controller/ledger" @@ -33,6 +34,8 @@ func createTransaction(w http.ResponseWriter, r *http.Request) { res, err := l.CreateTransaction(r.Context(), getCommandParameters(r, *payload.ToRunScript(api.QueryParamBool(r, "force")))) if err != nil { switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) case errors.Is(err, &ledgercontroller.ErrInsufficientFunds{}): api.BadRequest(w, ErrInsufficientFund, err) case errors.Is(err, &ledgercontroller.ErrInvalidVars{}) || errors.Is(err, ledgercontroller.ErrCompilationFailed{}): diff --git a/internal/api/v2/controllers_transactions_delete_metadata.go b/internal/api/v2/controllers_transactions_delete_metadata.go index bccf79a6e..397635bd9 100644 --- a/internal/api/v2/controllers_transactions_delete_metadata.go +++ b/internal/api/v2/controllers_transactions_delete_metadata.go @@ -1,6 +1,7 @@ package v2 import ( + "github.com/formancehq/go-libs/platform/postgres" "net/http" "strconv" @@ -30,6 +31,8 @@ func deleteTransactionMetadata(w http.ResponseWriter, r *http.Request) { Key: metadataKey, })); err != nil { switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) case errors.Is(err, ledgercontroller.ErrNotFound): api.NotFound(w, err) default: diff --git a/internal/api/v2/controllers_transactions_list.go b/internal/api/v2/controllers_transactions_list.go index 16657c25a..6af61c5eb 100644 --- a/internal/api/v2/controllers_transactions_list.go +++ b/internal/api/v2/controllers_transactions_list.go @@ -1,6 +1,7 @@ package v2 import ( + "github.com/formancehq/go-libs/platform/postgres" "net/http" "errors" @@ -38,6 +39,8 @@ func listTransactions(w http.ResponseWriter, r *http.Request) { cursor, err := l.ListTransactions(r.Context(), *query) if err != nil { switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) case errors.Is(err, ledgercontroller.ErrInvalidQuery{}) || errors.Is(err, ledgercontroller.ErrMissingFeature{}): api.BadRequest(w, ErrValidation, err) default: diff --git a/internal/api/v2/controllers_transactions_read.go b/internal/api/v2/controllers_transactions_read.go index 936f20a1d..228b9b121 100644 --- a/internal/api/v2/controllers_transactions_read.go +++ b/internal/api/v2/controllers_transactions_read.go @@ -1,6 +1,7 @@ package v2 import ( + "errors" "net/http" "strconv" @@ -38,6 +39,8 @@ func readTransaction(w http.ResponseWriter, r *http.Request) { tx, err := l.GetTransaction(r.Context(), query) if err != nil { switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) case postgres.IsNotFoundError(err): api.NotFound(w, err) default: diff --git a/internal/api/v2/controllers_transactions_revert.go b/internal/api/v2/controllers_transactions_revert.go index 9fd787e49..37d9855ba 100644 --- a/internal/api/v2/controllers_transactions_revert.go +++ b/internal/api/v2/controllers_transactions_revert.go @@ -1,6 +1,7 @@ package v2 import ( + "github.com/formancehq/go-libs/platform/postgres" "net/http" "strconv" @@ -31,6 +32,8 @@ func revertTransaction(w http.ResponseWriter, r *http.Request) { ) if err != nil { switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) case errors.Is(err, &ledgercontroller.ErrInsufficientFunds{}): api.BadRequest(w, ErrInsufficientFund, err) case errors.Is(err, ledgercontroller.ErrAlreadyReverted{}): diff --git a/internal/api/v2/controllers_volumes.go b/internal/api/v2/controllers_volumes.go index 1d22e433e..a06a439db 100644 --- a/internal/api/v2/controllers_volumes.go +++ b/internal/api/v2/controllers_volumes.go @@ -1,6 +1,7 @@ package v2 import ( + "github.com/formancehq/go-libs/platform/postgres" "net/http" "errors" @@ -38,6 +39,8 @@ func readVolumes(w http.ResponseWriter, r *http.Request) { if err != nil { switch { + case errors.Is(err, postgres.ErrTooManyClient{}): + api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err) case errors.Is(err, ledgercontroller.ErrInvalidQuery{}) || errors.Is(err, ledgercontroller.ErrMissingFeature{}): api.BadRequest(w, ErrValidation, err) default: diff --git a/internal/controller/ledger/controller_with_events.go b/internal/controller/ledger/controller_with_events.go index 3a2ae9e17..16d4886bd 100644 --- a/internal/controller/ledger/controller_with_events.go +++ b/internal/controller/ledger/controller_with_events.go @@ -13,12 +13,11 @@ type ControllerWithEvents struct { } func NewControllerWithEvents(ledger ledger.Ledger, underlying Controller, listener Listener) *ControllerWithEvents { - ret := &ControllerWithEvents{ + return &ControllerWithEvents{ Controller: underlying, ledger: ledger, listener: listener, } - return ret } func (ctrl *ControllerWithEvents) CreateTransaction(ctx context.Context, parameters Parameters[RunScript]) (*ledger.CreatedTransaction, error) { ret, err := ctrl.Controller.CreateTransaction(ctx, parameters) diff --git a/internal/controller/ledger/controller_with_too_many_client_handling.go b/internal/controller/ledger/controller_with_too_many_client_handling.go new file mode 100644 index 000000000..aa963f4c2 --- /dev/null +++ b/internal/controller/ledger/controller_with_too_many_client_handling.go @@ -0,0 +1,97 @@ +package ledger + +import ( + "context" + "errors" + "github.com/formancehq/go-libs/platform/postgres" + ledger "github.com/formancehq/ledger/internal" + "github.com/formancehq/ledger/internal/tracing" + "go.opentelemetry.io/otel/attribute" + "time" +) + +//go:generate mockgen -write_source_comment=false -write_package_comment=false -source controller_with_too_many_client_handling.go -destination controller_with_too_many_client_handling_generated_test.go -package ledger . DelayCalculator -typed +type DelayCalculator interface { + Next(int) time.Duration +} +type DelayCalculatorFn func(int) time.Duration + +func (fn DelayCalculatorFn) Next(iteration int) time.Duration { + return fn(iteration) +} + +type ControllerWithTooManyClientHandling struct { + Controller + delayCalculator DelayCalculator +} + +func NewControllerWithTooManyClientHandling(underlying Controller, delayCalculator DelayCalculator) *ControllerWithTooManyClientHandling { + return &ControllerWithTooManyClientHandling{ + Controller: underlying, + delayCalculator: delayCalculator, + } +} + +func (ctrl *ControllerWithTooManyClientHandling) CreateTransaction(ctx context.Context, parameters Parameters[RunScript]) (*ledger.CreatedTransaction, error) { + return handleRetry(ctx, ctrl.delayCalculator, parameters, ctrl.Controller.CreateTransaction) +} + +func (ctrl *ControllerWithTooManyClientHandling) RevertTransaction(ctx context.Context, parameters Parameters[RevertTransaction]) (*ledger.RevertedTransaction, error) { + return handleRetry(ctx, ctrl.delayCalculator, parameters, ctrl.Controller.RevertTransaction) +} + +func (ctrl *ControllerWithTooManyClientHandling) SaveTransactionMetadata(ctx context.Context, parameters Parameters[SaveTransactionMetadata]) error { + _, err := handleRetry(ctx, ctrl.delayCalculator, parameters, func(ctx context.Context, parameters Parameters[SaveTransactionMetadata]) (*struct{}, error) { + return nil, ctrl.Controller.SaveTransactionMetadata(ctx, parameters) + }) + return err +} + +func (ctrl *ControllerWithTooManyClientHandling) SaveAccountMetadata(ctx context.Context, parameters Parameters[SaveAccountMetadata]) error { + _, err := handleRetry(ctx, ctrl.delayCalculator, parameters, func(ctx context.Context, parameters Parameters[SaveAccountMetadata]) (*struct{}, error) { + return nil, ctrl.Controller.SaveAccountMetadata(ctx, parameters) + }) + return err +} + +func (ctrl *ControllerWithTooManyClientHandling) DeleteTransactionMetadata(ctx context.Context, parameters Parameters[DeleteTransactionMetadata]) error { + _, err := handleRetry(ctx, ctrl.delayCalculator, parameters, func(ctx context.Context, parameters Parameters[DeleteTransactionMetadata]) (*struct{}, error) { + return nil, ctrl.Controller.DeleteTransactionMetadata(ctx, parameters) + }) + return err +} + +func (ctrl *ControllerWithTooManyClientHandling) DeleteAccountMetadata(ctx context.Context, parameters Parameters[DeleteAccountMetadata]) error { + _, err := handleRetry(ctx, ctrl.delayCalculator, parameters, func(ctx context.Context, parameters Parameters[DeleteAccountMetadata]) (*struct{}, error) { + return nil, ctrl.Controller.DeleteAccountMetadata(ctx, parameters) + }) + return err +} + +var _ Controller = (*ControllerWithTooManyClientHandling)(nil) + +func handleRetry[INPUT, OUTPUT any](ctx context.Context, delayCalculator DelayCalculator, parameters Parameters[INPUT], fn func(ctx context.Context, parameters Parameters[INPUT]) (*OUTPUT, error)) (*OUTPUT, error) { + + ctx, span := tracing.Start(ctx, "Retrier") + defer span.End() + + count := 0 + for { + output, err := fn(ctx, parameters) + if err != nil && errors.Is(err, postgres.ErrTooManyClient{}) { + delay := delayCalculator.Next(count) + if delay == 0 { + return nil, err + } + select { + case <-ctx.Done(): + return nil, ctx.Err() + case <-time.After(delay): + count++ + span.SetAttributes(attribute.Int("retry", count)) + continue + } + } + return output, err + } +} diff --git a/internal/controller/ledger/controller_with_too_many_client_handling_generated_test.go b/internal/controller/ledger/controller_with_too_many_client_handling_generated_test.go new file mode 100644 index 000000000..2f0c421cb --- /dev/null +++ b/internal/controller/ledger/controller_with_too_many_client_handling_generated_test.go @@ -0,0 +1,50 @@ +// Code generated by MockGen. DO NOT EDIT. +// +// Generated by this command: +// +// mockgen -write_source_comment=false -write_package_comment=false -source controller_with_too_many_client_handling.go -destination controller_with_too_many_client_handling_generated_test.go -package ledger . DelayCalculator -typed +package ledger + +import ( + reflect "reflect" + time "time" + + gomock "go.uber.org/mock/gomock" +) + +// MockDelayCalculator is a mock of DelayCalculator interface. +type MockDelayCalculator struct { + ctrl *gomock.Controller + recorder *MockDelayCalculatorMockRecorder +} + +// MockDelayCalculatorMockRecorder is the mock recorder for MockDelayCalculator. +type MockDelayCalculatorMockRecorder struct { + mock *MockDelayCalculator +} + +// NewMockDelayCalculator creates a new mock instance. +func NewMockDelayCalculator(ctrl *gomock.Controller) *MockDelayCalculator { + mock := &MockDelayCalculator{ctrl: ctrl} + mock.recorder = &MockDelayCalculatorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockDelayCalculator) EXPECT() *MockDelayCalculatorMockRecorder { + return m.recorder +} + +// Next mocks base method. +func (m *MockDelayCalculator) Next(arg0 int) time.Duration { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Next", arg0) + ret0, _ := ret[0].(time.Duration) + return ret0 +} + +// Next indicates an expected call of Next. +func (mr *MockDelayCalculatorMockRecorder) Next(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Next", reflect.TypeOf((*MockDelayCalculator)(nil).Next), arg0) +} diff --git a/internal/controller/ledger/controller_with_too_many_client_handling_test.go b/internal/controller/ledger/controller_with_too_many_client_handling_test.go new file mode 100644 index 000000000..07dc6661b --- /dev/null +++ b/internal/controller/ledger/controller_with_too_many_client_handling_test.go @@ -0,0 +1,79 @@ +package ledger + +import ( + "errors" + "github.com/formancehq/go-libs/logging" + "github.com/formancehq/go-libs/platform/postgres" + "github.com/formancehq/go-libs/time" + ledger "github.com/formancehq/ledger/internal" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + "testing" +) + +func TestNewControllerWithTooManyClientHandling(t *testing.T) { + t.Parallel() + + t.Run("finally passing", func(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + underlyingLedgerController := NewMockController(ctrl) + delayCalculator := NewMockDelayCalculator(ctrl) + ctx := logging.TestingContext() + + parameters := Parameters[RunScript]{} + + underlyingLedgerController.EXPECT(). + CreateTransaction(gomock.Any(), parameters). + Return(nil, postgres.ErrTooManyClient{}). + Times(2) + + underlyingLedgerController.EXPECT(). + CreateTransaction(gomock.Any(), parameters). + Return(&ledger.CreatedTransaction{ + Transaction: ledger.NewTransaction(), + }, nil) + + delayCalculator.EXPECT(). + Next(0). + Return(time.Millisecond) + + delayCalculator.EXPECT(). + Next(1). + Return(10 * time.Millisecond) + + ledgerController := NewControllerWithTooManyClientHandling(underlyingLedgerController, delayCalculator) + _, err := ledgerController.CreateTransaction(ctx, parameters) + require.NoError(t, err) + }) + + t.Run("finally failing", func(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + underlyingLedgerController := NewMockController(ctrl) + delayCalculator := NewMockDelayCalculator(ctrl) + ctx := logging.TestingContext() + + parameters := Parameters[RunScript]{} + + underlyingLedgerController.EXPECT(). + CreateTransaction(gomock.Any(), parameters). + Return(nil, postgres.ErrTooManyClient{}). + Times(2) + + delayCalculator.EXPECT(). + Next(0). + Return(time.Millisecond) + + delayCalculator.EXPECT(). + Next(1). + Return(time.Duration(0)) + + ledgerController := NewControllerWithTooManyClientHandling(underlyingLedgerController, delayCalculator) + _, err := ledgerController.CreateTransaction(ctx, parameters) + require.Error(t, err) + require.True(t, errors.Is(err, postgres.ErrTooManyClient{})) + }) +} diff --git a/internal/controller/ledger/errors.go b/internal/controller/ledger/errors.go index 682b2f299..112cd4675 100644 --- a/internal/controller/ledger/errors.go +++ b/internal/controller/ledger/errors.go @@ -12,6 +12,7 @@ import ( ) var ErrNotFound = postgres.ErrNotFound +type ErrTooManyClient = postgres.ErrTooManyClient type ErrImport struct { err error diff --git a/internal/controller/ledger/log_process.go b/internal/controller/ledger/log_process.go index 03c35c7e2..b9d9abf85 100644 --- a/internal/controller/ledger/log_process.go +++ b/internal/controller/ledger/log_process.go @@ -44,10 +44,6 @@ func runTx[INPUT any, OUTPUT ledger.LogPayload](ctx context.Context, store Store } // todo: metrics, add deadlocks -// todo: handle too many clients error -// notes(gfyrag): how? -// By retrying? Is the server already overloaded? Add a limit on the retries number? -// Ask the client to retry later? func forgeLog[INPUT any, OUTPUT ledger.LogPayload](ctx context.Context, store Store, parameters Parameters[INPUT], fn func(ctx context.Context, sqlTX TX, input INPUT) (*OUTPUT, error)) (*OUTPUT, error) { if parameters.IdempotencyKey != "" { output, err := fetchLogWithIK[INPUT, OUTPUT](ctx, store, parameters) diff --git a/internal/controller/system/controller.go b/internal/controller/system/controller.go index ef11366f9..868787534 100644 --- a/internal/controller/system/controller.go +++ b/internal/controller/system/controller.go @@ -2,6 +2,7 @@ package system import ( "context" + "time" "github.com/formancehq/ledger/internal/tracing" @@ -28,6 +29,7 @@ type DefaultController struct { listener ledgercontroller.Listener compiler ledgercontroller.Compiler registry *ledgercontroller.StateRegistry + databaseRetryConfiguration DatabaseRetryConfiguration } func (ctrl *DefaultController) GetLedgerController(ctx context.Context, name string) (ledgercontroller.Controller, error) { @@ -43,6 +45,15 @@ func (ctrl *DefaultController) GetLedgerController(ctx context.Context, name str ledgercontroller.NewDefaultMachineFactory(ctrl.compiler), ) + // Add too many client error handling + ledgerController = ledgercontroller.NewControllerWithTooManyClientHandling(ledgerController, ledgercontroller.DelayCalculatorFn(func(i int) time.Duration { + if i < ctrl.databaseRetryConfiguration.MaxRetry { + return time.Duration(i+1)*ctrl.databaseRetryConfiguration.Delay + } + + return 0 + })) + // Add cache regarding database state ledgerController = ledgercontroller.NewControllerWithCache(*l, ledgerController, ctrl.registry) @@ -106,11 +117,17 @@ func NewDefaultController(store Store, listener ledgercontroller.Listener, opts return ret } -type Option func(r *DefaultController) +type Option func(ctrl *DefaultController) func WithCompiler(compiler ledgercontroller.Compiler) Option { - return func(r *DefaultController) { - r.compiler = compiler + return func(ctrl *DefaultController) { + ctrl.compiler = compiler + } +} + +func WithDatabaseRetryConfiguration(configuration DatabaseRetryConfiguration) Option { + return func(ctrl *DefaultController) { + ctrl.databaseRetryConfiguration = configuration } } diff --git a/internal/controller/system/module.go b/internal/controller/system/module.go index a0b7370cd..ce69118ec 100644 --- a/internal/controller/system/module.go +++ b/internal/controller/system/module.go @@ -4,10 +4,17 @@ import ( "github.com/formancehq/go-libs/logging" ledgercontroller "github.com/formancehq/ledger/internal/controller/ledger" "go.uber.org/fx" + "time" ) +type DatabaseRetryConfiguration struct { + MaxRetry int + Delay time.Duration +} + type ModuleConfiguration struct { NSCacheConfiguration ledgercontroller.CacheConfiguration + DatabaseRetryConfiguration DatabaseRetryConfiguration } func NewFXModule(configuration ModuleConfiguration) fx.Option { @@ -27,6 +34,8 @@ func NewFXModule(configuration ModuleConfiguration) fx.Option { configuration.NSCacheConfiguration, ))) } + options = append(options, WithDatabaseRetryConfiguration(configuration.DatabaseRetryConfiguration)) + return NewDefaultController(store, listener, options...) }), )