zfs: use channel as iterator for ZFSList results
The old approach with ZFSList would keep the two-dimensional array of lines and their fields in memory (for a short time), which could easily consume 100s of MiB with > 10000 snapshots / bookmarks (see #34) fixes #61
This commit is contained in:
+64
@@ -10,6 +10,8 @@ import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"context"
|
||||
"github.com/problame/go-rwccmd"
|
||||
"github.com/zrepl/zrepl/util"
|
||||
)
|
||||
|
||||
@@ -185,6 +187,68 @@ func ZFSList(properties []string, zfsArgs ...string) (res [][]string, err error)
|
||||
return
|
||||
}
|
||||
|
||||
type ZFSListResult struct {
|
||||
fields []string
|
||||
err error
|
||||
}
|
||||
|
||||
// ZFSListChan executes `zfs list` and sends the results to the `out` channel.
|
||||
// The `out` channel is always closed by ZFSListChan:
|
||||
// If an error occurs, it is closed after sending a result with the err field set.
|
||||
// If no error occurs, it is just closed.
|
||||
// If the operation is cancelled via context, the channel is just closed.
|
||||
//
|
||||
// However, if callers do not drain `out` or cancel via `ctx`, the process will leak either running because
|
||||
// IO is pending or as a zombie.
|
||||
func ZFSListChan(ctx context.Context, out chan ZFSListResult, properties []string, zfsArgs ...string) {
|
||||
defer close(out)
|
||||
|
||||
args := make([]string, 0, 4+len(zfsArgs))
|
||||
args = append(args,
|
||||
"list", "-H", "-p",
|
||||
"-o", strings.Join(properties, ","))
|
||||
args = append(args, zfsArgs...)
|
||||
|
||||
sendResult := func(fields []string, err error) (done bool) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return true
|
||||
case out <- ZFSListResult{fields, err}:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
cmd, err := rwccmd.CommandContext(ctx, ZFS_BINARY, args, []string{})
|
||||
if err != nil {
|
||||
sendResult(nil, err)
|
||||
return
|
||||
}
|
||||
if err = cmd.Start(); err != nil {
|
||||
sendResult(nil, err)
|
||||
return
|
||||
}
|
||||
defer cmd.Close()
|
||||
|
||||
s := bufio.NewScanner(cmd)
|
||||
buf := make([]byte, 1024) // max line length
|
||||
s.Buffer(buf, 0)
|
||||
|
||||
for s.Scan() {
|
||||
fields := strings.SplitN(s.Text(), "\t", len(properties))
|
||||
if len(fields) != len(properties) {
|
||||
sendResult(nil, errors.New("unexpected output"))
|
||||
return
|
||||
}
|
||||
if sendResult(fields, nil) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if s.Err() != nil {
|
||||
sendResult(nil, s.Err())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ZFSSend(fs *DatasetPath, from, to *FilesystemVersion) (stream io.Reader, err error) {
|
||||
|
||||
args := make([]string, 0)
|
||||
|
||||
Reference in New Issue
Block a user