-
-
Notifications
You must be signed in to change notification settings - Fork 7
/
conn.go
124 lines (105 loc) · 2.17 KB
/
conn.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package godartsass
import (
"bufio"
"bytes"
"errors"
"io"
"os"
"os/exec"
"regexp"
"runtime"
"time"
)
func newConn(cmd *exec.Cmd) (_ conn, err error) {
in, err := cmd.StdinPipe()
if err != nil {
return conn{}, err
}
defer func() {
if err != nil {
in.Close()
}
}()
out, err := cmd.StdoutPipe()
stdErr := &tailBuffer{limit: 1024}
buff := bufio.NewReader(out)
c := conn{buff, buff, out, in, stdErr, cmd}
cmd.Stderr = c.stdErr
return c, err
}
type byteReadWriteCloser interface {
io.ReadWriteCloser
io.ByteReader
}
type conn struct {
io.ByteReader
io.Reader
readerCloser io.Closer
io.WriteCloser
stdErr *tailBuffer
cmd *exec.Cmd
}
// Start starts conn's Cmd.
func (c conn) Start() error {
err := c.cmd.Start()
if err != nil {
return c.Close()
}
return err
}
// Close closes conn's WriteCloser, ReadClosers, and waits for the command to finish.
func (c conn) Close() error {
writeErr := c.WriteCloser.Close()
readErr := c.readerCloser.Close()
var interruptErr error
if runtime.GOOS != "windows" {
// See https://github.com/bep/godartsass/issues/19
interruptErr = c.cmd.Process.Signal(os.Interrupt)
if interruptErr == os.ErrProcessDone {
interruptErr = nil
}
}
cmdErr := c.waitWithTimeout()
if writeErr != nil {
return writeErr
}
if readErr != nil {
return readErr
}
if interruptErr != nil {
return interruptErr
}
return cmdErr
}
var brokenPipeRe = regexp.MustCompile("Broken pipe|pipe is being closed")
// dart-sass ends on itself on EOF, this is just to give it some
// time to do so.
func (c conn) waitWithTimeout() error {
result := make(chan error, 1)
go func() { result <- c.cmd.Wait() }()
select {
case err := <-result:
if eerr, ok := err.(*exec.ExitError); ok {
if eerr.Error() == "signal: interrupt" {
return nil
}
if brokenPipeRe.MatchString(c.stdErr.String()) {
return nil
}
}
return err
case <-time.After(5 * time.Second):
return errors.New("timed out waiting for dart-sass to finish")
}
}
type tailBuffer struct {
limit int
bytes.Buffer
}
func (b *tailBuffer) Write(p []byte) (n int, err error) {
if len(p)+b.Buffer.Len() > b.limit {
b.Reset()
}
n, err = b.Buffer.Write(p)
return
}