github.com/voedger/voedger@v0.0.0-20240520144910-273e84102129/pkg/in10nmem/v1/impl.go (about) 1 /* 2 * Copyright (c) 2021-present Sigma-Soft, Ltd. 3 * Aleksei Ponomarev 4 */ 5 6 package in10nmemv1 7 8 import ( 9 "context" 10 "fmt" 11 "sync" 12 "time" 13 14 "github.com/google/uuid" 15 "github.com/voedger/voedger/pkg/in10n" 16 istructs "github.com/voedger/voedger/pkg/istructs" 17 ) 18 19 type N10nBroker struct { 20 projections map[in10n.ProjectionKey]*istructs.Offset 21 channels map[in10n.ChannelID]*channelType 22 quotas in10n.Quotas 23 metricBySubject map[istructs.SubjectLogin]*metricType 24 numSubscriptions int 25 now func() time.Time 26 sync.RWMutex 27 } 28 29 type projectionOffsets struct { 30 deliveredOffset istructs.Offset 31 currentOffset *istructs.Offset 32 } 33 34 type channelType struct { 35 subject istructs.SubjectLogin 36 subscriptions map[in10n.ProjectionKey]*projectionOffsets 37 channelDuration time.Duration 38 createTime time.Time 39 } 40 41 type metricType struct { 42 numChannels int 43 numSubscriptions int 44 } 45 46 // NewChannel @ConcurrentAccess 47 // Create new channel. 48 // On timeout channel will be closed. channelDuration determines time during with it will be open. 49 func (nb *N10nBroker) NewChannel(subject istructs.SubjectLogin, channelDuration time.Duration) (channelID in10n.ChannelID, err error) { 50 nb.Lock() 51 defer nb.Unlock() 52 var metric *metricType 53 if len(nb.channels) >= nb.quotas.Channels { 54 return "", in10n.ErrQuotaExceeded_Channels 55 } 56 metric = nb.metricBySubject[subject] 57 if metric != nil { 58 if metric.numChannels >= nb.quotas.ChannelsPerSubject { 59 return "", in10n.ErrQuotaExceeded_ChannelsPerSubject 60 } 61 } else { 62 metric = new(metricType) 63 nb.metricBySubject[subject] = metric 64 } 65 metric.numChannels++ 66 channelID = in10n.ChannelID(uuid.New().String()) 67 channel := channelType{ 68 subject: subject, 69 subscriptions: make(map[in10n.ProjectionKey]*projectionOffsets), 70 channelDuration: channelDuration, 71 createTime: nb.now(), 72 } 73 nb.channels[channelID] = &channel 74 return channelID, err 75 } 76 77 // Subscribe @ConcurrentAccess 78 // Subscribe to the channel for the projection. If channel does not exist: will return error ErrChannelNotExists 79 func (nb *N10nBroker) Subscribe(channelID in10n.ChannelID, projectionKey in10n.ProjectionKey) (err error) { 80 nb.Lock() 81 defer nb.Unlock() 82 channel, channelOK := nb.channels[channelID] 83 if !channelOK { 84 return in10n.ErrChannelDoesNotExist 85 } 86 87 metric, metricOK := nb.metricBySubject[channel.subject] 88 if !metricOK { 89 return ErrMetricDoesNotExists 90 } 91 92 if nb.numSubscriptions >= nb.quotas.Subscriptions { 93 return in10n.ErrQuotaExceeded_Subsciptions 94 } 95 if metric.numSubscriptions >= nb.quotas.SubscriptionsPerSubject { 96 return in10n.ErrQuotaExceeded_SubsciptionsPerSubject 97 } 98 99 subscription := projectionOffsets{ 100 deliveredOffset: istructs.Offset(0), 101 currentOffset: guaranteeOffsetPointer(nb.projections, projectionKey), 102 } 103 channel.subscriptions[projectionKey] = &subscription 104 metric.numSubscriptions++ 105 nb.numSubscriptions++ 106 return err 107 } 108 109 func (nb *N10nBroker) Unsubscribe(channelID in10n.ChannelID, projection in10n.ProjectionKey) (err error) { 110 nb.Lock() 111 defer nb.Unlock() 112 113 channel, cOK := nb.channels[channelID] 114 if !cOK { 115 return in10n.ErrChannelDoesNotExist 116 } 117 metric, mOK := nb.metricBySubject[channel.subject] 118 if !mOK { 119 return ErrMetricDoesNotExists 120 } 121 delete(channel.subscriptions, projection) 122 metric.numSubscriptions-- 123 nb.numSubscriptions-- 124 return err 125 } 126 127 // WatchChannel @ConcurrentAccess 128 // Create WatchChannel for notify clients about changed projections. If channel for this demand does not exist or 129 // channel already watched - exit. 130 func (nb *N10nBroker) WatchChannel(ctx context.Context, channelID in10n.ChannelID, notifySubscriber func(projection in10n.ProjectionKey, offset istructs.Offset)) { 131 // check that the channelID with the given ChannelID exists 132 channel, metric := func() (*channelType, *metricType) { 133 nb.RLock() 134 defer nb.RUnlock() 135 channel, channelOK := nb.channels[channelID] 136 if !channelOK { 137 panic(fmt.Errorf("channel with channelID: %s must exists %w", channelID, in10n.ErrChannelDoesNotExist)) 138 } 139 metric, metricOK := nb.metricBySubject[channel.subject] 140 if !metricOK { 141 panic(fmt.Errorf("metric for channel with channelID: %s must exists", channelID)) 142 } 143 return channel, metric 144 }() 145 146 defer func() { 147 nb.Lock() 148 metric.numChannels-- 149 metric.numSubscriptions -= len(channel.subscriptions) 150 nb.numSubscriptions -= len(channel.subscriptions) 151 delete(nb.channels, channelID) 152 nb.Unlock() 153 }() 154 155 ticker := time.NewTicker(pollingInterval) 156 defer ticker.Stop() 157 158 updateUnits := make([]UpdateUnit, 0) 159 for range ticker.C { 160 161 if ctx.Err() != nil { 162 return 163 } 164 165 err := nb.validateChannel(channel) 166 if err != nil { 167 return 168 } 169 170 // find projection for update and collect 171 nb.Lock() 172 for projection, channelOffsets := range channel.subscriptions { 173 if *channelOffsets.currentOffset > channelOffsets.deliveredOffset { 174 updateUnits = append(updateUnits, 175 UpdateUnit{ 176 Projection: projection, 177 Offset: *channelOffsets.currentOffset, 178 }) 179 channelOffsets.deliveredOffset = *channelOffsets.currentOffset 180 } 181 } 182 nb.Unlock() 183 for _, unit := range updateUnits { 184 notifySubscriber(unit.Projection, unit.Offset) 185 } 186 updateUnits = updateUnits[:0] 187 } 188 } 189 190 func guaranteeOffsetPointer(projections map[in10n.ProjectionKey]*istructs.Offset, projection in10n.ProjectionKey) (offsetPointer *istructs.Offset) { 191 offsetPointer = projections[projection] 192 if offsetPointer == nil { 193 offsetPointer = new(istructs.Offset) 194 projections[projection] = offsetPointer 195 } 196 return 197 } 198 199 // Update @ConcurrentAccess 200 // Update projections map with new offset 201 func (nb *N10nBroker) Update(projection in10n.ProjectionKey, offset istructs.Offset) { 202 nb.Lock() 203 defer nb.Unlock() 204 *guaranteeOffsetPointer(nb.projections, projection) = offset 205 } 206 207 // MetricNumChannels @ConcurrentAccess 208 // return channels count 209 func (nb *N10nBroker) MetricNumChannels() int { 210 nb.RLock() 211 defer nb.RUnlock() 212 return len(nb.channels) 213 } 214 215 func (nb *N10nBroker) MetricNumSubcriptions() int { 216 nb.RLock() 217 defer nb.RUnlock() 218 return nb.numSubscriptions 219 } 220 221 func (nb *N10nBroker) MetricSubject(ctx context.Context, cb func(subject istructs.SubjectLogin, numChannels int, numSubscriptions int)) { 222 postMetric := func(subject istructs.SubjectLogin, metric *metricType) (err error) { 223 nb.RLock() 224 defer nb.RUnlock() 225 cb(subject, metric.numChannels, metric.numSubscriptions) 226 return err 227 } 228 for subject, subjectMetric := range nb.metricBySubject { 229 if ctx.Err() != nil { 230 return 231 } 232 err := postMetric(subject, subjectMetric) 233 if err != nil { 234 return 235 } 236 } 237 } 238 239 func NewN10nBroker(quotas in10n.Quotas, now func() time.Time) *N10nBroker { 240 broker := N10nBroker{ 241 projections: make(map[in10n.ProjectionKey]*istructs.Offset), 242 channels: make(map[in10n.ChannelID]*channelType), 243 metricBySubject: make(map[istructs.SubjectLogin]*metricType), 244 quotas: quotas, 245 now: now, 246 } 247 return &broker 248 } 249 250 func (nb *N10nBroker) validateChannel(channel *channelType) error { 251 nb.RLock() 252 defer nb.RUnlock() 253 // if channel lifetime > channelDuration defined in NewChannel when create channel - must exit 254 if nb.Since(channel.createTime) > channel.channelDuration { 255 return ErrChannelExpired 256 } 257 return nil 258 } 259 260 func (nb *N10nBroker) Since(t time.Time) time.Duration { 261 return nb.now().Sub(t) 262 }