Holding the lock while calling the transport can cause a deadlock, as
shown in #696. In previous auditing for deadlock prevention I considered
heavily Call interactions, but failed to consider shutdown() and realize
it was holding a lock while calling transport.shutdown().
We still hold a lock when calling transport.start(). Although it is
conceivable that this could cause a deadlock as the code evolves over
time, I don't believe it can cause a deadlock today or that the risk is
very high. In addition, it would require more effort to solve.
Note that even more importantly, this translates a RST_STREAM error code
to a gRPC status code. This is generally useful, but also necessary for
DEADLINE_EXCEEDED to be more reliable in 0eae0d9.
Fixes#687
This does make use of the fact we are no longer using Multimap. Doing
entries() for ArrayListMultimap must create a new Map.Entry for every
entry. Since we are now using HashMap, we are able to use entries() with
no extra cost.
Merging particular Keys no longer needs to deserialize.
Multimap creates at least one new object for every access, because all
the objects it returns are "live" and when mutated they need to update
the multimap's size. Many common operations thus require at least an
object allocation per key.
Note that previously remove() was non-functional as it removed the wrong
type from the multimap. The type system did not catch this because
remove() is passed an Object for all collection types.
The return type of removeAll() was changed to Iterable to prevent the
need of converting to Key if the caller doesn't consume the return
value.
Although it appears serialize() is now more expensive in terms of
allocations because it first accumulates into an ArrayList, the memory
usage is approximately the same since Multimap.values() makes an
Iterator for each key. The new code would allocate fewer bytes overall
and in fewer allocations, but the older code retained less memory while
processing. If we want to optimize serialize() we can track the number
of entries without needing to do any wrapping like Multimap does. I
didn't bother because the ArrayList is a fraction of the cost compared
to actually serializing the values.
This makes the reconfiguration code more concise.
- Remove configureNewStub().
- Add mutation methods withDeadlineNanoTime(), withChannel() etc that
returns the reconfigured stub.
CANCELLED is certainly not the right status code. Communicating the
exception to the client removes the need for logging, which also makes
it more clear which call experienced the problem.
Improved some consistency. writeHeaders was the only non-final
implementation method of ServerStream, even though it is really no
different than the others.