Signed-off-by: Brian Goff <cpuguy83@gmail.com>
| ... | ... |
@@ -323,25 +323,13 @@ func (streamConfig *StreamConfig) StdinPipe() io.WriteCloser {
|
| 323 | 323 |
|
| 324 | 324 |
func (streamConfig *StreamConfig) StdoutPipe() io.ReadCloser {
|
| 325 | 325 |
reader, writer := io.Pipe() |
| 326 |
- streamConfig.stdout.AddWriter(writer, "") |
|
| 326 |
+ streamConfig.stdout.AddWriter(writer) |
|
| 327 | 327 |
return ioutils.NewBufReader(reader) |
| 328 | 328 |
} |
| 329 | 329 |
|
| 330 | 330 |
func (streamConfig *StreamConfig) StderrPipe() io.ReadCloser {
|
| 331 | 331 |
reader, writer := io.Pipe() |
| 332 |
- streamConfig.stderr.AddWriter(writer, "") |
|
| 333 |
- return ioutils.NewBufReader(reader) |
|
| 334 |
-} |
|
| 335 |
- |
|
| 336 |
-func (streamConfig *StreamConfig) StdoutLogPipe() io.ReadCloser {
|
|
| 337 |
- reader, writer := io.Pipe() |
|
| 338 |
- streamConfig.stdout.AddWriter(writer, "stdout") |
|
| 339 |
- return ioutils.NewBufReader(reader) |
|
| 340 |
-} |
|
| 341 |
- |
|
| 342 |
-func (streamConfig *StreamConfig) StderrLogPipe() io.ReadCloser {
|
|
| 343 |
- reader, writer := io.Pipe() |
|
| 344 |
- streamConfig.stderr.AddWriter(writer, "stderr") |
|
| 332 |
+ streamConfig.stderr.AddWriter(writer) |
|
| 345 | 333 |
return ioutils.NewBufReader(reader) |
| 346 | 334 |
} |
| 347 | 335 |
|
| ... | ... |
@@ -1,33 +1,20 @@ |
| 1 | 1 |
package broadcastwriter |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "bytes" |
|
| 5 | 4 |
"io" |
| 6 | 5 |
"sync" |
| 7 |
- "time" |
|
| 8 |
- |
|
| 9 |
- "github.com/Sirupsen/logrus" |
|
| 10 |
- "github.com/docker/docker/pkg/jsonlog" |
|
| 11 |
- "github.com/docker/docker/pkg/timeutils" |
|
| 12 | 6 |
) |
| 13 | 7 |
|
| 14 | 8 |
// BroadcastWriter accumulate multiple io.WriteCloser by stream. |
| 15 | 9 |
type BroadcastWriter struct {
|
| 16 | 10 |
sync.Mutex |
| 17 |
- buf *bytes.Buffer |
|
| 18 |
- jsLogBuf *bytes.Buffer |
|
| 19 |
- streams map[string](map[io.WriteCloser]struct{})
|
|
| 11 |
+ writers map[io.WriteCloser]struct{}
|
|
| 20 | 12 |
} |
| 21 | 13 |
|
| 22 |
-// AddWriter adds new io.WriteCloser for stream. |
|
| 23 |
-// If stream is "", then all writes proceed as is. Otherwise every line from |
|
| 24 |
-// input will be packed to serialized jsonlog.JSONLog. |
|
| 25 |
-func (w *BroadcastWriter) AddWriter(writer io.WriteCloser, stream string) {
|
|
| 14 |
+// AddWriter adds new io.WriteCloser. |
|
| 15 |
+func (w *BroadcastWriter) AddWriter(writer io.WriteCloser) {
|
|
| 26 | 16 |
w.Lock() |
| 27 |
- if _, ok := w.streams[stream]; !ok {
|
|
| 28 |
- w.streams[stream] = make(map[io.WriteCloser]struct{})
|
|
| 29 |
- } |
|
| 30 |
- w.streams[stream][writer] = struct{}{}
|
|
| 17 |
+ w.writers[writer] = struct{}{}
|
|
| 31 | 18 |
w.Unlock() |
| 32 | 19 |
} |
| 33 | 20 |
|
| ... | ... |
@@ -35,67 +22,12 @@ func (w *BroadcastWriter) AddWriter(writer io.WriteCloser, stream string) {
|
| 35 | 35 |
// this call. |
| 36 | 36 |
func (w *BroadcastWriter) Write(p []byte) (n int, err error) {
|
| 37 | 37 |
w.Lock() |
| 38 |
- if writers, ok := w.streams[""]; ok {
|
|
| 39 |
- for sw := range writers {
|
|
| 40 |
- if n, err := sw.Write(p); err != nil || n != len(p) {
|
|
| 41 |
- // On error, evict the writer |
|
| 42 |
- delete(writers, sw) |
|
| 43 |
- } |
|
| 44 |
- } |
|
| 45 |
- if len(w.streams) == 1 {
|
|
| 46 |
- if w.buf.Len() >= 4096 {
|
|
| 47 |
- w.buf.Reset() |
|
| 48 |
- } else {
|
|
| 49 |
- w.buf.Write(p) |
|
| 50 |
- } |
|
| 51 |
- w.Unlock() |
|
| 52 |
- return len(p), nil |
|
| 38 |
+ for sw := range w.writers {
|
|
| 39 |
+ if n, err := sw.Write(p); err != nil || n != len(p) {
|
|
| 40 |
+ // On error, evict the writer |
|
| 41 |
+ delete(w.writers, sw) |
|
| 53 | 42 |
} |
| 54 | 43 |
} |
| 55 |
- if w.jsLogBuf == nil {
|
|
| 56 |
- w.jsLogBuf = new(bytes.Buffer) |
|
| 57 |
- w.jsLogBuf.Grow(1024) |
|
| 58 |
- } |
|
| 59 |
- var timestamp string |
|
| 60 |
- created := time.Now().UTC() |
|
| 61 |
- w.buf.Write(p) |
|
| 62 |
- for {
|
|
| 63 |
- if n := w.buf.Len(); n == 0 {
|
|
| 64 |
- break |
|
| 65 |
- } |
|
| 66 |
- i := bytes.IndexByte(w.buf.Bytes(), '\n') |
|
| 67 |
- if i < 0 {
|
|
| 68 |
- break |
|
| 69 |
- } |
|
| 70 |
- lineBytes := w.buf.Next(i + 1) |
|
| 71 |
- if timestamp == "" {
|
|
| 72 |
- timestamp, err = timeutils.FastMarshalJSON(created) |
|
| 73 |
- if err != nil {
|
|
| 74 |
- continue |
|
| 75 |
- } |
|
| 76 |
- } |
|
| 77 |
- |
|
| 78 |
- for stream, writers := range w.streams {
|
|
| 79 |
- if stream == "" {
|
|
| 80 |
- continue |
|
| 81 |
- } |
|
| 82 |
- jsonLog := jsonlog.JSONLogBytes{Log: lineBytes, Stream: stream, Created: timestamp}
|
|
| 83 |
- err = jsonLog.MarshalJSONBuf(w.jsLogBuf) |
|
| 84 |
- if err != nil {
|
|
| 85 |
- logrus.Errorf("Error making JSON log line: %s", err)
|
|
| 86 |
- continue |
|
| 87 |
- } |
|
| 88 |
- w.jsLogBuf.WriteByte('\n')
|
|
| 89 |
- b := w.jsLogBuf.Bytes() |
|
| 90 |
- for sw := range writers {
|
|
| 91 |
- if _, err := sw.Write(b); err != nil {
|
|
| 92 |
- delete(writers, sw) |
|
| 93 |
- } |
|
| 94 |
- } |
|
| 95 |
- } |
|
| 96 |
- w.jsLogBuf.Reset() |
|
| 97 |
- } |
|
| 98 |
- w.jsLogBuf.Reset() |
|
| 99 | 44 |
w.Unlock() |
| 100 | 45 |
return len(p), nil |
| 101 | 46 |
} |
| ... | ... |
@@ -104,19 +36,16 @@ func (w *BroadcastWriter) Write(p []byte) (n int, err error) {
|
| 104 | 104 |
// will be saved. |
| 105 | 105 |
func (w *BroadcastWriter) Clean() error {
|
| 106 | 106 |
w.Lock() |
| 107 |
- for _, writers := range w.streams {
|
|
| 108 |
- for w := range writers {
|
|
| 109 |
- w.Close() |
|
| 110 |
- } |
|
| 107 |
+ for w := range w.writers {
|
|
| 108 |
+ w.Close() |
|
| 111 | 109 |
} |
| 112 |
- w.streams = make(map[string](map[io.WriteCloser]struct{}))
|
|
| 110 |
+ w.writers = make(map[io.WriteCloser]struct{})
|
|
| 113 | 111 |
w.Unlock() |
| 114 | 112 |
return nil |
| 115 | 113 |
} |
| 116 | 114 |
|
| 117 | 115 |
func New() *BroadcastWriter {
|
| 118 | 116 |
return &BroadcastWriter{
|
| 119 |
- streams: make(map[string](map[io.WriteCloser]struct{})),
|
|
| 120 |
- buf: bytes.NewBuffer(nil), |
|
| 117 |
+ writers: make(map[io.WriteCloser]struct{}),
|
|
| 121 | 118 |
} |
| 122 | 119 |
} |
| ... | ... |
@@ -32,9 +32,9 @@ func TestBroadcastWriter(t *testing.T) {
|
| 32 | 32 |
|
| 33 | 33 |
// Test 1: Both bufferA and bufferB should contain "foo" |
| 34 | 34 |
bufferA := &dummyWriter{}
|
| 35 |
- writer.AddWriter(bufferA, "") |
|
| 35 |
+ writer.AddWriter(bufferA) |
|
| 36 | 36 |
bufferB := &dummyWriter{}
|
| 37 |
- writer.AddWriter(bufferB, "") |
|
| 37 |
+ writer.AddWriter(bufferB) |
|
| 38 | 38 |
writer.Write([]byte("foo"))
|
| 39 | 39 |
|
| 40 | 40 |
if bufferA.String() != "foo" {
|
| ... | ... |
@@ -48,7 +48,7 @@ func TestBroadcastWriter(t *testing.T) {
|
| 48 | 48 |
// Test2: bufferA and bufferB should contain "foobar", |
| 49 | 49 |
// while bufferC should only contain "bar" |
| 50 | 50 |
bufferC := &dummyWriter{}
|
| 51 |
- writer.AddWriter(bufferC, "") |
|
| 51 |
+ writer.AddWriter(bufferC) |
|
| 52 | 52 |
writer.Write([]byte("bar"))
|
| 53 | 53 |
|
| 54 | 54 |
if bufferA.String() != "foobar" {
|
| ... | ... |
@@ -100,7 +100,7 @@ func TestRaceBroadcastWriter(t *testing.T) {
|
| 100 | 100 |
writer := New() |
| 101 | 101 |
c := make(chan bool) |
| 102 | 102 |
go func() {
|
| 103 |
- writer.AddWriter(devNullCloser(0), "") |
|
| 103 |
+ writer.AddWriter(devNullCloser(0)) |
|
| 104 | 104 |
c <- true |
| 105 | 105 |
}() |
| 106 | 106 |
writer.Write([]byte("hello"))
|
| ... | ... |
@@ -111,9 +111,9 @@ func BenchmarkBroadcastWriter(b *testing.B) {
|
| 111 | 111 |
writer := New() |
| 112 | 112 |
setUpWriter := func() {
|
| 113 | 113 |
for i := 0; i < 100; i++ {
|
| 114 |
- writer.AddWriter(devNullCloser(0), "stdout") |
|
| 115 |
- writer.AddWriter(devNullCloser(0), "stderr") |
|
| 116 |
- writer.AddWriter(devNullCloser(0), "") |
|
| 114 |
+ writer.AddWriter(devNullCloser(0)) |
|
| 115 |
+ writer.AddWriter(devNullCloser(0)) |
|
| 116 |
+ writer.AddWriter(devNullCloser(0)) |
|
| 117 | 117 |
} |
| 118 | 118 |
} |
| 119 | 119 |
testLine := "Line that thinks that it is log line from docker" |
| ... | ... |
@@ -142,33 +142,3 @@ func BenchmarkBroadcastWriter(b *testing.B) {
|
| 142 | 142 |
b.StartTimer() |
| 143 | 143 |
} |
| 144 | 144 |
} |
| 145 |
- |
|
| 146 |
-func BenchmarkBroadcastWriterWithoutStdoutStderr(b *testing.B) {
|
|
| 147 |
- writer := New() |
|
| 148 |
- setUpWriter := func() {
|
|
| 149 |
- for i := 0; i < 100; i++ {
|
|
| 150 |
- writer.AddWriter(devNullCloser(0), "") |
|
| 151 |
- } |
|
| 152 |
- } |
|
| 153 |
- testLine := "Line that thinks that it is log line from docker" |
|
| 154 |
- var buf bytes.Buffer |
|
| 155 |
- for i := 0; i < 100; i++ {
|
|
| 156 |
- buf.Write([]byte(testLine + "\n")) |
|
| 157 |
- } |
|
| 158 |
- // line without eol |
|
| 159 |
- buf.Write([]byte(testLine)) |
|
| 160 |
- testText := buf.Bytes() |
|
| 161 |
- b.SetBytes(int64(5 * len(testText))) |
|
| 162 |
- b.ResetTimer() |
|
| 163 |
- for i := 0; i < b.N; i++ {
|
|
| 164 |
- setUpWriter() |
|
| 165 |
- |
|
| 166 |
- for j := 0; j < 5; j++ {
|
|
| 167 |
- if _, err := writer.Write(testText); err != nil {
|
|
| 168 |
- b.Fatal(err) |
|
| 169 |
- } |
|
| 170 |
- } |
|
| 171 |
- |
|
| 172 |
- writer.Clean() |
|
| 173 |
- } |
|
| 174 |
-} |
| ... | ... |
@@ -3,7 +3,6 @@ package jsonlog |
| 3 | 3 |
import ( |
| 4 | 4 |
"encoding/json" |
| 5 | 5 |
"fmt" |
| 6 |
- "io" |
|
| 7 | 6 |
"time" |
| 8 | 7 |
) |
| 9 | 8 |
|
| ... | ... |
@@ -29,28 +28,3 @@ func (jl *JSONLog) Reset() {
|
| 29 | 29 |
jl.Stream = "" |
| 30 | 30 |
jl.Created = time.Time{}
|
| 31 | 31 |
} |
| 32 |
- |
|
| 33 |
-func WriteLog(src io.Reader, dst io.Writer, format string, since time.Time) error {
|
|
| 34 |
- dec := json.NewDecoder(src) |
|
| 35 |
- l := &JSONLog{}
|
|
| 36 |
- for {
|
|
| 37 |
- l.Reset() |
|
| 38 |
- if err := dec.Decode(l); err != nil {
|
|
| 39 |
- if err == io.EOF {
|
|
| 40 |
- return nil |
|
| 41 |
- } |
|
| 42 |
- return err |
|
| 43 |
- } |
|
| 44 |
- if !since.IsZero() && l.Created.Before(since) {
|
|
| 45 |
- continue |
|
| 46 |
- } |
|
| 47 |
- |
|
| 48 |
- line, err := l.Format(format) |
|
| 49 |
- if err != nil {
|
|
| 50 |
- return err |
|
| 51 |
- } |
|
| 52 |
- if _, err := io.WriteString(dst, line); err != nil {
|
|
| 53 |
- return err |
|
| 54 |
- } |
|
| 55 |
- } |
|
| 56 |
-} |
| 57 | 32 |
deleted file mode 100644 |
| ... | ... |
@@ -1,157 +0,0 @@ |
| 1 |
-package jsonlog |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "bytes" |
|
| 5 |
- "encoding/json" |
|
| 6 |
- "io/ioutil" |
|
| 7 |
- "regexp" |
|
| 8 |
- "strconv" |
|
| 9 |
- "strings" |
|
| 10 |
- "testing" |
|
| 11 |
- "time" |
|
| 12 |
- |
|
| 13 |
- "github.com/docker/docker/pkg/timeutils" |
|
| 14 |
-) |
|
| 15 |
- |
|
| 16 |
-// Invalid json should return an error |
|
| 17 |
-func TestWriteLogWithInvalidJSON(t *testing.T) {
|
|
| 18 |
- json := strings.NewReader("Invalid json")
|
|
| 19 |
- w := bytes.NewBuffer(nil) |
|
| 20 |
- if err := WriteLog(json, w, "json", time.Time{}); err == nil {
|
|
| 21 |
- t.Fatalf("Expected an error, got [%v]", w.String())
|
|
| 22 |
- } |
|
| 23 |
-} |
|
| 24 |
- |
|
| 25 |
-// Any format is valid, it will just print it |
|
| 26 |
-func TestWriteLogWithInvalidFormat(t *testing.T) {
|
|
| 27 |
- testLine := "Line that thinks that it is log line from docker\n" |
|
| 28 |
- var buf bytes.Buffer |
|
| 29 |
- e := json.NewEncoder(&buf) |
|
| 30 |
- for i := 0; i < 35; i++ {
|
|
| 31 |
- e.Encode(JSONLog{Log: testLine, Stream: "stdout", Created: time.Now()})
|
|
| 32 |
- } |
|
| 33 |
- w := bytes.NewBuffer(nil) |
|
| 34 |
- if err := WriteLog(&buf, w, "invalid format", time.Time{}); err != nil {
|
|
| 35 |
- t.Fatal(err) |
|
| 36 |
- } |
|
| 37 |
- res := w.String() |
|
| 38 |
- t.Logf("Result of WriteLog: %q", res)
|
|
| 39 |
- lines := strings.Split(strings.TrimSpace(res), "\n") |
|
| 40 |
- expression := "^invalid format Line that thinks that it is log line from docker$" |
|
| 41 |
- logRe := regexp.MustCompile(expression) |
|
| 42 |
- expectedLines := 35 |
|
| 43 |
- if len(lines) != expectedLines {
|
|
| 44 |
- t.Fatalf("Must be %v lines but got %d", expectedLines, len(lines))
|
|
| 45 |
- } |
|
| 46 |
- for _, l := range lines {
|
|
| 47 |
- if !logRe.MatchString(l) {
|
|
| 48 |
- t.Fatalf("Log line not in expected format [%v]: %q", expression, l)
|
|
| 49 |
- } |
|
| 50 |
- } |
|
| 51 |
-} |
|
| 52 |
- |
|
| 53 |
-// Having multiple Log/Stream element |
|
| 54 |
-func TestWriteLogWithMultipleStreamLog(t *testing.T) {
|
|
| 55 |
- testLine := "Line that thinks that it is log line from docker\n" |
|
| 56 |
- var buf bytes.Buffer |
|
| 57 |
- e := json.NewEncoder(&buf) |
|
| 58 |
- for i := 0; i < 35; i++ {
|
|
| 59 |
- e.Encode(JSONLog{Log: testLine, Stream: "stdout", Created: time.Now()})
|
|
| 60 |
- } |
|
| 61 |
- w := bytes.NewBuffer(nil) |
|
| 62 |
- if err := WriteLog(&buf, w, "invalid format", time.Time{}); err != nil {
|
|
| 63 |
- t.Fatal(err) |
|
| 64 |
- } |
|
| 65 |
- res := w.String() |
|
| 66 |
- t.Logf("Result of WriteLog: %q", res)
|
|
| 67 |
- lines := strings.Split(strings.TrimSpace(res), "\n") |
|
| 68 |
- expression := "^invalid format Line that thinks that it is log line from docker$" |
|
| 69 |
- logRe := regexp.MustCompile(expression) |
|
| 70 |
- expectedLines := 35 |
|
| 71 |
- if len(lines) != expectedLines {
|
|
| 72 |
- t.Fatalf("Must be %v lines but got %d", expectedLines, len(lines))
|
|
| 73 |
- } |
|
| 74 |
- for _, l := range lines {
|
|
| 75 |
- if !logRe.MatchString(l) {
|
|
| 76 |
- t.Fatalf("Log line not in expected format [%v]: %q", expression, l)
|
|
| 77 |
- } |
|
| 78 |
- } |
|
| 79 |
-} |
|
| 80 |
- |
|
| 81 |
-// Write log with since after created, it won't print anything |
|
| 82 |
-func TestWriteLogWithDate(t *testing.T) {
|
|
| 83 |
- created, _ := time.Parse("YYYY-MM-dd", "2015-01-01")
|
|
| 84 |
- var buf bytes.Buffer |
|
| 85 |
- testLine := "Line that thinks that it is log line from docker\n" |
|
| 86 |
- jsonLog := JSONLog{Log: testLine, Stream: "stdout", Created: created}
|
|
| 87 |
- if err := json.NewEncoder(&buf).Encode(jsonLog); err != nil {
|
|
| 88 |
- t.Fatal(err) |
|
| 89 |
- } |
|
| 90 |
- w := bytes.NewBuffer(nil) |
|
| 91 |
- if err := WriteLog(&buf, w, "json", time.Now()); err != nil {
|
|
| 92 |
- t.Fatal(err) |
|
| 93 |
- } |
|
| 94 |
- res := w.String() |
|
| 95 |
- if res != "" {
|
|
| 96 |
- t.Fatalf("Expected empty log, got [%v]", res)
|
|
| 97 |
- } |
|
| 98 |
-} |
|
| 99 |
- |
|
| 100 |
-// Happy path :) |
|
| 101 |
-func TestWriteLog(t *testing.T) {
|
|
| 102 |
- testLine := "Line that thinks that it is log line from docker\n" |
|
| 103 |
- format := timeutils.RFC3339NanoFixed |
|
| 104 |
- logs := map[string][]string{
|
|
| 105 |
- "": {"35", "^Line that thinks that it is log line from docker$"},
|
|
| 106 |
- "json": {"1", `^{\"log\":\"Line that thinks that it is log line from docker\\n\",\"stream\":\"stdout\",\"time\":.{30,}\"}$`},
|
|
| 107 |
- // 30+ symbols, five more can come from system timezone |
|
| 108 |
- format: {"35", `.{30,} Line that thinks that it is log line from docker`},
|
|
| 109 |
- } |
|
| 110 |
- for givenFormat, expressionAndLines := range logs {
|
|
| 111 |
- expectedLines, _ := strconv.Atoi(expressionAndLines[0]) |
|
| 112 |
- expression := expressionAndLines[1] |
|
| 113 |
- var buf bytes.Buffer |
|
| 114 |
- e := json.NewEncoder(&buf) |
|
| 115 |
- for i := 0; i < 35; i++ {
|
|
| 116 |
- e.Encode(JSONLog{Log: testLine, Stream: "stdout", Created: time.Now()})
|
|
| 117 |
- } |
|
| 118 |
- w := bytes.NewBuffer(nil) |
|
| 119 |
- if err := WriteLog(&buf, w, givenFormat, time.Time{}); err != nil {
|
|
| 120 |
- t.Fatal(err) |
|
| 121 |
- } |
|
| 122 |
- res := w.String() |
|
| 123 |
- t.Logf("Result of WriteLog: %q", res)
|
|
| 124 |
- lines := strings.Split(strings.TrimSpace(res), "\n") |
|
| 125 |
- if len(lines) != expectedLines {
|
|
| 126 |
- t.Fatalf("Must be %v lines but got %d", expectedLines, len(lines))
|
|
| 127 |
- } |
|
| 128 |
- logRe := regexp.MustCompile(expression) |
|
| 129 |
- for _, l := range lines {
|
|
| 130 |
- if !logRe.MatchString(l) {
|
|
| 131 |
- t.Fatalf("Log line not in expected format [%v]: %q", expression, l)
|
|
| 132 |
- } |
|
| 133 |
- } |
|
| 134 |
- } |
|
| 135 |
-} |
|
| 136 |
- |
|
| 137 |
-func BenchmarkWriteLog(b *testing.B) {
|
|
| 138 |
- var buf bytes.Buffer |
|
| 139 |
- e := json.NewEncoder(&buf) |
|
| 140 |
- testLine := "Line that thinks that it is log line from docker\n" |
|
| 141 |
- for i := 0; i < 30; i++ {
|
|
| 142 |
- e.Encode(JSONLog{Log: testLine, Stream: "stdout", Created: time.Now()})
|
|
| 143 |
- } |
|
| 144 |
- r := bytes.NewReader(buf.Bytes()) |
|
| 145 |
- w := ioutil.Discard |
|
| 146 |
- format := timeutils.RFC3339NanoFixed |
|
| 147 |
- b.SetBytes(int64(r.Len())) |
|
| 148 |
- b.ResetTimer() |
|
| 149 |
- for i := 0; i < b.N; i++ {
|
|
| 150 |
- if err := WriteLog(r, w, format, time.Time{}); err != nil {
|
|
| 151 |
- b.Fatal(err) |
|
| 152 |
- } |
|
| 153 |
- b.StopTimer() |
|
| 154 |
- r.Seek(0, 0) |
|
| 155 |
- b.StartTimer() |
|
| 156 |
- } |
|
| 157 |
-} |