go-coap
go-coap copied to clipboard
Responses are unconditionally cached for `EXCHANGE_LIFETIME`
When processing a response, all responses to CON and NON messages are cached for 247 seconds (EXCHANGE_LIFETIME):
https://github.com/plgd-dev/go-coap/blob/91a04eabf7408addb30cca17ea86c07092d9e102/udp/client/conn.go#L721
https://github.com/plgd-dev/go-coap/blob/91a04eabf7408addb30cca17ea86c07092d9e102/udp/client/conn.go#L623
https://github.com/plgd-dev/go-coap/blob/91a04eabf7408addb30cca17ea86c07092d9e102/udp/client/conn.go#L34
This is due to the language in Section 4.5 of RFC 7252.
A recipient might receive the same Confirmable message (as indicated by the Message ID and source endpoint) multiple times within the EXCHANGE_LIFETIME (Section 4.8.2), for example, when its Acknowledgement went missing or didn't reach the original sender before the first timeout. The recipient SHOULD acknowledge each duplicate copy of a Confirmable message using the same Acknowledgement or Reset message but SHOULD process any request or response in the message only once.
Each Conn maintains its own responseMsgCache:
https://github.com/plgd-dev/go-coap/blob/91a04eabf7408addb30cca17ea86c07092d9e102/udp/client/conn.go#L148
The cache is checked for expiration whenever the Conn expirations are checked:
https://github.com/plgd-dev/go-coap/blob/91a04eabf7408addb30cca17ea86c07092d9e102/udp/client/conn.go#L910
This allows for a recipient to respond to duplicate requests without processing multiple times. However, it also means that endpoints that send large volumes of data have the potential to grow very large caches. There is no mechanism in go-coap today to modify the EXCHANGE_LIFETIME, and the responseMsgCache has unbounded size. This is particularly an issue in the case of blockwise transfers, where a single large object may be transmitted over many messages. With the current implementation, that entire large object could end up in the cache. Furthermore, if multiple connections are all fetching the same large object, many copies of the same object may end up in the cache (i.e. one in each Conn responseMsgCache). In the same section of RFC 7252, it allows for relaxation of caching behavior:
A server might relax the requirement to answer all retransmissions of an idempotent request with the same response (Section 4.2), so that it does not have to maintain state for Message IDs. For example, an implementation might want to process duplicate transmissions of a GET, PUT, or DELETE request as separate requests if the effort incurred by duplicate processing is less expensive than keeping track of previous responses would be.
There are a few different mechanisms that could be introduced (potentially alongside each other) to address this behavior:
- Make a
ConnresponseMsgCachesize bounded such that it cannot grow unconditionally. Using an LRU strategy could be simple approach to ensuring that responses sent most recently are more likely to be in the cache. - Support specifying messages that are not to be cached.
- Support configuring the
EXCHANGE_LIFTETIME.
I would be happy to work on implementation if these features are of interest. Thanks!