Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Handle errors in renter package #91

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.13

require (
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da
github.com/hashicorp/go-multierror v1.1.0
Copy link
Owner

Choose a reason for hiding this comment

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

I don't add new dependencies lightly. This one does not strike me as necessary.

github.com/pkg/errors v0.9.1
gitlab.com/NebulousLabs/Sia v1.4.8
go.etcd.io/bbolt v1.3.4
Expand Down
11 changes: 7 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ github.com/hanwen/go-fuse v1.0.0 h1:GxS9Zrn6c35/BnfiVsZVWmsG803xwE7eVRDvcf/BEVc=
github.com/hanwen/go-fuse v1.0.0/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok=
github.com/hanwen/go-fuse/v2 v2.0.2 h1:BtsqKI5RXOqDMnTgpCb0IWgvRgGLJdqYVZ/Hm6KgKto=
github.com/hanwen/go-fuse/v2 v2.0.2/go.mod h1:HH3ygZOoyRbP9y2q7y3+JM6hPL+Epe29IbWaS0UA81o=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf h1:WfD7VjIE6z8dIvMsI4/s+1qr5EL+zoIGev1BQj1eoJ8=
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf/go.mod h1:hyb9oH7vZsitZCiBt0ZvifOrB+qc8PS5IiilCIb87rg=
Expand All @@ -41,6 +45,7 @@ github.com/klauspost/reedsolomon v1.9.2 h1:E9CMS2Pqbv+C7tsrYad4YC9MfhnMVWhMRsTi7
github.com/klauspost/reedsolomon v1.9.2/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
github.com/klauspost/reedsolomon v1.9.3 h1:N/VzgeMfHmLc+KHMD1UL/tNkfXAt8FnUqlgXGIduwAY=
github.com/klauspost/reedsolomon v1.9.3/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
Expand Down Expand Up @@ -85,6 +90,7 @@ gitlab.com/NebulousLabs/merkletree v0.0.0-20190207030457-bc4a11e31a0d h1:ObC0V0W
gitlab.com/NebulousLabs/merkletree v0.0.0-20190207030457-bc4a11e31a0d/go.mod h1:xItahGeKIkh9BQfxDEX6O3eWxOxbLBPX738sXm0uVaQ=
gitlab.com/NebulousLabs/merkletree v0.0.0-20200118113624-07fbf710afc4 h1:iuNdBfBg0umjOvrEf9MxGzK+NwAyE2oCZjDqUx9zVFs=
gitlab.com/NebulousLabs/merkletree v0.0.0-20200118113624-07fbf710afc4/go.mod h1:0cjDwhA+Pv9ZQXHED7HUSS3sCvo2zgsoaMgE7MeGBWo=
gitlab.com/NebulousLabs/monitor v0.0.0-20191205095550-2b0fd3e1012a h1:fs891phmYZrVdaCVPXfHGDMpV5LWPKvnOMjx70EpJkw=
gitlab.com/NebulousLabs/monitor v0.0.0-20191205095550-2b0fd3e1012a/go.mod h1:QxXtb5hIp2xQkfb+lzBDIqQIGEj22U7AkYCXO3hkhqc=
gitlab.com/NebulousLabs/ratelimit v0.0.0-20180716154200-1308156c2eaf h1:B0oWvYNYeov4s6nzoVPN4qxdAanrlR9mx552axpnXmg=
gitlab.com/NebulousLabs/ratelimit v0.0.0-20180716154200-1308156c2eaf/go.mod h1:vowDA1cdvtWW678ugB7L/yKT2pCN37aH6zYp9NF5Isc=
Expand All @@ -103,8 +109,6 @@ go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191105034135-c7e5f84aec59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20191107222254-f4817d981bb6/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
Expand All @@ -117,6 +121,7 @@ golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPI
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
Expand All @@ -128,8 +133,6 @@ golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f h1:gWF768j/LaZugp8dyS4UwsslYCYz9XgFxvlgsn0n9H8=
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
Expand Down
46 changes: 36 additions & 10 deletions renter/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ import (
"unsafe"

"github.com/aead/chacha20/chacha"
"github.com/hashicorp/go-multierror"
"github.com/pkg/errors"
"gitlab.com/NebulousLabs/Sia/crypto"
"lukechampine.com/frand"

"lukechampine.com/us/hostdb"
"lukechampine.com/us/merkle"
"lukechampine.com/us/renterhost"
Expand Down Expand Up @@ -190,7 +192,7 @@ func NewMetaFile(mode os.FileMode, size int64, hosts []hostdb.HostPublicKey, min

// WriteMetaFile creates a gzipped tar archive containing m's index and shards,
// and writes it to filename. The write is atomic.
func WriteMetaFile(filename string, m *MetaFile) error {
func WriteMetaFile(filename string, m *MetaFile) (err error) {
// validate before writing
if err := validateShards(m.Shards); err != nil {
return errors.Wrap(err, "invalid shards")
Expand All @@ -200,7 +202,11 @@ func WriteMetaFile(filename string, m *MetaFile) error {
if err != nil {
return errors.Wrap(err, "could not create archive")
}
defer f.Close()
defer func() {
if e := f.Close(); e != nil && !errors.Is(e, os.ErrClosed) {
err = multierror.Append(err, e)
}
}()
Copy link
Owner

Choose a reason for hiding this comment

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

The error from f.Close is already checked at the bottom of this function. And if we return early, then we don't care about this error anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually, it should remove the temporary file here if exists. If writing a metafile fails, no one removes it.

Copy link
Owner

Choose a reason for hiding this comment

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

Failure to Close does not indicate that the file is corrupt, so removing it would be premature. This behavior is in line with standard library functions such as ioutil.WriteFile.

zip := gzip.NewWriter(f)
tw := tar.NewWriter(zip)

Expand Down Expand Up @@ -256,12 +262,16 @@ func WriteMetaFile(filename string, m *MetaFile) error {
}

// ReadMetaFile reads a metafile archive into memory.
func ReadMetaFile(filename string) (*MetaFile, error) {
func ReadMetaFile(filename string) (_ *MetaFile, err error) {
f, err := os.Open(filename)
if err != nil {
return nil, errors.Wrap(err, "could not open archive")
}
defer f.Close()
defer func() {
if e := f.Close(); e != nil {
err = multierror.Append(err, e)
}
}()
Copy link
Owner

Choose a reason for hiding this comment

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

We don't care about this error -- there's nothing we can do with this information.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

At least we can know the storage is going to die :)

Copy link
Owner

@lukechampine lukechampine Aug 5, 2020

Choose a reason for hiding this comment

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

Close on a read-only file is basically a no-op anyway, so I don't think it will indicate anything about the health of your disk. Even ioutil.ReadFile ignores this error: https://golang.org/src/io/ioutil/ioutil.go?s=1503:1549#L42

zip, err := gzip.NewReader(f)
if err != nil {
return nil, errors.Wrap(err, "could not read gzip header")
Expand Down Expand Up @@ -331,18 +341,26 @@ func ReadMetaFile(filename string) (*MetaFile, error) {
}

// ReadMetaIndex reads the index of a metafile without reading any shards.
func ReadMetaIndex(filename string) (MetaIndex, error) {
func ReadMetaIndex(filename string) (_ MetaIndex, err error) {
f, err := os.Open(filename)
if err != nil {
return MetaIndex{}, errors.Wrap(err, "could not open archive")
}
defer f.Close()
defer func() {
if e := f.Close(); e != nil {
err = multierror.Append(err, e)
}
}()
Copy link
Owner

Choose a reason for hiding this comment

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

same as above -- not actionable


zip, err := gzip.NewReader(f)
if err != nil {
return MetaIndex{}, errors.Wrap(err, "could not read gzip header")
}
defer zip.Close()
defer func() {
if e := zip.Close(); e != nil {
err = multierror.Append(err, e)
}
}()
Copy link
Owner

Choose a reason for hiding this comment

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

ditto


var index MetaIndex
tr := tar.NewReader(zip)
Expand Down Expand Up @@ -387,18 +405,26 @@ func MetaFileCanDownload(filename string) (bool, error) {

// readMetaFileShards reads a metafile and returns its index and the number of
// shards that represent fully-uploaded shards of the erasure-encoded file.
func readMetaFileShards(filename string) (MetaIndex, int, error) {
func readMetaFileShards(filename string) (_ MetaIndex, _ int, err error) {
f, err := os.Open(filename)
if err != nil {
return MetaIndex{}, 0, errors.Wrap(err, "could not open archive")
}
defer f.Close()
defer func() {
if e := f.Close(); e != nil {
err = multierror.Append(err, e)
}
}()
Copy link
Owner

Choose a reason for hiding this comment

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

ditto


zip, err := gzip.NewReader(f)
if err != nil {
return MetaIndex{}, 0, errors.Wrap(err, "could not read gzip header")
}
defer zip.Close()
defer func() {
if e := zip.Close(); e != nil {
err = multierror.Append(err, e)
}
}()
Copy link
Owner

Choose a reason for hiding this comment

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

ditto

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As same as the other errors, if this fails, something really bad is going to happen (mostly physical error).
Thus, tracking any errors is important in production.

Copy link
Owner

Choose a reason for hiding this comment

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

zip.Close doesn't touch the disk at all, it just verifies an internal checksum.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If the internal checksum is not verified, I think that is a problem...

Copy link
Owner

Choose a reason for hiding this comment

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

I suppose. I wouldn't guarantee it in the API, but as long as it's there, we might as well use it. I'll add it to the PR.


var haveIndex bool
var index MetaIndex
Expand Down
15 changes: 12 additions & 3 deletions renter/proto/formcontract.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import (
"sort"
"time"

"github.com/hashicorp/go-multierror"
"github.com/pkg/errors"
"gitlab.com/NebulousLabs/Sia/crypto"
"gitlab.com/NebulousLabs/Sia/modules"
"gitlab.com/NebulousLabs/Sia/types"

"lukechampine.com/us/ed25519hash"
"lukechampine.com/us/hostdb"
"lukechampine.com/us/renterhost"
Expand All @@ -23,13 +25,17 @@ const (

// FormContract forms a contract with a host. The resulting contract will have
// renterPayout coins in the renter output.
func FormContract(w Wallet, tpool TransactionPool, key ed25519.PrivateKey, host hostdb.ScannedHost, renterPayout types.Currency, startHeight, endHeight types.BlockHeight) (ContractRevision, []types.Transaction, error) {
func FormContract(w Wallet, tpool TransactionPool, key ed25519.PrivateKey, host hostdb.ScannedHost, renterPayout types.Currency, startHeight, endHeight types.BlockHeight) (_ ContractRevision, _ []types.Transaction, err error) {
s, err := NewUnlockedSession(host.NetAddress, host.PublicKey, 0)
if err != nil {
return ContractRevision{}, nil, err
}
s.host = host
defer s.Close()
defer func() {
if e := s.Close(); e != nil {
err = multierror.Append(err, e)
}
}()
Copy link
Owner

Choose a reason for hiding this comment

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

There's no reason to handle this error. If we formed the contract successfully, we don't care whether closing the session fails. If we fail for some other reason and return early, then we also don't care whether closing the session fails.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If this fails and the connection keeps alive, the following lock requests will fail, won't it?

Copy link
Owner

Choose a reason for hiding this comment

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

This session only exists within the FormContract function, so no.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I might not understand it correctly but even if s.Close fails and the current session is alive, can we call NewSession without "contract is locked by another party" error?

Copy link
Owner

@lukechampine lukechampine Aug 5, 2020

Choose a reason for hiding this comment

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

Ah, I see. For FormContract the answer is still no, because the contract doesn't exist yet. But for RenewContract and other RPCs...maybe? If closing the session fails because the host already closed the connection, then the contract should be unlocked the next time you call Lock. If something weird happens to the network, then the connection could be left in a half-open state, which would cause the host to wait for the connection to time out (during which you'd be unable to acquire the lock). But I don't think failing to close the connection will be correlated with the host being stuck half-open. Usually a half-open connection only happens if the machine loses power or someone pulls the ethernet cable out.

My suggestion is that you add some internal logging for the errors you're worried about, and observe whether any of them occur in production. I'm not keen on cluttering my code without clear benefit.

EDIT: It looks like Go uses a TCP keepalive of 15 seconds by default. So even if the host gets stuck half-open, it should time out within 15 seconds. Perhaps this means the default Lock timeout should be increased to 15 seconds (probably longer).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think even if there is a half-open socket, the host can accept another connection and then get conflict. If I'm not mistaken, a half-open connection could happen if a switch gets overflowed.

Btw, don't hosts change the default keepalive?

return s.FormContract(w, tpool, key, renterPayout, startHeight, endHeight)
}

Expand Down Expand Up @@ -168,7 +174,10 @@ func (s *Session) FormContract(w Wallet, tpool TransactionPool, key ed25519.Priv
err = w.SignTransaction(&txn, toSign)
if err != nil {
err = errors.Wrap(err, "failed to sign transaction")
s.sess.WriteResponse(nil, errors.New("internal error")) // don't want to reveal too much
// don't want to reveal too much
if e := s.sess.WriteResponse(nil, errors.New("internal error")); e != nil {
err = multierror.Append(err, e)
}
Copy link
Owner

Choose a reason for hiding this comment

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

We don't care if the host received this message or not. If they did, great. If they didn't, that's unfortunate, but we're not going to attempt to resend it or anything -- the connection is being closed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We should log that the host doesn't receive any request, shouldn't we?

Copy link
Owner

Choose a reason for hiding this comment

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

I wouldn't recommend it. You should only punish a host for errors that prevent you from completing an RPC.

return ContractRevision{}, nil, err
}

Expand Down
18 changes: 14 additions & 4 deletions renter/proto/renew.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"math"
"time"

"github.com/hashicorp/go-multierror"
"github.com/pkg/errors"
"gitlab.com/NebulousLabs/Sia/build"
"gitlab.com/NebulousLabs/Sia/crypto"
Expand All @@ -17,13 +18,18 @@ import (

// RenewContract negotiates a new file contract and initial revision for data
// already stored with a host.
func RenewContract(w Wallet, tpool TransactionPool, id types.FileContractID, key ed25519.PrivateKey, host hostdb.ScannedHost, renterPayout types.Currency, startHeight, endHeight types.BlockHeight) (ContractRevision, []types.Transaction, error) {
func RenewContract(w Wallet, tpool TransactionPool, id types.FileContractID, key ed25519.PrivateKey, host hostdb.ScannedHost, renterPayout types.Currency, startHeight, endHeight types.BlockHeight) (_ ContractRevision, _ []types.Transaction, err error) {
s, err := NewUnlockedSession(host.NetAddress, host.PublicKey, 0)
if err != nil {
return ContractRevision{}, nil, err
}
s.host = host
defer s.Close()
defer func() {
if e := s.Close(); e != nil {
err = multierror.Append(err, e)
}
}()
Copy link
Owner

Choose a reason for hiding this comment

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

same as above


if err := s.Lock(id, key); err != nil {
return ContractRevision{}, nil, err
}
Expand Down Expand Up @@ -164,7 +170,9 @@ func (s *Session) RenewContract(w Wallet, tpool TransactionPool, renterPayout ty
err = w.SignTransaction(&txn, toSign)
if err != nil {
err = errors.Wrap(err, "failed to sign transaction")
s.sess.WriteResponse(nil, err)
if e := s.sess.WriteResponse(nil, err); e != nil {
err = multierror.Append(err, e)
}
Copy link
Owner

Choose a reason for hiding this comment

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

ditto

return ContractRevision{}, nil, err
}

Expand Down Expand Up @@ -369,7 +377,9 @@ func (s *Session) RenewAndClearContract(w Wallet, tpool TransactionPool, renterP
err = w.SignTransaction(&txn, toSign)
if err != nil {
err = errors.Wrap(err, "failed to sign transaction")
s.sess.WriteResponse(nil, err)
if e := s.sess.WriteResponse(nil, err); e != nil {
err = multierror.Append(err, e)
}
Copy link
Owner

Choose a reason for hiding this comment

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

ditto

return ContractRevision{}, nil, err
}

Expand Down
29 changes: 23 additions & 6 deletions renter/proto/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ import (
"sort"
"time"

"github.com/hashicorp/go-multierror"
"github.com/pkg/errors"
"gitlab.com/NebulousLabs/Sia/crypto"
"gitlab.com/NebulousLabs/Sia/modules"
"gitlab.com/NebulousLabs/Sia/types"

"lukechampine.com/us/ed25519hash"
"lukechampine.com/us/hostdb"
"lukechampine.com/us/merkle"
Expand Down Expand Up @@ -384,7 +386,11 @@ func (s *Session) Read(w io.Writer, sections []renterhost.RPCReadRequestSection)

// host will now stream back responses; ensure we send RPCLoopReadStop
// before returning
defer s.sess.WriteResponse(&renterhost.RPCReadStop, nil)
defer func() {
if e := s.sess.WriteResponse(&renterhost.RPCReadStop, nil); e != nil {
err = multierror.Append(err, e)
}
}()
Copy link
Owner

Choose a reason for hiding this comment

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

This one is a little more subtle. In this case, it's true that we're sending a message to the host, and it's not an error message that we're sending immediately before we close the connection.

However, the success of this RPC does not depend on this message being sent. If we receive all of the data from the host, but then fail to send RPCReadStop, we still want to return nil. It just means that the next time we call an RPC, we'll get a network error. That's fine.

var hostSig []byte
for _, sec := range sections {
// NOTE: normally, we would call ReadResponse here to read an AEAD RPC
Expand Down Expand Up @@ -577,7 +583,9 @@ func (s *Session) Write(actions []renterhost.RPCWriteAction) (err error) {
<-precompChan
if newFileSize > 0 && !merkle.VerifyDiffProof(actions, s.rev.NumSectors(), proofHashes, leafHashes, oldRoot, newRoot, s.appendRoots) {
err := ErrInvalidMerkleProof
s.sess.WriteResponse(nil, err)
if e := s.sess.WriteResponse(nil, err); e != nil {
err = multierror.Append(err, e)
}
Copy link
Owner

Choose a reason for hiding this comment

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

not actionable

return err
}

Expand Down Expand Up @@ -701,11 +709,15 @@ func NewSession(hostIP modules.NetAddress, hostKey hostdb.HostPublicKey, id type
return nil, err
}
if err := s.Lock(id, key); err != nil {
s.Close()
if e := s.Close(); e != nil {
err = multierror.Append(err, e)
}
Copy link
Owner

Choose a reason for hiding this comment

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

ditto

return nil, err
}
if _, err := s.Settings(); err != nil {
s.Close()
if e := s.Close(); e != nil {
err = multierror.Append(err, e)
}
Copy link
Owner

Choose a reason for hiding this comment

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

ditto

return nil, err
}
return s, nil
Expand All @@ -727,10 +739,15 @@ func newUnlockedSession(hostIP modules.NetAddress, hostKey hostdb.HostPublicKey,
}
latency := time.Since(start)
conn := &statsConn{Conn: tcpConn}
conn.SetDeadline(time.Now().Add(60 * time.Second))
err = conn.SetDeadline(time.Now().Add(60 * time.Second))
if err != nil {
return nil, err
}
Copy link
Owner

Choose a reason for hiding this comment

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

Practically speaking, I don't think it's possible for an error to be returned here. Looking at the source, (TCPConn).SetDeadline will only return an error if the underlying file descriptor is closed, and we know that it's open here. And since this is a TCPConn, we don't need to worry about other types that implement SetDeadline with weird behavior.

However, it's possible that the implementation of TCPConn could change in the future. Furthermore, I will probably allow arbitrary net.Conns in Session soon (as per #92). And in the worst case, failing to set a deadline could cause a Read or Write to block indefinitely. So I suppose this error should be handled properly.

s, err := renterhost.NewRenterSession(conn, hostKey.Ed25519())
if err != nil {
conn.Close()
if e := conn.Close(); e != nil {
err = multierror.Append(err, e)
}
Copy link
Owner

Choose a reason for hiding this comment

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

not actionable

return nil, err
}
return &Session{
Expand Down
12 changes: 10 additions & 2 deletions renter/renterutil/filesystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import (
"sync"
"time"

"github.com/hashicorp/go-multierror"
"github.com/pkg/errors"
"gitlab.com/NebulousLabs/Sia/crypto"

"lukechampine.com/us/hostdb"
"lukechampine.com/us/renter"
)
Expand Down Expand Up @@ -460,11 +462,17 @@ func (fs *PseudoFS) Close() error {
}
delete(fs.files, fd)
}
var err *multierror.Error
for fd, d := range fs.dirs {
d.Close()
if e := d.Close(); e != nil {
err = multierror.Append(err, e)
}
Copy link
Owner

Choose a reason for hiding this comment

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

There's rarely any reason to care about Close failing on a directory, but I guess this should be handled. We can use a simple []string and errors.New for this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately, such a simple implementation with []string and errors.New doesn't work because we cannot retrieve individual errors. As far as I know, go-multierror is one of the best options that allow us to check if a returned error containers some specific error.

Copy link
Owner

Choose a reason for hiding this comment

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

I can't think of any realistic circumstance in which you would need to programmatically handle the individual errors from closing directories. This is the sort of error that you would, at most, log somewhere and then manually inspect later. But in reality if you get an error when closing a directory, it's probably because your disk is failing and your log is gonna have a LOT of errors alerting you to that fact (assuming your system is usable at all).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We cannot know what error happens in advance. It's always better to prepare for it.
Actually, if the error means the closing directory/file doesn't exist, the user might remove it and we can ignore/warn it, but if disk drive dies, we need to log it.

We need to debug our applications remotely. Any information is helpful.

delete(fs.dirs, fd)
}
return fs.hosts.Close()
if e := fs.hosts.Close(); e != nil {
err = multierror.Append(err, e)
}
return err.ErrorOrNil()
}

// NewFileSystem returns a new pseudo-filesystem rooted at root, which must be a
Expand Down
Loading