How To Manage The imp’s TCP Send Buffer
When you send data from an imp-enabled device to its agent using agent.send(), impOS™ encrypts the data and writes it to a TCP packet ‘first in, first out’ (FIFO) send buffer which empties into the WiFi chip’s internal buffer. The WiFi chip schedules the packet transmissions depending on radio conditions, and will retransmit if no acknowledgement (ACK) is received from the access point. This process continues until all the data in the TCP FIFO has been sent.
If you send a large volume of data or make many agent.send() calls in rapid succession, it is possible to fill impOS’ send buffer. This will prevent subsequent agent.send() calls from adding data to the buffer. This is also the outcome if WiFi has disconnected. In each case, the impOS buffer can’t be emptied because the WiFi sub-system can’t empty its own buffer first.
This situation can cause seemingly strange connection and program execution behaviour. For instance, by default agent.send() blocks until its data is written into the buffer (another possibility is discussed below). If the send buffer is full, agent.send() may block until the send timeout is reached (30 seconds by default), which can cause the device to appear unresponsive.
Fortunately, this and many other parts of the device-to-agent data transfer process can be configured to minimize the impact of this behavior.
If you need to send particularly large amounts of data, consider increasing the TCP send buffer’s size using imp.setsendbuffersize(). The default size is 3456 bytes (six packets of 576 bytes each), but this can be increased to 30KB, free memory permitting. It is only possible to increase the buffer’s size during execution; the size can only be reduced by restarting the imp, which sets it to the default.
function increaseBuffer(dataToSend) {
local oldSize = 0;
local newSize = dataToSend.len();
if (newSize > 3456) oldSize = imp.setsendbuffersize(newSize);
server.log("Buffer increased to " + newSize + " bytes from " + oldSize + " bytes");
}
You can control whether agent.send() returns as soon as the data has been placed in the impOS send buffer or waits until the data has been passed to WiFi, sent and its receipt acknowledged by the imp server. The first of these is recommended for most applications and so is the default behavior, though it can be selected explicitly by passing the constant WAIT_TIL_SENT onto the method server.setsendtimeoutpolicy()’s second parameter.
However, once the packet leaves impOS’ FIFO, impOS can’t tell whether the packet has been sent successfully, so a packet leaving the FIFO does not guarantee delivery: it could be stuck within the WiFi chip, within the WiFi access point, or in an intermediate router between the WiFi network and the imp server. To force the imp to wait until it has received acknowledgement from the server of receipt of the data, call server.setsendtimeoutpolicy() at the start of your device code and pass the constant WAIT_FOR_ACK as its second parameter. WAIT_FOR_ACK allows you to call agent.send() again safe in the knowledge that the impOS buffer is now clear, provided no disconnection has taken place, of course (see below).
Note WAIT_FOR_ACK can decrease throughput noticeably, and it does not guarantee the agent will get the message, only that the server has received it. If the agent is overloaded with messages or in the process of restarting, for example, the data could still be dropped. The only way to be sure is to code the agent to respond to the successful arrival of a message with a message of its own.
server.settimeoutpolicy(SUSPEND_ON_ERROR, WAIT_FOR_ACK, 10);
// Program does some work...
foreach (count, blob in buffer) {
// Every send will not return until the server has ACK’d
// Assume no timeout for this example
local outcome = agent.send(“process.data”, blob);
server.log(“Blob %u sent to server”, count + 1);
}
Whichever of these approaches you choose, you can specify how long agent.send() waits for the data to be placed in the send buffer or for the server to acknowledge receipt of the data at protocol level. This is done by setting server.setsendtimeoutpolicy()’s third parameter, which is a timeout value in seconds; the default is 30 seconds. agent.send() will block until it returns; setting a shorter timeout ensures the call returns more quickly if the operation times out: it is unable to add the data to the buffer (the buffer is full) or the agent is taking longer than expected to acknowledge (the device may have disconnected).
agent.send() always returns a value to inform you the result of its operation: a 0 if it was successful, or a Send Error Code to indicate the cause of failure:
Code Constant | Description |
---|---|
SEND_ERROR_NOT_CONNECTED | There is no connection to the server |
SEND_ERROR_TIMEOUT | The timeout expired before all the data was sent to the server |
SEND_ERROR_DISCONNECTED | The connection was disconnected before all data was sent to the server |
Reading this return value allows your code to decide how to proceed. For example, non-critical data may be discarded and important data stored for re-transmission once the connection with the server is back up.
If your code is working to the default disconnection policy, SUSPEND_ON_ERROR, on anything other than a success, the imp will halt Squirrel execution immediately while impOS attempts to reconnect to the server. However, if you have chosen the RETURN_ON_ERROR policy — by passing this constant as server.setsendtimeoutpolicy()’s first parameter — you can adapt your code’s response to an agent.send() failure by examining the call’s return value: again, storing or rejecting data in the case of a disconnection, or attempting to resend the data after a suitable pause in the case of a timeout:
server.settimeoutpolicy(RETURN_ON_ERROR, WAIT_TIL_SENT, 10);
dataToSend <- null;
retryTime <- 600; // Ten minutes
// Code at some point will place the data it needs to send into dataToSend and then call
// transmitter(), a wrapper for agent.send() and related calls, to send it.
function transmitter() {
// Make sure we have data to send
if (dataToSend == null) return;
if (server.isconnected()) {
// At this point we have WiFi connected so try to add dataToSend to TCP send buffer
local outcome = agent.send(“message”, dataToSend);
if (outcome == 0) {
// Record successful addition of data to impOS buffer by zero-ing dataToSend
// This can be used to indicate dataToSend is free for more data
dataToSend = null;
} else {
// Couldn’t add dataToSend to the buffer OR
// WiFi has been lost OR some other failure
// RETURN_ON_ERROR in force so we need to re-connect before
// attempting to add dataToSend to the buffer again
// Reschedule transmitter()
imp.wakeup(retryTime, transmitter);
}
} else {
// WiFi is disconnected, so attempt to reconnect as per RETURN_ON_ERROR
// Whatever the outcome, we come back to transmitter().
// We can't schedule transmitter() directly as server.connect()'s
// callback expects a single parameter to take outcome indicator integer
server.connect(function(reason) {
transmitter();
});
}
}
This chart summarizes the possible outcomes of agent.send():
0
.0
.