Simple Flow Control Made Easy

We are currently working on a product that utilizes a long-range and low-bandwidth network. Moreover, the devices that are being communicated with are severely limited with regard to memory availability and CPU cycles. Given these constraints, it’s important that the network and devices are not overwhelmed with traffic.

Recently, we encountered an acute problem where the network and devices could easily be overwhelmed if they are powered on and join the network at approximately the same time.

h2. Problem

When the devices join they communicate with a server, informing it that it has just joined and that it needs to be configured (name, time, etc.). It will continue to send these configuration requests at a steady rate until it receives all of the necessary configuration information. The problem is that the server would gladly respond to _every_ request, even if the information it is sending is duplicated.

We had to make sure that duplicate messages were _dropped_ within a certain time period.

h2. Solution

The first thing that came to my mind was “I wish there was some kind of expiring cache where the key represented the message and data.” Luckily for me, the smart people at Google already created a Cache Object that supports expiring keys/value pairs into their “Guava”:https://code.google.com/p/guava-libraries/ library.

It was fairly simple to use:

        flowcontrolCache = CacheBuilder
                .newBuilder()
                .expireAfterWrite(5, TimeUnit.SECONDS)
                .maximumSize(2000)
                .build();

That above code snippet creates a cache where the entry will expire in 5 seconds (after creation or most recent replacement) and has a maximum number of entries of 2,000 (old entries are booted).

Which could then be used like this to exercise basic flow control:

        flowcontrolCache.cleanUp();
        MessageKey key = buildKey(...)
        if (flowcontrolCache.getIfPresent(key) == null) {
            flowcontrolCache.put(key, new Random().nextLong());
            try {
                sendMessage(...);
            } catch (Exception e) {
                logger.error("Error occurred while sending message", e);
            }
        }

In this particular case the value wasn’t important, so we used a random value. In some cases the value may be important where the entry should expire if the value is to be used _or_ it plays a role in expiring the cache (See weakValues).

Voila! Flow control is now in place with a relatively low amount of effort.