-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
Fix throttling #25801
base: dev
Are you sure you want to change the base?
Fix throttling #25801
Conversation
c4397d3
to
453979c
Compare
I've also renamed the parameter from |
Ah, I read code again and notice it has |
Let's keep it @ivanfmartinez could you also take a look? |
If I understand correctly the last message my implementation is working @gracianodias3 just have not restarted the zigbee2mqtt process when tried first. I dont know much about the z2m internals, but as other parameters can be changed from UI probably throttle can be changed also, I just dont know how to do this changes on UI. |
1d8b00a
to
9be98ab
Compare
The code you make works good, but me (and I think other people too) thought that when we restart Home Assistant, it would restart Zigbee2MQTT also, but não é assim when using HAOS (I not know about other ways to install). I put UI elements in PR (I think I did right), but when trying to make device settings read new value immediately, I could not make work. |
I got a bit lost in what problem you are trying to solve here, with the example code from throttleit: const throttle = require('throttleit');
// Throttling a function that processes data.
function processData(data) {
console.log('Processing:', data);
// Add data processing logic here.
}
// Throttle the `processData` function to be called at most once every 3 seconds.
const throttledProcessData = throttle(processData, 3000);
// Simulate calling the function multiple times with different data.
throttledProcessData('Data 1');
throttledProcessData('Data 2');
throttledProcessData('Data 3'); I get the following output:
That looks correct to me? |
Sorry, my English is not so good. I've run this through ChatGPT to improve it, I don't know if it says what I mean though: Sorry, it's become a bit of a mess. I, along with others, found that when we updated the throttle value it wasn't respected, even with a restart of Home Assistant. I initially thought it was due to the throttleit library, but that's behaving as expected. Poking around, I discovered a few things:
So a description of the changes
There are people who've complained about the throttling functionality not working. I suspect they're running into the same problem that I was, which was changing the throttle value via the filesystem didn't result in messages being throttled, even after a restart. This PR fixes that. |
@@ -71,8 +71,9 @@ export default class Receive extends Extension { | |||
} | |||
|
|||
async publishThrottle(device: Device, payload: KeyValue, time: number): Promise<void> { | |||
if (!this.throttlers[device.ieeeAddr]) { | |||
this.throttlers[device.ieeeAddr] = { | |||
const throttleKey = device.ieeeAddr + device.options.throttle; |
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.
This will keep stale values in memory indefinitely (until Z2M shutdown). Better to invalidate the throttler when the option changes for a device.
Can't we just set the option as "restart mandatory" though (cleaner for such a feature)?
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.
Thanks for review! I am again running my message in ChatGPT, please tell me if unclear.
This will keep stale values in memory indefinitely (until Z2M shutdown). Better to invalidate the throttler when the option changes for a device.
I considered the memory issue, but here's why I think it's okay:
- Memory Impact: These chatty devices won't be used in large numbers since they can slow down networks. Most users won't have hundreds of them or frequently change their throttle settings.
- Cache Invalidation: I looked into clearing the throttle cache when settings change, but wasn't sure how to connect the settings UI to the ExtensionReceive component. Do you know an easy way?
- Current Approach: Given the small real-world memory impact, keeping values in memory seems better than adding complex state management.
- Alternative Solution: We could auto-clear individual cached values after
2*throttleValue
using setTimeout, but this adds complexity for minimal benefit.
Can't we just set the option as "restart mandatory" though (cleaner for such a feature)?
That's not user-friendly. Plus, as we've seen, restarting Home Assistant on HAOS doesn't restart Zigbee2MQTT, which confuses users.
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.
Can't we just set the option as "restart mandatory" though (cleaner for such a feature)?
I'm also in favour of this. The restart button will automatically show up in the frontend. Settings requiresRestart
true
will enable this:
zigbee2mqtt/lib/util/settings.schema.json
Line 57 in 060ae99
"requiresRestart": true |
@@ -0,0 +1,23 @@ | |||
export default function throttle<Args extends unknown[]>(fn: (...args: Args) => Promise<void>, wait: number): (...args: Args) => Promise<void> { |
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.
I didn't test it, but this looks to be messing with context, and also the return type is no longer generic as before.
Can we just bring the same code over, update it to typescript (the type is already in there as d.ts
), and avoid potential issues down the road?
And also include a link to the source.
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.
Thanks for catching that. While my tests pass and it's working on my system, there are some TypeScript issues to address.
I tried implementing your suggestion but ran into problems with the any type. We'd need to enable noImplicitThis to use ThisType. I'm not very experienced with some TypeScript features, so there might be a simple solution I'm missing. I have a partial solution, but it's not perfect:
export default function throttle<T extends (...arguments_: unknown[]) => unknown>(function_: T, wait: number): T {
if (typeof function_ !== 'function') {
throw new TypeError(`Expected the first argument to be a \`function\`, got \`${typeof function_}\`.`);
}
// TODO: Add `wait` validation too in the next major version.
let timeoutId: NodeJS.Timeout;
let lastCallTime = 0;
return function throttled(...arguments_) {
clearTimeout(timeoutId);
const now = Date.now();
const timeSinceLastCall = now - lastCallTime;
const delayForNextCall = wait - timeSinceLastCall;
if (delayForNextCall <= 0) {
lastCallTime = now;
function_.apply(this, arguments_);
} else {
timeoutId = setTimeout(() => {
lastCallTime = Date.now();
function_.apply(this, arguments_);
}, delayForNextCall);
}
} as T;
}
This still relies on runtime type checking. While we could use runtime type checking, I prefer not to since:
- The throttle function will likely only be used for this specific throttling feature
- It's better to let the TypeScript compiler catch issues at compile time
- We should make the types as strict as possible
You're right about adding attribution. If you know a better way to make the type checker happy, I'm interested. While my current TypeScript implementation works, I'm not sure if changing the function's context could cause problems.
If the library works fine, lets keep on using it and no reinvent the wheel here. The package is also widely used (6M downloads per week) |
Olá!
I am having same problem as peoples in #25030. After looking at the good work in #24122, I investigate in code and discover that
throttleit
package is not throwing away extra calls, but putting them in queue (I think this is not what we want it to do). Also, I think it's not so good to add new dependency (because of security risks with supply chain attacks) just for one small function, so I coped important parts into throttle.ts.