Emulating line-buffering mode (Gradle example)

C printf() function doesn't flush buffers by default, and a lot of code using it may miss this, therefore creating interactivity problems to the end user. It's not specific to C though - it's about the standard I/O streams, and the problem may appear anywhere. The post is based on StackExchange discussion, so you can dig deeper if you want: Turn off buffering in pipe.

Example is based on C code that is specific for macos and has the functionality to read connected USB devices and wait for the specific one.

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/usb/IOUSBLib.h>
int main(int argc, const char *argv[]) {
if (argc < 2) {
printf("Please specify the device name.\n");
return 1;
}
const char* requiredName = argv[1];
size_t length = strlen(requiredName);
printf("Waiting for %s\n", requiredName);
CFMutableDictionaryRef matchingDict;
bool found = false;
while (!found && (matchingDict = IOServiceMatching(kIOUSBDeviceClassName))) {
io_iterator_t iter;
io_service_t device;
bool iterIsValid = IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &iter) == KERN_SUCCESS;
unsigned counter = 0;
while (!found && iterIsValid && (device = IOIteratorNext(iter))) {
io_name_t devName;
counter++;
bool nameIsValid = IORegistryEntryGetName(device, devName) == KERN_SUCCESS;
found = nameIsValid && !strncmp(devName, requiredName, length);
IOObjectRelease(device);
}
printf("Got %d connected devices. %s\n", counter, found ? "Found!" : "Still waiting...");
iterIsValid && IOObjectRelease(iter);
!found && sleep(1);
}
return !found;
}
view raw wait.c hosted with ❤ by GitHub

The Gradle script contains two tasks - runSimple task that runs the program naively, and run task that using "script" utility to emulate the line-buffering mode.

def APPLICATION_GROUP = 'Application'
ext {
device = 'Cruzer'
program = 'wait'
}
task compile(type: Exec) {
group APPLICATION_GROUP
description 'Compiles the native program.'
def src = 'wait.c'
inputs.files src
outputs.files program
commandLine 'clang', '-framework', 'IOKit', src, '-o', program
}
task runSimple(type: Exec, dependsOn: compile) {
group APPLICATION_GROUP
description 'Waits for the device.'
commandLine "./$program", device
}
task run(type: Exec, dependsOn: compile) {
group APPLICATION_GROUP
description 'Waits for the device using the line-buffered mode.'
commandLine 'script', '-q', '/dev/null', "./$program", device
}
view raw build.gradle hosted with ❤ by GitHub

If you use runSimple, then there will be no output until the device is actually connected (and no output at all if you use Ctrl-C to break the execution):

~/ > gradle runSimple
Starting a Gradle Daemon, 1 busy Daemon could not be reused, use --status for details
:compile UP-TO-DATE
:runSimple
Waiting for Cruzer
Got 4 connected devices. Still waiting...
Got 4 connected devices. Still waiting...

BUILD SUCCESSFUL

Total time: 7.851 secs

However, run works correctly and shows the output as needed. The best solution though would be using fflush() function, but unfortunately it's not always possible (especially if we use 3rd party program that we can't recompile).

Comments

Popular posts from this blog

Web application framework comparison by memory consumption

Trac Ticket Workflow

Python vs JS vs PHP for embedded systems