Support double-closing of pipe reading/writers

This is consistent with a normal file in Python, e.g.:

    >>> f = open('/dev/null')
    >>> f.close()
    >>> f.close()
    >>>

Failures to "del self._bd" -- presumably from multiple .close() calls --
have caused a number of confusing stack traces in various situations.

The order of operations here have been carefully re-ordered so that the
invalidation used to assert closed-ness (self._fd = -1) can only occur
if the delegated "_fp.close()" succeeds.  And, in case of any raised
asynchronous exception, the "del attr" constructs have been made
optional, as so close() can be called after an exception and "closed"
reports properly.