-
-
Notifications
You must be signed in to change notification settings - Fork 4k
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
lib/connections, lib/config: Bandwidth throttling per remote device (fixes #4516) #4603
Changes from 1 commit
bf78111
7ca0546
e486481
79f96e5
9b14e01
c1665b3
d175118
1070dd6
b52b326
ae58320
b6d723d
a33855f
8d637d6
5157154
cc63da2
36368f1
2ce47ad
f6a92d5
353983f
6967960
0298b9f
628ae2b
56b471f
da98cfe
a38f5ea
e160d1d
c3facc1
d03b6ff
291a198
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,10 +24,11 @@ type limiter struct { | |
write *rate.Limiter | ||
read *rate.Limiter | ||
limitsLAN atomicBool | ||
deviceReadLimiters *sync.Map | ||
deviceWriteLimiters *sync.Map | ||
deviceReadLimiters map[protocol.DeviceID]*rate.Limiter | ||
deviceWriteLimiters map[protocol.DeviceID]*rate.Limiter | ||
myID protocol.DeviceID | ||
mu *sync.Mutex | ||
deviceMapMutex *sync.RWMutex | ||
} | ||
|
||
const limiterBurstSize = 4 * 128 << 10 | ||
|
@@ -39,97 +40,167 @@ func newLimiter(deviceID protocol.DeviceID, cfg *config.Wrapper) *limiter { | |
read: rate.NewLimiter(rate.Inf, limiterBurstSize), | ||
myID: deviceID, | ||
mu: &sync.Mutex{}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adjust here to nothing at all (standard lib |
||
deviceReadLimiters: new(sync.Map), | ||
deviceWriteLimiters: new(sync.Map), | ||
deviceReadLimiters: make(map[protocol.DeviceID]*rate.Limiter), | ||
deviceWriteLimiters: make(map[protocol.DeviceID]*rate.Limiter), | ||
deviceMapMutex: &sync.RWMutex{}, | ||
} | ||
|
||
// Get initial device configuration | ||
devices := cfg.RawCopy().Devices | ||
for _, value := range devices { | ||
value.MaxRecvKbps = -1 | ||
value.MaxSendKbps = -1 | ||
|
||
// Keep read/write limiters for every connected device | ||
l.deviceWriteLimiters[value.DeviceID] = rate.NewLimiter(rate.Inf, limiterBurstSize) | ||
l.deviceReadLimiters[value.DeviceID] = rate.NewLimiter(rate.Inf, limiterBurstSize) | ||
} | ||
prev := config.Configuration{Options: config.OptionsConfiguration{MaxRecvKbps: -1, MaxSendKbps: -1}, Devices: devices} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe the entire above block isn't necessary, as CommitConfiguration will add all missing devices anyway, so https://github.com/syncthing/syncthing/pull/4603/files#diff-da45d4f40dfa16a9aae760dd650fe79aL35 should be sufficient. |
||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could just call rebuildMap here? |
||
// Keep read/write limiters for every connected device | ||
l.rebuildMap(prev) | ||
|
||
cfg.Subscribe(l) | ||
l.CommitConfiguration(prev, cfg.RawCopy()) | ||
return l | ||
} | ||
|
||
// Create new maps with new limiters | ||
func (lim *limiter) rebuildMap(to config.Configuration) { | ||
deviceWriteLimiters := new(sync.Map) | ||
deviceReadLimiters := new(sync.Map) | ||
// This function sets limiters according to corresponding DeviceConfiguration | ||
func (lim *limiter) setLimitsForDevice(v config.DeviceConfiguration, readLimiter, writeLimiter *rate.Limiter) { | ||
// The rate variables are in KiB/s in the config (despite the camel casing | ||
// of the name). We multiply by 1024 to get bytes/s. | ||
if v.MaxRecvKbps <= 0 { | ||
readLimiter.SetLimit(rate.Inf) | ||
} else { | ||
readLimiter.SetLimit(1024 * rate.Limit(v.MaxRecvKbps)) | ||
} | ||
|
||
if v.MaxSendKbps <= 0 { | ||
writeLimiter.SetLimit(rate.Inf) | ||
} else { | ||
writeLimiter.SetLimit(1024 * rate.Limit(v.MaxSendKbps)) | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now this lives on the lim struct, but doesn't actually use it, meaning it should either be a utility function not part of lim, or use lim and reach into lim.device{Write,Read}Limiters directly. |
||
|
||
// This function handles removing, adding and updating of device limiters. | ||
// Pass pointer to avoid copying. Pointer already points to copy of configuration | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not clear what the pointer comments are about. The configuration parameters are not pointers. |
||
// so we don't have to worry about modifying real config. | ||
func (lim *limiter) processDevicesConfiguration(from, to *config.Configuration) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't need to pass configs as pointers. |
||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Noop space. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. empty |
||
// copy *limiter in case remote device is still connected, when remote device is added we create new read/write limiters | ||
devicesToRemove := make(map[protocol.DeviceID]bool) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could be Or better would be:
|
||
// Get all devices that were previously in map, these are candidates for removal | ||
for _, v := range from.Devices { | ||
if v.DeviceID != to.MyID { | ||
devicesToRemove[v.DeviceID] = true | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could be |
||
|
||
// Mark devices which should not be removed, create new limiters if needed and assign new limiter rate | ||
for _, v := range to.Devices { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does "v" stand for? We usually use "dev". |
||
if readLimiter, ok := lim.deviceReadLimiters.Load(v.DeviceID); ok { | ||
deviceReadLimiters.Store(v.DeviceID, readLimiter) | ||
} else { | ||
deviceReadLimiters.Store(v.DeviceID, rate.NewLimiter(rate.Inf, limiterBurstSize)) | ||
if v.DeviceID == to.MyID { | ||
// This limiter was created for local device. Should skip this device | ||
continue | ||
} | ||
|
||
if writeLimiter, ok := lim.deviceWriteLimiters.Load(v.DeviceID); ok { | ||
deviceWriteLimiters.Store(v.DeviceID, writeLimiter) | ||
} else { | ||
deviceWriteLimiters.Store(v.DeviceID, rate.NewLimiter(rate.Inf, limiterBurstSize)) | ||
readLimiter, okR := lim.getDeviceReadLimiter(v.DeviceID) | ||
// Device has not been removed or added, this is no longer a candidate for removal | ||
if okR { | ||
devicesToRemove[v.DeviceID] = false | ||
} | ||
|
||
writeLimiter, okW := lim.getDeviceWriteLimiter(v.DeviceID) | ||
if okW { | ||
if devicesToRemove[v.DeviceID] { | ||
// Device has not been removed or added, we should've | ||
// already marked it as false in devicesToRemove | ||
panic("broken symmetry in device read/write limiters") | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is too complicated. The only way these things get set are under a lock, so it's very unlikely they get out of sync. I am thinking about a case where a new device gets added, and there is a race between CommitConfiguration here, and us making a connection. It's probably always better wrap every connection in a limiter (and create one if it doesn't exist) and then adjust the rates later. |
||
|
||
// There was no limiter for this ID in map. | ||
// This means that we added this device and we should create new limiter | ||
if !okR && !okW { | ||
readLimiter = rate.NewLimiter(rate.Inf, limiterBurstSize) | ||
writeLimiter = rate.NewLimiter(rate.Inf, limiterBurstSize) | ||
|
||
lim.setDeviceLimiters(v.DeviceID, writeLimiter, readLimiter) | ||
} else if !okR || !okW { | ||
// One of the read/write limiters is not present while the | ||
// corresponding write/read one exists. Something has gone wrong. | ||
panic("broken symmetry in device read/write limiters") | ||
} | ||
|
||
// limiters for this device are created so we can store previous rates for logging | ||
previousReadLimit := readLimiter.Limit() | ||
previousWriteLimit := writeLimiter.Limit() | ||
|
||
l.Debugf("okR: %t, okW: %t, prevWriteLim: %d, prevReadLim: %d", okR, okW, previousReadLimit, previousWriteLimit) | ||
|
||
lim.setLimitsForDevice(v, readLimiter, writeLimiter) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it not better not to do this if the limits are the same as previously? |
||
|
||
// Nothing about this device has changed. Start processing next device | ||
if okR && okW && | ||
readLimiter.Limit() == previousReadLimit && | ||
writeLimiter.Limit() == previousWriteLimit { | ||
continue | ||
} | ||
|
||
readLimitStr := "is unlimited" | ||
if v.MaxRecvKbps > 0 { | ||
readLimitStr = fmt.Sprintf("limit is %d KiB/s", v.MaxRecvKbps) | ||
} | ||
writeLimitStr := "is unlimited" | ||
if v.MaxSendKbps > 0 { | ||
writeLimitStr = fmt.Sprintf("limit is %d KiB/s", v.MaxSendKbps) | ||
} | ||
|
||
l.Infof("Device %s send rate %s, receive rate %s", v.DeviceID, readLimitStr, writeLimitStr) | ||
} | ||
|
||
// assign new maps | ||
lim.mu.Lock() | ||
defer lim.mu.Unlock() | ||
lim.deviceWriteLimiters = deviceWriteLimiters | ||
lim.deviceReadLimiters = deviceReadLimiters | ||
// Delete remote devices which were removed in new configuration | ||
for deviceID, shouldBeRemoved := range devicesToRemove { | ||
l.Debugf("deviceID: %s, shouldBeRemoved: %t", deviceID, shouldBeRemoved) | ||
if shouldBeRemoved { | ||
lim.deleteDeviceLimiters(deviceID) | ||
} | ||
} | ||
|
||
l.Debugln("Rebuild of device limiters map finished") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wouldn't it be better for rebuild to happen in place, this way getting rid of the lock that we have to get on every read and write. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a lie, as I don't think we're rebuilding stuff here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can imagine stuff getting out of sync a bit if two saves happen in parallel, but to be honest, I am not worried about a case like that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's why I added mutex at the beginning of CommitConfiguration call |
||
} | ||
|
||
// Compare read/write limits in configurations | ||
func (lim *limiter) checkDeviceLimits(from, to config.Configuration) bool { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, doesn't need to live on lim. |
||
if len(from.Devices) != len(to.Devices) { | ||
return false | ||
} | ||
// len(from.Devices) == len(to.Devices) so we can do range from.Devices | ||
for i := range from.Devices { | ||
if from.Devices[i].DeviceID != to.Devices[i].DeviceID { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should blow up if a device is removed, as len(to) < len(from). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should probably write a test case for this logic. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a nit-pick, but we don't really need to rely on the device order here. It's possible that rates aren't changed, just the order gets changed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There should be some sort of cfg.DeviceMap() or something like that utility function. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that should be moved from model to config: https://github.com/syncthing/syncthing/blob/master/lib/model/model.go#L2569 |
||
// Something has changed in device configuration | ||
lim.rebuildMap(to) | ||
return false | ||
} | ||
// Read/write limits were changed for this device | ||
if from.Devices[i].MaxSendKbps != to.Devices[i].MaxSendKbps || from.Devices[i].MaxRecvKbps != to.Devices[i].MaxRecvKbps { | ||
if from.Devices[i].MaxSendKbps != to.Devices[i].MaxSendKbps || | ||
from.Devices[i].MaxRecvKbps != to.Devices[i].MaxRecvKbps { | ||
return false | ||
} | ||
} | ||
return true | ||
} | ||
|
||
func (lim *limiter) newReadLimiter(remoteID protocol.DeviceID, r io.Reader, isLAN bool) io.Reader { | ||
return &limitedReader{reader: r, limiter: lim, isLAN: isLAN, remoteID: remoteID} | ||
} | ||
|
||
func (lim *limiter) newWriteLimiter(remoteID protocol.DeviceID, w io.Writer, isLAN bool) io.Writer { | ||
return &limitedWriter{writer: w, limiter: lim, isLAN: isLAN, remoteID: remoteID} | ||
} | ||
|
||
func (lim *limiter) VerifyConfiguration(from, to config.Configuration) error { | ||
return nil | ||
} | ||
|
||
func (lim *limiter) CommitConfiguration(from, to config.Configuration) bool { | ||
if len(from.Devices) == len(to.Devices) && | ||
from.Options.MaxRecvKbps == to.Options.MaxRecvKbps && | ||
// to ensure atomic update of configuration | ||
lim.mu.Lock() | ||
defer lim.mu.Unlock() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this necessary? The map mutex should be protection enough, right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess when many routines try to save configs at the same time it could cause some trouble when handling device limiters. I.e. first we remove device X then add the same device X and then update of device X rates. Routines could get scheduled so that remove stops right before the loop where deleting is done. Then add would process the device and next config would update it's rates. After removing is resumed we're left with no limiter for X despite it was set it in the most recent config. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right. To me it seems redundant to have two locks protecting the limiter. Couldn't you just have a single lock that you acquire during |
||
|
||
if from.Options.MaxRecvKbps == to.Options.MaxRecvKbps && | ||
from.Options.MaxSendKbps == to.Options.MaxSendKbps && | ||
from.Options.LimitBandwidthInLan == to.Options.LimitBandwidthInLan && | ||
lim.checkDeviceLimits(from, to) { | ||
return true | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this global check is not very useful as global limits are reset even if just a device changed and whether device change is checked again later on. So I would either not check at all and just reset everything or check the different parameters separately. |
||
|
||
// A device has been added or removed | ||
if len(from.Devices) != len(to.Devices) { | ||
lim.rebuildMap(to) | ||
} | ||
|
||
// The rate variables are in KiB/s in the config (despite the camel casing | ||
// of the name). We multiply by 1024 to get bytes/s. | ||
if to.Options.MaxRecvKbps <= 0 { | ||
|
@@ -146,38 +217,8 @@ func (lim *limiter) CommitConfiguration(from, to config.Configuration) bool { | |
|
||
lim.limitsLAN.set(to.Options.LimitBandwidthInLan) | ||
|
||
// Set limits for devices | ||
for _, v := range to.Devices { | ||
if v.DeviceID == lim.myID { | ||
// This limiter was created for local device. Should skip this device | ||
continue | ||
} | ||
|
||
readLimiter, _ := lim.deviceReadLimiters.Load(v.DeviceID) | ||
if v.MaxRecvKbps <= 0 { | ||
readLimiter.(*rate.Limiter).SetLimit(rate.Inf) | ||
} else { | ||
readLimiter.(*rate.Limiter).SetLimit(1024 * rate.Limit(v.MaxRecvKbps)) | ||
} | ||
|
||
writeLimiter, _ := lim.deviceWriteLimiters.Load(v.DeviceID) | ||
if v.MaxSendKbps <= 0 { | ||
writeLimiter.(*rate.Limiter).SetLimit(rate.Inf) | ||
} else { | ||
writeLimiter.(*rate.Limiter).SetLimit(1024 * rate.Limit(v.MaxSendKbps)) | ||
} | ||
|
||
sendLimitStr := "is unlimited" | ||
recvLimitStr := "is unlimited" | ||
if v.MaxSendKbps > 0 { | ||
sendLimitStr = fmt.Sprintf("limit is %d KiB/s", v.MaxSendKbps) | ||
} | ||
|
||
if v.MaxRecvKbps > 0 { | ||
recvLimitStr = fmt.Sprintf("limit is %d KiB/s", v.MaxRecvKbps) | ||
} | ||
l.Infof("Device %s: send rate %s, receive rate %s", v.DeviceID, sendLimitStr, recvLimitStr) | ||
} | ||
// Delete, add or update limiters for devices | ||
lim.processDevicesConfiguration(&from, &to) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this a pointer? |
||
|
||
sendLimitStr := "is unlimited" | ||
recvLimitStr := "is unlimited" | ||
|
@@ -203,6 +244,14 @@ func (lim *limiter) String() string { | |
return "connections.limiter" | ||
} | ||
|
||
func (lim *limiter) newReadLimiter(remoteID protocol.DeviceID, r io.Reader, isLAN bool) io.Reader { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While you're changing this: Wouldn't There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree |
||
return &limitedReader{reader: r, limiter: lim, isLAN: isLAN, remoteID: remoteID} | ||
} | ||
|
||
func (lim *limiter) newWriteLimiter(remoteID protocol.DeviceID, w io.Writer, isLAN bool) io.Writer { | ||
return &limitedWriter{writer: w, limiter: lim, isLAN: isLAN, remoteID: remoteID} | ||
} | ||
|
||
// limitedReader is a rate limited io.Reader | ||
type limitedReader struct { | ||
reader io.Reader | ||
|
@@ -214,16 +263,11 @@ type limitedReader struct { | |
func (r *limitedReader) Read(buf []byte) (int, error) { | ||
n, err := r.reader.Read(buf) | ||
if !r.isLAN || r.limiter.limitsLAN.get() { | ||
|
||
// in case rebuildMap was called | ||
r.limiter.mu.Lock() | ||
deviceLimiter, ok := r.limiter.deviceReadLimiters.Load(r.remoteID) | ||
r.limiter.mu.Unlock() | ||
deviceLimiter, ok := r.limiter.getDeviceReadLimiter(r.remoteID) | ||
if !ok { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To be honest, if !ok, I'd panic (or do nothing), as it should clearly be there. I'd do a if deviceLimiter != nil check in take |
||
l.Debugln("deviceReadLimiter was not in the map") | ||
deviceLimiter = rate.NewLimiter(rate.Inf, limiterBurstSize) | ||
} | ||
take(r.limiter.read, deviceLimiter.(*rate.Limiter), n) | ||
take(r.limiter.read, deviceLimiter, n) | ||
} | ||
return n, err | ||
} | ||
|
@@ -238,41 +282,42 @@ type limitedWriter struct { | |
|
||
func (w *limitedWriter) Write(buf []byte) (int, error) { | ||
if !w.isLAN || w.limiter.limitsLAN.get() { | ||
|
||
// in case rebuildMap was called | ||
w.limiter.mu.Lock() | ||
deviceLimiter, ok := w.limiter.deviceWriteLimiters.Load(w.remoteID) | ||
w.limiter.mu.Unlock() | ||
deviceLimiter, ok := w.limiter.getDeviceWriteLimiter(w.remoteID) | ||
if !ok { | ||
l.Debugln("deviceWriteLimiter was not in the map") | ||
deviceLimiter = rate.NewLimiter(rate.Inf, limiterBurstSize) | ||
} | ||
take(w.limiter.write, deviceLimiter.(*rate.Limiter), len(buf)) | ||
take(w.limiter.write, deviceLimiter, len(buf)) | ||
} | ||
return w.writer.Write(buf) | ||
} | ||
|
||
// take is a utility function to consume tokens from a rate.Limiter. No call | ||
// to WaitN can be larger than the limiter burst size so we split it up into | ||
// take is a utility function to consume tokens from a overall rate.Limiter and, if present, deviceLimiter. | ||
// No call to WaitN can be larger than the limiter burst size so we split it up into | ||
// several calls when necessary. | ||
func take(l, deviceLimiter *rate.Limiter, tokens int) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the l could do with a better name now, and the doc string should be updated. |
||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. empty line |
||
if tokens < limiterBurstSize { | ||
// This is the by far more common case so we get it out of the way | ||
// early. | ||
deviceLimiter.WaitN(context.TODO(), tokens) | ||
if deviceLimiter != nil { | ||
deviceLimiter.WaitN(context.TODO(), tokens) | ||
} | ||
l.WaitN(context.TODO(), tokens) | ||
return | ||
} | ||
|
||
for tokens > 0 { | ||
// Consume limiterBurstSize tokens at a time until we're done. | ||
if tokens > limiterBurstSize { | ||
deviceLimiter.WaitN(context.TODO(), limiterBurstSize) | ||
if deviceLimiter != nil { | ||
deviceLimiter.WaitN(context.TODO(), limiterBurstSize) | ||
} | ||
l.WaitN(context.TODO(), limiterBurstSize) | ||
tokens -= limiterBurstSize | ||
} else { | ||
deviceLimiter.WaitN(context.TODO(), tokens) | ||
if deviceLimiter != nil { | ||
deviceLimiter.WaitN(context.TODO(), tokens) | ||
} | ||
l.WaitN(context.TODO(), tokens) | ||
tokens = 0 | ||
} | ||
|
@@ -292,3 +337,32 @@ func (b *atomicBool) set(v bool) { | |
func (b *atomicBool) get() bool { | ||
return atomic.LoadInt32((*int32)(b)) != 0 | ||
} | ||
|
||
// Utility functions for atomic operations on device limiters map | ||
func (lim *limiter) getDeviceWriteLimiter(deviceID protocol.DeviceID) (*rate.Limiter, bool) { | ||
lim.deviceMapMutex.RLock() | ||
defer lim.deviceMapMutex.RUnlock() | ||
limiter, ok := lim.deviceWriteLimiters[deviceID] | ||
return limiter, ok | ||
} | ||
|
||
func (lim *limiter) getDeviceReadLimiter(deviceID protocol.DeviceID) (*rate.Limiter, bool) { | ||
lim.deviceMapMutex.RLock() | ||
defer lim.deviceMapMutex.RUnlock() | ||
limiter, ok := lim.deviceReadLimiters[deviceID] | ||
return limiter, ok | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this boolean is needed, all you care about when deciding to print or not print the message about rates is whether the rates are different from what they were before. |
||
} | ||
|
||
func (lim *limiter) setDeviceLimiters(deviceID protocol.DeviceID, writeLimiter, readLimiter *rate.Limiter) { | ||
lim.deviceMapMutex.Lock() | ||
defer lim.deviceMapMutex.Unlock() | ||
lim.deviceWriteLimiters[deviceID] = writeLimiter | ||
lim.deviceReadLimiters[deviceID] = readLimiter | ||
} | ||
|
||
func (lim *limiter) deleteDeviceLimiters(deviceID protocol.DeviceID) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is literally used in one place, just embed this code in that place and be done with it. |
||
lim.deviceMapMutex.Lock() | ||
defer lim.deviceMapMutex.Unlock() | ||
delete(lim.deviceWriteLimiters, deviceID) | ||
delete(lim.deviceReadLimiters, deviceID) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just
sync.Mutex
, no need for a pointer (regardless of which sync package)