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

Do we expect StandardEWMA.Tick to be called concurrently? #286

Open
localvar opened this issue Sep 14, 2021 · 2 comments
Open

Do we expect StandardEWMA.Tick to be called concurrently? #286

localvar opened this issue Sep 14, 2021 · 2 comments

Comments

@localvar
Copy link

Do we expect StandardEWMA.Tick to be called concurrently?

If not, then there's no need to use a mutex and atomic operations on a.init, just below code is fine:

    if a.init == 0 {
        atomic.StoreUint64(&a.rate, math.Float64bits(a.fetchInstantRate())) 
        a.init = 1
    } else {
         a.updateRate(a.fetchInstantRate()) 
    }

if yes, then the current implementation is incorrect, because, after the current goroutine executed L115, other goroutines could execute L103, which creates a race condition with L116 of the current goroutine, and L103 itself could create race conditions between concurrent goroutines.

go-metrics/ewma.go

Lines 100 to 120 in cf1acfc

func (a *StandardEWMA) Tick() {
// Optimization to avoid mutex locking in the hot-path.
if atomic.LoadUint32(&a.init) == 1 {
a.updateRate(a.fetchInstantRate())
} else {
// Slow-path: this is only needed on the first Tick() and preserves transactional updating
// of init and rate in the else block. The first conditional is needed below because
// a different thread could have set a.init = 1 between the time of the first atomic load and when
// the lock was acquired.
a.mutex.Lock()
if atomic.LoadUint32(&a.init) == 1 {
// The fetchInstantRate() uses atomic loading, which is unecessary in this critical section
// but again, this section is only invoked on the first successful Tick() operation.
a.updateRate(a.fetchInstantRate())
} else {
atomic.StoreUint32(&a.init, 1)
atomic.StoreUint64(&a.rate, math.Float64bits(a.fetchInstantRate()))
}
a.mutex.Unlock()
}
}

@localvar
Copy link
Author

After review the code and comments, I don't think StandardEWMA.Tick should be called concurrently, otherwise, it will output wrong results.

for example, after the first tick, even if the race condition is not triggered because we are lucky enough, calling this function from two goroutines at the same time, is just like doing below in a single goroutine:

a.updateRate(a.fetchInstantRate()) // desired update
a.updateRate(a.fetchInstantRate()) // not desired

so we can remove the mutex to make the code simpler and faster.

@zeim839
Copy link

zeim839 commented Aug 3, 2023

updateRate and fetchInstanceRate are not public functions, so as long as they're not unexpectedly called by the go-metrics library itself, then they wont race.

Tick() is meant to be called by an external clock every 5 seconds. See meter.go: L226-L251

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

No branches or pull requests

2 participants