Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add buffered write syncer #952

Merged
merged 65 commits into from
Jun 8, 2021

Conversation

moisesvega
Copy link
Contributor

This diff addresses comments in #782

  • Add BufferedWriteSyncer
  • Use new Clock Interface for testing

API for BufferedWriteSyncer:

type BufferedWriteSyncer struct {
	WriteSyncer

	Size          int
	FlushInterval time.Duration
	Clock         Clock

	// unexported fields for state
}

buf := &bytes.Buffer{}
ws := &BufferedWriteSyncer{WriteSyncer: AddSync(buf)} //  BufferedWriteSyncer with the default values
ws.Write([]byte("foo"))

 // Close closes the buffer, cleans up background goroutines, and flushes remaining, unwritten data.
ws.Close()

@codecov
Copy link

codecov bot commented May 25, 2021

Codecov Report

Merging #952 (f32b796) into master (cfe34dc) will increase coverage by 0.03%.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #952      +/-   ##
==========================================
+ Coverage   98.12%   98.16%   +0.03%     
==========================================
  Files          44       45       +1     
  Lines        1975     2014      +39     
==========================================
+ Hits         1938     1977      +39     
  Misses         29       29              
  Partials        8        8              
Impacted Files Coverage Δ
zapcore/buffered_write_syncer.go 100.00% <100.00%> (ø)
zaptest/observer/observer.go 100.00% <0.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update cfe34dc...f32b796. Read the comment docs.

@moisesvega moisesvega requested a review from abhinav May 25, 2021 17:32
@moisesvega moisesvega force-pushed the buffered-write-syncer branch 2 times, most recently from fc8195f to d335a50 Compare May 25, 2021 20:56
@moisesvega moisesvega force-pushed the buffered-write-syncer branch from d335a50 to 2312b39 Compare May 25, 2021 21:39
zapcore/buffered_write_syncer.go Outdated Show resolved Hide resolved
zapcore/buffered_write_syncer_bench_test.go Outdated Show resolved Hide resolved
zapcore/buffered_write_syncer_bench_test.go Outdated Show resolved Hide resolved
zapcore/buffered_write_syncer.go Show resolved Hide resolved
zapcore/buffered_write_syncer.go Show resolved Hide resolved
zapcore/write_syncer_bench_test.go Show resolved Hide resolved
zapcore/write_syncer_bench_test.go Show resolved Hide resolved
zapcore/buffered_write_syncer_bench_test.go Show resolved Hide resolved
zapcore/buffered_write_syncer.go Show resolved Hide resolved
Copy link
Collaborator

@abhinav abhinav left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the delay. I'll look at the next iteration quicker!

Comment on lines 31 to 36
// The default values are; Size 256kb, FlushInterval 30s.
type BufferedWriteSyncer struct {
WriteSyncer

Size int
FlushInterval time.Duration
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor nit: we should specify the defaults above the fields as we document the fields:

// Size specifies the maximum amount of data the writer will buffer before
// flushing. Defaults to 256 kB.

// FlushInterval specifies how often the writer should flush data if there
// have been no writes. Defaults to 30 seconds.

zapcore/buffered_write_syncer.go Outdated Show resolved Hide resolved
zapcore/buffered_write_syncer.go Show resolved Hide resolved
_defaultFlushInterval = 30 * time.Second
)

func (s *BufferedWriteSyncer) loadConfig() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor naming: can we name this init or initialize because the bool is called initialize. Or we can name the bool "loaded" and call this "load", but I'm more in favor of init or initialize.

}
s.ticker = s.Clock.NewTicker(flushInterval)

s.writer = bufio.NewWriterSize(s.WriteSyncer, size)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor optimization on the WriteSyncer:
since BufferedWriteSyncer is already thread-safe, we can optimize this a bit if s.WriteSyncer was wrapped with zapcore.Lock.

writer := s.WriteSyncer
if w, ok := writer.(*lockedWriteSyncer); ok {
  writer = w.ws // don't double lock
}
s.writer = bufio.NewWriterSize(writer, size)

zapcore/buffered_write_syncer.go Show resolved Hide resolved
zapcore/buffered_write_syncer.go Show resolved Hide resolved
zapcore/buffered_write_syncer_bench_test.go Show resolved Hide resolved
Copy link
Collaborator

@abhinav abhinav left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly LGTM. For the wrap-twice case, can we add a test with an explicit sync to verify that the inner BufWriter was also flushed?

zapcore/buffered_write_syncer_test.go Outdated Show resolved Hide resolved
zapcore/buffered_write_syncer_test.go Show resolved Hide resolved
zapcore/buffered_write_syncer_test.go Outdated Show resolved Hide resolved
Copy link
Contributor

@shirchen shirchen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm, just nits about closing files in tests and not needing to manually closing files.

moisesvega and others added 3 commits June 7, 2021 18:58
Co-authored-by: Abhinav Gupta <mail@abhinavg.net>
Co-authored-by: Abhinav Gupta <mail@abhinavg.net>
Copy link
Contributor

@shirchen shirchen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm but wait for @abhinav 's stamp too.

@abhinav abhinav requested a review from prashantv June 8, 2021 12:06
@moisesvega moisesvega merged commit 13315a1 into uber-go:master Jun 8, 2021
@abhinav
Copy link
Collaborator

abhinav commented Jun 8, 2021

No, we don't want a merge commit for this. Reverting.

Comment on lines +145 to +146
close(s.stop)
return s.Sync()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we wait for the background goroutine to stop before returning here? It leads to:

  • no goroutine running after Close guaranteed
  • avoids odd cases where a Sync can happen after the Close returns

// remaining, unwritten data. This will not close the underlying WriteSyncer.
func (s *BufferedWriteSyncer) Close() error {
s.ticker.Stop()
close(s.stop)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this will panic if we do it twice, do we want to explicitly call out that Close must only be called once, or put some protection here in case Close is called twice?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going with a doc for now. Guarding against it is possible but seems unnecessary given that standard usage will probably be:

ws := zapcore.BufferedWriteSyncer{...}
defer ws.Stop()

(we do need to guard against Stop without Start, so going to add that too.)

}

// Close closes the buffer, cleans up background goroutines, and flushes
// remaining, unwritten data. This will not close the underlying WriteSyncer.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The warning that this will not close the underlying WriteSyncer makes me wonder if Close is a little surprising. Would this be better named something else like Stop to make it very clear that it doesn't close the underlying writer?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reasonable. Renaming.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. Stop makes more sense in the context of this specific writer.

@abhinav
Copy link
Collaborator

abhinav commented Jun 8, 2021

Reopened in #961 @prashantv

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

5 participants