fix: track press-and-hold per device action and make it thread-safe - #1493
Merged
Merged
Conversation
Holds were keyed by device only in an unlocked static dictionary, so two simultaneous holds on one device (e.g. pan + tilt for a diagonal PTZ move) collided: a duplicate-key exception, a second action that never started, and an axis that stopped ~1s late. Holds are now keyed by device and action path, and start/reset/stop/expiry run under a lock so each action starts and stops exactly once. The device-only overload is kept for plugins. Fixes #1489 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Timer-generation races, failed-start cleanup, and global callback serialization remain unresolved.
Review effort: Balanced
Findings: 3
Open (3)
What changed in this PR
Fixes #1489 by tracking press-and-hold state per device action and synchronizing timer access.
Changes:
- Adds action-specific hold keys across 47 call sites.
- Synchronizes hold lifecycle operations.
- Preserves the legacy device-only overload.
| File | Description |
|---|---|
MobileControlEssentialsRoomBridge.cs |
Keys master-volume holds per direction. |
PressAndHoldHandler.cs |
Adds action keys and synchronized timer management. |
ILevelControlsMessenger.cs |
Separates level-control holds. |
IBasicVolumeControlsMessenger.cs |
Separates volume holds. |
CameraControlMessenger.cs |
Separates PTZ holds per action. |
ITransportMessenger.cs |
Adds transport action keys. |
ISetTopBoxControlsMessenger.cs |
Adds set-top-box action keys. |
INumericMessenger.cs |
Adds numeric-button action keys. |
IDvrMessenger.cs |
Adds DVR action keys. |
IDPadMessenger.cs |
Adds directional-pad action keys. |
IColorMessenger.cs |
Adds color-button action keys. |
IChannelMessenger.cs |
Adds channel-control action keys. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Addresses review on #1493: device callbacks now run under a per-hold lock instead of one global lock, a start that throws no longer leaves the key held, and each heartbeat replaces the hold's timer so an expiry already queued for the old timer can't end a hold that was just extended. A hold stays registered until its stop has run, keeping start/stop ordered per key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
Fixes #1489.
PressAndHoldHandlerkeyed holds by device only, in an unlocked static dictionary. Two holds on one device at once (e.g.cameraLeft+cameraUpfor a diagonal PTZ move) collided: a duplicate-key exception, a second action that never started, and an axis that stopped about 1 s late.HandlePressAndHold(deviceKey, actionPath, content, action)overload. All 47 call sites pass their own path, andCameraControlMessengerkeys its holds per action too.Not in this PR:
SIMPLCameraMessenger,SIMPLVtcMessenger,IHasCodecCamerasMessengerandMobileControlSIMPLRoomBridgecallGetPressAndHoldHandlerwith the message value (pressed/held/released) as the hold key, so heartbeats and releases never find the hold. That's a separate bug in those callers.Test plan
cameraLeft/cameraUppressed at the same moment from two threads, held, released, 200 runs: each axis started and stopped exactly once every time.🤖 Generated with Claude Code