-
-
Notifications
You must be signed in to change notification settings - Fork 19
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
Add new overloads for GlobalExclusiveDeviceAccess.CommunicateWithDevice
#377
Add new overloads for GlobalExclusiveDeviceAccess.CommunicateWithDevice
#377
Conversation
Important Review skippedReview was skipped due to path filters ⛔ Files ignored due to path filters (6)
CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including You can disable this status message by setting the Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
Help me understand the exact scenario here as I may have missed something: when connected to nanoff or the debug library, the device is already in exclusive usage. The serial port cannot be shared anyway, so it can only be open by one application. |
GlobalExclusiveDeviceAccess was introduced in this PR. GlobalExclusiveDeviceAccess provides an exclusive access mechanism via a system-wide mutex. It can be waited on, and you can set a timeout for gaining access. With the "regular" code that accesses the serial port a failure/exception can mean two things: (1) the port is in use by another application, or (2) the port cannot be used/there is a problem. If all nanoFramework tools use GlobalExclusiveDeviceAccess to get access to a port when they want to communicate with the port, then a failure is indeed: cannot use the port. The GlobalExclusiveDeviceAccess is a feature I made for the test framework v3. You don't want a failed test because the Device Explorer is checking the port at the wrong moment, it is better that the test host waits until the Device Explorer is finished and then continues with running the tests. Or the other way around: if the Device Explorer cannot access a port for some time (e.g., because a test is running, or the Device Explorer of another Visual Studio instance), it does not show the device. With GlobalExclusiveDeviceAccess, it waits until the test has ended and then shows the device in the list. I think there are also scenarios where Visual Studio starts multiple test hosts, each running tests from a single test assembly, that want to access the same device; GlobalExclusiveDeviceAccess will protect against that. The GlobalExclusiveDeviceAccess is of limited use if it is only used in the test framework, so I made a PR for it in the library that is used by all tools. I'll add the locks to nanoff (nanoframework/nanoFirmwareFlasher#290) and once this PR has been accepted to the Device Explorer as well. I don't know how to add it to the Visual Studio/nanoFramework debugger, but at least the protection will be part of most tools. |
I'm not sure this is being address in the most efficient (usable?) way... Using Shouldn't we use instead a pattern that creates a global mutex in the NanoDevice constructor, for example, ensuring global lock. |
No, the NanoDevice is not the correct place. The way it is used, it represents a device that has been recognized as a nanoDevice, but not one the software is actually communicating with. E.g., the Device Explorer uses the DeviceWatchers to discover devices. The result is a list of NanoDevice instances, but the Device Explorer itself is not yet doing anything with that. The Device Explorer should obtain a lock if it wants to communicate with the device, e.g, just before doing a ping or obtaining the device information. I think that in most cases this coincides with the creation of a debugger engine. If you are looking for ways to integrate the lock with existing code, the Device.Connect() is a place to consider, as that is always called at the start of a period of communication with a device. But that would require some major refactoring, as the call to Device.Connect() is sometimes indirect (from DebugEngine.Connect()) and the current code is not designed for a graceful exit from that point. The idea is that a timeout should be handled differently than an error, but that is easier at a higher level in the code. The lock can't be in Device.Connect() only, because it is quite "deep" in the code. E.g., in case of the test host, the first action is to find out if a device exists for a port, via PortSerialManager.AddDevice. Sometime during the execution of that method there is code that runs Device.Connect(), checks that the device is valid, and calls Device.Disconnect(). When the assemblies are deployed to the device, the underlying code executes Device.Connect() again. But you don't want to run the risk that there is another application (potentially another test host) that has obtained exclusive access to the port inbetween the Device.Disconnect() and subsequent Device.Connect() call. The test host should get exclusive access before checking whether there is a device connected to the port, and release the lock after the last test assembly has been executed. Given the current code base, I still think the easiest way is to have the |
Yes, NanoDevice constructor wasn't a good suggestion. Also, because there are operations that can't be interrupted and others that is OK to use. On the original design of the debugger library this was (kind of) accomplished by the use of locks. A concern I have with the current proposal is that only the code that adheres to this new global lock approach will be locked. Other callers can still call the various APIs without going through the lock and mess things up. Please note that I see value on the proposition of a global lock. I'm concerned that it won't be that effective if the code doesn't enforce it "by itself" and by design. Relying on "disciplined" callers is not a good approach and is prone to failure. What about "moving" this global lock to inside the library and make use of it in the APIs that can't be interrupted? |
At first I thought it would be relatively easy to do, but then reality set in. Changes are: GlobalExclusiveDeviceAccess
API integration
Device watcher (serial ports)
Implementation notes For technical reasons the code now uses a Semaphore instead of a Mutex. That is also system-wide on Windows, but apparently not on other platforms. It is cross-process within the same Unix shell (tested that). |
@josesimoes just to verify: is there anything you expect from me at this moment for this PR? |
@frobijn thanks for checking, I'll get back to this PR today and resume the review with your recent changes. |
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.
@frobijn minor code layout changes to improve readability and maintainability.
One last question about a CancellationTokenSource on a separate comment.
Tested it with several devices and plugging, unplugging and it works quite smoothly. Responsiveness to devices arrival/departure has increased.
And discovery process also feels much smooth.
Great work on this!! 💯
nanoFramework.Tools.DebugLibrary.Shared/NFDevice/GlobalExclusiveDeviceAccess.cs
Outdated
Show resolved
Hide resolved
The main reason for the retry mechanism is that some devices (like ESP32) can have quite different boot times. Because of the time it takes to bootloader to load the CLR. And then the CLR going through type resolution stage and having Wire Protocol responsive. Others have the internal USB CDC hardware that starts late in the boot process. All this calls for the retry workflow that you see here. Otherwise, it would be a simple: open port -> fails ? -> I'm done with this device. |
Aha, then let's keep the retry-workflow in the code. |
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.
LGTM
GlobalExclusiveDeviceAccess.CommunicateWithDevice
@frobijn thank you again for your contribution! 🙏😄 .NET nanoFramework is all about community involvement, and no contribution is too small. Please edit it and add an entry with your GitHub username in the appropriate location (names are sorted alphabetically):
(Feel free to adjust your name if it's not correct) |
Description
Added overloads for GlobalExclusiveDeviceAccess.CommunicateWithDevice:
Func<Task> communicationAsync
as argument, in addition toAction communication
:Now you can write:
Motivation and Context
In nanoff the action to be protected is async code, but the GlobalExclusiveDeviceAccess did not have a method for that.
While trying to add the lock to the Device Explorer (e.g., for the Ping command): the code only knows about NanoDeviceBase, how to get from that to a serial port or network address should not be part of the Device Explorer code.
Types of changes
Checklist: