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

Deadlock in metrics #15

Closed
yukw777 opened this issue Dec 14, 2017 · 2 comments
Closed

Deadlock in metrics #15

yukw777 opened this issue Dec 14, 2017 · 2 comments
Assignees

Comments

@yukw777
Copy link

yukw777 commented Dec 14, 2017

There's a deadlock in how the go client handles metrics data, specifically when it handles the "sent" event. There are other issues in the codebase that were uncovered during debugging, so I'll list all of them here for now, but I'm happy to make a separate issue for each if necessary.

The deadlock happens when you don't pass in an "optional" event listener (WithListener) when initializing the client. The unbuffered sent channel receives a value

m.sent <- payload

, but b/c the event listener is nil, the receiver is never started
if uc.options.listener != nil {
if eListener, ok := uc.options.listener.(ErrorListener); ok {
uc.errorListener = eListener
}
if rListener, ok := uc.options.listener.(RepositoryListener); ok {
uc.repositoryListener = rListener
}
if mListener, ok := uc.options.listener.(MetricListener); ok {
uc.metricsListener = mListener
}
defer func() {
go uc.sync()
}()
}

, so the next time a payload is sent to the sent channel, it blocks.

There are some other issues related to this bug.

  1. WithDisableMetrics doesn't work, b/c the value doesn't get passed into metrics from the clinet.

    unleash-client-go/client.go

    Lines 163 to 171 in c586a9e

    metricsOptions{
    appName: uc.options.appName,
    instanceId: uc.options.instanceId,
    strategies: strategyNames,
    metricsInterval: uc.options.metricsInterval,
    url: *parsedUrl,
    httpClient: uc.options.httpClient,
    customHeaders: uc.options.customHeaders,
    },
  2. If you pass in the value down to metrics, you get a nil pointer exception b/c the timer is never initialized.
    if m.options.disableMetrics {
    return
    }
    m.timer = time.NewTimer(m.options.metricsInterval)
  3. Setting metrics interval to 0 doesn't work either since it now blocks on other channels.
    if m.options.metricsInterval > 0 {
    m.startTimer()
    m.registerInstance()
    go m.sync()
    }
  4. Metrics is too tightly coupled to the client. Proper dependency inversion would help here.

The only true workaround that works right now is to set a listener, even if it's a noop.

Happy to help out in anyway I can. Thanks!

@jrbarron
Copy link
Collaborator

@yukw777 Thanks for the report. This deadlock issue is known and documented here (last sentence under "Basics"):

https://godoc.org/github.com/Unleash/unleash-client-go#hdr-Basics

It was a side effect of the API design where I wanted to force people to use a listener so as not to ignore errors which as you know is bad practice in Go. In retrospect, this may have been a mistake so I have contemplated changing it, perhaps in the v3 branch.

The additional bugs you mention there certainly sound valid so I'll try to take a look at those when I get some time. I will happily accept a PR for these too if you have something ready. I've also starting writing some unit tests for the metrics struct now so hopefully that will catch some of these issues.

Thanks for the great report!

jrbarron added a commit that referenced this issue Jan 24, 2019
This was previously broken because we were never passing down the
boolean value from the client to the metrics struct. Doing so implies
that the timer is never created so we can't read from the channel in the
sync function. If metrics are disabled, there is really no reason to
even run the metrics routine, so just return instead.

Fixes #15
@jrbarron
Copy link
Collaborator

The bugs relating to disabling metrics and also the deadlocks (except for the nil Listener which is documented) should now be fixed so I'm closing this.

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