Architecture and Threading
This page describes the internal structure of CosmicSDK, the threading model it relies on, and the thread-safety guarantees (and limitations) that apply to every API surface the SDK exposes.
SDK Layers
CosmicSDK is structured as a five-layer stack. From lowest to highest:
Layer |
Responsibility |
|---|---|
|
Opens the USB HID device via HIDAPI, enumerates by VID/PID, and owns a dedicated polling thread that continuously reads raw bytes from the hardware. |
|
Frames raw bytes into USB transfer packets. |
|
Owns the |
|
The public asynchronous API. Commands are fire-and-forget; results arrive through
a user-registered |
|
The public blocking C++ API. Wraps |
|
The public blocking C API. Exposes the C++ API as plain C functions through a handle pointer. |
Language bindings (Python, Java, C#) sit above CosmicSDK and call it through the
C shared library (CosmicSDK_API_c.h).
Internal Threading Model
One OS thread per CosmicDriver instance — the polling thread — reads from the USB
HID device in a tight loop for the lifetime of the connection. It delivers each complete
response to the CommunicationManager, which dispatches it to the registered callback.
For blocking calls (CosmicSDK), the BlockingApi layer parks the calling thread on a
per-request condition variable and wakes it once the polling thread delivers the matching
response.
Warning
Notification callbacks (I3C hot-join/IBI, GPIO, UART) fire on the polling thread. Keep their bodies short and non-blocking. Issuing any SDK call from inside a notification callback will deadlock because the polling thread cannot deliver its own response.
Thread Safety
Warning
The C/C++ API and all language bindings are not thread-safe.
A CosmicSDK_Handle — and the equivalent wrapper objects in Python, Java, and C# —
must not be called concurrently from multiple threads without external synchronization.
The SDK does not protect against concurrent access: two threads issuing commands on the
same handle simultaneously will corrupt internal state.
API surface |
Thread-safe? |
Notes |
|---|---|---|
|
No |
Caller must serialize all calls on a given instance. |
|
No |
Caller must serialize all calls on a given instance. |
C API ( |
No |
Wraps the C++ API; inherits the same constraint. |
Python binding ( |
No |
Python GIL does not protect the underlying C calls. |
Java binding ( |
No |
JNA provides no automatic synchronization. |
C# binding ( |
No |
P/Invoke calls are unguarded. |
Recommended Pattern
Protect every call on a shared handle with a single mutex. The mutex must be held for the entire duration of the call — the SDK is blocking, so the calling thread holds it for the full firmware round-trip.
C++ (std::mutex)
#include <mutex>
std::mutex sdk_mutex;
CosmicSDK sdk;
// In any thread:
{
std::lock_guard<std::mutex> lock(sdk_mutex);
sdk.I2cControllerInit(I2C_BUS_0, 100000, I2C_PULLUP_INTERNAL);
}
C API
/* Acquire your mutex before every call that uses the same handle. */
your_mutex_lock(&sdk_mutex);
CosmicSDK_I2cControllerInit(handle, I2C_BUS_0, 100000, I2C_PULLUP_INTERNAL);
your_mutex_unlock(&sdk_mutex);
Python
import threading
lock = threading.Lock()
# In any thread:
with lock:
sdk.i2c_controller_init(I2C_BUS_0, 100000, I2C_PULLUP_INTERNAL)
Java
// In any thread:
synchronized (sdk) {
sdk.i2cControllerInit(I2C_BUS_0, 100000, I2C_PULLUP_INTERNAL);
}
C#
// In any thread:
lock (sdk)
{
sdk.I2cControllerInit(I2cBus.Bus0, 100000, I2cPullup.Internal);
}
Tip
If all SDK calls originate from a single thread, no mutex is needed. Locking is relevant only when multiple threads share the same handle.