Skip to content
This repository has been archived by the owner on Aug 15, 2020. It is now read-only.

Commit

Permalink
Rewrite the whole package.
Browse files Browse the repository at this point in the history
It's now thread safe.
  • Loading branch information
maruel committed Apr 8, 2015
1 parent 04c1b30 commit 2f164a1
Show file tree
Hide file tree
Showing 7 changed files with 524 additions and 385 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Expand Up @@ -12,4 +12,4 @@ before_install:
- go get github.com/maruel/pre-commit-go

script:
- pre-commit-go
- pre-commit-go installrun -level 3
6 changes: 2 additions & 4 deletions README.md
Expand Up @@ -21,9 +21,6 @@ Features
Examples
--------

It is recommended to use multiple techniques simultaneously. For example, using
synchronous write to file coupled with web server.


Web
===
Expand Down Expand Up @@ -85,8 +82,8 @@ Asynchronous
logBuffer.WriteTo(f)
}()
wg.Wait()
// Will not be written.
log.Printf("One more line")
logBuffer.Flush()
logBuffer.Close()
f.Close()

Expand Down Expand Up @@ -139,5 +136,6 @@ Synchronous
}
log.SetOutput(io.MultiWriter(logBuffer, f))
log.Printf("One more line")
logBuffer.Flush()
logBuffer.Close()
f.Close()
87 changes: 87 additions & 0 deletions auto_flush.go
@@ -0,0 +1,87 @@
// Copyright 2015 Marc-Antoine Ruel. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

package circular

import (
"io"
"sync"
"time"
)

type AutoFlusher interface {
io.Writer

Flush()
}

// AutoFlush converts an io.Writer supporting http.Flusher to call Flush()
// automatically after each write after a small delay.
//
// The main use case is http connection when piping a circular buffer to it.
func AutoFlush(w io.Writer, delay time.Duration) AutoFlusher {
f, _ := w.(flusher)
if f == nil {
return noOpFlush{w}
}
return &autoFlusher{w: w, f: f, delay: delay}
}

func AutoFlushInstant(w io.Writer) AutoFlusher {
return AutoFlush(w, time.Duration(0))
}

// Internal details.

// flusher is the equivalent of http.Flusher. Importing net/http is quite
// heavy. It's not worth importing it just for this interface which is
// guaranteed to be stable for Go v1.x.
type flusher interface {
Flush()
}

type noOpFlush struct {
io.Writer
}

func (n noOpFlush) Flush() {
// For some reason, calls to this function are not caught by go test -cover.
}

type autoFlusher struct {
f flusher
w io.Writer
delay time.Duration
lock sync.Mutex
flushPending bool
}

func (a *autoFlusher) Write(p []byte) (int, error) {
// Never call .Write() and .Flush() concurrently.
a.lock.Lock()
n, err := a.w.Write(p)
defer a.lock.Unlock()
if n != 0 && !a.flushPending {
a.flushPending = true
go func() {
if a.delay != 0 {
<-time.After(a.delay)
}
a.lock.Lock()
if a.flushPending {
a.f.Flush()
a.flushPending = false
}
a.lock.Unlock()
}()
}
return n, err
}

func (a *autoFlusher) Flush() {
a.lock.Lock()
defer a.lock.Unlock()
a.flushPending = false
a.f.Flush()
}
25 changes: 25 additions & 0 deletions auto_flush_test.go
@@ -0,0 +1,25 @@
// Copyright 2015 Marc-Antoine Ruel. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

package circular

import (
"bytes"
"testing"

"github.com/maruel/ut"
)

func TestAutoFlushShallow(t *testing.T) {
b := &bytes.Buffer{}
a := AutoFlush(b, 0)
a.Flush()
}

func TestAutoFlush(t *testing.T) {
f := &flusherWriter{}
a := AutoFlush(f, 0)
a.Flush()
ut.AssertEqual(t, true, f.flushed)
}

0 comments on commit 2f164a1

Please sign in to comment.