BIND 10 #805: Client side code to request new sockets (C++)
BIND 10 Development
do-not-reply at isc.org
Wed Jan 4 07:15:04 UTC 2012
#805: Client side code to request new sockets (C++)
-------------------------------------+-------------------------------------
Reporter: | Owner: jinmei
stephen | Status: reviewing
Type: | Milestone:
enhancement | Sprint-20120110
Priority: | Resolution:
blocker | Sensitive: 0
Component: | Sub-Project: DNS
Unclassified | Estimated Difficulty: 5
Keywords: | Total Hours: 0
Defect Severity: N/A |
Feature Depending on Ticket: |
Socket creator |
Add Hours to Ticket: 0 |
Internal?: 0 |
-------------------------------------+-------------------------------------
Comment (by jinmei):
I've completed my review.
First, I made a few minor changes directly to the branch which I
believe are correct and/or non controversial.
Second, while looking at details, a lot of design level
questions/suggestions/concerns arose. I think these are substantial
(at least to me) to some extent, but I'm afraid we don't have time to
resolve all of them before the release (even if the "resolution"
results in rejecting the issue after discussions). And, in some sense
many of these points are minor in that it would work somehow as an
initial proof of concept. So, maybe the possible way forward is to
only address easy points and merge the result, then work on more
fundamental points immediately after the release.
'''higher level points'''
The following points are some of the design level points. I'd like to
resolve the first one before the release (I have a concrete proposal
so if we agree with it that part is easy), but the other two can be
deferred.
- I have to say I don't think it a good idea to use multiple
inheritance for TestSocketRequestor. While I'm generally more
convinced that multiple inheritance does more harm than good in many
cases, I'm also willing to be flexible and can live with it if it
really makes the design elegant which couldn't be realized in other
ways. Still, in this specific case, it seems to me an unnecessary
abuse of it. I have a counter proposal of doing the same thing
using more straightforward composition while avoiding (diamond
style) multiple inheritance. Please see the attached diff.
- although this is not in the direct scope of this branch: while
thinking about various issues I encountered in the branch (detailed
below), I now wonder why we need to make SocketRequestor a singleton
by design. Singleton is a very strong idiom and inherits many bad
properties of global variables (the fact that we need to use
something like "initTest" for tests is one of them), so we'd rather
avoid relying on it unless we have a strong need for it. In our use
case, the application would still need to explicitly create a
specific instance either by SocketRequestor::init() or instantiating
TestSocketRequestor, we could make the init function a factory
(returning an instance) and use a pointer or reference to it
wherever we need to get access to it. Then the most part of the
application code won't have to be aware that SocketRequestor is a
singleton, and, in particular, we should be able to simplify tests
(we'd be able to just create and discard a test requestor for each
test case). If we still want to make sure the requestor is never
instantiated twice in normal applications by interface, we could
implement that logic in the factory function.
- more or less related to the above two points, it seems to me the
portconfig logic is getting more complicated with various states (at
least the requestor and "current_sockets" were newly introduced
states that are cooperating in the same context), so I wonder it
makes more sense to introduce a class to enclose the configuration
logic. Then we'd construct a "configurator" object with a pointer
or reference to the socket requestor (thereby eliminating the need
for getting access to the requestor via the singleton interface) and
have the object maintain "current sockets" as a class attribute
(thereby eliminating the use of semi-global namespace scope static
variable). But, aside from whether this idea is a good one, I also
think we need to revisit how to configure listen ports in general
(detailed below). So this point should be considered as part of
revisiting the entire scheme.
'''(test) socket_request.h'''
- can't we avoid using the global portconfig::test_mode variable? I
understand we sometimes have to sacrifice the cleanness for
testability as a matter of practice, but this variable is accessed
far from the original module (i.e. server_common) and it's difficult
to track the dependency. We could, for example, introduce an
abstraction of DNSService and use a special derived class of it for
tests, where addServerXXXFromFD does nothing.
- personally I'd hide gtest many of the function definitions from the
header file. in particular, I'd avoid directly relying on gtest
specific definitions in a header file (and including gtest.h, which
subsequently imports many other definitions including macros and
will make this header file more susceptible to ordering issues).
'''portconfig.cc'''
- use of namespace scope static variable doesn't seem to be good:
{{{#!c++
vector<string> current_sockets;
}}}
both in terms of the risk of having initialization fiasco and of
introducing a semi-global mutable variable, which is difficult to be
tracked (causing bugs, making debug more difficult).
- `setAddresses()` doesn't provide the strong exception guarantee (for
current_sockets), and if something goes wrong in the middle of this
function current_sockets and the boss will (possibly) have some
inconsistent states. In this context note also that even
releaseSocket() could fail and throw; it's not a usual resource
release function (which would generally be considered exception
free), but involves many complicated things that can fail, including
message creation and network communication. Further, the entire
logic of the port configuration doesn't seem to be a good one to me
in the first place. It first clears all server objects (so the
corresponding sockets will be closed) even if we are reusing the
some of the address/port pairs. This could lead to dropping queries
even if the server could otherwise handle. We should at least avoid
the unnecessary close-rebind process. Also, as commented in
installListenAddresses(), it doesn't seem to be the best behavior to
try to use the all or nothing policy here. We can discuss this, but
my expected behavior would be to handle each address-port config
separately and if some of them fails we basically keep going with
others (while leaving error logs for the failure). For that matter,
that's how BIND 9 works, too.
Frankly, I don't know what we should do with thin in the mean time.
All these weird things seem to suggest that it's time to revisit the
whole design of port configuration, especially with taking into
account the introduction of the socket creator. On the other hand,
I don't think we can complete that task before the coming release.
One possible compromise might be to introduce this version for the
release, ignoring all these issues, and immediately revisit the
design after the release at the highest priority.
- setAddresses(): what's the magic string of "dummy_app"?
- While this is minor compared to the bigger issue explained in the
bigger comment above, the recovery process in
installListenAddresses() is now even more unreliable:
{{{#!c++
try {
setAddresses(service, addressStore);
}
}}}
because even releasing the address in setAddresses() may now fail.
- I don't understand this comment:
{{{#!c++
// Releasing them should really work
}}}
(What does "should really work" mean?)
'''auth_srv_unittest.cc'''
- In the following way, dnss_ and address_store_ are passed to
TestSocketRequestor before being initialized.
{{{#!c++
AuthSrvTest() :
TestSocketRequestor(dnss_, address_store_, 53210),
}}}
It may or may not be (happen to be) safe depending on the details of
TestSocketRequestor, but not a good practice anyway. But more
fundamentally I'd rather avoid using inheritance here (see the
higher level issues above). Note: this initialization issue exists
in other cases where TestSocketRequestor() is initialized. Note
also that this problem doesn't exist in the proposed diff.
'''portconfig_unittest.cc'''
- I suspect this TODO is now even more impossible.
{{{#!c++
// TODO Maybe some test to actually connect to them
}}}
- Shouldn't the exception class be more specific?
{{{#!c++
// This should not bind them, but should leave the original addresses
EXPECT_THROW(installListenAddresses(invalid_, store_, dnss_),
exception);
}}}
Same for the brokenRollback test.
- I don't understand this comment. What's 'everything'? Perhaps you
mean 'releases' instead of 'returns'?
{{{#!c++
// Try it at least returns everything when even the rollback fails.
}}}
- in the following comment, "These" should be "the first pair"?
{{{#!c++
// These should be requested in the first part of the failure to bind
// and the second pair in the first part of rollback
}}}
- I'd say there will be more error cases to test. For example,
there's a case when release fails. But I'm not sure if we bother to
be complete here, if our next action is to revisit the design
fundamentally.
--
Ticket URL: <http://bind10.isc.org/ticket/805#comment:14>
BIND 10 Development <http://bind10.isc.org>
BIND 10 Development
More information about the bind10-tickets
mailing list