From 5912b84044ab74e9108675272e18807ce413461b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akan=20Sidenvall?= Date: Mon, 17 Aug 2026 15:38:41 +0200 Subject: [PATCH 1/2] FIX: reject non-finite InputSystem.pollingFrequency (ISX-2837) --- Assets/Tests/InputSystem/CoreTests_Devices.cs | 18 ++++++++++++++++++ Packages/com.unity.inputsystem/CHANGELOG.md | 2 ++ .../InputSystem/Runtime/InputManager.cs | 7 +++++-- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/Assets/Tests/InputSystem/CoreTests_Devices.cs b/Assets/Tests/InputSystem/CoreTests_Devices.cs index ed90efe605..795fe6228a 100644 --- a/Assets/Tests/InputSystem/CoreTests_Devices.cs +++ b/Assets/Tests/InputSystem/CoreTests_Devices.cs @@ -4384,6 +4384,24 @@ public void Devices_CanSetPollingFrequency() Assert.That(InputSystem.pollingFrequency, Is.EqualTo(120).Within(0.000001)); } + [Test] + [Category("Devices")] + public void Devices_CannotSetPollingFrequencyToNonFiniteValue() + { + // A plain `value <= 0` guard misses NaN (which compares false against everything) and + // +Infinity, leaving pollingFrequency stuck at that value on every read. (ISX-2837) + InputSystem.pollingFrequency = 120; + + Assert.That(() => InputSystem.pollingFrequency = float.NaN, Throws.ArgumentException); + Assert.That(() => InputSystem.pollingFrequency = float.PositiveInfinity, Throws.ArgumentException); + Assert.That(() => InputSystem.pollingFrequency = float.NegativeInfinity, Throws.ArgumentException); + Assert.That(() => InputSystem.pollingFrequency = 0f, Throws.ArgumentException); + Assert.That(() => InputSystem.pollingFrequency = -1f, Throws.ArgumentException); + + // A rejected assignment must leave the previous value untouched. + Assert.That(InputSystem.pollingFrequency, Is.EqualTo(120).Within(0.000001)); + } + #if UNITY_INPUT_SYSTEM_PLATFORM_POLLING_FREQUENCY [Test] [Category("Devices")] diff --git a/Packages/com.unity.inputsystem/CHANGELOG.md b/Packages/com.unity.inputsystem/CHANGELOG.md index 9a6d3514da..42dd0f8ddf 100644 --- a/Packages/com.unity.inputsystem/CHANGELOG.md +++ b/Packages/com.unity.inputsystem/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Fixed +- Fixed `InputSystem.pollingFrequency` accepting non-finite values: `NaN` (and `+Infinity`) slipped past the `value <= 0` guard, leaving the property returning `NaN` on every read and disabling background polling. Non-finite values are now rejected with an `ArgumentException`. [ISX-2837](https://jira.unity3d.com/browse/ISX-2837) + - Fixed the Inspector help button for a selected `.inputactions` asset ("Open Reference for Input Action Importer") opening a missing documentation page; it now links to the Action Assets manual page [UUM-149518](https://issuetracker.unity3d.com/product/unity/issues/guid/UUM-149518) - Fixed an `OverflowException` when creating a control scheme (or other named item) whose all-numeric name exceeds `Int32.MaxValue`, which previously discarded the entered name and fell back to the default [UUM-145766](https://issuetracker.unity3d.com/product/unity/issues/guid/UUM-145766) - Fixed the Input Actions editor window logging a "Failed to load asset" exception on editor startup when its saved window layout was restored in a project where the referenced asset GUID did not resolve; the window now closes quietly instead [UUM-144318](https://issuetracker.unity3d.com/product/unity/issues/guid/UUM-144318) diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs index 994538ecc2..209b894c69 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs @@ -278,8 +278,11 @@ public float pollingFrequency set { ////REVIEW: allow setting to zero to turn off polling altogether? - if (value <= 0) - throw new ArgumentException("Polling frequency must be greater than zero", "value"); + // NaN slips past a plain `value <= 0` check (NaN compares false against everything), + // and +Infinity does too; either would be cached and read back forever. Reject + // non-finite values explicitly. (float.IsFinite is unavailable on netstandard2.0.) + if (value <= 0 || float.IsNaN(value) || float.IsInfinity(value)) + throw new ArgumentException("Polling frequency must be a finite value greater than zero", "value"); #if UNITY_INPUT_SYSTEM_PLATFORM_POLLING_FREQUENCY m_Runtime.pollingFrequency = value; From fd73afbc5c662a9279c9866505be3218ed4ac57d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akan=20Sidenvall?= Date: Thu, 20 Aug 2026 16:19:07 +0200 Subject: [PATCH 2/2] DOCS: explain why non-finite and zero pollingFrequency are rejected (ISX-2837) Follow-up to the ISX-2837 guard. No behaviour change. The original comment justified rejecting +Infinity as the same caching bug as NaN. It is not. The native backend accepts +Infinity as "poll continuously" and zero as "disable polling", so rejecting either here is a deliberate narrowing of a supported native mode and the reason should say so. - Explain the +Infinity and zero rejections on their own terms, and drop the ////REVIEW asking whether zero should turn off polling - that question is answered in the comment now. - Add the accepted range and an tag to the public InputSystem.pollingFrequency XMLDoc, which had neither. - Note on the test's +Infinity assertion that it deliberately diverges from the native SetPollingFrequency_ShouldAcceptInfinityAsContinuousPolling. - Reword the changelog to cover both non-finite values and both consequences. --- Assets/Tests/InputSystem/CoreTests_Devices.cs | 4 ++++ Packages/com.unity.inputsystem/CHANGELOG.md | 3 +-- .../InputSystem/Runtime/InputManager.cs | 13 +++++++++---- .../InputSystem/Runtime/InputSystem.cs | 6 ++++++ 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/Assets/Tests/InputSystem/CoreTests_Devices.cs b/Assets/Tests/InputSystem/CoreTests_Devices.cs index 795fe6228a..5b92d32755 100644 --- a/Assets/Tests/InputSystem/CoreTests_Devices.cs +++ b/Assets/Tests/InputSystem/CoreTests_Devices.cs @@ -4393,6 +4393,10 @@ public void Devices_CannotSetPollingFrequencyToNonFiniteValue() InputSystem.pollingFrequency = 120; Assert.That(() => InputSystem.pollingFrequency = float.NaN, Throws.ArgumentException); + + // The native backend does accept +Infinity, as "poll continuously". This public API + // deliberately does not - it pins a core, and is far more likely to be a `1f/0f` accident + // at the call site than a deliberate choice. Assert.That(() => InputSystem.pollingFrequency = float.PositiveInfinity, Throws.ArgumentException); Assert.That(() => InputSystem.pollingFrequency = float.NegativeInfinity, Throws.ArgumentException); Assert.That(() => InputSystem.pollingFrequency = 0f, Throws.ArgumentException); diff --git a/Packages/com.unity.inputsystem/CHANGELOG.md b/Packages/com.unity.inputsystem/CHANGELOG.md index 42dd0f8ddf..e36fcd77ce 100644 --- a/Packages/com.unity.inputsystem/CHANGELOG.md +++ b/Packages/com.unity.inputsystem/CHANGELOG.md @@ -9,8 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Fixed -- Fixed `InputSystem.pollingFrequency` accepting non-finite values: `NaN` (and `+Infinity`) slipped past the `value <= 0` guard, leaving the property returning `NaN` on every read and disabling background polling. Non-finite values are now rejected with an `ArgumentException`. [ISX-2837](https://jira.unity3d.com/browse/ISX-2837) - +- Fixed `InputSystem.pollingFrequency` accepting non-finite values. Both `NaN` and `+Infinity` slipped past the `value <= 0` guard - `NaN` left the property returning `NaN` on every read and disabled background polling, while `+Infinity` set the backend to poll continuously and occupy a CPU core. Since both are almost always the result of an arithmetic accident at the call site (`0f/0f` and `1f/0f` respectively), they are now rejected with an `ArgumentException`. [ISX-2837](https://jira.unity3d.com/browse/ISX-2837) - Fixed the Inspector help button for a selected `.inputactions` asset ("Open Reference for Input Action Importer") opening a missing documentation page; it now links to the Action Assets manual page [UUM-149518](https://issuetracker.unity3d.com/product/unity/issues/guid/UUM-149518) - Fixed an `OverflowException` when creating a control scheme (or other named item) whose all-numeric name exceeds `Int32.MaxValue`, which previously discarded the entered name and fell back to the default [UUM-145766](https://issuetracker.unity3d.com/product/unity/issues/guid/UUM-145766) - Fixed the Input Actions editor window logging a "Failed to load asset" exception on editor startup when its saved window layout was restored in a project where the referenced asset GUID did not resolve; the window now closes quietly instead [UUM-144318](https://issuetracker.unity3d.com/product/unity/issues/guid/UUM-144318) diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs index 209b894c69..eb4b4e4922 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs @@ -277,10 +277,15 @@ public float pollingFrequency set { - ////REVIEW: allow setting to zero to turn off polling altogether? - // NaN slips past a plain `value <= 0` check (NaN compares false against everything), - // and +Infinity does too; either would be cached and read back forever. Reject - // non-finite values explicitly. (float.IsFinite is unavailable on netstandard2.0.) + // Non-finite values almost always arrive by accident - `0f/0f` gives NaN and `1f/0f` + // gives +Infinity - and both slip past a plain `value <= 0` check. NaN gets cached + // and read back forever. +Infinity means continuous polling in the native backend, + // which leaves the polling thread nothing to wait on and pins a core. Neither is + // something a public setter should accept. Zero is likewise supported by the native + // backend, where it disables polling outright, but is not exposed here - silently + // stopping all polled-device input from a frequency setter is a footgun rather than + // a discoverable off switch. + // (float.IsFinite is unavailable on netstandard2.0.) if (value <= 0 || float.IsNaN(value) || float.IsInfinity(value)) throw new ArgumentException("Polling frequency must be a finite value greater than zero", "value"); diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputSystem.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputSystem.cs index fce3a22117..c9cbece292 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputSystem.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputSystem.cs @@ -1337,7 +1337,13 @@ public static event InputDeviceFindControlLayoutDelegate onFindLayoutForDevice /// /// Also note that the polling frequency applies to all devices that are polled. It is not possible /// to set polling frequency on a per-device basis. + /// + /// Only finite values greater than zero are accepted. Non-finite values are rejected rather than + /// passed through, as they are almost always the result of an arithmetic accident at the call site + /// (0f/0f yields NaN, 1f/0f yields +Infinity) and would otherwise leave + /// polling in an unusable state. /// + /// Value is not a finite number greater than zero. public static float pollingFrequency { get => s_Manager.pollingFrequency;