> So this case we must issue synchronous TDI_SEND. But how will
tdi process the
NON_BLOCKING transmit ?
From what I remember, TCP ignores this.
TDI_SEND to TCP is pended till all ACKs will arrive for this portion.
This is obvious, since in case of packet loss TCP will need to retransmit, and
your “native” IRP’s MDL is the only source of data from which to retransmit.
TCP does not make any private copies of data of its own (this is the meaning of
“TCP is not a buffering transport TDI-wise”).
And, after all ACKs will arrive, no need in retransmitting the portion, so, TCP
can safely complete TDI_SEND.
Receive in TCP:
- if TCP has pending TDI_RECEIVE IRPs - it fills them (from what I know, all
but last are completely filled, the last is probably partially filled) and
completes them.
- after this, if TCP still has data in it - it calls ClientEventReceive,
showing both the first contiguous chunk size, a pointer to it and the total
data in TCP.
- CER can do the following:
- if BytesIndicated == BytesAvailable - receive all this data and return
STATUS_SUCCESS
- otherwise, receive BytesIndicated (there is a pointer to them), build a
TDI_RECEIVE IRP to receive the rest of BytesAvailable, and return
STATUS_MORE_PROCESSING_REQUIRED with this IRP. Do not forget to call
IoSetNextIrpStackLocation on it.
If you want to apply the receiver-side backpressure - then return
STATUS_DATA_NOT_ACCEPTED from CER, this blocks further CER calls. After this,
to restart the data flow, you will mandatory need a TDI_RECEIVE IRP.
One of the possible approaches to TCP receive is:
- the primary receiver is CER
- it allocates the buffers of BytesAvailable size, and copies the lookahead
there.
- if buffer allocation failed due to hitting the limit (the total space in
allocated buffers is limited as SO_RCVBUF option) - then return
STATUS_DATA_NOT_ACCEPTED and mark the fact.
- if lookahead does not cover the whole buffer - then a TDI_RECEIVE IRP is
created and returned together with STATUS_MORE_PROCESSING_REQUIRED. The rest of
the task is done in it’s completion routine.
- otherwise, the full buffer is “put to list”.
Completion routine for TDI_RECEIVE also put the buffer to list.
The app’s recv() just consumes this list. If the list is empty, and if
STATUS_DATA_NOT_ACCEPTED was ever returned - it builds its own TDI_RECEIVE and
send it down to unfreeze the pipeline. First this TDI_RECEIVE will be
completed, delivering a buffer, then the usual CER mechanism will work.
This is how AFD works (I omitted the optimization when AFD uses the app’s
MDL from the app’s MJ_READ IRP instead of this buffer).
Other schemes are maybe also possible, which rely on TDI_RECEIVE more and
even have no CER.
To repeat the behaviour of the TDI transport:
a) if the data arrives and there is a TDI_RECEIVE at the head of the
pending list - it is satisfied with the data and completed. This step is
repeated till the list of pending TDI_RECEIVE will be empty.
b) after this, if the endpoint is not “stalled” and there is a CER
handler - CER is called.
c) at return from CER, the transport either have a command to set the
“stalled” flag (STATUS_DATA_NOT_ACCEPTED), or a direction to discard some
consumed data, or a new TDI_RECEIVE IRP.
d) the “stalled” flag is reset on each TDI_RECEIVE IRP arrival to the
dispatch routine.
Once more - CER is called only if there is no pending TDI_RECEIVE IRPs.
Maxim Shatskih, Windows DDK MVP
StorageCraft Corporation
xxxxx@storagecraft.com
http://www.storagecraft.com