Description
I'd like to propose tracking requests and responses by message id to support interleaved responses on the shared socket in Net::LDAP::Connection
.
The Problem
Searches (and other operations) done on one instance of Net::LDAP::Connection
share the same TCPSocket (code). This allows the caller to initiate requests before responses for prior requests have completed. Net::LDAP
uses a shared variable @open_connection
to memoize existing connections before executing an operation.
We run into trouble because we do not inspect the response message id's and try to map them back to their original requests. Consider the following:
# search1: This will instantiate an instance of `Net::LDAP::Connection` and store it in `@open_connection`.
Net::LDAP.search(...) do |outer_entry|
# search2: sub-search based on some attributes of entry
Net::LDAP.search(:filter => ...) do |inner_entry|
end
end
Because we don't map the message id back to the original search request, results can be interleaved:
client: search1 search2
-------------------------------------------------------------------------> time
server: 1 1 1 1 1 1 2 2 2 2 1 1 1
^ ^ ^
| | |
\ | /
\ | /
server is still responding to search1,
but search2 may see search1 results
because we're on the same socket
The Proposal
I created a proof of concept using fibers to control the execution flow, and adding Request
and Response
classes to track message ids and to buffer any incoming results that don't match the current request.
As a user of the API, you don't have to reason about when results come in. Your code will block until it starts seeing results that match your request message_id.
Although the gist I posted is a proof of concept, I think it maps pretty closely to the existing code. We would need to add additional state to the Net::LDAP::Connection
class, but we could leave the existing public API intact. This also opens up the option for abandoning requests, though that would be in a separate PR.
For additional context, check out #133 and github/github-ldap@620ce32