Comment on page
All internals need to be written non-blocking and cannot just hang around and wait for things to occur. At the same time, the multi interface allows users to call libcurl to perform virtually at any time, even if no action has happened or a timeout has triggered.
In the external API libcurl provides a single timeout at a time, no matter how many concurrent transfers and what options are set. An application can get the timeout value it with
curl_multi_timeout()or in a
CURLMOPT_TIMERFUNCTIONcallback, depending on what API it wants to use.
Internally, this is done like this:
- Every easy handle keeps an array of timeouts, in a sorted order. The closest (next-timeout) in time is first in the list.
- All easy handles are put in a splay tree which is binary self-balancing search tree that makes it fast to insert and remove nodes depending on their timeouts.
- As soon as any handle's next-timeout changes, the splay tree is re-balanced.
Extracting the easy handles with expired timeouts is a quick operation.
The internal function for setting a timeout is called
Curl_expire(). It asks that libcurl gets called again for this handle in a certain amount of milliseconds into the future. A timeout is set with a specific ID, to make sure that it overrides previous values set for the same timeout etc. The existing timeout IDs are limited and the set is hard-coded.
A timeout can be removed again with
Curl_expire_clear(), which then removes that timeout from the list of timeouts for the given easy handle.
Expiration of a timeout means that the application knows that it needs to call libcurl again. When the socket_action API is used, it even knows to call libcurl again for a specific given easy handle for which the timeout has expired.
There is no other special action or activity happening when a timeout expires than that the perform function will be called. Each state or internal function needs to know what times or states to check for and act accordingly when called (again).