-
Notifications
You must be signed in to change notification settings - Fork 59
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
Cancel propagation #306
Cancel propagation #306
Conversation
Fix Consul Loader panic from openconfig#272
…ionList_ONCE. notification listener on a.c.Subscribe(sc.stream.Context(), ro) can't return on err as this could block other sender on the same channel. Sender closes the channel after sending the error and that would do. in case of stream cancelation, error are popagated to gnmi_server.go.Subscribe() for proper clean up.
Which other senders are you considering here ? |
in Cache we create goroutine per subscription and if we have more than one subscription then we are doing to have more that one cache side goroutine (senders) and anyone of them can stop the listener by sending error. I think better way to stop listener is to close the channel when needed and this is happening once func (gc *gnmiCache) subscribe(ctx context.Context, ro *ReadOpts, ch chan *Notification) {
defer close(ch)
switch ro.Mode {
case ReadMode_Once:
gc.handleSingleQuery(ctx, ro, ch)
case ReadMode_StreamOnChange: // default:
ro.SuppressRedundant = false
gc.handleOnChangeQuery(ctx, ro, ch)
case ReadMode_StreamSample:
gc.handleSampledQuery(ctx, ro, ch)
}
} |
Right but each subscription sends to a different channel. There is only one sender per channel, if it returns an error the channel will be closed right after. |
I think each subscription sends to same channel func (gc *gnmiCache) handleSingleQuery(ctx context.Context, ro *ReadOpts, ch chan *Notification) {
if gc.debug {
gc.logger.Printf("running single query for target %q", ro.Target)
}
caches := gc.getCaches(ro.Subscription)
if gc.debug {
gc.logger.Printf("single query got %d caches", len(caches))
}
wg := new(sync.WaitGroup)
wg.Add(len(caches))
for name, c := range caches {
go func(name string, c *subCache) {
defer wg.Done()
for _, p := range ro.Paths {
fp, err := path.CompletePath(p, nil)
if err != nil {
gc.logger.Printf("failed to generate CompletePath from %v", p)
ch <- &Notification{Name: name, Err: err}
return
}
err = c.c.Query(ro.Target, fp,
func(_ []string, l *ctree.Leaf, _ interface{}) error {
if err != nil {
return err
}
switch gl := l.Value().(type) {
case *gnmi.Notification:
if ro.OverrideTS {
// override timestamp
gl = proto.Clone(gl).(*gnmi.Notification)
gl.Timestamp = time.Now().UnixNano()
}
//no suppress redundant, send to channel and return
if !ro.SuppressRedundant {
ch <- &Notification{Name: name, Notification: gl}
return nil
}
// suppress redundant part
if ro.lastSent == nil {
ro.lastSent = make(map[string]*gnmi.TypedValue)
ro.m = new(sync.RWMutex)
}
prefix := utils.GnmiPathToXPath(gl.GetPrefix(), true)
target := gl.GetPrefix().GetTarget()
for _, upd := range gl.GetUpdate() {
path := utils.GnmiPathToXPath(upd.GetPath(), true)
valXPath := strings.Join([]string{target, prefix, path}, "/")
ro.m.RLock()
sv, ok := ro.lastSent[valXPath]
ro.m.RUnlock()
if !ok || !proto.Equal(sv, upd.Val) {
ch <- &Notification{
Name: name,
Notification: &gnmi.Notification{
Timestamp: gl.GetTimestamp(),
Prefix: gl.GetPrefix(),
Update: []*gnmi.Update{upd},
},
}
ro.m.Lock()
ro.lastSent[valXPath] = upd.Val
ro.m.Unlock()
}
}
if gl.GetDelete() != nil {
ch <- &Notification{
Name: name,
Notification: &gnmi.Notification{
Timestamp: gl.GetTimestamp(),
Prefix: gl.GetPrefix(),
Delete: gl.GetDelete(),
},
}
}
return nil
}
return nil
})
if err != nil {
gc.logger.Printf("target %q failed internal cache query: %v", ro.Target, err)
ch <- &Notification{Name: name, Err: err}
return
}
}
}(name, c)
}
wg.Wait()
} |
The subscribe we are talking about is here.
That is a single subscription that goes to multiple subcaches, in which case you are right multiple goroutines send to the same channel but that should not be solved there (return->continue). I think it should be solved within each cache implementation. |
So cleanup if any of the cache-goroutine hit the error? |
On a separate note, what is the use of |
yes |
It probably can replace the errChan in that function, I have to find some time to look into that part of the code again. |
PR got closed as i had to delete the branch. New PR: #32 |
Following three issues are addressed:
1-
errChan
ingnmi_server.go.Subscribe()
is only used for the caseSubscriptionList_ONCE
so moved it underSubscriptionList_ONCE
case2-
notification listener on a.c.Subscribe(sc.stream.Context(), ro) can't return on err
as this could block other senders on the same channel. Sender (subscription goroutine) closes the channel
just before returning (using
defer
) and that would do.3-
in case of stream cancelation, error are popagated to gnmi_server.go.Subscribe()
for proper clean up.