BIND 10 #2853: Python wrapper of data source extensions
BIND 10 Development
do-not-reply at isc.org
Thu Jun 13 17:28:37 UTC 2013
#2853: Python wrapper of data source extensions
-------------------------------------+-------------------------------------
Reporter: jinmei | Owner:
Type: task | vorner
Priority: medium | Status:
Component: data source | reviewing
Keywords: | Milestone:
Sensitive: 0 | Sprint-20130625
Sub-Project: DNS | Resolution:
Estimated Difficulty: 5 | CVSS Scoring:
Total Hours: 0 | Defect Severity: N/A
| Feature Depending on Ticket:
| shared memory data source
| Add Hours to Ticket: 0
| Internal?: 0
-------------------------------------+-------------------------------------
Comment (by jinmei):
(Sorry for the frequent intrusion. I've been making additional notes
as I found things related to this branch while working on #2854, but
now I'm done with #2854 it won't happen anymore)
Replying to [comment:26 muks]:
> > Regarding the test: if you meant how to test the case of unused state,
> > you should simply use a data source with disabling in-memory cache.
>
> With `MasterFiles`, it expects the cache to be always enabled. With
> other names, it looks for a loadable `.so` module of that name.
I don't see why it's a problem, or I still didn't understand what you
mean. Some tests for the python datasrc modules actually use sqlite3
data sources.
> > Regarding the code, I personally think it's better to check the state
> > explicitly and use Py_None if it's SEGMENT_UNUSED.
>
> I wanted to avoid doing this in the Python wrapper, because we are
> introducing this `SEGMENT_UNUSED` checking behavior in two places (one
> in the C++ code and another in the Python wrapper). Is there something
> wrong with how we do it in the current Python wrapper?
There's nothing absolutely "wrong", but it doesn't seem to be better
either.
In general, I'd avoid using C++ exceptions in this type condition,
i.e., checking a direct result of a single function call. C++
exceptions are generally more expensive than a simple if block
(although in this specific case performance wouldn't matter much), and
the code is more complicated - we at least need a pair of try and
catch; and the exception isn't expected to propagated from a deeper
level of call stack, so we are not saving any intermediate code in
this case.
Also, in my sense C++ exceptions are for really unexpected events.
The original C++ `getSegmentType()` method shouldn't really called for
with an UNUSED state, and the exception is thrown to indicate the
programming error. This Python wrapper actually uses this mechanism
as a test if the state is UNUSED, with the understanding it's a
possible case. Such "abuse" of C++ exception makes me confused
(because if I see try-catch, this should be something related to error
handling or some other unexpected event).
This is also related to your reasoning of avoiding the double check
for "UNUSED". As a general matter, I agree; so if we were
implementing a Python wrapper of `getSegmentType()` itself, I'd
definitely leave it to the actual C++ code, rather than checking the
state within the python wrapper. But I don't think it applies to the
`getStatus` case; it actually does the check for the state, simply
with a different tool (i.e., exception, and in my sense an abuse of
it) instead of the straightforward `if`.
Further, the `InvalidOperation` exception is quite generic, and,
although it's unlikely such a simple method as getSegmentType() would
throw it for a different reason, it might still be possible in a
future extension. If and when that happens this implementation will
become buggy. The explicit check on the state is more robust against
such changes.
That said...I admit these may be subtle points and can be a matter of
taste. And, especially because I'm not the official reviewer of the
branch, I'd leave it you and Michal.
> > And, not directly on this point, but on a closer look the code looks
> > generally unsafe. For example, in `ConfigurableClientList_getStatus`
> > if this fails:
> >
> > {{{#!cpp
> > PyObject* tup = Py_BuildValue("(sOI)",
> > status[i].getName().c_str(),
> > segment_type,
> >
status[i].getSegmentState());
> > }}}
> >
> > then the reference to segment_type will be lost. You'll need a help
> > of `PyObjectContainer` here.
>
> By "fails", do you mean it throws an exception that causes
> control flow to exit that block at that location? `Py_BuildValue()` is a
> C function. If there is a failure within it, it will return `NULL`. In
No, I understand it.
> this case, we first drop our reference on `segment_type` before
> returning a failure. `status[i]`, `getName()` and `getSegmentState()`
> should not throw in the way our code is written.
On looking at it again, I realized `segment_type` won't leak. So,
after a very careful read I can conclude there's no safety issue in
the code. I'd still suggest revising it with `PyObjectContainer`,
though, because the safety isn't obvious and is (IMO) indeed fragile:
- One confusing point is that this block works for both normal and
failure cases:
{{{#!cpp
if (segment_type) {
// The Py_BuildValue() above increments its refcount,
// so we drop our reference.
Py_DECREF(segment_type);
}
}}}
But from a quick look it seems it only intends to handle the
successful case, so I need to make an extra effort to be sure about
the safety.
- Failure of this isn't handled immediately:
{{{#!cpp
segment_type = Py_BuildValue(
"s", status[i].getSegmentType().c_str());
}}}
Again, with a careful read you can understand it's still safe
because this will make the next Py_BuildValue fails, and the `if`
block is skipped as `segment_type` should be NULL in this case,
and the error will be returned here:
{{{#!cpp
if (!tup) {
Py_DECREF(slist);
return (NULL);
}
}}}
But deferring the error handling is itself not a good practice, and
even if it's safe in the end, you'll need to use an extra effort to
be sure about it. It's also more fragile to future extensions.
On the other hand, we could catch the failure of building
segment_type immediately with an additional `if-else`. There's
nothing obviously wrong here, too, but this way the entire code will
be full of this type of error handling, making it more unreadable.
But, like the case of the previous point, I won't insist. I'd leave
it to you two, too.
--
Ticket URL: <http://bind10.isc.org/ticket/2853#comment:29>
BIND 10 Development <http://bind10.isc.org>
BIND 10 Development
More information about the bind10-tickets
mailing list