• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 12312390362

13 Dec 2024 08:44AM UTC coverage: 57.458% (+8.5%) from 48.92%
12312390362

Pull #9343

github

ellemouton
fn: rework the ContextGuard and add tests

In this commit, the ContextGuard struct is re-worked such that the
context that its new main WithCtx method provides is cancelled in sync
with a parent context being cancelled or with it's quit channel being
cancelled. Tests are added to assert the behaviour. In order for the
close of the quit channel to be consistent with the cancelling of the
derived context, the quit channel _must_ be contained internal to the
ContextGuard so that callers are only able to close the channel via the
exposed Quit method which will then take care to first cancel any
derived context that depend on the quit channel before returning.
Pull Request #9343: fn: expand the ContextGuard and add tests

101853 of 177264 relevant lines covered (57.46%)

24972.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.86
/chanbackup/backupfile.go
1
package chanbackup
2

3
import (
4
        "fmt"
5
        "os"
6
        "path/filepath"
7

8
        "github.com/lightningnetwork/lnd/keychain"
9
)
10

11
const (
12
        // DefaultBackupFileName is the default name of the auto updated static
13
        // channel backup fie.
14
        DefaultBackupFileName = "channel.backup"
15

16
        // DefaultTempBackupFileName is the default name of the temporary SCB
17
        // file that we'll use to atomically update the primary back up file
18
        // when new channel are detected.
19
        DefaultTempBackupFileName = "temp-dont-use.backup"
20
)
21

22
var (
23
        // ErrNoBackupFileExists is returned if caller attempts to call
24
        // UpdateAndSwap with the file name not set.
25
        ErrNoBackupFileExists = fmt.Errorf("back up file name not set")
26

27
        // ErrNoTempBackupFile is returned if caller attempts to call
28
        // UpdateAndSwap with the temp back up file name not set.
29
        ErrNoTempBackupFile = fmt.Errorf("temp backup file not set")
30
)
31

32
// MultiFile represents a file on disk that a caller can use to read the packed
33
// multi backup into an unpacked one, and also atomically update the contents
34
// on disk once new channels have been opened, and old ones closed. This struct
35
// relies on an atomic file rename property which most widely use file systems
36
// have.
37
type MultiFile struct {
38
        // fileName is the file name of the main back up file.
39
        fileName string
40

41
        // tempFileName is the name of the file that we'll use to stage a new
42
        // packed multi-chan backup, and the rename to the main back up file.
43
        tempFileName string
44

45
        // tempFile is an open handle to the temp back up file.
46
        tempFile *os.File
47
}
48

49
// NewMultiFile create a new multi-file instance at the target location on the
50
// file system.
51
func NewMultiFile(fileName string) *MultiFile {
6✔
52

6✔
53
        // We'll our temporary backup file in the very same directory as the
6✔
54
        // main backup file.
6✔
55
        backupFileDir := filepath.Dir(fileName)
6✔
56
        tempFileName := filepath.Join(
6✔
57
                backupFileDir, DefaultTempBackupFileName,
6✔
58
        )
6✔
59

6✔
60
        return &MultiFile{
6✔
61
                fileName:     fileName,
6✔
62
                tempFileName: tempFileName,
6✔
63
        }
6✔
64
}
6✔
65

66
// UpdateAndSwap will attempt write a new temporary backup file to disk with
67
// the newBackup encoded, then atomically swap (via rename) the old file for
68
// the new file by updating the name of the new file to the old.
69
func (b *MultiFile) UpdateAndSwap(newBackup PackedMulti) error {
5✔
70
        // If the main backup file isn't set, then we can't proceed.
5✔
71
        if b.fileName == "" {
6✔
72
                return ErrNoBackupFileExists
1✔
73
        }
1✔
74

75
        log.Infof("Updating backup file at %v", b.fileName)
4✔
76

4✔
77
        // If the old back up file still exists, then we'll delete it before
4✔
78
        // proceeding.
4✔
79
        if _, err := os.Stat(b.tempFileName); err == nil {
5✔
80
                log.Infof("Found old temp backup @ %v, removing before swap",
1✔
81
                        b.tempFileName)
1✔
82

1✔
83
                err = os.Remove(b.tempFileName)
1✔
84
                if err != nil {
1✔
85
                        return fmt.Errorf("unable to remove temp "+
×
86
                                "backup file: %v", err)
×
87
                }
×
88
        }
89

90
        // Now that we know the staging area is clear, we'll create the new
91
        // temporary back up file.
92
        var err error
4✔
93
        b.tempFile, err = os.Create(b.tempFileName)
4✔
94
        if err != nil {
4✔
95
                return fmt.Errorf("unable to create temp file: %w", err)
×
96
        }
×
97

98
        // With the file created, we'll write the new packed multi backup and
99
        // remove the temporary file all together once this method exits.
100
        _, err = b.tempFile.Write([]byte(newBackup))
4✔
101
        if err != nil {
4✔
102
                return fmt.Errorf("unable to write backup to temp file: %w",
×
103
                        err)
×
104
        }
×
105
        if err := b.tempFile.Sync(); err != nil {
4✔
106
                return fmt.Errorf("unable to sync temp file: %w", err)
×
107
        }
×
108
        defer os.Remove(b.tempFileName)
4✔
109

4✔
110
        log.Infof("Swapping old multi backup file from %v to %v",
4✔
111
                b.tempFileName, b.fileName)
4✔
112

4✔
113
        // Before we rename the swap (atomic name swap), we'll make
4✔
114
        // sure to close the current file as some OSes don't support
4✔
115
        // renaming a file that's already open (Windows).
4✔
116
        if err := b.tempFile.Close(); err != nil {
4✔
117
                return fmt.Errorf("unable to close file: %w", err)
×
118
        }
×
119

120
        // Finally, we'll attempt to atomically rename the temporary file to
121
        // the main back up file. If this succeeds, then we'll only have a
122
        // single file on disk once this method exits.
123
        return os.Rename(b.tempFileName, b.fileName)
4✔
124
}
125

126
// ExtractMulti attempts to extract the packed multi backup we currently point
127
// to into an unpacked version. This method will fail if no backup file
128
// currently exists as the specified location.
129
func (b *MultiFile) ExtractMulti(keyChain keychain.KeyRing) (*Multi, error) {
4✔
130
        var err error
4✔
131

4✔
132
        // We'll return an error if the main file isn't currently set.
4✔
133
        if b.fileName == "" {
5✔
134
                return nil, ErrNoBackupFileExists
1✔
135
        }
1✔
136

137
        // Now that we've confirmed the target file is populated, we'll read
138
        // all the contents of the file. This function ensures that file is
139
        // always closed, even if we can't read the contents.
140
        multiBytes, err := os.ReadFile(b.fileName)
3✔
141
        if err != nil {
4✔
142
                return nil, err
1✔
143
        }
1✔
144

145
        // Finally, we'll attempt to unpack the file and return the unpack
146
        // version to the caller.
147
        packedMulti := PackedMulti(multiBytes)
2✔
148
        return packedMulti.Unpack(keyChain)
2✔
149
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc