Skip to content

Commit b22bf8c

Browse files
authored
Fix output synchronization by flushing appropriately (#80)
1 parent 5171c7c commit b22bf8c

5 files changed

Lines changed: 82 additions & 10 deletions

File tree

goawk_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,10 @@ func TestCommandLine(t *testing.T) {
395395
{[]string{"--", "{ print $1 }", "-file"}, "", "", `file "-file" not found`},
396396
{[]string{"{ print $1 }", "-file"}, "", "", `file "-file" not found`},
397397

398+
// Output synchronization
399+
{[]string{`BEGIN { print "1"; print "2"|"cat" }`}, "", "1\n2\n", ""},
400+
{[]string{`BEGIN { print "1"; "echo 2" | getline x; print x }`}, "", "1\n2\n", ""},
401+
398402
// Parse error formatting
399403
{[]string{"@"}, "", "", "<cmdline>:1:1: unexpected char\n@\n^"},
400404
{[]string{"BEGIN {\n\tx*;\n}"}, "", "", "<cmdline>:2:4: expected expression instead of ;\n x*;\n ^"},
@@ -415,6 +419,13 @@ func TestCommandLine(t *testing.T) {
415419
}
416420
}
417421

422+
func TestDevStdout(t *testing.T) {
423+
if runtime.GOOS == "windows" {
424+
t.Skip("/dev/stdout not presnt on Windows")
425+
}
426+
runAWKs(t, []string{`BEGIN { print "1"; print "2">"/dev/stdout" }`}, "", "1\n2\n", "")
427+
}
428+
418429
func runGoAWK(args []string, stdin string) (stdout, stderr string, err error) {
419430
cmd := exec.Command(goAWKExe, args...)
420431
if stdin != "" {

interp/functions.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,9 +195,10 @@ func (p *interp) callBuiltin(op Token, argExprs []Expr) (value, error) {
195195
cmd := p.execShell(cmdline)
196196
cmd.Stdout = p.output
197197
cmd.Stderr = p.errorOutput
198+
_ = p.flushAll() // ensure synchronization
198199
err := cmd.Start()
199200
if err != nil {
200-
fmt.Fprintln(p.errorOutput, err)
201+
p.printErrorf("%s\n", err)
201202
return num(-1), nil
202203
}
203204
err = cmd.Wait()
@@ -206,7 +207,7 @@ func (p *interp) callBuiltin(op Token, argExprs []Expr) (value, error) {
206207
code := exitErr.ProcessState.ExitCode()
207208
return num(float64(code)), nil
208209
} else {
209-
fmt.Fprintf(p.errorOutput, "unexpected error running command %q: %v\n", cmdline, err)
210+
p.printErrorf("unexpected error running command %q: %v\n", cmdline, err)
210211
return num(-1), nil
211212
}
212213
}

interp/interp.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -893,6 +893,7 @@ func (p *interp) eval(expr Expr) (value, error) {
893893
}
894894
line = scanner.Text()
895895
default:
896+
p.flushOutputAndError() // Flush output in case they've written a prompt
896897
var err error
897898
line, err = p.nextLine()
898899
if err == io.EOF {

interp/interp_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,6 +1099,38 @@ BEGIN {
10991099
if !reflect.DeepEqual(f.flushes, expected) {
11001100
t.Fatalf("expected flushes %q, got %q", expected, f.flushes)
11011101
}
1102+
1103+
// Ensure output is flushed before getline reads from stdin
1104+
src = `
1105+
BEGIN {
1106+
printf "Prompt: "
1107+
getline x
1108+
print x
1109+
}`
1110+
f = &mockFlusher{}
1111+
testGoAWK(t, src, "42\n", "", "", nil, func(config *interp.Config) {
1112+
config.Output = f
1113+
})
1114+
expected = []string{"Prompt: ", "Prompt: 42\n"}
1115+
if !reflect.DeepEqual(f.flushes, expected) {
1116+
t.Fatalf("expected flushes %q, got %q", expected, f.flushes)
1117+
}
1118+
1119+
// Ensure output is flushed before system()
1120+
src = `
1121+
BEGIN {
1122+
print "one"
1123+
system("echo .")
1124+
print "two"
1125+
}`
1126+
f = &mockFlusher{}
1127+
testGoAWK(t, src, "", "", "", nil, func(config *interp.Config) {
1128+
config.Output = f
1129+
})
1130+
expected = []string{"one\n", "one\n.\ntwo\n"}
1131+
if !reflect.DeepEqual(f.flushes, expected) {
1132+
t.Fatalf("expected flushes %q, got %q", expected, f.flushes)
1133+
}
11021134
}
11031135

11041136
type errorFlusher struct {

interp/io.go

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ type bufferedWriteCloser struct {
3333
io.Closer
3434
}
3535

36-
func newBufferedWriteClose(w io.WriteCloser) *bufferedWriteCloser {
36+
func newBufferedWriteCloser(w io.WriteCloser) *bufferedWriteCloser {
3737
writer := bufio.NewWriterSize(w, outputBufSize)
3838
return &bufferedWriteCloser{writer, w}
3939
}
@@ -76,6 +76,7 @@ func (p *interp) getOutputStream(redirect Token, dest Expr) (io.Writer, error) {
7676
if p.noFileWrites {
7777
return nil, newError("can't write to file due to NoFileWrites")
7878
}
79+
p.flushOutputAndError() // ensure synchronization
7980
flags := os.O_CREATE | os.O_WRONLY
8081
if redirect == GREATER {
8182
flags |= os.O_TRUNC
@@ -86,7 +87,7 @@ func (p *interp) getOutputStream(redirect Token, dest Expr) (io.Writer, error) {
8687
if err != nil {
8788
return nil, newError("output redirection error: %s", err)
8889
}
89-
buffered := newBufferedWriteClose(w)
90+
buffered := newBufferedWriteCloser(w)
9091
p.outputStreams[name] = buffered
9192
return buffered, nil
9293

@@ -102,13 +103,14 @@ func (p *interp) getOutputStream(redirect Token, dest Expr) (io.Writer, error) {
102103
}
103104
cmd.Stdout = p.output
104105
cmd.Stderr = p.errorOutput
106+
p.flushOutputAndError() // ensure synchronization
105107
err = cmd.Start()
106108
if err != nil {
107-
fmt.Fprintln(p.errorOutput, err)
109+
p.printErrorf("%s\n", err)
108110
return ioutil.Discard, nil
109111
}
110112
p.commands[name] = cmd
111-
buffered := newBufferedWriteClose(w)
113+
buffered := newBufferedWriteCloser(w)
112114
p.outputStreams[name] = buffered
113115
return buffered, nil
114116

@@ -166,9 +168,10 @@ func (p *interp) getInputScannerPipe(name string) (*bufio.Scanner, error) {
166168
if err != nil {
167169
return nil, newError("error connecting to stdout pipe: %v", err)
168170
}
171+
p.flushOutputAndError() // ensure synchronization
169172
err = cmd.Start()
170173
if err != nil {
171-
fmt.Fprintln(p.errorOutput, err)
174+
p.printErrorf("%s\n", err)
172175
return bufio.NewScanner(strings.NewReader("")), nil
173176
}
174177
scanner := p.newScanner(r)
@@ -468,7 +471,7 @@ func (p *interp) flushAll() bool {
468471
func (p *interp) flushStream(name string) bool {
469472
writer := p.outputStreams[name]
470473
if writer == nil {
471-
fmt.Fprintf(p.errorOutput, "error flushing %q: not an output file or pipe\n", name)
474+
p.printErrorf("error flushing %q: not an output file or pipe\n", name)
472475
return false
473476
}
474477
return p.flushWriter(name, writer)
@@ -481,11 +484,35 @@ type flusher interface {
481484
// Flush given output writer, and report whether it was flushed successfully
482485
// (logging an error if not).
483486
func (p *interp) flushWriter(name string, writer io.Writer) bool {
484-
flusher := writer.(flusher)
487+
flusher, ok := writer.(flusher)
488+
if !ok {
489+
return true // not a flusher, don't error
490+
}
485491
err := flusher.Flush()
486492
if err != nil {
487-
fmt.Fprintf(p.errorOutput, "error flushing %q: %v\n", name, err)
493+
p.printErrorf("error flushing %q: %v\n", name, err)
488494
return false
489495
}
490496
return true
491497
}
498+
499+
// Flush output and error streams.
500+
func (p *interp) flushOutputAndError() {
501+
if flusher, ok := p.output.(flusher); ok {
502+
_ = flusher.Flush()
503+
}
504+
if flusher, ok := p.errorOutput.(flusher); ok {
505+
_ = flusher.Flush()
506+
}
507+
}
508+
509+
// Print a message to the error output stream, flushing as necessary.
510+
func (p *interp) printErrorf(format string, args ...interface{}) {
511+
if flusher, ok := p.output.(flusher); ok {
512+
_ = flusher.Flush() // ensure synchronization
513+
}
514+
fmt.Fprintf(p.errorOutput, format, args...)
515+
if flusher, ok := p.errorOutput.(flusher); ok {
516+
_ = flusher.Flush()
517+
}
518+
}

0 commit comments

Comments
 (0)