| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | package runstream | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | 	"math" | 
					
						
							|  |  |  | 	"sync" | 
					
						
							|  |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-30 16:57:50 +08:00
										 |  |  | 	"github.com/grafana/grafana-plugin-sdk-go/backend" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-13 12:11:35 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/apimachinery/identity" | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/infra/log" | 
					
						
							| 
									
										
										
										
											2023-09-25 18:10:47 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/plugins" | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var ( | 
					
						
							|  |  |  | 	logger = log.New("live.runstream") | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-24 16:07:09 +08:00
										 |  |  | //go:generate mockgen -destination=mock.go -package=runstream github.com/grafana/grafana/pkg/services/live/runstream ChannelLocalPublisher,NumLocalSubscribersGetter,StreamRunner,PluginContextGetter
 | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-24 16:07:09 +08:00
										 |  |  | type ChannelLocalPublisher interface { | 
					
						
							|  |  |  | 	PublishLocal(channel string, data []byte) error | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | type PluginContextGetter interface { | 
					
						
							| 
									
										
										
										
											2023-08-30 22:51:18 +08:00
										 |  |  | 	GetPluginContext(ctx context.Context, user identity.Requester, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, error) | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-24 16:07:09 +08:00
										 |  |  | type NumLocalSubscribersGetter interface { | 
					
						
							|  |  |  | 	// GetNumSubscribers returns number of channel subscribers throughout all nodes.
 | 
					
						
							|  |  |  | 	GetNumLocalSubscribers(channel string) (int, error) | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type StreamRunner interface { | 
					
						
							| 
									
										
										
										
											2021-05-26 01:29:02 +08:00
										 |  |  | 	RunStream(ctx context.Context, request *backend.RunStreamRequest, sender *backend.StreamSender) error | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-26 01:29:02 +08:00
										 |  |  | type packetSender struct { | 
					
						
							| 
									
										
										
										
											2021-06-24 16:07:09 +08:00
										 |  |  | 	channelLocalPublisher ChannelLocalPublisher | 
					
						
							|  |  |  | 	channel               string | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-26 01:29:02 +08:00
										 |  |  | func (p *packetSender) Send(packet *backend.StreamPacket) error { | 
					
						
							| 
									
										
										
										
											2021-06-24 16:07:09 +08:00
										 |  |  | 	return p.channelLocalPublisher.PublishLocal(p.channel, packet.Data) | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Manager manages streams from Grafana to plugins (i.e. RunStream method).
 | 
					
						
							|  |  |  | type Manager struct { | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 	mu                      sync.RWMutex | 
					
						
							|  |  |  | 	baseCtx                 context.Context | 
					
						
							|  |  |  | 	streams                 map[string]streamContext | 
					
						
							|  |  |  | 	datasourceStreams       map[string]map[string]struct{} | 
					
						
							| 
									
										
										
										
											2021-06-24 16:07:09 +08:00
										 |  |  | 	presenceGetter          NumLocalSubscribersGetter | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 	pluginContextGetter     PluginContextGetter | 
					
						
							| 
									
										
										
										
											2021-06-24 16:07:09 +08:00
										 |  |  | 	channelSender           ChannelLocalPublisher | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 	registerCh              chan submitRequest | 
					
						
							|  |  |  | 	closedCh                chan struct{} | 
					
						
							|  |  |  | 	checkInterval           time.Duration | 
					
						
							|  |  |  | 	maxChecks               int | 
					
						
							|  |  |  | 	datasourceCheckInterval time.Duration | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ManagerOption modifies Manager behavior (used for tests for example).
 | 
					
						
							|  |  |  | type ManagerOption func(*Manager) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // WithCheckConfig allows setting custom check rules.
 | 
					
						
							|  |  |  | func WithCheckConfig(interval time.Duration, maxChecks int) ManagerOption { | 
					
						
							|  |  |  | 	return func(sm *Manager) { | 
					
						
							|  |  |  | 		sm.checkInterval = interval | 
					
						
							|  |  |  | 		sm.maxChecks = maxChecks | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const ( | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 	defaultCheckInterval           = 5 * time.Second | 
					
						
							| 
									
										
										
										
											2022-11-22 15:09:15 +08:00
										 |  |  | 	defaultDatasourceCheckInterval = time.Minute | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 	defaultMaxChecks               = 3 | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NewManager creates new Manager.
 | 
					
						
							| 
									
										
										
										
											2021-06-24 16:07:09 +08:00
										 |  |  | func NewManager(channelSender ChannelLocalPublisher, presenceGetter NumLocalSubscribersGetter, pluginContextGetter PluginContextGetter, opts ...ManagerOption) *Manager { | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | 	sm := &Manager{ | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 		streams:                 make(map[string]streamContext), | 
					
						
							|  |  |  | 		datasourceStreams:       map[string]map[string]struct{}{}, | 
					
						
							| 
									
										
										
										
											2021-05-26 01:29:02 +08:00
										 |  |  | 		channelSender:           channelSender, | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 		presenceGetter:          presenceGetter, | 
					
						
							|  |  |  | 		pluginContextGetter:     pluginContextGetter, | 
					
						
							|  |  |  | 		registerCh:              make(chan submitRequest), | 
					
						
							|  |  |  | 		closedCh:                make(chan struct{}), | 
					
						
							|  |  |  | 		checkInterval:           defaultCheckInterval, | 
					
						
							|  |  |  | 		maxChecks:               defaultMaxChecks, | 
					
						
							|  |  |  | 		datasourceCheckInterval: defaultDatasourceCheckInterval, | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	for _, opt := range opts { | 
					
						
							|  |  |  | 		opt(sm) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return sm | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | func (s *Manager) HandleDatasourceDelete(orgID int64, dsUID string) error { | 
					
						
							|  |  |  | 	return s.handleDatasourceEvent(orgID, dsUID, false) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *Manager) HandleDatasourceUpdate(orgID int64, dsUID string) error { | 
					
						
							|  |  |  | 	return s.handleDatasourceEvent(orgID, dsUID, true) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *Manager) handleDatasourceEvent(orgID int64, dsUID string, resubmit bool) error { | 
					
						
							|  |  |  | 	dsKey := datasourceKey(orgID, dsUID) | 
					
						
							|  |  |  | 	s.mu.RLock() | 
					
						
							|  |  |  | 	dsStreams, ok := s.datasourceStreams[dsKey] | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		s.mu.RUnlock() | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-06-15 02:16:36 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	resubmitRequests := make([]streamRequest, 0, len(dsStreams)) | 
					
						
							|  |  |  | 	waitChannels := make([]chan struct{}, 0, len(dsStreams)) | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 	for channel := range dsStreams { | 
					
						
							|  |  |  | 		streamCtx, ok := s.streams[channel] | 
					
						
							|  |  |  | 		if !ok { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-06-15 02:16:36 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 		streamCtx.cancelFn() | 
					
						
							| 
									
										
										
										
											2024-06-15 02:16:36 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 		waitChannels = append(waitChannels, streamCtx.CloseCh) | 
					
						
							|  |  |  | 		resubmitRequests = append(resubmitRequests, streamCtx.streamRequest) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-06-15 02:16:36 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 	s.mu.RUnlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Wait for all streams to stop.
 | 
					
						
							|  |  |  | 	for _, ch := range waitChannels { | 
					
						
							|  |  |  | 		<-ch | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if resubmit { | 
					
						
							|  |  |  | 		// Re-submit streams.
 | 
					
						
							|  |  |  | 		for _, sr := range resubmitRequests { | 
					
						
							| 
									
										
										
										
											2021-12-15 01:12:00 +08:00
										 |  |  | 			_, err := s.SubmitStream(s.baseCtx, sr.user, sr.Channel, sr.Path, sr.Data, sr.PluginContext, sr.StreamRunner, true) | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				// Log error but do not prevent execution of caller routine.
 | 
					
						
							|  |  |  | 				logger.Error("Error re-submitting stream", "path", sr.Path, "error", err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func datasourceKey(orgID int64, dsUID string) string { | 
					
						
							|  |  |  | 	return fmt.Sprintf("%d_%s", orgID, dsUID) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | func (s *Manager) stopStream(sr streamRequest, cancelFn func()) { | 
					
						
							|  |  |  | 	s.mu.Lock() | 
					
						
							|  |  |  | 	defer s.mu.Unlock() | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 	streamCtx, ok := s.streams[sr.Channel] | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | 	if !ok { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 	closeCh := streamCtx.CloseCh | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | 	delete(s.streams, sr.Channel) | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 	if sr.PluginContext.DataSourceInstanceSettings != nil { | 
					
						
							|  |  |  | 		dsUID := sr.PluginContext.DataSourceInstanceSettings.UID | 
					
						
							|  |  |  | 		dsKey := datasourceKey(sr.PluginContext.OrgID, dsUID) | 
					
						
							|  |  |  | 		delete(s.datasourceStreams[dsKey], sr.Channel) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | 	cancelFn() | 
					
						
							|  |  |  | 	close(closeCh) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *Manager) watchStream(ctx context.Context, cancelFn func(), sr streamRequest) { | 
					
						
							|  |  |  | 	numNoSubscribersChecks := 0 | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 	presenceTicker := time.NewTicker(s.checkInterval) | 
					
						
							|  |  |  | 	defer presenceTicker.Stop() | 
					
						
							|  |  |  | 	datasourceTicker := time.NewTicker(s.datasourceCheckInterval) | 
					
						
							|  |  |  | 	defer datasourceTicker.Stop() | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | 	for { | 
					
						
							|  |  |  | 		select { | 
					
						
							|  |  |  | 		case <-ctx.Done(): | 
					
						
							|  |  |  | 			return | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 		case <-datasourceTicker.C: | 
					
						
							|  |  |  | 			if sr.PluginContext.DataSourceInstanceSettings != nil { | 
					
						
							|  |  |  | 				dsUID := sr.PluginContext.DataSourceInstanceSettings.UID | 
					
						
							| 
									
										
										
										
											2023-06-08 19:59:51 +08:00
										 |  |  | 				pCtx, err := s.pluginContextGetter.GetPluginContext(ctx, sr.user, sr.PluginContext.PluginID, dsUID, false) | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 				if err != nil { | 
					
						
							| 
									
										
										
										
											2023-09-25 18:10:47 +08:00
										 |  |  | 					if errors.Is(err, plugins.ErrPluginNotRegistered) { | 
					
						
							| 
									
										
										
										
											2023-06-08 19:59:51 +08:00
										 |  |  | 						logger.Debug("Datasource not found, stop stream", "channel", sr.Channel, "path", sr.Path) | 
					
						
							|  |  |  | 						return | 
					
						
							|  |  |  | 					} | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 					logger.Error("Error getting datasource context", "channel", sr.Channel, "path", sr.Path, "error", err) | 
					
						
							|  |  |  | 					continue | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				if pCtx.DataSourceInstanceSettings.Updated != sr.PluginContext.DataSourceInstanceSettings.Updated { | 
					
						
							|  |  |  | 					logger.Debug("Datasource changed, re-establish stream", "channel", sr.Channel, "path", sr.Path) | 
					
						
							|  |  |  | 					err := s.HandleDatasourceUpdate(pCtx.OrgID, dsUID) | 
					
						
							|  |  |  | 					if err != nil { | 
					
						
							|  |  |  | 						logger.Error("Error re-establishing stream", "channel", sr.Channel, "path", sr.Path, "error", err) | 
					
						
							|  |  |  | 						continue | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					return | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		case <-presenceTicker.C: | 
					
						
							| 
									
										
										
										
											2021-06-24 16:07:09 +08:00
										 |  |  | 			numSubscribers, err := s.presenceGetter.GetNumLocalSubscribers(sr.Channel) | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2021-06-24 16:07:09 +08:00
										 |  |  | 				logger.Error("Error checking num subscribers", "channel", sr.Channel, "path", sr.Path, "error", err) | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if numSubscribers > 0 { | 
					
						
							|  |  |  | 				// reset counter since channel has active subscribers.
 | 
					
						
							|  |  |  | 				numNoSubscribersChecks = 0 | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			numNoSubscribersChecks++ | 
					
						
							|  |  |  | 			if numNoSubscribersChecks >= s.maxChecks { | 
					
						
							|  |  |  | 				logger.Debug("Stop stream since no active subscribers", "channel", sr.Channel, "path", sr.Path) | 
					
						
							|  |  |  | 				s.stopStream(sr, cancelFn) | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const streamDurationThreshold = 100 * time.Millisecond | 
					
						
							|  |  |  | const coolDownDelay = 100 * time.Millisecond | 
					
						
							|  |  |  | const maxDelay = 5 * time.Second | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func getDelay(numErrors int) time.Duration { | 
					
						
							|  |  |  | 	if numErrors == 0 { | 
					
						
							|  |  |  | 		return 0 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	delay := coolDownDelay * time.Duration(math.Pow(2, float64(numErrors))) | 
					
						
							|  |  |  | 	if delay > maxDelay { | 
					
						
							|  |  |  | 		return maxDelay | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return delay | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // run stream until context canceled or stream finished without an error.
 | 
					
						
							|  |  |  | func (s *Manager) runStream(ctx context.Context, cancelFn func(), sr streamRequest) { | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 	defer func() { s.stopStream(sr, cancelFn) }() | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | 	var numFastErrors int | 
					
						
							|  |  |  | 	var delay time.Duration | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 	var isReconnect bool | 
					
						
							|  |  |  | 	startTime := time.Now() | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | 	for { | 
					
						
							|  |  |  | 		select { | 
					
						
							|  |  |  | 		case <-ctx.Done(): | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		pluginCtx := sr.PluginContext | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if isReconnect { | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | 			// Best effort to cool down re-establishment process. We don't have a
 | 
					
						
							|  |  |  | 			// nice way to understand whether we really need to wait here - so relying
 | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 			// on duration time of running a stream.
 | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | 			if time.Since(startTime) < streamDurationThreshold { | 
					
						
							|  |  |  | 				if delay < maxDelay { | 
					
						
							|  |  |  | 					// Due to not calling getDelay after we have delay larger than maxDelay
 | 
					
						
							|  |  |  | 					// we avoid possible float overflow errors while calculating delay duration
 | 
					
						
							|  |  |  | 					// based on numFastErrors.
 | 
					
						
							|  |  |  | 					delay = getDelay(numFastErrors) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				numFastErrors++ | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				// Assuming that stream successfully started.
 | 
					
						
							|  |  |  | 				delay = 0 | 
					
						
							|  |  |  | 				numFastErrors = 0 | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 			select { | 
					
						
							|  |  |  | 			case <-ctx.Done(): | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			case <-time.After(delay): | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			startTime = time.Now() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// Resolve new plugin context as it could be modified since last call.
 | 
					
						
							|  |  |  | 			// We are using the same user here which initiated stream originally.
 | 
					
						
							|  |  |  | 			var datasourceUID string | 
					
						
							|  |  |  | 			if pluginCtx.DataSourceInstanceSettings != nil { | 
					
						
							|  |  |  | 				datasourceUID = pluginCtx.DataSourceInstanceSettings.UID | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2023-06-08 19:59:51 +08:00
										 |  |  | 			newPluginCtx, err := s.pluginContextGetter.GetPluginContext(ctx, sr.user, pluginCtx.PluginID, datasourceUID, false) | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2023-09-25 18:10:47 +08:00
										 |  |  | 				if errors.Is(err, plugins.ErrPluginNotRegistered) { | 
					
						
							| 
									
										
										
										
											2023-06-08 19:59:51 +08:00
										 |  |  | 					logger.Info("No plugin context found, stopping stream", "path", sr.Path) | 
					
						
							|  |  |  | 					return | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 				logger.Error("Error getting plugin context", "path", sr.Path, "error", err) | 
					
						
							|  |  |  | 				isReconnect = true | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			pluginCtx = newPluginCtx | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		err := sr.StreamRunner.RunStream( | 
					
						
							|  |  |  | 			ctx, | 
					
						
							|  |  |  | 			&backend.RunStreamRequest{ | 
					
						
							|  |  |  | 				PluginContext: pluginCtx, | 
					
						
							|  |  |  | 				Path:          sr.Path, | 
					
						
							| 
									
										
										
										
											2021-12-15 01:12:00 +08:00
										 |  |  | 				Data:          sr.Data, | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 			}, | 
					
						
							| 
									
										
										
										
											2021-06-24 16:07:09 +08:00
										 |  |  | 			backend.NewStreamSender(&packetSender{channelLocalPublisher: s.channelSender, channel: sr.Channel}), | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 		) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			if errors.Is(ctx.Err(), context.Canceled) { | 
					
						
							|  |  |  | 				logger.Debug("Stream cleanly finished", "path", sr.Path) | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | 			logger.Error("Error running stream, re-establishing", "path", sr.Path, "error", err, "wait", delay) | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 			isReconnect = true | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		logger.Debug("Stream finished without error, stopping it", "path", sr.Path) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var errClosed = errors.New("stream manager closed") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | type streamContext struct { | 
					
						
							|  |  |  | 	CloseCh       chan struct{} | 
					
						
							|  |  |  | 	cancelFn      func() | 
					
						
							|  |  |  | 	streamRequest streamRequest | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | func (s *Manager) registerStream(ctx context.Context, sr submitRequest) { | 
					
						
							|  |  |  | 	s.mu.Lock() | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 	if streamCtx, ok := s.streams[sr.streamRequest.Channel]; ok { | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | 		s.mu.Unlock() | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 		sr.responseCh <- submitResponse{Result: submitResult{StreamExists: true, CloseNotify: streamCtx.CloseCh}} | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	ctx, cancel := context.WithCancel(ctx) | 
					
						
							|  |  |  | 	defer cancel() | 
					
						
							|  |  |  | 	closeCh := make(chan struct{}) | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 	s.streams[sr.streamRequest.Channel] = streamContext{ | 
					
						
							|  |  |  | 		CloseCh:       closeCh, | 
					
						
							|  |  |  | 		cancelFn:      cancel, | 
					
						
							|  |  |  | 		streamRequest: sr.streamRequest, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if sr.streamRequest.PluginContext.DataSourceInstanceSettings != nil { | 
					
						
							|  |  |  | 		dsUID := sr.streamRequest.PluginContext.DataSourceInstanceSettings.UID | 
					
						
							|  |  |  | 		dsKey := datasourceKey(sr.streamRequest.PluginContext.OrgID, dsUID) | 
					
						
							|  |  |  | 		if _, ok := s.datasourceStreams[dsKey]; !ok { | 
					
						
							|  |  |  | 			s.datasourceStreams[dsKey] = map[string]struct{}{} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		s.datasourceStreams[dsKey][sr.streamRequest.Channel] = struct{}{} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | 	s.mu.Unlock() | 
					
						
							|  |  |  | 	sr.responseCh <- submitResponse{Result: submitResult{StreamExists: false, CloseNotify: closeCh}} | 
					
						
							|  |  |  | 	go s.watchStream(ctx, cancel, sr.streamRequest) | 
					
						
							|  |  |  | 	s.runStream(ctx, cancel, sr.streamRequest) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Run Manager till context canceled.
 | 
					
						
							|  |  |  | func (s *Manager) Run(ctx context.Context) error { | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 	s.baseCtx = ctx | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | 	for { | 
					
						
							|  |  |  | 		select { | 
					
						
							|  |  |  | 		case sr := <-s.registerCh: | 
					
						
							|  |  |  | 			go s.registerStream(ctx, sr) | 
					
						
							|  |  |  | 		case <-ctx.Done(): | 
					
						
							|  |  |  | 			close(s.closedCh) | 
					
						
							|  |  |  | 			return ctx.Err() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type streamRequest struct { | 
					
						
							|  |  |  | 	Channel       string | 
					
						
							|  |  |  | 	Path          string | 
					
						
							| 
									
										
										
										
											2023-08-30 22:51:18 +08:00
										 |  |  | 	user          identity.Requester | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | 	PluginContext backend.PluginContext | 
					
						
							|  |  |  | 	StreamRunner  StreamRunner | 
					
						
							| 
									
										
										
										
											2021-12-15 01:12:00 +08:00
										 |  |  | 	Data          []byte | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type submitRequest struct { | 
					
						
							|  |  |  | 	responseCh    chan submitResponse | 
					
						
							|  |  |  | 	streamRequest streamRequest | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type submitResult struct { | 
					
						
							|  |  |  | 	// StreamExists tells whether stream have been already opened.
 | 
					
						
							|  |  |  | 	StreamExists bool | 
					
						
							|  |  |  | 	// CloseNotify will be closed as soon as stream cleanly exited.
 | 
					
						
							|  |  |  | 	CloseNotify chan struct{} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type submitResponse struct { | 
					
						
							|  |  |  | 	Error  error | 
					
						
							|  |  |  | 	Result submitResult | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | var errDatasourceNotFound = errors.New("datasource not found") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | // SubmitStream submits stream handler in Manager to manage.
 | 
					
						
							|  |  |  | // The stream will be opened and kept till channel has active subscribers.
 | 
					
						
							| 
									
										
										
										
											2023-08-30 22:51:18 +08:00
										 |  |  | func (s *Manager) SubmitStream(ctx context.Context, user identity.Requester, channel string, path string, data []byte, pCtx backend.PluginContext, streamRunner StreamRunner, isResubmit bool) (*submitResult, error) { | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 	if isResubmit { | 
					
						
							|  |  |  | 		// Resolve new plugin context as it could be modified since last call.
 | 
					
						
							|  |  |  | 		var datasourceUID string | 
					
						
							|  |  |  | 		if pCtx.DataSourceInstanceSettings != nil { | 
					
						
							|  |  |  | 			datasourceUID = pCtx.DataSourceInstanceSettings.UID | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-06-08 19:59:51 +08:00
										 |  |  | 		newPluginCtx, err := s.pluginContextGetter.GetPluginContext(ctx, user, pCtx.PluginID, datasourceUID, false) | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2023-09-25 18:10:47 +08:00
										 |  |  | 			if errors.Is(err, plugins.ErrPluginNotRegistered) { | 
					
						
							| 
									
										
										
										
											2023-06-08 19:59:51 +08:00
										 |  |  | 				return nil, errDatasourceNotFound | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		pCtx = newPluginCtx | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | 	req := submitRequest{ | 
					
						
							|  |  |  | 		responseCh: make(chan submitResponse, 1), | 
					
						
							|  |  |  | 		streamRequest: streamRequest{ | 
					
						
							| 
									
										
										
										
											2021-05-19 02:39:56 +08:00
										 |  |  | 			user:          user, | 
					
						
							|  |  |  | 			Channel:       channel, | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | 			Path:          path, | 
					
						
							|  |  |  | 			PluginContext: pCtx, | 
					
						
							|  |  |  | 			StreamRunner:  streamRunner, | 
					
						
							| 
									
										
										
										
											2021-12-15 01:12:00 +08:00
										 |  |  | 			Data:          data, | 
					
						
							| 
									
										
										
										
											2021-04-17 06:17:08 +08:00
										 |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Send submit request.
 | 
					
						
							|  |  |  | 	select { | 
					
						
							|  |  |  | 	case s.registerCh <- req: | 
					
						
							|  |  |  | 	case <-s.closedCh: | 
					
						
							|  |  |  | 		close(s.registerCh) | 
					
						
							|  |  |  | 		return nil, errClosed | 
					
						
							|  |  |  | 	case <-ctx.Done(): | 
					
						
							|  |  |  | 		return nil, ctx.Err() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Wait for submit response.
 | 
					
						
							|  |  |  | 	select { | 
					
						
							|  |  |  | 	case resp := <-req.responseCh: | 
					
						
							|  |  |  | 		if resp.Error != nil { | 
					
						
							|  |  |  | 			return nil, resp.Error | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return &resp.Result, nil | 
					
						
							|  |  |  | 	case <-s.closedCh: | 
					
						
							|  |  |  | 		return nil, errClosed | 
					
						
							|  |  |  | 	case <-ctx.Done(): | 
					
						
							|  |  |  | 		return nil, ctx.Err() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } |