preact
preact copied to clipboard
Fix event bubbling can break in test conditions (#4161)
This should fix #4161.
The idea is to use a global counter that will increment on event dispatch only, so it’s less likely that the globalCounter will overflow and the order of _attached/_dispatched is going to be guaranteed.
📊 Tachometer Benchmark Results
Summary
duration
- 02_replace1k: unsure 🔍 -1% - +1% (-0.97ms - +1.08ms)
preact-local vs preact-main - 03_update10th1k_x16: unsure 🔍 -4% - +3% (-1.31ms - +1.21ms)
preact-local vs preact-main - 07_create10k: unsure 🔍 -1% - +1% (-5.12ms - +4.43ms)
preact-local vs preact-main - filter_list: unsure 🔍 -3% - +8% (-0.51ms - +1.29ms)
preact-local vs preact-main - hydrate1k: unsure 🔍 -4% - +1% (-2.92ms - +0.94ms)
preact-local vs preact-main - many_updates: unsure 🔍 -1% - +3% (-0.15ms - +0.43ms)
preact-local vs preact-main - text_update: unsure 🔍 -5% - +4% (-0.12ms - +0.10ms)
preact-local vs preact-main - todo: unsure 🔍 -1% - +1% (-0.18ms - +0.24ms)
preact-local vs preact-main
usedJSHeapSize
- 02_replace1k: faster ✔ 0% - 1% (0.01ms - 0.02ms)
preact-local vs preact-main - 03_update10th1k_x16: faster ✔ 0% - 1% (0.01ms - 0.03ms)
preact-local vs preact-main - 07_create10k: faster ✔ 1% - 1% (0.24ms - 0.24ms)
preact-local vs preact-main - filter_list: unsure 🔍 -0% - +0% (-0.00ms - +0.00ms)
preact-local vs preact-main - hydrate1k: faster ✔ 1% - 1% (0.03ms - 0.05ms)
preact-local vs preact-main - many_updates: unsure 🔍 -0% - +0% (-0.00ms - +0.00ms)
preact-local vs preact-main - text_update: unsure 🔍 -1% - +5% (-0.01ms - +0.04ms)
preact-local vs preact-main - todo: unsure 🔍 +0% - +0% (+0.00ms - +0.00ms)
preact-local vs preact-main
Results
02_replace1k
- Browser: chrome-headless 122.0.6261.128
- Sample size: 80
- Built by: Benchmarks #1360
-
Commit: 1749ffd
duration
| Version | Avg time | vs preact-main | vs preact-local | vs preact-hooks |
|---|---|---|---|---|
| preact-main | 72.60ms - 73.82ms | - | unsure 🔍 -1% - +1% -1.08ms - +0.97ms | faster ✔ 1% - 3% 0.44ms - 2.03ms |
| preact-local | 72.45ms - 74.09ms | unsure 🔍 -1% - +1% -0.97ms - +1.08ms | - | faster ✔ 0% - 3% 0.22ms - 2.14ms |
| preact-hooks | 73.94ms - 74.95ms | slower ❌ 1% - 3% 0.44ms - 2.03ms | slower ❌ 0% - 3% 0.22ms - 2.14ms | - |
usedJSHeapSize
| Version | Avg time | vs preact-main | vs preact-local | vs preact-hooks |
|---|---|---|---|---|
| preact-main | 3.40ms - 3.40ms | - | slower ❌ 0% - 1% 0.01ms - 0.02ms | unsure 🔍 -0% - -0% -0.00ms - -0.00ms |
| preact-local | 3.38ms - 3.39ms | faster ✔ 0% - 1% 0.01ms - 0.02ms | - | faster ✔ 0% - 1% 0.02ms - 0.02ms |
| preact-hooks | 3.41ms - 3.41ms | unsure 🔍 +0% - +0% +0.00ms - +0.00ms | slower ❌ 0% - 1% 0.02ms - 0.02ms | - |
run-warmup-0
| Version | Avg time | vs preact-main | vs preact-local | vs preact-hooks |
|---|---|---|---|---|
| preact-main | 29.44ms - 30.26ms | - | unsure 🔍 -1% - +3% -0.35ms - +0.74ms | unsure 🔍 -4% - +0% -1.29ms - +0.09ms |
| preact-local | 29.30ms - 30.02ms | unsure 🔍 -2% - +1% -0.74ms - +0.35ms | - | faster ✔ 0% - 5% 0.13ms - 1.45ms |
| preact-hooks | 29.89ms - 31.00ms | unsure 🔍 -0% - +4% -0.09ms - +1.29ms | slower ❌ 0% - 5% 0.13ms - 1.45ms | - |
run-warmup-1
| Version | Avg time | vs preact-main | vs preact-local | vs preact-hooks |
|---|---|---|---|---|
| preact-main | 39.93ms - 41.41ms | - | unsure 🔍 -4% - +1% -1.52ms - +0.52ms | faster ✔ 1% - 5% 0.31ms - 2.12ms |
| preact-local | 40.48ms - 41.87ms | unsure 🔍 -1% - +4% -0.52ms - +1.52ms | - | unsure 🔍 -4% - +0% -1.58ms - +0.16ms |
| preact-hooks | 41.36ms - 42.40ms | slower ❌ 1% - 5% 0.31ms - 2.12ms | unsure 🔍 -0% - +4% -0.16ms - +1.58ms | - |
run-warmup-2
| Version | Avg time | vs preact-main | vs preact-local | vs preact-hooks |
|---|---|---|---|---|
| preact-main | 26.37ms - 27.00ms | - | unsure 🔍 -1% - +3% -0.21ms - +0.73ms | slower ❌ 0% - 3% 0.13ms - 0.84ms |
| preact-local | 26.08ms - 26.77ms | unsure 🔍 -3% - +1% -0.73ms - +0.21ms | - | unsure 🔍 -1% - +2% -0.17ms - +0.61ms |
| preact-hooks | 26.03ms - 26.37ms | faster ✔ 0% - 3% 0.13ms - 0.84ms | unsure 🔍 -2% - +1% -0.61ms - +0.17ms | - |
run-warmup-3
| Version | Avg time | vs preact-main | vs preact-local | vs preact-hooks |
|---|---|---|---|---|
| preact-main | 24.58ms - 25.72ms | - | unsure 🔍 -3% - +3% -0.67ms - +0.85ms | unsure 🔍 -1% - +6% -0.19ms - +1.45ms |
| preact-local | 24.55ms - 25.57ms | unsure 🔍 -3% - +3% -0.85ms - +0.67ms | - | unsure 🔍 -1% - +5% -0.24ms - +1.33ms |
| preact-hooks | 23.92ms - 25.11ms | unsure 🔍 -6% - +1% -1.45ms - +0.19ms | unsure 🔍 -5% - +1% -1.33ms - +0.24ms | - |
run-warmup-4
| Version | Avg time | vs preact-main | vs preact-local | vs preact-hooks |
|---|---|---|---|---|
| preact-main | 26.73ms - 28.32ms | - | slower ❌ 7% - 15% 1.78ms - 3.62ms | unsure 🔍 -2% - +5% -0.41ms - +1.34ms |
| preact-local | 24.35ms - 25.29ms | faster ✔ 7% - 13% 1.78ms - 3.62ms | - | faster ✔ 6% - 10% 1.64ms - 2.83ms |
| preact-hooks | 26.69ms - 27.43ms | unsure 🔍 -5% - +1% -1.34ms - +0.41ms | slower ❌ 6% - 12% 1.64ms - 2.83ms | - |
run-final
| Version | Avg time | vs preact-main | vs preact-local | vs preact-hooks |
|---|---|---|---|---|
| preact-main | 22.66ms - 23.32ms | - | slower ❌ 1% - 5% 0.28ms - 1.18ms | faster ✔ 3% - 7% 0.66ms - 1.76ms |
| preact-local | 21.95ms - 22.57ms | faster ✔ 1% - 5% 0.28ms - 1.18ms | - | faster ✔ 6% - 10% 1.40ms - 2.48ms |
| preact-hooks | 23.76ms - 24.64ms | slower ❌ 3% - 8% 0.66ms - 1.76ms | slower ❌ 6% - 11% 1.40ms - 2.48ms | - |
03_update10th1k_x16
- Browser: chrome-headless 122.0.6261.128
- Sample size: 60
- Built by: Benchmarks #1360
-
Commit: 1749ffd
duration
| Version | Avg time | vs preact-main | vs preact-local | vs preact-hooks |
|---|---|---|---|---|
| preact-main | 34.59ms - 36.36ms | - | unsure 🔍 -3% - +4% -1.21ms - +1.31ms | unsure 🔍 -4% - +3% -1.49ms - +1.13ms |
| preact-local | 34.53ms - 36.32ms | unsure 🔍 -4% - +3% -1.31ms - +1.21ms | - | unsure 🔍 -4% - +3% -1.54ms - +1.09ms |
| preact-hooks | 34.69ms - 36.62ms | unsure 🔍 -3% - +4% -1.13ms - +1.49ms | unsure 🔍 -3% - +4% -1.09ms - +1.54ms | - |
usedJSHeapSize
| Version | Avg time | vs preact-main | vs preact-local | vs preact-hooks |
|---|---|---|---|---|
| preact-main | 3.37ms - 3.38ms | - | slower ❌ 0% - 1% 0.01ms - 0.03ms | unsure 🔍 -0% - +0% -0.01ms - +0.01ms |
| preact-local | 3.35ms - 3.36ms | faster ✔ 0% - 1% 0.01ms - 0.03ms | - | faster ✔ 0% - 1% 0.01ms - 0.03ms |
| preact-hooks | 3.37ms - 3.38ms | unsure 🔍 -0% - +0% -0.01ms - +0.01ms | slower ❌ 0% - 1% 0.01ms - 0.03ms | - |
07_create10k
- Browser: chrome-headless 122.0.6261.128
- Sample size: 50
- Built by: Benchmarks #1360
-
Commit: 1749ffd
duration
| Version | Avg time | vs preact-main | vs preact-local | vs preact-hooks |
|---|---|---|---|---|
| preact-main | 883.22ms - 888.11ms | - | unsure 🔍 -1% - +1% -4.43ms - +5.12ms | unsure 🔍 -1% - +0% -9.01ms - +2.22ms |
| preact-local | 881.22ms - 889.42ms | unsure 🔍 -1% - +1% -5.12ms - +4.43ms | - | unsure 🔍 -1% - +0% -10.25ms - +2.76ms |
| preact-hooks | 884.01ms - 894.12ms | unsure 🔍 -0% - +1% -2.22ms - +9.01ms | unsure 🔍 -0% - +1% -2.76ms - +10.25ms | - |
usedJSHeapSize
| Version | Avg time | vs preact-main | vs preact-local | vs preact-hooks |
|---|---|---|---|---|
| preact-main | 26.40ms - 26.40ms | - | slower ❌ 1% - 1% 0.24ms - 0.24ms | slower ❌ 1% - 1% 0.22ms - 0.22ms |
| preact-local | 26.16ms - 26.17ms | faster ✔ 1% - 1% 0.24ms - 0.24ms | - | unsure 🔍 -0% - -0% -0.02ms - -0.02ms |
| preact-hooks | 26.19ms - 26.19ms | faster ✔ 1% - 1% 0.22ms - 0.22ms | unsure 🔍 +0% - +0% +0.02ms - +0.02ms | - |
filter_list
- Browser: chrome-headless 122.0.6261.128
- Sample size: 90
- Built by: Benchmarks #1360
-
Commit: 1749ffd
duration
| Version | Avg time | vs preact-main | vs preact-local | vs preact-hooks |
|---|---|---|---|---|
| preact-main | 16.58ms - 16.78ms | - | unsure 🔍 -7% - +3% -1.29ms - +0.51ms | unsure 🔍 -1% - +1% -0.12ms - +0.16ms |
| preact-local | 16.17ms - 17.96ms | unsure 🔍 -3% - +8% -0.51ms - +1.29ms | - | unsure 🔍 -3% - +8% -0.49ms - +1.30ms |
| preact-hooks | 16.57ms - 16.76ms | unsure 🔍 -1% - +1% -0.16ms - +0.12ms | unsure 🔍 -8% - +3% -1.30ms - +0.49ms | - |
usedJSHeapSize
| Version | Avg time | vs preact-main | vs preact-local | vs preact-hooks |
|---|---|---|---|---|
| preact-main | 1.42ms - 1.42ms | - | unsure 🔍 -0% - +0% -0.00ms - +0.00ms | faster ✔ 2% - 2% 0.02ms - 0.03ms |
| preact-local | 1.42ms - 1.42ms | unsure 🔍 -0% - +0% -0.00ms - +0.00ms | - | faster ✔ 2% - 2% 0.02ms - 0.03ms |
| preact-hooks | 1.44ms - 1.45ms | slower ❌ 2% - 2% 0.02ms - 0.03ms | slower ❌ 2% - 2% 0.02ms - 0.03ms | - |
hydrate1k
- Browser: chrome-headless 122.0.6261.128
- Sample size: 50
- Built by: Benchmarks #1360
-
Commit: 1749ffd
duration
| Version | Avg time | vs preact-main | vs preact-local | vs preact-hooks |
|---|---|---|---|---|
| preact-main | 79.60ms - 82.16ms | - | unsure 🔍 -1% - +4% -0.94ms - +2.92ms | unsure 🔍 -1% - +4% -0.44ms - +3.26ms |
| preact-local | 78.44ms - 81.34ms | unsure 🔍 -4% - +1% -2.92ms - +0.94ms | - | unsure 🔍 -2% - +3% -1.55ms - +2.39ms |
| preact-hooks | 78.14ms - 80.81ms | unsure 🔍 -4% - +1% -3.26ms - +0.44ms | unsure 🔍 -3% - +2% -2.39ms - +1.55ms | - |
usedJSHeapSize
| Version | Avg time | vs preact-main | vs preact-local | vs preact-hooks |
|---|---|---|---|---|
| preact-main | 6.10ms - 6.10ms | - | slower ❌ 1% - 1% 0.03ms - 0.05ms | unsure 🔍 +0% - +0% +0.01ms - +0.03ms |
| preact-local | 6.05ms - 6.07ms | faster ✔ 1% - 1% 0.03ms - 0.05ms | - | faster ✔ 0% - 1% 0.01ms - 0.03ms |
| preact-hooks | 6.08ms - 6.09ms | unsure 🔍 -0% - -0% -0.03ms - -0.01ms | slower ❌ 0% - 1% 0.01ms - 0.03ms | - |
many_updates
- Browser: chrome-headless 122.0.6261.128
- Sample size: 50
- Built by: Benchmarks #1360
-
Commit: 1749ffd
duration
| Version | Avg time | vs preact-main | vs preact-local | vs preact-hooks |
|---|---|---|---|---|
| preact-main | 16.55ms - 16.60ms | - | unsure 🔍 -3% - +1% -0.43ms - +0.15ms | unsure 🔍 -0% - +0% -0.02ms - +0.04ms |
| preact-local | 16.43ms - 17.00ms | unsure 🔍 -1% - +3% -0.15ms - +0.43ms | - | unsure 🔍 -1% - +3% -0.14ms - +0.44ms |
| preact-hooks | 16.55ms - 16.59ms | unsure 🔍 -0% - +0% -0.04ms - +0.02ms | unsure 🔍 -3% - +1% -0.44ms - +0.14ms | - |
usedJSHeapSize
| Version | Avg time | vs preact-main | vs preact-local | vs preact-hooks |
|---|---|---|---|---|
| preact-main | 4.52ms - 4.52ms | - | unsure 🔍 -0% - +0% -0.00ms - +0.00ms | faster ✔ 0% - 1% 0.02ms - 0.02ms |
| preact-local | 4.52ms - 4.52ms | unsure 🔍 -0% - +0% -0.00ms - +0.00ms | - | faster ✔ 0% - 1% 0.02ms - 0.02ms |
| preact-hooks | 4.54ms - 4.54ms | slower ❌ 0% - 1% 0.02ms - 0.02ms | slower ❌ 0% - 1% 0.02ms - 0.02ms | - |
text_update
- Browser: chrome-headless 122.0.6261.128
- Sample size: 60
- Built by: Benchmarks #1360
-
Commit: 1749ffd
duration
| Version | Avg time | vs preact-main | vs preact-local | vs preact-hooks |
|---|---|---|---|---|
| preact-main | 2.42ms - 2.57ms | - | unsure 🔍 -4% - +5% -0.10ms - +0.12ms | faster ✔ 5% - 16% 0.13ms - 0.45ms |
| preact-local | 2.41ms - 2.57ms | unsure 🔍 -5% - +4% -0.12ms - +0.10ms | - | faster ✔ 5% - 16% 0.14ms - 0.46ms |
| preact-hooks | 2.65ms - 2.93ms | slower ❌ 5% - 18% 0.13ms - 0.45ms | slower ❌ 5% - 19% 0.14ms - 0.46ms | - |
usedJSHeapSize
| Version | Avg time | vs preact-main | vs preact-local | vs preact-hooks |
|---|---|---|---|---|
| preact-main | 0.80ms - 0.80ms | - | unsure 🔍 -5% - +1% -0.04ms - +0.01ms | faster ✔ 2% - 3% 0.02ms - 0.03ms |
| preact-local | 0.79ms - 0.84ms | unsure 🔍 -1% - +5% -0.01ms - +0.04ms | - | unsure 🔍 -4% - +2% -0.04ms - +0.02ms |
| preact-hooks | 0.82ms - 0.83ms | slower ❌ 2% - 3% 0.02ms - 0.03ms | unsure 🔍 -2% - +4% -0.02ms - +0.04ms | - |
todo
- Browser: chrome-headless 122.0.6261.128
- Sample size: 50
- Built by: Benchmarks #1360
-
Commit: 1749ffd
duration
| Version | Avg time | vs preact-main | vs preact-local | vs preact-hooks |
|---|---|---|---|---|
| preact-main | 26.86ms - 27.16ms | - | unsure 🔍 -1% - +1% -0.24ms - +0.18ms | faster ✔ 3% - 5% 0.70ms - 1.29ms |
| preact-local | 26.89ms - 27.18ms | unsure 🔍 -1% - +1% -0.18ms - +0.24ms | - | faster ✔ 2% - 4% 0.67ms - 1.26ms |
| preact-hooks | 27.75ms - 28.26ms | slower ❌ 3% - 5% 0.70ms - 1.29ms | slower ❌ 2% - 5% 0.67ms - 1.26ms | - |
usedJSHeapSize
| Version | Avg time | vs preact-main | vs preact-local | vs preact-hooks |
|---|---|---|---|---|
| preact-main | 0.88ms - 0.88ms | - | unsure 🔍 -0% - -0% -0.00ms - -0.00ms | faster ✔ 3% - 3% 0.03ms - 0.03ms |
| preact-local | 0.88ms - 0.88ms | unsure 🔍 +0% - +0% +0.00ms - +0.00ms | - | faster ✔ 3% - 3% 0.03ms - 0.03ms |
| preact-hooks | 0.91ms - 0.91ms | slower ❌ 3% - 3% 0.03ms - 0.03ms | slower ❌ 3% - 3% 0.03ms - 0.03ms | - |
Yes, I tested issue #3927 that was closed by #4126. Works fine.
coverage: 99.47% (-0.1%) from 99.601% when pulling 1749ffdd0ce2282f480d4a909a0fe1de60fc4cb0 on nbalatsk-oracle:main into bc7c5515599634a427334eb03236ddf19e13e618 on preactjs:main.
Hi guys, I've refreshed my branch with the latest code. Let me know if you have any comments.
@JoviDeCroock I have seen that you have gone for the 32-bit integer solution, as opposed to just counting from Number.MIN_SAFE_INTEGER upwards.
With 32-bit integers, there is a slight chance that it rolls over if many events are fired and if you have a system that runs for a long time (kiosk systems?). If I understand the code correctly, every event attached before MaxInt32 wouldn't be fired after rolling over.
Counting upwards from Number.MIN_SAFE_INTEGER however, you would practically never reach Number.MAX_SAFE_INTEGER. Even with 10,000 events a second, this would take 57,123 years.
@sschultze this isn't my PR just my comment 😅
@JoviDeCroock Sorry 😅
@nbalatsk-oracle @JoviDeCroock What about using BigInt for the counter if the browser supports it? BigInts have an arbitrary size and will never overflow, and also no strange things happen if they get very large, giving some peace of mind.
The performance cost should be negligible. On my computer, 10 million increments of a BigInt take 151ms.
Initialization for the counter could go like this:
let counter = window.BigInt ? BigInt(0) : 0;
The ++ operand and the comparison would work fine on both number and bigint, so no need for another case distinction.
@ritschwumm @JoviDeCroock do we want to use BigInt for the counter as suggested by @sschultze? Do you any concerns for performance?
@nbalatsk-oracle can't the rollover be made negligible by just comparing for equality with the previous value? then the only problem left is when 2^32 events happen "at the same instant"...
Probably another issue, but isn't it problematic that, even in the original code, the _attached property is added to the event handler function? The same event handler might be added to multiple nodes, potentially leading to internal mess-up. Even I re-use event handlers in my code (e.g. for list items differentiated by a data-key attribute), and my UI is not that special.
I also noticed that in the minified version, _attached is shortened to u. Couldn't it happen that there's a conflict between the minified name and the "official" properties of the function?
@nbalatsk-oracle can't the rollover be made negligible by just comparing for equality with the previous value? then the only problem left is when 2^32 events happen "at the same instant"...
@ritschwumm I don't think that's how the current proposed code changes are working. The idea is that the counter is incremented when an event listener is newly attached for the first time during a render cycle. All event listeners that are newly attached during that render cycle will share the same counter value; all event listeners attached during previous renders will have lower counter values, all event listeners attached in future renders will have higher counter values. As a result, event listeners attached during the 0th render cycle appear indistinguishable from event listeners attached during the 2^32nd render cycle, hence the "rollover" issue.
Probably another issue, but isn't it problematic that, even in the original code, the
_attachedproperty is added to the event handler function? The same event handler might be added to multiple nodes, potentially leading to internal mess-up. Even I re-use event handlers in my code (e.g. for list items differentiated by adata-keyattribute), and my UI is not that special.
@sschultze Is it really a problem? It seems that it does not really matter that the handler is shared as long as the _attached/_dispatched order is correct. Even if _attached is updated by some DOM mutation (new elements are added that share the same handler), the event will not propagate to those updated elements. What is the use-case where we want it? And if you trigger an event on one of those older elements the event should work because the order of the _attached/_dispatched pair is still correct. Right?
@sschultze Is it really a problem? It seems that it does not really matter that the handler is shared as long as the _attached/_dispatched order is correct. Even if _attached is updated by some DOM mutation (new elements are added that share the same handler), the event will not propagate to those updated elements. What is the use-case where we want it? And if you trigger an event on one of those older elements the event should work because the order of the _attached/_dispatched pair is still correct. Right?
@nbalatsk-oracle You are right. Even though adding the same event handler in multiple places was probably not thought of when writing the original _attached/_dispatched workaround (or perhaps it was thought of, i don't know), in practice this shouldn't be a problem as the order will still be correct.
I mean, I can't say for sure - I'm not deep enough into the internal workings of Preact to guarantee that there isn't some weird edge case where still would still create a problem.
I did however write the following test code which re-uses the same event handler handleButtonClick and aggressively creates new DOM nodes, just for a quick validation, and it works fine. No click event is ignored.
import { JSX, useCallback, useState } from 'preact/compat';
interface SectionProps {
readonly onButtonClick: JSX.MouseEventHandler<HTMLButtonElement>;
}
function Section(props: SectionProps) {
return (
<div>
<button onClick={props.onButtonClick}>Add Section</button>
</div>
);
}
let domNodeCreationCounter = 0;
export function PreactTest(props: {}) {
const [count, setCount] = useState(1);
const handleButtonClick = useCallback(() => {
setCount(oldCount => oldCount + 1);
}, []);
// console.log((handleButtonClick as any).u); - shows ._attached in the minified version that I use
return (
<div key={domNodeCreationCounter++}> {/* Should create new DOM nodes every time */}
{Array(count).fill(0).map((_, index) => (
<Section key={index} onButtonClick={handleButtonClick} />
))}
</div>
);
}
Perhaps it would make sense to add a "warning comment" for anyone who updates the code later? Saying that the property is added to the function itself, even though it might be added to multiple nodes, but as you say this still doesn't create a problem as the _attached/_dispatched order is still okay?
@nbalatsk-oracle A thought experiment: If it would indeed be 100% safe to share event handlers between nodes, and still always get the correct behavior here, then it would - as a consequence - also be safe to share the same one event handler for everything in the application. Meaning there's really only one event handler function for everything in the application in this theoretical scenario.
In this scenario, the _attached property would always be set in the same function object.
If this would still lead to the correct behavior, than _attached could be a (module-)global variable, and not be attached to the event handler functions in the first place.
What I want to say by this is:
- either event handlers can really be shared between nodes and still get 100% correct behavior, in which case
_attachedcould also be made a (module-)global variable - or neither the original nor the corrected workaround provide a 100% solution.
In any case, especially if we get the overflow problem away (e.g. with the bigint proposal), the new code definitely provides better results that the old code relying on Date. (The Date-based workaround not only makes problems in the test scenario, but also if the users changes their system date/time while the app is running.)
Updated the code to use BigInt for the counters to avoid overflow and added a few comments to _attached/_detached usage.
@JoviDeCroock @developit @andrewiggins @marvinhagemeister Just curious if you might be able to provide any indication on the likelihood of moving forward with the approach in this PR or if there are other approaches to this problem that you might like to see investigated? Our product is currently blocked from moving forward from preact 10.17.1 due to https://github.com/preactjs/preact/issues/4161 so we're very interested in finding some acceptable resolution. Happy to explore other options if there's a preference, thanks!