[...]
>>> +static void
>>> +virNWFilterObjPromoteToWrite(virNWFilterObjPtr obj)
>>> +{
>>> + virObjectRWUnlock(obj);
>>> + virObjectRWLockWrite(obj);
>>> +}
>>
>> How can this not deadlock? This will work only if @obj is locked exactly
>> once. But since we allow recursive locks any lock() that happens in the
>> 2nd level must deadlock with this. On the other hand, there's no such
>> case in the code currently. But that doesn't stop anybody from calling
>> PromoteWrite() without understanding its limitations.
>>
>> Maybe the fact that we need recursive lock for NWFilterObj means it's
>> not a good candidate for virObjectRWLocable? It is still a good
>> candidate for virObject though.
>>
>> Or if you want to spend extra time on this, maybe introducing
>> virObjectRecursiveLockable may be worth it (terrible name, I know).
>>
>>
>
> I dunno, worked for me. The helper is being called from a thread that
> has taken out a READ lock at most one time and needs to get the WRITE
> lock in order to change things. If something else has the READ lock that
> thread waits until the other thread releases the READ lock as far as I
> understand things.
Yes, I can see that. It's just that since the original lock is recursive
I expected things to get recursive and thus I've been thinking how would
this work there, nested in 2nd, 3rd, .. level. But as noted earlier, the
places where lock promoting is used are kind of safe since all the
locking is done at the first level.
The reason I chose rwlocks and taking a read lock by default in the
FindByUUID and FindByName (as opposed to other vir*FindBy* API's which
return a write locked object) is because I know there's the potential
for other code to be doing Reads and rwlocks allowed for that without
the need to create any sort of recursive lockable object or any sort of
API to perform "trylock"'s (all things I tried until the epiphany to use
rwlocks was reached when you added them for other drivers and their list
protection).
In the long/short run I figured that having a common way to get the lock
and then deciding to change it from Read to Write as necessary would
work just fine for the purpose that was needed.
>
> Back when I first did this I had quite a lot of debug code and I have a
> vague recollection of seeing output from this particular path and seeing
> a couple of unlocks before the WRITE was taken while running the avocado
> tests.
>
> I have zero interest in spending more time on this. That ship sailed. If
> the decision is to drop the patches, then fine. I tried. I disagree
> that NWFilterObj is not a candidate for RWLockable - in fact it's quite
> the opposite *because* of the recursive locks.
Well, there's a difference between recursive locks and rwlocks. And it's
exactly in what happens in recursion. With recursive locks we don't need
any lock promoting and thus it's safe to do read/write after lock().
With lock promoting (esp. done in unlock()+lockWrite() manner) we get
TOCTOU bugs.
If we had a real lock manager available this wouldn't be a problem ;-)
[sorry just my OpenVMS roots and bias showing]. Still if you read up a
bit on "dlm" you'll get an idea of what I mean.
There's so many locks in the nwfilter code it's a wonder it all works.
It's truly unfortunate that the wrlock mechanism is undefined for
promoting a read lock to write leaving the consumer with few options.
Since we can limit the "updating" of the fields in @obj and getting a
write lock to very specific code paths, hopefully we can limit damage
and/or deadlock type and/or TOCTOU issues
>
> Additionally, because of the recursive locks we cannot use the
> virObjectLockable and quite frankly I see zero benefit from going down
> the path of just virObject. As they say, patches welcome.
>
> Somewhere along the line, I recall trying to post patches that were
> essentially virObjectRecursiveLockable and got NACK'd.
Why is that? IUUC the aim here is to unify all the vir*ObjectList
implementations so that we can drop code duplication. And so far what
I've seen only a couple of virObject* functions are needed
(virObjectRef, virObjectUnref, virObjectLock and virObjectUnlock). So if
we have virObjectRecursiveLockable class then we can still use those 4
functions safely and unify the code here. If you're not interested, I
can cook the patches.
Yes, reduction of duplication of vir*ObjectList is/was a goal. How to
get there is where opinions start to vary - my code was essentially
NACK'd, but getting to a point where someone else could try something
different I figured would at least be helpful. This nwfilter code is the
black sheep of the libvirt family in more ways than one.
BTW: We're still not at a point where common List mgmt code could be
written because the domain list mgmt varies greatly between the various
hypervisor's. I started writing some patches to work through those
issues, but have been side tracked by other higher priority items.
BTW, see:
https://www.redhat.com/archives/libvir-list/2017-May/msg01183.html
for my pass at virObjectLockableRecursiveNew
>
>>> @@ -347,26 +426,39 @@ virNWFilterObjListAssignDef(virNWFilterObjListPtr
nwfilters,
>>> }
>>>
>>>
>>> + /* Get a READ lock and immediately promote to WRITE while we adjust
>>> + * data within. */
>>> if ((obj = virNWFilterObjListFindByName(nwfilters, def->name))) {
>>>
>>> objdef = obj->def;
>>> if (virNWFilterDefEqual(def, objdef)) {
>>> + virNWFilterObjPromoteToWrite(obj);
>>> virNWFilterDefFree(objdef);
>>> obj->def = def;
>>> + virNWFilterObjDemoteFromWrite(obj);
>>> return obj;
>>> }
>>
>> What is the idea behind this if()? I don't understand it. There doesn't
>> seem to be any fields in @objdef or
>>
>
> Or you lost your train of thought? Happens with this code. The *DefEqual
> ensures that the new definition is the exact same as the old, but
> replaces the def for whatever reason - kind of the redefine type logic.
> If something is different, then we're stuck going through the
> FilterRebuild logic.
>
> That's about the extent of what I understand.
Yes, but the part that I'm not understanding is why we are replacing the
definition in the first place. I mean, if this were some integers
instead of pointers:
int x = 42;
if (struct.member == x) {
struct.member = x;
}
It makes exactly zero sense to me. But whatever, it's pre-existing.
Right, pre-existing is what I'm going with 0-)
John