How I built the Optimizely C SDK Wrapper using the Go SDK in a week (part 1)

Ola Nordstrom
Engineers @ Optimizely
6 min readMar 25, 2020

Hack Week

One of the more rewarding and fun aspects of working at Optimizely is our biannual hack week where engineers get to work on projects they normally wouldn’t be able to work on. Anything and everything is on the table. Unsurprisingly, engineers have a tendency to scratch their own itch and often the hacks produce innovations for the company too! I spent a lot of my early career working on kernel modules in Linux, drivers on Windows and other low level system components across multiple platforms.

Over the years a number of new Optimizely features and products were originally created as hack week projects. This includes the Desktop App, feature rollouts and the first Optimizely SDK. Today Optimizely has SDKs for the following languages and environments: Android, C#, Go, Java, Javascript (Browser, Node and React), PHP, Python, Ruby, and Swift.

Notably absent is C or C++. C was the first programming language I learned and it is crucial in understanding operating systems and how many software security vulnerabilities are exploited (my day job is software security). Over the years, I have developed on many different platforms in the C family of languages. Various platforms have their own strengths as do different programming languages and I had begun to wonder if the demand for web, and all web friendly languages had pushed aside the need for C or C++.

So prior to hack week instead of wondering whether C or C++ had declined in popularity due to the industry propensity to make all systems web accessible and I decided to investigate the popularity of C and C++ to determine if their relative popularity had gone down. To my surprise the IEEE Spectrum’s job language analysis for 2019 lists C as the third most requested language (C++ is 4th) for programming jobs. Similarly, the Stack Overflow 2019 developer survey and Github’s The State of the Octoverse have both languages in their top 10. So there is still a strong demand for these languages and thus many opportunities to use Optimizely’s feature rollouts and experimentation.

At Optimizely, we are constantly working on bringing our experimentation toolset into more spaces where our customers are running code. One way of delivering support for native code environments would be to pick a compiled language and a set of well supported libraries to provide an API wrapper for api.optimizely.com. For example, libcurl, the library behind curl could be used with C. Or another C++ framework that had support for JSON and making web requests could be used to develop a clean API that works for both C and C++. Also, both the language and libraries picked must be checked to ensure they support a variety of environments (Linux, OS X, Windows, …) and architectures (Intel, Arm, …). Writing cross platform C can be tricky and requires great diligence. Writing an SDK using either C or C++ is not doable in a single hack week so originally I nixed the idea and signed up to work with another team to add migration flags for Python projects.

However, Go is compiled into native code and supports the aforementioned platforms. Starting with Golang 1.5, released in August of 2015, the -buildmode link flag supports producing shared libraries that other languages can call. This means Go source code can now be compiled into libraries that are linked into all other natively compiled code — not just C or C++ but any language that can work with shared libraries.

While Go may never be as performant as C, the Optimizely SDKs are intended to simplify the interaction with api.optimizely.com. The SDKs are primarily stateless shims that free the user from having to write the low level networking code to interact with the Optimizely API endpoint. Using Go helps ensure simplicity and maintainability since the Go SDK is already available. There may be some overhead of the language compared with native C however the network requests will likely be the bottleneck and most time will be spent waiting for packets to go back and forth. Part two of this blog will investigate performance.

Golang CGO

The rest of this blog details how the Optimizely Go SDK C wrapper supports experimentation in C, C++ and other programming languages compiled to native code. This wrapper SDK is now available at

https://github.com/optimizely/go-sdk-c-wrapper

The Go cgo documentation provides a good overview of Go and C interoperability if you are unfamiliar with it.

To make the Optimizely Go SDK work with C a C friendly API needs to be exposed. The primary API entry point in the Go SDK is the client package. This package needs to look and feel like a C API. The functions exposed in the Optimizely C API are done by annotating the Go functions with //export <function name>. The optimizely_sdk_is_feature_enabled() function declaration is shown below.

//export optimizely_sdk_is_feature_enabled

func optimizely_sdk_is_feature_enabled(handle int32, feature_name *C.char, attribs *C.struct_optimizely_user_attributes, err **C.char) int32 { … }

To build this code run go build as follows

go build -buildmode=c-shared csdk.go

The go compiler will produce the C header file and shared library.

IsFeatureEnabled

Suppose one wants to use a feature flag to check whether a new feature has gone live. To do this only a few simple calls are needed. In the Optimizely Go SDK this is done via the following calls

optimizelyClient, err := optly.Client(“sdk key”)

optimizelyClient.IsFeatureEnabled(“feature key”, userContext)

This can now be done in C. Here is the full example, reproduced from examples/is-feature-enabled.c.

is-feature-enabled.c from the Optimizely C Wrapper SDK

It really is that simple!

Integrating Go with C

There are many examples of calling C from Go that can be found with a quick Google search. This task is straightforward and was an explicit design goal of Go. It should be easy to integrate legacy libraries, often written in C, into Go — and it is.

The resources for going the other way, that is exposing a non-trivial Go package back to C are much more scarce. Go provides great support for converting simple data types such as integers and strings into their C equivalent. The difficulty lies in the handling of structures and heap allocated data. The most important rule to remember is that any data allocated on the Golang runtime (on it’s heap) must never be shared,

…return nothing allocated by Go, period.

This is the most relevant advice shared on golang-nuts mailing list. To avoid this one could re-allocate structures and manually copy each struct field to the C equivalent since Go and C pack structures differently. This gets messy since one may have to deal with nested structs and if state is maintained (such as auth tokens and network connections) the task quickly balloons in scope.

The most straightforward approach is to do what has been done in systems programming from time immemorial — return handles from one system to another. This is what a file descriptor is as defined by Posix. In the Windows API it is a HANDLE (which is a DWORD which is an unsigned int).

A way of turning multiple return values from Go functions, which in many cases return a result or an error into their C equivalent function, is to return the error in a pointer passed in by the caller but allocated and set by the SDK. If the error NULL upon return there are no errors. This is the approach used by the Optimizely Go SDK C Wrapper.

Dealing with dynamic memory can be done by allowing the API caller to allocate the necessary memory, forcing multiple calls and maintaining state in between each call or having a callback function to provide the necessary memory by the caller (libcurl has a nice example of this here). However the CGO runtime already allocates C strings in the conversion functions it provides such as C.CString(MyGoString). Therefore it made sense to simply allocate the necessary memory directly in the SDK during the translation of data structures from Go to C. Then the caller must free all strings returned by the SDK.

In part 2 we will integrate the Optimizely C SDK with Nginx to demonstrate how to use feature flags in the Nginx webserver and investigate performance.

Many thanks to Kody O’Connell and Yeesheen Yang for reviewing drafts of this post and making many helpful suggestions and edits. Questions, comments and pull requests are welcome!

https://github.com/optimizely/go-sdk-c-wrapper

Ola Nordstrom

Update: You can find part 2 here.

--

--