-
Notifications
You must be signed in to change notification settings - Fork 75
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
Destructible ports #171
base: master
Are you sure you want to change the base?
Destructible ports #171
Conversation
❌ Build asyn 1.0.161 failed (commit a796d33d75 by @exzombie) |
I've simplified things a bit since yesterday, and did a smoke test with areaDetector. I noticed that its plugins already have working destructors, so I just forced the NDPosPlugin to declare itself as destructible. valgrind reported no new issues. The base class destructor in asynPortDriver ensures that the port is marked as defunct, so even if a driver is deleted directly, everything should be fine. @MarkRivers, where do I update the documentation? I noticed that you just added a whole new subdirectory. |
❌ Build asyn 1.0.176 failed (commit f56e71eb33 by @exzombie) |
✅ Build asyn 1.0.177 completed (commit 35add117e6 by @exzombie) |
The documentation is now in docs/source/.rst. I have deleted all of the old documentation/.html files that have been converted to .rst. There are still a few small files left to do. There is a Github Actions that publishes the docs to https://epics-modules.github.io/asyn |
✅ Build asyn 1.0.196 completed (commit 921b11a85d by @exzombie) |
f518e9e
to
f24071a
Compare
I've rebased, wrote the documentation, and extended the example driver. While writing the docs, I noticed that I also reintroduced the |
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.
Rather than having every driver create an exiting_ flag and set it in the destructor, would it be nicer to provide an asynManager method pasynManager->isExiting(), and/or an asynPortDriver method isExiting()?
✅ Build asyn 1.0.214 completed (commit 33edcb38c1 by @exzombie) |
✅ Build asyn 1.0.215 completed (commit b66b9a3621 by @exzombie) |
Ah, you're referring to the changes I made to the example driver. Well, having Let's suppose there exists an First, setting an "exit" flag may not be enough on its own. Often, the Second, the flag used by Which brings us to the question: who sets the exiting flag? I had written a Is it worth having all threads halt at the same time, and not being able to directly |
For this PR I think it would be good to create an Issue that contains the following:
|
Sure, I can add some background. But why open an issue? This stuff belongs to the cover letter of the PR, no? Especially the goals and limitations. |
General advice on issues vs. PRs:
The Why? question is the most expensive question that we encounter. An issue provides a direct explanation why a specific software change was initiated. |
Issues describe why, PRs describe how. |
In other words, the "problems" go into the issue, and the "goals" and "limitations" go into the description above. Clear enough. The reason I asked is that, personally, I have no problem if the problem itself is described in the PR, with no corresponding issue ticket. And I was just wondering if you want a separate issue for some functional reasons, or if it was just aesthetics. In any case, I'll open the issue with the "problem", no problem :) |
Aesthetics, along the lines of what @prjemian said. |
static const iocshFuncDef asynShutdownPortDef = | ||
{"asynShutdownPort", 1, asynShutdownPortArgs | ||
#ifdef IOCSHFUNCDEF_HAS_USAGE | ||
, | ||
#else | ||
}; | ||
static const char asynShutdownPortUsage[] = | ||
#endif | ||
"Permanently disables the port and destroys the port driver,\n" | ||
"releasing its resources.\n" | ||
#ifdef IOCSHFUNCDEF_HAS_USAGE | ||
}; | ||
#define asynShutdownPortUsage asynShutdownPortDef.usage | ||
#else | ||
; | ||
#endif |
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.
A much cleaner way to optionally define this struct member, without this ifdef forest, is with something like what we have in one of our projects.
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.
It is an eye sore, isn't it? I try to follow the existing style, so I just copied the existing pattern. However, I see that there are only three occurrences of this at the moment, so it's not too much work to use the opportunity and prettify all three. It's not worth the overhead of a separate pull request.
asyn/asynDriver/asynManager.c
Outdated
return asynError; | ||
} | ||
|
||
// Disabling the port short-circuits queueRequest(), preventing usage of |
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.
The standard for comments seems to be /* ... */
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.
In this file, yes. Will fix.
if (cbThread) | ||
delete cbThread; |
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.
AFAIK it's safe to delete a nullptr, so you shouldn't need the conditional.
I see now this was removed in a following commit.
void initialize(const char *portNameIn, int maxAddrIn, int interfaceMask, int interruptMask, int asynFlags, | ||
int autoConnect, int priority, int stackSize); | ||
|
||
asynParamSet* paramSet; |
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.
It might be nice to document this member with doxygen as well.
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.
Perhaps, but I didn't introduce it; it registers as a change because of incidental reordering with initialize()
that happened as the PR evolved.
epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize, | ||
"asynManager:destroyPortDriver cannot connect to port"); | ||
if(status != asynSuccess) { | ||
printf("%s\n", pasynUser->errorMessage); |
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 realize we can't use pasynUser->errorMessage
for the errors in this function, but it feels wrong to print them to stdout
, nonetheless.
It feels more reasonable when done in the iocsh commands, since those are usually interactive.
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 function is a consumer of pasynUser->errorMessage
. It is run at IOC exit, and if an error happens, it's supposed to present it to the user somehow. The only alternative I see is errlog, but it seems like a deviation given that printf()
is used for this purpose elsewhere (e.g. in registerPort()
. And given that the process is going away, printing this through the errlog task may not be the best idea. Or is it?
if (exception == asynExceptionShutdown) { | ||
// This code is only excuted once: asynManager will not raise the |
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.
Comment style
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.
Unlike asynManager.c
which has a consistent comment style, this is not the case for the rest of the asyn code. asynPortDriver.cpp
doesn't have many //
-style comments, but I don't see a reason not to introduce more.
@ericonr Thank you very much for the review! |
✅ Build asyn 1.0.222 completed (commit 39e3cc2b48 by @exzombie) |
@anjohnson, @mdavidsaver, and @ralphlange could you please look at this? I would like to merge into the next asyn release if you are good with it. Thanks. |
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 haven't been through all the code changes in detail, but it looks good to me and is definitely worth doing.
Looks good and appropriate to me. |
96e12b0
to
eb0e698
Compare
❌ Build asyn 1.0.267 failed (commit 32ca62eacb by @exzombie) |
The port driver needs to declare that it supports destruction by passing a new flag, ASYN_DESTRUCTIBLE, when registering the port. It also needs to handle the new asynExceptionShutdown. A new function is added, asynManager::shutdown(). It disables the port, nullifies the private driver pointers reachable from the port, and triggers the shutdown exception.
Shutdown is enabled by passing ASYN_DESTRUCTIBLE through asynFlags of the constructor.
The destructor is only registered if the port is declared destructible.
This affects the iocshell command and the epicsAtExit hook. Error message handling has also been improved.
Derived classes might want to stop things in their destructors, not in shutdown(). So we should only stop cbThread at the very end.
This would *always* be printed because shutdown is now called from the asynPortDriver destructor.
This avoids shadowing a system call by a static function. The corresponding field of `asynManager` was also renamed for consistency.
This fixes a segfault for a number of iocsh commands, thought not all.
Also, add the check for a disabled port to queueLockPort. Previously, this function would only return asynDisabled on a blocking port (courtesy of queueRequest), but didn't check on a synchronous port.
This includes changes to the documentation and making the needsShutdown flag atomic. It is clarified that the shutdown function is called unlocked, and that it has to be called when destroying the driver manually.
When the port driver is destroyed, the drvPvt in the interfaces is set to NULL. A destructible driver (which asynPortDriver can be) must be able to handle this.
The mutex that protects the enabled flag, defunct flag, and the interface list is asynManagerLock, not synchronousLock.
The interfaces are not destroyed together with the port. asynPortManager will overwrite the driver pointer with NULL. This allows the interfaces to gracefully deny access to non-existing driver.
This prevents device support code from accessing the interfaces of a port that was destroyed befort initialization code has run.
This significantly reduces the chance of name collision and unintentional function override for existing drivers.
We have encountered subclassed drivers that directly access the protected `asynStdInterfaces` field. Turning it into a pointer broke things, so, this commit makes it a reference instead.
eb0e698
to
d066b68
Compare
I encountered drivers that access the I also noticed that a lot of drivers have a |
❌ Build asyn 1.0.268 failed (commit 4098fe0f52 by @exzombie) |
✅ Build asyn 1.0.269 completed (commit 7015fe467b by @exzombie) |
This PR extends
asynManager
to allow a port to be destructible, solving #172. It complements epics-base/epics-base#283.Goals
asynManager
and a corresponding iocsh command that shuts down a port.asynPortDriver
base class uses the new functionality.Port shutdown
There is a new function,
asynManager::shutdown()
, whichasynManager::queueRequest()
andasynManager::lockPort()
, thus preventing holders ofasynUser
dangling references from using them;asynExceptionShutdown
, which is to be handled by a conforming driver.There is a new iocshell command that shuts down a port, useful mostly for testing.
This functionality is opt-in. A conforming driver declares that it is destructible by passing a new attribute,
ASYN_DESTRUCTIBLE
. In such a case,asynManager
will register anepicsAtExit
hook to callshutdown()
on IOC exit. A conforming driver has to handleasynExceptionShutdown
to perform cleanup, and may invalidate itself completely by callingdelete
.asynPortDriver and inheritance
asynPortDriver
uses the new functionality byASYN_DESTRUCTIBLE
if it is given to it via theasynFlags
parameter;asynPortDriver::shutdown()
, which allows things not possible in a destructor;pasynManager::shutdown()
from its destructor (while preventing recursion) so that callingdelete
on the driver also disables the port.Because this functionality is opt-in, there's a question of backwards compatibility in an inheritance tree, such as those in areaDetector. This can be solved by prescribing that a driver may only receive the
ASYN_DESTRUCTIBLE
flag and act upon it, but may not force it. This flag should come in via the iocshell command that instantiates a driver. This way, the driver will be destructible only if the most derived class is instantiated with this flag.Limitations
asynUser
instances that the users don't destroy. But, as stated in asyn ports cannot be destroyed on IOC shutdown #172, memory is not the resource that anyone is concerned about on IOC shutdown.asynPortDriver
directly, without callingasynManager::shutdown()
, can work, but only if the most derived class callsasynPortDriver::shutdown()
from its destructor. This is encouraged by the documentation but not enforced. It only affects unit tests that want to delete drivers, to my knowledge.TODO