Skip to content

Commit c978e4a

Browse files
authored
command/run: handle large tokens in scanner (#438)
1 parent 9a7fa24 commit c978e4a

3 files changed

Lines changed: 96 additions & 33 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
- Fixed a bug where errors did not result a non-zero exit code. ([#304](https://github.com/peak/s5cmd/issues/304))
2525
- Print error if the commands file of `run` command is not accessible. ([#410](https://github.com/peak/s5cmd/pull/410))
2626
- Updated region detection call to use current session's address resolving method ([#314](https://github.com/peak/s5cmd/issues/314))
27+
- Fixed a bug where lines with large tokens fail in `run` command. `sync` was failing when it finds multiple files to remove. ([#435](https://github.com/peak/s5cmd/issues/435), [#436](https://github.com/peak/s5cmd/issues/436))
2728

2829
## v1.4.0 - 21 Sep 2021
2930

command/run.go

Lines changed: 39 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ func NewRun(c *cli.Context, r io.Reader) Run {
8181
}
8282

8383
func (r Run) Run(ctx context.Context) error {
84-
reader := r.reader
8584
pm := parallel.New(r.numWorkers)
8685
defer pm.Close()
8786

@@ -96,10 +95,10 @@ func (r Run) Run(ctx context.Context) error {
9695
}
9796
}()
9897

99-
scanner := NewScanner(ctx, reader)
98+
reader := NewReader(ctx, r.reader)
10099

101100
lineno := -1
102-
for line := range scanner.Scan() {
101+
for line := range reader.Read() {
103102
lineno++
104103

105104
line = strings.TrimSpace(line)
@@ -153,60 +152,67 @@ func (r Run) Run(ctx context.Context) error {
153152
waiter.Wait()
154153
<-errDoneCh
155154

156-
return multierror.Append(merrorWaiter, scanner.Err()).ErrorOrNil()
155+
if reader.Err() != nil {
156+
printError(commandFromContext(r.c), r.c.Command.Name, reader.Err())
157+
}
158+
159+
return multierror.Append(merrorWaiter, reader.Err()).ErrorOrNil()
157160
}
158161

159-
// Scanner is a cancelable scanner.
160-
type Scanner struct {
161-
*bufio.Scanner
162+
// Reader is a cancelable reader.
163+
type Reader struct {
164+
*bufio.Reader
162165
err error
163166
linech chan string
164167
ctx context.Context
165168
}
166169

167-
// NewScanner creates a new scanner with cancellation.
168-
func NewScanner(ctx context.Context, r io.Reader) *Scanner {
169-
scanner := &Scanner{
170-
ctx: ctx,
171-
Scanner: bufio.NewScanner(r),
172-
linech: make(chan string),
170+
// NewReader creates a new reader with cancellation.
171+
func NewReader(ctx context.Context, r io.Reader) *Reader {
172+
reader := &Reader{
173+
ctx: ctx,
174+
Reader: bufio.NewReader(r),
175+
linech: make(chan string),
173176
}
174177

175-
go scanner.scan()
176-
return scanner
178+
go reader.read()
179+
return reader
177180
}
178181

179-
// scan read the underlying reader.
180-
func (s *Scanner) scan() {
181-
defer close(s.linech)
182+
// read reads lines from the underlying reader.
183+
func (r *Reader) read() {
184+
defer close(r.linech)
182185

183186
for {
184187
select {
185-
case <-s.ctx.Done():
186-
s.err = s.ctx.Err()
188+
case <-r.ctx.Done():
189+
r.err = r.ctx.Err()
187190
return
188191
default:
189-
if !s.Scanner.Scan() {
190-
return
192+
// If ReadString encounters an error before finding a delimiter,
193+
// it returns the data read before the error and the error itself (often io.EOF).
194+
line, err := r.ReadString('\n')
195+
if line != "" {
196+
r.linech <- line
197+
}
198+
if err != nil {
199+
if err == io.EOF {
200+
return
201+
}
202+
r.err = multierror.Append(r.err, err)
191203
}
192-
193-
s.linech <- s.Scanner.Text()
194204
}
195205
}
196206
}
197207

198-
// Scan returns read-only channel to consume lines.
199-
func (s *Scanner) Scan() <-chan string {
200-
return s.linech
208+
// Read returns read-only channel to consume lines.
209+
func (r *Reader) Read() <-chan string {
210+
return r.linech
201211
}
202212

203213
// Err returns encountered errors, if any.
204-
func (s *Scanner) Err() error {
205-
if s.err != nil {
206-
return s.err
207-
}
208-
209-
return s.Scanner.Err()
214+
func (r *Reader) Err() error {
215+
return r.err
210216
}
211217

212218
func validateRunCommand(c *cli.Context) error {

e2e/sync_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1621,3 +1621,59 @@ func TestSyncLocalDirectoryToS3WithExcludeFilter(t *testing.T) {
16211621
})
16221622
}
16231623
}
1624+
1625+
// sync --delete somedir s3://bucket/ (removes 10k objects)
1626+
func TestIssue435(t *testing.T) {
1627+
t.Parallel()
1628+
1629+
bucket := s3BucketFromTestName(t)
1630+
1631+
s3client, s5cmd, cleanup := setup(t, withS3Backend("mem"))
1632+
defer cleanup()
1633+
1634+
createBucket(t, s3client, bucket)
1635+
1636+
// empty folder
1637+
folderLayout := []fs.PathOp{}
1638+
1639+
workdir := fs.NewDir(t, "somedir", folderLayout...)
1640+
defer workdir.Remove()
1641+
1642+
const filecount = 10_000
1643+
1644+
filenameFunc := func(i int) string { return fmt.Sprintf("file_%06d", i) }
1645+
contentFunc := func(i int) string { return fmt.Sprintf("file body %06d", i) }
1646+
1647+
for i := 0; i < filecount; i++ {
1648+
filename := filenameFunc(i)
1649+
content := contentFunc(i)
1650+
putFile(t, s3client, bucket, filename, content)
1651+
}
1652+
1653+
src := fmt.Sprintf("%v/", workdir.Path())
1654+
src = filepath.ToSlash(src)
1655+
dst := fmt.Sprintf("s3://%v/", bucket)
1656+
1657+
cmd := s5cmd("--log", "debug", "sync", "--delete", src, dst)
1658+
result := icmd.RunCmd(cmd)
1659+
1660+
result.Assert(t, icmd.Success)
1661+
1662+
assertLines(t, result.Stderr(), map[int]compareFunc{})
1663+
1664+
expected := make(map[int]compareFunc)
1665+
for i := 0; i < filecount; i++ {
1666+
expected[i] = contains("rm s3://%v/file_%06d", bucket, i)
1667+
}
1668+
1669+
assertLines(t, result.Stdout(), expected, sortInput(true))
1670+
1671+
// assert s3 objects
1672+
for i := 0; i < filecount; i++ {
1673+
filename := filenameFunc(i)
1674+
content := contentFunc(i)
1675+
1676+
err := ensureS3Object(s3client, bucket, filename, content)
1677+
assertError(t, err, errS3NoSuchKey)
1678+
}
1679+
}

0 commit comments

Comments
 (0)