github.com/containerd/Containerd@v1.4.13/runtime/v2/shim/publisher.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package shim 18 19 import ( 20 "context" 21 "sync" 22 "time" 23 24 v1 "github.com/containerd/containerd/api/services/ttrpc/events/v1" 25 "github.com/containerd/containerd/events" 26 "github.com/containerd/containerd/namespaces" 27 "github.com/containerd/containerd/pkg/ttrpcutil" 28 "github.com/containerd/ttrpc" 29 "github.com/containerd/typeurl" 30 "github.com/sirupsen/logrus" 31 ) 32 33 const ( 34 queueSize = 2048 35 maxRequeue = 5 36 ) 37 38 type item struct { 39 ev *v1.Envelope 40 ctx context.Context 41 count int 42 } 43 44 func NewPublisher(address string) (*RemoteEventsPublisher, error) { 45 client, err := ttrpcutil.NewClient(address) 46 if err != nil { 47 return nil, err 48 } 49 50 l := &RemoteEventsPublisher{ 51 client: client, 52 closed: make(chan struct{}), 53 requeue: make(chan *item, queueSize), 54 } 55 56 go l.processQueue() 57 return l, nil 58 } 59 60 type RemoteEventsPublisher struct { 61 client *ttrpcutil.Client 62 closed chan struct{} 63 closer sync.Once 64 requeue chan *item 65 } 66 67 func (l *RemoteEventsPublisher) Done() <-chan struct{} { 68 return l.closed 69 } 70 71 func (l *RemoteEventsPublisher) Close() (err error) { 72 err = l.client.Close() 73 l.closer.Do(func() { 74 close(l.closed) 75 }) 76 return err 77 } 78 79 func (l *RemoteEventsPublisher) processQueue() { 80 for i := range l.requeue { 81 if i.count > maxRequeue { 82 logrus.Errorf("evicting %s from queue because of retry count", i.ev.Topic) 83 // drop the event 84 continue 85 } 86 87 if err := l.forwardRequest(i.ctx, &v1.ForwardRequest{Envelope: i.ev}); err != nil { 88 logrus.WithError(err).Error("forward event") 89 l.queue(i) 90 } 91 } 92 } 93 94 func (l *RemoteEventsPublisher) queue(i *item) { 95 go func() { 96 i.count++ 97 // re-queue after a short delay 98 time.Sleep(time.Duration(1*i.count) * time.Second) 99 l.requeue <- i 100 }() 101 } 102 103 func (l *RemoteEventsPublisher) Publish(ctx context.Context, topic string, event events.Event) error { 104 ns, err := namespaces.NamespaceRequired(ctx) 105 if err != nil { 106 return err 107 } 108 any, err := typeurl.MarshalAny(event) 109 if err != nil { 110 return err 111 } 112 i := &item{ 113 ev: &v1.Envelope{ 114 Timestamp: time.Now(), 115 Namespace: ns, 116 Topic: topic, 117 Event: any, 118 }, 119 ctx: ctx, 120 } 121 122 if err := l.forwardRequest(i.ctx, &v1.ForwardRequest{Envelope: i.ev}); err != nil { 123 l.queue(i) 124 return err 125 } 126 127 return nil 128 } 129 130 func (l *RemoteEventsPublisher) forwardRequest(ctx context.Context, req *v1.ForwardRequest) error { 131 service, err := l.client.EventsService() 132 if err == nil { 133 fCtx, cancel := context.WithTimeout(ctx, 5*time.Second) 134 _, err = service.Forward(fCtx, req) 135 cancel() 136 if err == nil { 137 return nil 138 } 139 } 140 141 if err != ttrpc.ErrClosed { 142 return err 143 } 144 145 // Reconnect and retry request 146 if err = l.client.Reconnect(); err != nil { 147 return err 148 } 149 150 service, err = l.client.EventsService() 151 if err != nil { 152 return err 153 } 154 155 // try again with a fresh context, otherwise we may get a context timeout unexpectedly. 156 fCtx, cancel := context.WithTimeout(ctx, 5*time.Second) 157 _, err = service.Forward(fCtx, req) 158 cancel() 159 if err != nil { 160 return err 161 } 162 163 return nil 164 }